components are in,

this should actually be the full model, the whole graph is in - now to optimize full model and check for circular deps.
then, it's time to move back in history and rebuild the swagger model.
This commit is contained in:
Dave Shanley
2022-08-21 12:05:16 -04:00
parent 5a2f3ca924
commit 00267c91b9
11 changed files with 374 additions and 65 deletions

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}