diff --git a/datamodel/high/3.0/components.go b/datamodel/high/3.0/components.go index f1284de..19a9dea 100644 --- a/datamodel/high/3.0/components.go +++ b/datamodel/high/3.0/components.go @@ -10,6 +10,17 @@ import ( "sync" ) +const ( + responses = iota + parameters + examples + requestBodies + headers + securitySchemes + links + callbacks +) + var seenSchemas map[string]*Schema func init() { @@ -49,50 +60,125 @@ func NewComponents(comp *low.Components) *Components { c := new(Components) c.low = comp c.Extensions = high.ExtractExtensions(comp.Extensions) - callbacks := make(map[string]*Callback) - links := make(map[string]*Link) + cbMap := make(map[string]*Callback) + linkMap := make(map[string]*Link) + responseMap := make(map[string]*Response) + parameterMap := make(map[string]*Parameter) + exampleMap := make(map[string]*Example) + requestBodyMap := make(map[string]*RequestBody) + headerMap := make(map[string]*Header) + securitySchemeMap := make(map[string]*SecurityScheme) schemas := make(map[string]*Schema) + schemaChan := make(chan componentResult[*Schema]) + cbChan := make(chan componentResult[*Callback]) + linkChan := make(chan componentResult[*Link]) + responseChan := make(chan componentResult[*Response]) + paramChan := make(chan componentResult[*Parameter]) + exampleChan := make(chan componentResult[*Example]) + requestBodyChan := make(chan componentResult[*RequestBody]) + headerChan := make(chan componentResult[*Header]) + securitySchemeChan := make(chan componentResult[*SecurityScheme]) + // build all components asynchronously. for k, v := range comp.Callbacks.Value { - callbacks[k.Value] = NewCallback(v.Value) + go buildComponent[*Callback, *low.Callback](callbacks, k.Value, v.Value, cbChan, NewCallback) } - c.Callbacks = callbacks for k, v := range comp.Links.Value { - links[k.Value] = NewLink(v.Value) + go buildComponent[*Link, *low.Link](links, k.Value, v.Value, linkChan, NewLink) } - c.Links = links - - sLock := sync.RWMutex{} - buildOutSchema := func(k lowmodel.KeyReference[string], - schema lowmodel.ValueReference[*low.Schema], doneChan chan bool, schemas map[string]*Schema) { - var sch *Schema - if ss := getSeenSchema(schema.GenerateMapKey()); ss != nil { - sch = ss - } else { - sch = NewSchema(schema.Value) - } - defer sLock.Unlock() - sLock.Lock() - schemas[k.Value] = sch - addSeenSchema(schema.GenerateMapKey(), sch) - doneChan <- true + for k, v := range comp.Responses.Value { + go buildComponent[*Response, *low.Response](responses, k.Value, v.Value, responseChan, NewResponse) + } + for k, v := range comp.Parameters.Value { + go buildComponent[*Parameter, *low.Parameter](parameters, k.Value, v.Value, paramChan, NewParameter) + } + for k, v := range comp.Examples.Value { + go buildComponent[*Example, *low.Example](parameters, k.Value, v.Value, exampleChan, NewExample) + } + for k, v := range comp.RequestBodies.Value { + go buildComponent[*RequestBody, *low.RequestBody](requestBodies, k.Value, v.Value, + requestBodyChan, NewRequestBody) + } + for k, v := range comp.Headers.Value { + go buildComponent[*Header, *low.Header](headers, k.Value, v.Value, headerChan, NewHeader) + } + for k, v := range comp.SecuritySchemes.Value { + go buildComponent[*SecurityScheme, *low.SecurityScheme](securitySchemes, k.Value, v.Value, + securitySchemeChan, NewSecurityScheme) } - doneChan := make(chan bool) for k, v := range comp.Schemas.Value { - go buildOutSchema(k, v, doneChan, schemas) + go buildSchema(k, v.Value, schemaChan) } - k := 0 - for k < len(comp.Schemas.Value) { + + totalComponents := len(comp.Callbacks.Value) + len(comp.Links.Value) + len(comp.Responses.Value) + + len(comp.Parameters.Value) + len(comp.Examples.Value) + len(comp.RequestBodies.Value) + + len(comp.Headers.Value) + len(comp.SecuritySchemes.Value) + len(comp.Schemas.Value) + + processedComponents := 0 + for processedComponents < totalComponents { select { - case <-doneChan: - k++ + case sRes := <-schemaChan: + processedComponents++ + schemas[sRes.key] = sRes.res + case cbRes := <-cbChan: + processedComponents++ + cbMap[cbRes.key] = cbRes.res + case lRes := <-linkChan: + processedComponents++ + linkMap[lRes.key] = lRes.res + case respRes := <-responseChan: + processedComponents++ + responseMap[respRes.key] = respRes.res + case pRes := <-paramChan: + processedComponents++ + parameterMap[pRes.key] = pRes.res + case eRes := <-exampleChan: + processedComponents++ + exampleMap[eRes.key] = eRes.res + case rbRes := <-requestBodyChan: + processedComponents++ + requestBodyMap[rbRes.key] = rbRes.res + case hRes := <-headerChan: + processedComponents++ + headerMap[hRes.key] = hRes.res + case ssRes := <-securitySchemeChan: + processedComponents++ + securitySchemeMap[ssRes.key] = ssRes.res } } c.Schemas = schemas + c.Callbacks = cbMap + c.Links = linkMap + c.Parameters = parameterMap + c.Headers = headerMap + c.Responses = responseMap + c.RequestBodies = requestBodyMap + c.Examples = exampleMap + c.SecuritySchemes = securitySchemeMap return c } +type componentResult[T any] struct { + res T + key string + comp int +} + +func buildComponent[N any, O any](comp int, key string, orig O, c chan componentResult[N], f func(O) N) { + c <- componentResult[N]{comp: comp, res: f(orig), key: key} +} + +func buildSchema(key lowmodel.KeyReference[string], orig *low.Schema, c chan componentResult[*Schema]) { + var sch *Schema + if ss := getSeenSchema(key.GenerateMapKey()); ss != nil { + sch = ss + } else { + sch = NewSchema(orig) + } + c <- componentResult[*Schema]{res: sch, key: key.Value} +} + func (c *Components) GoLow() *low.Components { return c.low } diff --git a/datamodel/high/3.0/document_test.go b/datamodel/high/3.0/document_test.go index 37b2b39..26d12ea 100644 --- a/datamodel/high/3.0/document_test.go +++ b/datamodel/high/3.0/document_test.go @@ -127,7 +127,7 @@ func TestNewDocument_Components(t *testing.T) { assert.Equal(t, "locateBurger", h.Components.Links["LocateBurger"].OperationId) assert.Equal(t, "$response.body#/id", h.Components.Links["LocateBurger"].Parameters["burgerId"]) assert.Len(t, h.Components.Callbacks, 1) - //assert.Equal(t, "Callback payload", - // h.Components.Callbacks["BurgerCallback"].Expression["{$request.query.queryUrl}"].Post.RequestBody.Description) + assert.Equal(t, "Callback payload", + h.Components.Callbacks["BurgerCallback"].Expression["{$request.query.queryUrl}"].Post.RequestBody.Description) } diff --git a/datamodel/high/3.0/oauth_flow.go b/datamodel/high/3.0/oauth_flow.go index 7a9f155..f59046e 100644 --- a/datamodel/high/3.0/oauth_flow.go +++ b/datamodel/high/3.0/oauth_flow.go @@ -3,7 +3,10 @@ package v3 -import low "github.com/pb33f/libopenapi/datamodel/low/3.0" +import ( + "github.com/pb33f/libopenapi/datamodel/high" + low "github.com/pb33f/libopenapi/datamodel/low/3.0" +) type OAuthFlow struct { AuthorizationUrl string @@ -14,6 +17,21 @@ type OAuthFlow struct { low *low.OAuthFlow } +func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow { + o := new(OAuthFlow) + o.low = flow + o.TokenUrl = flow.TokenUrl.Value + o.AuthorizationUrl = flow.AuthorizationUrl.Value + o.RefreshUrl = flow.RefreshUrl.Value + scopes := make(map[string]string) + for k, v := range flow.Scopes.Value { + scopes[k.Value] = v.Value + } + o.Scopes = scopes + o.Extensions = high.ExtractExtensions(flow.Extensions) + return o +} + func (o *OAuthFlow) GoLow() *low.OAuthFlow { return o.low } diff --git a/datamodel/high/3.0/oauth_flows.go b/datamodel/high/3.0/oauth_flows.go index e4dce04..f5939ac 100644 --- a/datamodel/high/3.0/oauth_flows.go +++ b/datamodel/high/3.0/oauth_flows.go @@ -3,7 +3,10 @@ package v3 -import low "github.com/pb33f/libopenapi/datamodel/low/3.0" +import ( + "github.com/pb33f/libopenapi/datamodel/high" + low "github.com/pb33f/libopenapi/datamodel/low/3.0" +) type OAuthFlows struct { Implicit *OAuthFlow @@ -14,6 +17,28 @@ type OAuthFlows struct { low *low.OAuthFlows } +func NewOAuthFlows(flows *low.OAuthFlows) *OAuthFlows { + o := new(OAuthFlows) + o.low = flows + if !flows.Implicit.IsEmpty() { + o.Implicit = NewOAuthFlow(flows.Implicit.Value) + } + if !flows.Password.IsEmpty() { + o.Password = NewOAuthFlow(flows.Password.Value) + } + if !flows.ClientCredentials.IsEmpty() { + o.ClientCredentials = NewOAuthFlow(flows.ClientCredentials.Value) + } + if !flows.AuthorizationCode.IsEmpty() { + o.AuthorizationCode = NewOAuthFlow(flows.AuthorizationCode.Value) + } + if !flows.Implicit.IsEmpty() { + o.Implicit = NewOAuthFlow(flows.Implicit.Value) + } + o.Extensions = high.ExtractExtensions(flows.Extensions) + return o +} + func (o *OAuthFlows) GoLow() *low.OAuthFlows { return o.low } diff --git a/datamodel/high/3.0/operation.go b/datamodel/high/3.0/operation.go index ed67f49..dbbfcab 100644 --- a/datamodel/high/3.0/operation.go +++ b/datamodel/high/3.0/operation.go @@ -6,39 +6,54 @@ package v3 import low "github.com/pb33f/libopenapi/datamodel/low/3.0" type Operation struct { - Tags []string - Summary string - Description string - ExternalDocs *ExternalDoc - OperationId string - Parameters []*Parameter - RequestBody *RequestBody - Responses *Responses - Callbacks map[string]*Callback - Deprecated bool - Security *SecurityRequirement - Servers []*Server - Extensions map[string]any - low *low.Operation + Tags []string + Summary string + Description string + ExternalDocs *ExternalDoc + OperationId string + Parameters []*Parameter + RequestBody *RequestBody + Responses *Responses + Callbacks map[string]*Callback + Deprecated bool + Security *SecurityRequirement + Servers []*Server + Extensions map[string]any + low *low.Operation } func NewOperation(operation *low.Operation) *Operation { - o := new(Operation) - o.low = operation - var tags []string - for i := range operation.Tags.Value { - tags = append(tags, operation.Tags.Value[i].Value) - } - o.Tags = tags - o.Summary = operation.Summary.Value - o.Description = operation.Description.Value - o.ExternalDocs = NewExternalDoc(operation.ExternalDocs.Value) - o.OperationId = operation.OperationId.Value - - // TODO: come back and finish. - return o + o := new(Operation) + o.low = operation + var tags []string + if !operation.Tags.IsEmpty() { + for i := range operation.Tags.Value { + tags = append(tags, operation.Tags.Value[i].Value) + } + } + o.Tags = tags + o.Summary = operation.Summary.Value + o.Description = operation.Description.Value + if !operation.ExternalDocs.IsEmpty() { + o.ExternalDocs = NewExternalDoc(operation.ExternalDocs.Value) + } + o.OperationId = operation.OperationId.Value + if !operation.Parameters.IsEmpty() { + params := make([]*Parameter, len(operation.Parameters.Value)) + for i := range operation.Parameters.Value { + params[i] = NewParameter(operation.Parameters.Value[i].Value) + } + o.Parameters = params + } + if !operation.RequestBody.IsEmpty() { + o.RequestBody = NewRequestBody(operation.RequestBody.Value) + } + if !operation.Responses.IsEmpty() { + o.Responses = NewResponses(operation.Responses.Value) + } + return o } func (o *Operation) GoLow() *low.Operation { - return o.low + return o.low } diff --git a/datamodel/high/3.0/path_item.go b/datamodel/high/3.0/path_item.go index a43f92d..6d6de49 100644 --- a/datamodel/high/3.0/path_item.go +++ b/datamodel/high/3.0/path_item.go @@ -5,6 +5,17 @@ package v3 import low "github.com/pb33f/libopenapi/datamodel/low/3.0" +const ( + get = iota + put + post + del + options + head + patch + trace +) + type PathItem struct { Description string Summary string @@ -33,6 +44,66 @@ func NewPathItem(pathItem *low.PathItem) *PathItem { } pi.Servers = servers + // build operation async + type opResult struct { + method int + op *Operation + } + opChan := make(chan opResult) + var buildOperation = func(method int, op *low.Operation, c chan opResult) { + if op == nil { + c <- opResult{method: method, op: nil} + return + } + c <- opResult{method: method, op: NewOperation(op)} + } + // build out operations async. + go buildOperation(get, pathItem.Get.Value, opChan) + go buildOperation(put, pathItem.Put.Value, opChan) + go buildOperation(post, pathItem.Post.Value, opChan) + go buildOperation(del, pathItem.Delete.Value, opChan) + go buildOperation(options, pathItem.Options.Value, opChan) + go buildOperation(head, pathItem.Head.Value, opChan) + go buildOperation(patch, pathItem.Patch.Value, opChan) + go buildOperation(trace, pathItem.Trace.Value, opChan) + + if !pathItem.Parameters.IsEmpty() { + params := make([]*Parameter, len(pathItem.Parameters.Value)) + for i := range pathItem.Parameters.Value { + params[i] = NewParameter(pathItem.Parameters.Value[i].Value) + } + pi.Parameters = params + } + + complete := false + opCount := 0 + for !complete { + select { + case opRes := <-opChan: + switch opRes.method { + case get: + pi.Get = opRes.op + case put: + pi.Put = opRes.op + case post: + pi.Post = opRes.op + case del: + pi.Delete = opRes.op + case options: + pi.Options = opRes.op + case head: + pi.Head = opRes.op + case patch: + pi.Patch = opRes.op + case trace: + pi.Trace = opRes.op + } + } + opCount++ + if opCount == 8 { + complete = true + } + } return pi } diff --git a/datamodel/high/3.0/request_body.go b/datamodel/high/3.0/request_body.go index 1878e5a..48e396c 100644 --- a/datamodel/high/3.0/request_body.go +++ b/datamodel/high/3.0/request_body.go @@ -3,7 +3,10 @@ package v3 -import low "github.com/pb33f/libopenapi/datamodel/low/3.0" +import ( + "github.com/pb33f/libopenapi/datamodel/high" + low "github.com/pb33f/libopenapi/datamodel/low/3.0" +) type RequestBody struct { Description string @@ -13,6 +16,16 @@ type RequestBody struct { low *low.RequestBody } +func NewRequestBody(rb *low.RequestBody) *RequestBody { + r := new(RequestBody) + r.low = rb + r.Description = rb.Description.Value + r.Required = rb.Required.Value + r.Extensions = high.ExtractExtensions(rb.Extensions) + r.Content = ExtractContent(rb.Content.Value) + return r +} + func (r *RequestBody) GoLow() *low.RequestBody { return r.low } diff --git a/datamodel/high/3.0/response.go b/datamodel/high/3.0/response.go index 0fe7006..fcfb341 100644 --- a/datamodel/high/3.0/response.go +++ b/datamodel/high/3.0/response.go @@ -3,7 +3,10 @@ package v3 -import low "github.com/pb33f/libopenapi/datamodel/low/3.0" +import ( + "github.com/pb33f/libopenapi/datamodel/high" + low "github.com/pb33f/libopenapi/datamodel/low/3.0" +) type Response struct { Description string @@ -14,6 +17,27 @@ type Response struct { low *low.Response } +func NewResponse(response *low.Response) *Response { + r := new(Response) + r.low = response + r.Description = response.Description.Value + if !response.Headers.IsEmpty() { + r.Headers = ExtractHeaders(response.Headers.Value) + } + r.Extensions = high.ExtractExtensions(response.Extensions) + if !response.Content.IsEmpty() { + r.Content = ExtractContent(response.Content.Value) + } + if !response.Links.IsEmpty() { + links := make(map[string]*Link) + for k, v := range response.Links.Value { + links[k.Value] = NewLink(v.Value) + } + r.Links = links + } + return r +} + func (r *Response) GoLow() *low.Response { return r.low } diff --git a/datamodel/high/3.0/responses.go b/datamodel/high/3.0/responses.go index 83bc9f2..312bad1 100644 --- a/datamodel/high/3.0/responses.go +++ b/datamodel/high/3.0/responses.go @@ -11,6 +11,42 @@ type Responses struct { low *low.Responses } +func NewResponses(response *low.Responses) *Responses { + r := new(Responses) + r.low = response + if !response.Default.IsEmpty() { + r.Default = NewResponse(response.Default.Value) + } + codes := make(map[string]*Response) + + // struct to hold response and code sent over chan. + type respRes struct { + code string + resp *Response + } + + // build each response async for speed + rChan := make(chan respRes) + var buildResponse = func(code string, resp *low.Response, c chan respRes) { + c <- respRes{code: code, resp: NewResponse(resp)} + } + for k, v := range response.Codes { + go buildResponse(k.Value, v.Value, rChan) + } + totalCodes := len(response.Codes) + codesParsed := 0 + for codesParsed < totalCodes { + select { + case re := <-rChan: + codesParsed++ + codes[re.code] = re.resp + } + } + r.Codes = codes + return r + +} + func (r *Responses) GoLow() *low.Responses { return r.low } diff --git a/datamodel/high/3.0/schema.go b/datamodel/high/3.0/schema.go index 3b93963..3e3e192 100644 --- a/datamodel/high/3.0/schema.go +++ b/datamodel/high/3.0/schema.go @@ -140,13 +140,14 @@ func NewSchema(schema *low.Schema) *Schema { var buildProps = func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*low.Schema], c chan bool, props map[string]*Schema) { if ss := getSeenSchema(v.GenerateMapKey()); ss != nil { + defer plock.Unlock() plock.Lock() props[k.Value] = ss - plock.Unlock() + } else { + defer plock.Unlock() plock.Lock() props[k.Value] = NewSchema(v.Value) - plock.Unlock() addSeenSchema(k.GenerateMapKey(), props[k.Value]) } s.Properties = props diff --git a/datamodel/high/3.0/security_scheme.go b/datamodel/high/3.0/security_scheme.go index 60aad29..360422f 100644 --- a/datamodel/high/3.0/security_scheme.go +++ b/datamodel/high/3.0/security_scheme.go @@ -3,7 +3,10 @@ package v3 -import low "github.com/pb33f/libopenapi/datamodel/low/3.0" +import ( + "github.com/pb33f/libopenapi/datamodel/high" + low "github.com/pb33f/libopenapi/datamodel/low/3.0" +) type SecurityScheme struct { Type string @@ -18,6 +21,23 @@ type SecurityScheme struct { low *low.SecurityScheme } +func NewSecurityScheme(ss *low.SecurityScheme) *SecurityScheme { + s := new(SecurityScheme) + s.low = ss + s.Type = ss.Type.Value + s.Description = ss.Description.Value + s.Name = ss.Name.Value + s.Scheme = ss.Scheme.Value + s.In = ss.In.Value + s.BearerFormat = ss.BearerFormat.Value + s.OpenIdConnectUrl = ss.OpenIdConnectUrl.Value + s.Extensions = high.ExtractExtensions(ss.Extensions) + if !ss.Flows.IsEmpty() { + s.Flows = NewOAuthFlows(ss.Flows.Value) + } + return s +} + func (s *SecurityScheme) GoLow() *low.SecurityScheme { return s.low }