diff --git a/datamodel/high/base/contact.go b/datamodel/high/base/contact.go index 35bdaa7..96c4f09 100644 --- a/datamodel/high/base/contact.go +++ b/datamodel/high/base/contact.go @@ -34,6 +34,11 @@ func (c *Contact) GoLow() *low.Contact { return c.low } +// GoLowUntyped will return the low-level Contact instance that was used to create the high-level one, with no type +func (c *Contact) GoLowUntyped() any { + return c.low +} + func (c *Contact) Render() ([]byte, error) { return yaml.Marshal(c) } diff --git a/datamodel/high/base/discriminator.go b/datamodel/high/base/discriminator.go index c1d472a..528d908 100644 --- a/datamodel/high/base/discriminator.go +++ b/datamodel/high/base/discriminator.go @@ -41,6 +41,11 @@ func (d *Discriminator) GoLow() *low.Discriminator { return d.low } +// GoLowUntyped will return the low-level Discriminator instance that was used to create the high-level one, with no type +func (d *Discriminator) GoLowUntyped() any { + return d.low +} + // Render will return a YAML representation of the Discriminator object as a byte slice. func (d *Discriminator) Render() ([]byte, error) { return yaml.Marshal(d) diff --git a/datamodel/high/base/example.go b/datamodel/high/base/example.go index 5ce4494..31d711b 100644 --- a/datamodel/high/base/example.go +++ b/datamodel/high/base/example.go @@ -38,6 +38,11 @@ func (e *Example) GoLow() *low.Example { return e.low } +// GoLowUntyped will return the low-level Example instance that was used to create the high-level one, with no type +func (e *Example) GoLowUntyped() any { + return e.low +} + // Render will return a YAML representation of the Example object as a byte slice. func (e *Example) Render() ([]byte, error) { return yaml.Marshal(e) diff --git a/datamodel/high/base/external_doc.go b/datamodel/high/base/external_doc.go index aee3e37..3830076 100644 --- a/datamodel/high/base/external_doc.go +++ b/datamodel/high/base/external_doc.go @@ -40,6 +40,11 @@ func (e *ExternalDoc) GoLow() *low.ExternalDoc { return e.low } +// GoLowUntyped will return the low-level ExternalDoc instance that was used to create the high-level one, with no type +func (e *ExternalDoc) GoLowUntyped() any { + return e.low +} + func (e *ExternalDoc) GetExtensions() map[string]any { return e.Extensions } diff --git a/datamodel/high/base/info.go b/datamodel/high/base/info.go index 33a6e35..ad55209 100644 --- a/datamodel/high/base/info.go +++ b/datamodel/high/base/info.go @@ -64,6 +64,11 @@ func (i *Info) GoLow() *low.Info { return i.low } +// GoLowUntyped will return the low-level Info instance that was used to create the high-level one, with no type +func (i *Info) GoLowUntyped() any { + return i.low +} + // Render will return a YAML representation of the Info object as a byte slice. func (i *Info) Render() ([]byte, error) { return yaml.Marshal(i) diff --git a/datamodel/high/base/license.go b/datamodel/high/base/license.go index 4e33294..066581e 100644 --- a/datamodel/high/base/license.go +++ b/datamodel/high/base/license.go @@ -36,6 +36,11 @@ func (l *License) GoLow() *low.License { return l.low } +// GoLowUntyped will return the low-level License instance that was used to create the high-level one, with no type +func (l *License) GoLowUntyped() any { + return l.low +} + // Render will return a YAML representation of the License object as a byte slice. func (l *License) Render() ([]byte, error) { return yaml.Marshal(l) diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index 02431fe..b71b75c 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -444,6 +444,11 @@ func (s *Schema) GoLow() *base.Schema { return s.low } +// GoLowUntyped will return the low-level Schema instance that was used to create the high-level one, with no type +func (s *Schema) GoLowUntyped() any { + return s.low +} + // Render will return a YAML representation of the Schema object as a byte slice. func (s *Schema) Render() ([]byte, error) { return yaml.Marshal(s) diff --git a/datamodel/high/base/schema_proxy.go b/datamodel/high/base/schema_proxy.go index 7d02734..f0796a3 100644 --- a/datamodel/high/base/schema_proxy.go +++ b/datamodel/high/base/schema_proxy.go @@ -86,25 +86,32 @@ func (sp *SchemaProxy) BuildSchema() (*Schema, error) { // GetBuildError returns any error that was thrown when calling Schema() func (sp *SchemaProxy) GetBuildError() error { - return sp.buildError + return sp.buildError } func (sp *SchemaProxy) GoLow() *base.SchemaProxy { - if sp.schema == nil { - return nil - } - return sp.schema.Value + if sp.schema == nil { + return nil + } + return sp.schema.Value +} + +func (sp *SchemaProxy) GoLowUntyped() any { + if sp.schema == nil { + return nil + } + return sp.schema.Value } // Render will return a YAML representation of the Schema object as a byte slice. func (sp *SchemaProxy) Render() ([]byte, error) { - return yaml.Marshal(sp) + return yaml.Marshal(sp) } // MarshalYAML will create a ready to render YAML representation of the ExternalDoc object. func (sp *SchemaProxy) MarshalYAML() (interface{}, error) { - if sp == nil { - return nil, nil + if sp == nil { + return nil, nil } var s *Schema var err error diff --git a/datamodel/high/base/security_requirement.go b/datamodel/high/base/security_requirement.go index 4952d44..e0bea3c 100644 --- a/datamodel/high/base/security_requirement.go +++ b/datamodel/high/base/security_requirement.go @@ -43,6 +43,11 @@ func (s *SecurityRequirement) GoLow() *base.SecurityRequirement { return s.low } +// GoLowUntyped will return the low-level Discriminator instance that was used to create the high-level one, with no type +func (s *SecurityRequirement) GoLowUntyped() any { + return s.low +} + // Render will return a YAML representation of the SecurityRequirement object as a byte slice. func (s *SecurityRequirement) Render() ([]byte, error) { return yaml.Marshal(s) diff --git a/datamodel/high/base/tag.go b/datamodel/high/base/tag.go index f08f120..b179620 100644 --- a/datamodel/high/base/tag.go +++ b/datamodel/high/base/tag.go @@ -45,6 +45,11 @@ func (t *Tag) GoLow() *low.Tag { return t.low } +// GoLowUntyped will return the low-level Tag instance that was used to create the high-level one, with no type +func (t *Tag) GoLowUntyped() any { + return t.low +} + // Render will return a YAML representation of the Info object as a byte slice. func (t *Tag) Render() ([]byte, error) { return yaml.Marshal(t) @@ -57,29 +62,4 @@ func (t *Tag) MarshalYAML() (interface{}, error) { } nb := high.NewNodeBuilder(t, t.low) return nb.Render(), nil -} - -// Experimental mutation API. -//func (t *Tag) SetName(value string) { -// t.GoLow().Name.ValueNode.Value = value -//} -//func (t *Tag) SetDescription(value string) { -// t.GoLow().Description.ValueNode.Value = value -//} - -//func (t *Tag) MarshalYAML() (interface{}, error) { -// m := make(map[string]interface{}) -// for i := range t.Extensions { -// m[i] = t.Extensions[i] -// } -// if t.Name != "" { -// m[NameLabel] = t.Name -// } -// if t.Description != "" { -// m[DescriptionLabel] = t.Description -// } -// if t.ExternalDocs != nil { -// m[ExternalDocsLabel] = t.ExternalDocs -// } -// return m, nil -//} +} \ No newline at end of file diff --git a/datamodel/high/base/xml.go b/datamodel/high/base/xml.go index b82c037..1bd661b 100644 --- a/datamodel/high/base/xml.go +++ b/datamodel/high/base/xml.go @@ -46,6 +46,11 @@ func (x *XML) GoLow() *low.XML { return x.low } +// GoLowUntyped will return the low-level XML instance that was used to create the high-level one, with no type +func (x *XML) GoLowUntyped() any { + return x.low +} + // Render will return a YAML representation of the XML object as a byte slice. func (x *XML) Render() ([]byte, error) { return yaml.Marshal(x) diff --git a/datamodel/high/node_builder.go b/datamodel/high/node_builder.go index b508f05..10e46d8 100644 --- a/datamodel/high/node_builder.go +++ b/datamodel/high/node_builder.go @@ -150,6 +150,16 @@ func (n *NodeBuilder) add(key string) { case reflect.Struct: y := value.Interface() if nb, ok := y.(low.HasValueNodeUntyped); ok { + + if nb.IsReference() { + if jk, kj := y.(low.HasKeyNode); kj { + nodeEntry.Line = jk.GetKeyNode().Line + break + } + panic("this should not break.") + + } + if nb.GetValueNode() != nil { nodeEntry.Line = nb.GetValueNode().Line } else { @@ -169,13 +179,32 @@ func (n *NodeBuilder) add(key string) { } } +func (n *NodeBuilder) renderReference() []*yaml.Node { + if fg, ok := n.Low.(low.IsReferenced); ok { + nodes := make([]*yaml.Node, 2) + nodes[0] = CreateStringNode("$ref") + nodes[1] = CreateStringNode(fg.GetReference()) + return nodes + } + return nil + +} + // Render will render the NodeBuilder back to a YAML node, iterating over every NodeEntry defined func (n *NodeBuilder) Render() *yaml.Node { // order nodes by line number, retain original order + m := CreateEmptyMapNode() + if fg, ok := n.Low.(low.IsReferenced); ok { + if fg.IsReference() { + m.Content = append(m.Content, n.renderReference()...) + return m + } + } + sort.Slice(n.Nodes, func(i, j int) bool { return n.Nodes[i].Line < n.Nodes[j].Line }) - m := CreateEmptyMapNode() + for i := range n.Nodes { node := n.Nodes[i] n.AddYAMLNode(m, node.Tag, node.Key, node.Value) @@ -296,7 +325,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any) Tag: uu.(string), Key: uu.(string), Line: 9999 + j, - Value: fg.MapIndex(ky).Interface(), + Value: m.MapIndex(k).Interface(), }) } } @@ -379,6 +408,32 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any) } var rawNode yaml.Node + m := reflect.ValueOf(value) + sl := CreateEmptySequenceNode() + for i := 0; i < m.Len(); i++ { + + sqi := m.Index(i).Interface() + if glu, ok := sqi.(GoesLowUntyped); ok { + if glu.GoLowUntyped().(low.IsReferenced).IsReference() { + + rt := CreateEmptyMapNode() + + nodes := make([]*yaml.Node, 2) + nodes[0] = CreateStringNode("$ref") + nodes[1] = CreateStringNode(glu.GoLowUntyped().(low.IsReferenced).GetReference()) + rt.Content = append(rt.Content, nodes...) + sl.Content = append(sl.Content, rt) + + } + } + + } + + if len(sl.Content) > 0 { + valueNode = sl + break + } + err := rawNode.Encode(value) if err != nil { return parent @@ -399,6 +454,15 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any) case reflect.Ptr: if r, ok := value.(Renderable); ok { + if gl, lg := value.(GoesLowUntyped); lg { + if gl.GoLowUntyped().(low.IsReferenced).IsReference() { + rvn := CreateEmptyMapNode() + rvn.Content = append(rvn.Content, CreateStringNode("$ref")) + rvn.Content = append(rvn.Content, CreateStringNode(gl.GoLowUntyped().(low.IsReferenced).GetReference())) + valueNode = rvn + break + } + } rawRender, _ := r.MarshalYAML() if rawRender != nil { valueNode = rawRender.(*yaml.Node) @@ -469,6 +533,14 @@ func CreateEmptyMapNode() *yaml.Node { return n } +func CreateEmptySequenceNode() *yaml.Node { + n := &yaml.Node{ + Kind: yaml.SequenceNode, + Tag: "!!seq", + } + return n +} + func CreateStringNode(str string) *yaml.Node { n := &yaml.Node{ Kind: yaml.ScalarNode, diff --git a/datamodel/high/shared.go b/datamodel/high/shared.go index a5504aa..24bce08 100644 --- a/datamodel/high/shared.go +++ b/datamodel/high/shared.go @@ -26,6 +26,15 @@ type GoesLow[T any] interface { GoLow() T } +// GoesLowUnTyped is used to represent any high-level model. All high level models meet this interface and can be used to +// extract low-level models from any high-level model. +type GoesLowUntyped interface { + + // GoLow returns the low-level object that was used to create the high-level object. This allows consumers + // to dive-down into the plumbing API at any point in the model. + GoLowUntyped() any +} + // ExtractExtensions is a convenience method for converting low-level extension definitions, to a high level map[string]any // definition that is easier to consume in applications. func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReference[any]) map[string]any { diff --git a/datamodel/high/v3/callback.go b/datamodel/high/v3/callback.go index bf3b08a..074f2ab 100644 --- a/datamodel/high/v3/callback.go +++ b/datamodel/high/v3/callback.go @@ -43,6 +43,11 @@ func (c *Callback) GoLow() *low.Callback { return c.low } +// GoLowUntyped will return the low-level Callback instance that was used to create the high-level one, with no type +func (c *Callback) GoLowUntyped() any { + return c.low +} + // Render will return a YAML representation of the Callback object as a byte slice. func (c *Callback) Render() ([]byte, error) { return yaml.Marshal(c) diff --git a/datamodel/high/v3/components.go b/datamodel/high/v3/components.go index 615e8be..6f050ae 100644 --- a/datamodel/high/v3/components.go +++ b/datamodel/high/v3/components.go @@ -30,20 +30,20 @@ const ( // will have no effect on the API unless they are explicitly referenced from properties outside the components object. // - https://spec.openapis.org/oas/v3.1.0#components-object type Components struct { - //Schemas map[string]*highbase.SchemaProxy `json:"schemas,omitempty" yaml:"schemas,omitempty"` - Schemas map[string]*highbase.SchemaProxy `json:"-" yaml:"-"` - Responses map[string]*Response `json:"-" yaml:"-"` - //Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` - Parameters map[string]*Parameter `json:"-" yaml:"-"` - //Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` - //Examples map[string]*highbase.Example `json:"examples,omitempty" yaml:"examples,omitempty"` - Examples map[string]*highbase.Example `json:"-" yaml:"-"` - RequestBodies map[string]*RequestBody `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` - Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` - SecuritySchemes map[string]*SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` - Links map[string]*Link `json:"links,omitempty" yaml:"links,omitempty"` - Callbacks map[string]*Callback `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Schemas map[string]*highbase.SchemaProxy `json:"schemas,omitempty" yaml:"schemas,omitempty"` + //Schemas map[string]*highbase.SchemaProxy `json:"-" yaml:"-"` + //Responses map[string]*Response `json:"-" yaml:"-"` + Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` + //Parameters map[string]*Parameter `json:"-" yaml:"-"` + Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Examples map[string]*highbase.Example `json:"examples,omitempty" yaml:"examples,omitempty"` + //Examples map[string]*highbase.Example `json:"-" yaml:"-"` + RequestBodies map[string]*RequestBody `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` + Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` + SecuritySchemes map[string]*SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` + Links map[string]*Link `json:"links,omitempty" yaml:"links,omitempty"` + Callbacks map[string]*Callback `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Components } diff --git a/datamodel/high/v3/document_test.go b/datamodel/high/v3/document_test.go index 8829d02..c311009 100644 --- a/datamodel/high/v3/document_test.go +++ b/datamodel/high/v3/document_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/url" "os" + "strings" "testing" "github.com/pb33f/libopenapi/datamodel" @@ -490,14 +491,149 @@ func TestDocument_MarshalYAML(t *testing.T) { os.WriteFile("rendered.yaml", r, 0644) // re-parse the document + + //TODO: pick up in the morning here, trying to figure out why headers are being rendered (UseOil) + info, _ := datamodel.ExtractSpecInfo(r) lDoc, e := lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration()) assert.Nil(t, e) highDoc := NewDocument(lDoc) assert.Equal(t, "3.1.0", highDoc.Version) - assert.Len(t, highDoc.Paths.PathItems, 1) + assert.Len(t, highDoc.Paths.PathItems, 5) } +func TestDocument_MarshalYAML_TestRefs(t *testing.T) { + + // create a new document + yml := `openapi: 3.1.0 +paths: + x-milky-milk: milky + /burgers: + x-burger-meta: meaty + post: + operationId: createBurger + tags: + - Burgers + summary: Create a new burger + description: A new burger for our menu, yummy yum yum. + responses: + "200": + headers: + UseOil: + $ref: '#/components/headers/UseOil' + description: A tasty burger for you to eat. + content: + application/json: + schema: + $ref: '#/components/schemas/Burger' + examples: + quarterPounder: + $ref: '#/components/examples/QuarterPounder' + filetOFish: + summary: a cripsy fish sammich filled with ocean goodness. + value: + name: Filet-O-Fish + numPatties: 1 +components: + headers: + UseOil: + description: this is a header example for UseOil + schema: + type: string + schemas: + Burger: + type: object + description: The tastiest food on the planet you would love to eat everyday + required: + - name + - numPatties + properties: + name: + type: string + description: The name of your tasty burger - burger names are listed in our menus + example: Big Mac + numPatties: + type: integer + description: The number of burger patties used + example: "2" + examples: + QuarterPounder: + summary: A juicy two hander sammich + value: + name: Quarter Pounder with Cheese + numPatties: 1` + + info, _ := datamodel.ExtractSpecInfo([]byte(yml)) + var err []error + lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: true, + AllowRemoteReferences: true, + }) + if err != nil { + panic("broken something") + } + h := NewDocument(lowDoc) + + // render the document to YAML and it should be identical. + r, _ := h.Render() + assert.Equal(t, yml, strings.TrimSpace(string(r))) +} + +func TestDocument_MarshalYAML_TestParamRefs(t *testing.T) { + + // create a new document + yml := `openapi: 3.1.0 +paths: + "/burgers/{burgerId}": + get: + operationId: locateBurger + tags: + - Burgers + summary: Search a burger by ID - returns the burger with that identifier + description: Look up a tasty burger take it and enjoy it + parameters: + - $ref: "#/components/parameters/BurgerId" + - $ref: "#/components/parameters/BurgerHeader" +components: + parameters: + BurgerHeader: + in: header + name: burgerHeader + schema: + properties: + burgerTheme: + type: string + description: something about a theme goes in here? + burgerTime: + type: number + description: number of burgers ordered so far this year. + BurgerId: + in: path + name: burgerId + schema: + type: string + example: big-mac + description: the name of the burger. use this to order your tasty burger + required: true +` + + info, _ := datamodel.ExtractSpecInfo([]byte(yml)) + var err []error + lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: true, + AllowRemoteReferences: true, + }) + if err != nil { + panic("broken something") + } + h := NewDocument(lowDoc) + + // render the document to YAML and it should be identical. + r, _ := h.Render() + assert.Equal(t, yml, strings.TrimSpace(string(r))) +} + + diff --git a/datamodel/high/v3/encoding.go b/datamodel/high/v3/encoding.go index dde562b..51aaf80 100644 --- a/datamodel/high/v3/encoding.go +++ b/datamodel/high/v3/encoding.go @@ -38,6 +38,11 @@ func (e *Encoding) GoLow() *low.Encoding { return e.low } +// GoLowUntyped will return the low-level Encoding instance that was used to create the high-level one, with no type +func (e *Encoding) GoLowUntyped() any { + return e.low +} + // Render will return a YAML representation of the Encoding object as a byte slice. func (e *Encoding) Render() ([]byte, error) { return yaml.Marshal(e) diff --git a/datamodel/high/v3/header.go b/datamodel/high/v3/header.go index e645649..af4f8fc 100644 --- a/datamodel/high/v3/header.go +++ b/datamodel/high/v3/header.go @@ -59,6 +59,11 @@ func (h *Header) GoLow() *low.Header { return h.low } +// GoLowUntyped will return the low-level Header instance that was used to create the high-level one, with no type +func (h *Header) GoLowUntyped() any { + return h.low +} + // ExtractHeaders will extract a hard to navigate low-level Header map, into simple high-level one. func ExtractHeaders(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Header]) map[string]*Header { extracted := make(map[string]*Header) diff --git a/datamodel/high/v3/link.go b/datamodel/high/v3/link.go index 3151711..1673d8e 100644 --- a/datamodel/high/v3/link.go +++ b/datamodel/high/v3/link.go @@ -57,6 +57,11 @@ func (l *Link) GoLow() *low.Link { return l.low } +// GoLowUntyped will return the low-level Link instance that was used to create the high-level one, with no type +func (l *Link) GoLowUntyped() any { + return l.low +} + // Render will return a YAML representation of the Link object as a byte slice. func (l *Link) Render() ([]byte, error) { return yaml.Marshal(l) diff --git a/datamodel/high/v3/media_type.go b/datamodel/high/v3/media_type.go index 58b3203..353a8e4 100644 --- a/datamodel/high/v3/media_type.go +++ b/datamodel/high/v3/media_type.go @@ -44,6 +44,11 @@ func (m *MediaType) GoLow() *low.MediaType { return m.low } +// GoLowUntyped will return the low-level MediaType instance that was used to create the high-level one, with no type +func (m *MediaType) GoLowUntyped() any { + return m.low +} + // Render will return a YAML representation of the MediaType object as a byte slice. func (m *MediaType) Render() ([]byte, error) { return yaml.Marshal(m) diff --git a/datamodel/high/v3/oauth_flow.go b/datamodel/high/v3/oauth_flow.go index 951e7d7..e0174cd 100644 --- a/datamodel/high/v3/oauth_flow.go +++ b/datamodel/high/v3/oauth_flow.go @@ -41,6 +41,11 @@ func (o *OAuthFlow) GoLow() *low.OAuthFlow { return o.low } +// GoLowUntyped will return the low-level Discriminator instance that was used to create the high-level one, with no type +func (o *OAuthFlow) GoLowUntyped() any { + return o.low +} + // Render will return a YAML representation of the OAuthFlow object as a byte slice. func (o *OAuthFlow) Render() ([]byte, error) { return yaml.Marshal(o) diff --git a/datamodel/high/v3/oauth_flows.go b/datamodel/high/v3/oauth_flows.go index 008c87e..16b6819 100644 --- a/datamodel/high/v3/oauth_flows.go +++ b/datamodel/high/v3/oauth_flows.go @@ -48,6 +48,11 @@ func (o *OAuthFlows) GoLow() *low.OAuthFlows { return o.low } +// GoLowUntyped will return the low-level OAuthFlows instance that was used to create the high-level one, with no type +func (o *OAuthFlows) GoLowUntyped() any { + return o.low +} + // Render will return a YAML representation of the OAuthFlows object as a byte slice. func (o *OAuthFlows) Render() ([]byte, error) { return yaml.Marshal(o) diff --git a/datamodel/high/v3/operation.go b/datamodel/high/v3/operation.go index 9eab93a..2057719 100644 --- a/datamodel/high/v3/operation.go +++ b/datamodel/high/v3/operation.go @@ -91,6 +91,11 @@ func (o *Operation) GoLow() *low.Operation { return o.low } +// GoLowUntyped will return the low-level Discriminator instance that was used to create the high-level one, with no type +func (o *Operation) GoLowUntyped() any { + return o.low +} + // Render will return a YAML representation of the Operation object as a byte slice. func (o *Operation) Render() ([]byte, error) { return yaml.Marshal(o) diff --git a/datamodel/high/v3/parameter.go b/datamodel/high/v3/parameter.go index a55d8d1..681a680 100644 --- a/datamodel/high/v3/parameter.go +++ b/datamodel/high/v3/parameter.go @@ -62,6 +62,11 @@ func (p *Parameter) GoLow() *low.Parameter { return p.low } +// GoLowUntyped will return the low-level Discriminator instance that was used to create the high-level one, with no type +func (p *Parameter) GoLowUntyped() any { + return p.low +} + // Render will return a YAML representation of the Encoding object as a byte slice. func (p *Parameter) Render() ([]byte, error) { return yaml.Marshal(p) diff --git a/datamodel/high/v3/path_item.go b/datamodel/high/v3/path_item.go index d60add8..03510c7 100644 --- a/datamodel/high/v3/path_item.go +++ b/datamodel/high/v3/path_item.go @@ -124,6 +124,11 @@ func (p *PathItem) GoLow() *low.PathItem { return p.low } +// GoLowUntyped will return the low-level PathItem instance that was used to create the high-level one, with no type +func (p *PathItem) GoLowUntyped() any { + return p.low +} + func (p *PathItem) GetOperations() map[string]*Operation { o := make(map[string]*Operation) if p.Get != nil { diff --git a/datamodel/high/v3/paths.go b/datamodel/high/v3/paths.go index b2dd1fe..a383383 100644 --- a/datamodel/high/v3/paths.go +++ b/datamodel/high/v3/paths.go @@ -58,6 +58,11 @@ func (p *Paths) GoLow() *low.Paths { return p.low } +// GoLowUntyped will return the low-level Paths instance that was used to create the high-level one, with no type +func (p *Paths) GoLowUntyped() any { + return p.low +} + // Render will return a YAML representation of the Paths object as a byte slice. func (p *Paths) Render() ([]byte, error) { return yaml.Marshal(p) @@ -88,7 +93,7 @@ func (p *Paths) MarshalYAML() (interface{}, error) { nb := high.NewNodeBuilder(p, p.low) extNode := nb.Render() - if extNode.Content != nil { + if extNode != nil && extNode.Content != nil { for u := range extNode.Content { mapped = append(mapped, &pathItem{nil, extNode.Content[u].Value, extNode.Content[u].Line, extNode.Content[u]}) diff --git a/datamodel/high/v3/request_body.go b/datamodel/high/v3/request_body.go index 55767e2..3237868 100644 --- a/datamodel/high/v3/request_body.go +++ b/datamodel/high/v3/request_body.go @@ -35,6 +35,11 @@ func (r *RequestBody) GoLow() *low.RequestBody { return r.low } +// GoLowUntyped will return the low-level RequestBody instance that was used to create the high-level one, with no type +func (r *RequestBody) GoLowUntyped() any { + return r.low +} + // Render will return a YAML representation of the RequestBody object as a byte slice. func (r *RequestBody) Render() ([]byte, error) { return yaml.Marshal(r) diff --git a/datamodel/high/v3/response.go b/datamodel/high/v3/response.go index 7b86cb0..5f9b4c0 100644 --- a/datamodel/high/v3/response.go +++ b/datamodel/high/v3/response.go @@ -50,6 +50,11 @@ func (r *Response) GoLow() *low.Response { return r.low } +// GoLowUntyped will return the low-level Response instance that was used to create the high-level one, with no type +func (r *Response) GoLowUntyped() any { + return r.low +} + // Render will return a YAML representation of the Response object as a byte slice. func (r *Response) Render() ([]byte, error) { return yaml.Marshal(r) diff --git a/datamodel/high/v3/responses.go b/datamodel/high/v3/responses.go index 9d9689d..516c33e 100644 --- a/datamodel/high/v3/responses.go +++ b/datamodel/high/v3/responses.go @@ -80,6 +80,11 @@ func (r *Responses) GoLow() *low.Responses { return r.low } +// GoLowUntyped will return the low-level Responses instance that was used to create the high-level one, with no type +func (r *Responses) GoLowUntyped() any { + return r.low +} + // Render will return a YAML representation of the Responses object as a byte slice. func (r *Responses) Render() ([]byte, error) { return yaml.Marshal(r) diff --git a/datamodel/high/v3/security_scheme.go b/datamodel/high/v3/security_scheme.go index f875064..3b0597f 100644 --- a/datamodel/high/v3/security_scheme.go +++ b/datamodel/high/v3/security_scheme.go @@ -55,6 +55,11 @@ func (s *SecurityScheme) GoLow() *low.SecurityScheme { return s.low } +// GoLowUntyped will return the low-level SecurityScheme instance that was used to create the high-level one, with no type +func (s *SecurityScheme) GoLowUntyped() any { + return s.low +} + // Render will return a YAML representation of the SecurityScheme object as a byte slice. func (s *SecurityScheme) Render() ([]byte, error) { return yaml.Marshal(s) diff --git a/datamodel/high/v3/server.go b/datamodel/high/v3/server.go index b170b34..7202432 100644 --- a/datamodel/high/v3/server.go +++ b/datamodel/high/v3/server.go @@ -39,6 +39,11 @@ func (s *Server) GoLow() *low.Server { return s.low } +// GoLowUntyped will return the low-level Server instance that was used to create the high-level one, with no type +func (s *Server) GoLowUntyped() any { + return s.low +} + // Render will return a YAML representation of the Server object as a byte slice. func (s *Server) Render() ([]byte, error) { return yaml.Marshal(s) diff --git a/datamodel/high/v3/server_variable.go b/datamodel/high/v3/server_variable.go index 408e178..0f01e55 100644 --- a/datamodel/high/v3/server_variable.go +++ b/datamodel/high/v3/server_variable.go @@ -41,6 +41,11 @@ func (s *ServerVariable) GoLow() *low.ServerVariable { return s.low } +// GoLowUntyped will return the low-level ServerVariable instance that was used to create the high-level one, with no type +func (s *ServerVariable) GoLowUntyped() any { + return s.low +} + // Render will return a YAML representation of the ServerVariable object as a byte slice. func (s *ServerVariable) Render() ([]byte, error) { return yaml.Marshal(s) diff --git a/datamodel/low/base/contact.go b/datamodel/low/base/contact.go index 8964468..f232678 100644 --- a/datamodel/low/base/contact.go +++ b/datamodel/low/base/contact.go @@ -18,10 +18,12 @@ type Contact struct { Name low.NodeReference[string] URL low.NodeReference[string] Email low.NodeReference[string] + *low.Reference } // Build is not implemented for Contact (there is nothing to build). func (c *Contact) Build(root *yaml.Node, idx *index.SpecIndex) error { + c.Reference = new(low.Reference) // not implemented. return nil } diff --git a/datamodel/low/base/discriminator.go b/datamodel/low/base/discriminator.go index 88f2675..cb965e3 100644 --- a/datamodel/low/base/discriminator.go +++ b/datamodel/low/base/discriminator.go @@ -21,6 +21,7 @@ import ( type Discriminator struct { PropertyName low.NodeReference[string] Mapping map[low.KeyReference[string]]low.ValueReference[string] + low.Reference } // FindMappingValue will return a ValueReference containing the string mapping value diff --git a/datamodel/low/base/example.go b/datamodel/low/base/example.go index cc5ce47..f299616 100644 --- a/datamodel/low/base/example.go +++ b/datamodel/low/base/example.go @@ -23,6 +23,7 @@ type Example struct { Value low.NodeReference[any] ExternalValue low.NodeReference[string] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindExtension returns a ValueReference containing the extension value, if found. @@ -59,6 +60,7 @@ func (ex *Example) Hash() [32]byte { // Build extracts extensions and example value func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error { + ex.Reference = new(low.Reference) ex.Extensions = low.ExtractExtensions(root) _, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content) diff --git a/datamodel/low/base/external_doc.go b/datamodel/low/base/external_doc.go index a977dbb..1acfeeb 100644 --- a/datamodel/low/base/external_doc.go +++ b/datamodel/low/base/external_doc.go @@ -22,6 +22,7 @@ type ExternalDoc struct { Description low.NodeReference[string] URL low.NodeReference[string] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindExtension returns a ValueReference containing the extension value, if found. @@ -31,6 +32,7 @@ func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] { // Build will extract extensions from the ExternalDoc instance. func (ex *ExternalDoc) Build(root *yaml.Node, idx *index.SpecIndex) error { + ex.Reference = new(low.Reference) ex.Extensions = low.ExtractExtensions(root) return nil } diff --git a/datamodel/low/base/info.go b/datamodel/low/base/info.go index dfe75d7..e7c126e 100644 --- a/datamodel/low/base/info.go +++ b/datamodel/low/base/info.go @@ -30,6 +30,7 @@ type Info struct { License low.NodeReference[*License] Version low.NodeReference[string] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindExtension attempts to locate an extension with the supplied key @@ -44,6 +45,7 @@ func (i *Info) GetExtensions() map[low.KeyReference[string]]low.ValueReference[a // Build will extract out the Contact and Info objects from the supplied root node. func (i *Info) Build(root *yaml.Node, idx *index.SpecIndex) error { + i.Reference = new(low.Reference) i.Extensions = low.ExtractExtensions(root) // extract contact diff --git a/datamodel/low/base/license.go b/datamodel/low/base/license.go index d957afd..96b54c8 100644 --- a/datamodel/low/base/license.go +++ b/datamodel/low/base/license.go @@ -17,10 +17,12 @@ import ( type License struct { Name low.NodeReference[string] URL low.NodeReference[string] + *low.Reference } // Build is not implemented for License (there is nothing to build) func (l *License) Build(root *yaml.Node, idx *index.SpecIndex) error { + l.Reference = new(low.Reference) return nil } diff --git a/datamodel/low/base/schema.go b/datamodel/low/base/schema.go index f7447e9..5edcfee 100644 --- a/datamodel/low/base/schema.go +++ b/datamodel/low/base/schema.go @@ -125,6 +125,7 @@ type Schema struct { // Parent Proxy refers back to the low level SchemaProxy that is proxying this schema. ParentProxy *SchemaProxy + *low.Reference } // Hash will calculate a SHA256 hash from the values of the schema, This allows equality checking against @@ -504,6 +505,7 @@ func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference // - UnevaluatedItems // - UnevaluatedProperties func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { + s.Reference = new(low.Reference) if h, _, _ := utils.IsNodeRefValue(root); h { ref, err := low.LocateRefNode(root, idx) if ref != nil { @@ -1233,7 +1235,8 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S if schNode != nil { // check if schema has already been built. schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, isReference: isRef, referenceLookup: refLocation} - return &low.NodeReference[*SchemaProxy]{Value: schema, KeyNode: schLabel, ValueNode: schNode}, nil + return &low.NodeReference[*SchemaProxy]{Value: schema, KeyNode: schLabel, ValueNode: schNode, ReferenceNode: isRef, + Reference: refLocation}, nil } return nil, nil } diff --git a/datamodel/low/base/schema_proxy.go b/datamodel/low/base/schema_proxy.go index 8035233..a3ac15e 100644 --- a/datamodel/low/base/schema_proxy.go +++ b/datamodel/low/base/schema_proxy.go @@ -104,6 +104,21 @@ func (sp *SchemaProxy) IsSchemaReference() bool { return sp.isReference } +// IsReference is an alias for IsSchemaReference() except it's compatible wih the IsReferenced interface type. +func (sp *SchemaProxy) IsReference() bool { + return sp.IsSchemaReference() +} + +// GetReference is an alias for GetSchemaReference() except it's compatible wih the IsReferenced interface type. +func (sp *SchemaProxy) GetReference() string { + return sp.GetSchemaReference() +} + +// SetReference will set the reference lookup for this SchemaProxy. +func (sp *SchemaProxy) SetReference(ref string) { + sp.referenceLookup = ref +} + // GetSchemaReference will return the lookup defined by the $ref that this schema points to. If the schema // is inline, and not a reference, then this method returns an empty string. Only useful when combined with // IsSchemaReference() diff --git a/datamodel/low/base/security_requirement.go b/datamodel/low/base/security_requirement.go index 94d7218..ac57e6e 100644 --- a/datamodel/low/base/security_requirement.go +++ b/datamodel/low/base/security_requirement.go @@ -23,10 +23,12 @@ import ( // - https://swagger.io/specification/#security-requirement-object type SecurityRequirement struct { Requirements low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]] + *low.Reference } // Build will extract security requirements from the node (the structure is odd, to be honest) func (s *SecurityRequirement) Build(root *yaml.Node, _ *index.SpecIndex) error { + s.Reference = new(low.Reference) var labelNode *yaml.Node valueMap := make(map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]) var arr []low.ValueReference[string] diff --git a/datamodel/low/base/tag.go b/datamodel/low/base/tag.go index 31ba383..dc99cb9 100644 --- a/datamodel/low/base/tag.go +++ b/datamodel/low/base/tag.go @@ -24,6 +24,7 @@ type Tag struct { Description low.NodeReference[string] ExternalDocs low.NodeReference[*ExternalDoc] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindExtension returns a ValueReference containing the extension value, if found. @@ -33,6 +34,7 @@ func (t *Tag) FindExtension(ext string) *low.ValueReference[any] { // Build will extract extensions and external docs for the Tag. func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error { + t.Reference = new(low.Reference) t.Extensions = low.ExtractExtensions(root) // extract externalDocs diff --git a/datamodel/low/base/xml.go b/datamodel/low/base/xml.go index c4eb6ca..607f709 100644 --- a/datamodel/low/base/xml.go +++ b/datamodel/low/base/xml.go @@ -25,10 +25,12 @@ type XML struct { Attribute low.NodeReference[bool] Wrapped low.NodeReference[bool] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // Build will extract extensions from the XML instance. func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error { + x.Reference = new(low.Reference) x.Extensions = low.ExtractExtensions(root) return nil } diff --git a/datamodel/low/extraction_functions.go b/datamodel/low/extraction_functions.go index 4997df4..756977c 100644 --- a/datamodel/low/extraction_functions.go +++ b/datamodel/low/extraction_functions.go @@ -146,6 +146,12 @@ func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecInd if err != nil { return n, err, isReference, referenceValue } + + // if this is a reference, keep track of the reference in the value + if isReference { + SetReference(n, referenceValue) + } + // do we want to throw an error as well if circular error reporting is on? if circError != nil && !idx.AllowCircularReferenceResolving() { return n, circError, isReference, referenceValue @@ -208,13 +214,19 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in return NodeReference[T]{}, err } - res := NodeReference[T]{ - Value: n, - KeyNode: ln, - ValueNode: vn, - IsReference: isReference, - Reference: referenceValue, + // if this is a reference, keep track of the reference in the value + if isReference { + SetReference(n, referenceValue) } + + res := NodeReference[T]{ + Value: n, + KeyNode: ln, + ValueNode: vn, + ReferenceNode: isReference, + Reference: referenceValue, + } + // do we want to throw an error as well if circular error reporting is on? if circError != nil && !idx.AllowCircularReferenceResolving() { return res, circError @@ -222,6 +234,15 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in return res, nil } +func SetReference(obj any, ref string) { + if obj == nil { + return + } + if r, ok := obj.(IsReferenced); ok { + r.SetReference(ref) + } +} + // ExtractArray will extract a slice of []ValueReference[T] from a root yaml.Node that is defined as a sequence. // Used when the value being extracted is an array. func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) ([]ValueReference[T], @@ -229,15 +250,11 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind ) { var ln, vn *yaml.Node var circError error - var isReference bool - var referenceValue string - if rf, rl, rv := utils.IsNodeRefValue(root); rf { + if rf, rl, _ := utils.IsNodeRefValue(root); rf { ref, err := LocateRefNode(root, idx) if ref != nil { vn = ref ln = rl - isReference = true - referenceValue = rv if err != nil { circError = err } @@ -248,12 +265,11 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind } else { _, ln, vn = utils.FindKeyNodeFullTop(label, root.Content) if vn != nil { - if h, _, rVal := utils.IsNodeRefValue(vn); h { + if h, _, _ := utils.IsNodeRefValue(vn); h { ref, err := LocateRefNode(vn, idx) if ref != nil { vn = ref - isReference = true - referenceValue = rVal + //referenceValue = rVal if err != nil { circError = err } @@ -274,13 +290,13 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind } for _, node := range vn.Content { localReferenceValue := "" - localIsReference := false + //localIsReference := false if rf, _, rv := utils.IsNodeRefValue(node); rf { - ref, err := LocateRefNode(node, idx) - if ref != nil { - node = ref - localIsReference = true + refg, err := LocateRefNode(node, idx) + if refg != nil { + node = refg + //localIsReference = true localReferenceValue = rv if err != nil { circError = err @@ -302,16 +318,15 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind return nil, ln, vn, berr } - if localReferenceValue == "" { - localReferenceValue = referenceValue - localIsReference = isReference + if localReferenceValue != "" { + SetReference(n, localReferenceValue) } items = append(items, ValueReference[T]{ - Value: n, - ValueNode: node, - IsReference: localIsReference, - Reference: localReferenceValue, + Value: n, + ValueNode: node, + ReferenceNode: localReferenceValue != "", + Reference: localReferenceValue, }) } } @@ -395,14 +410,18 @@ func ExtractMapNoLookup[PT Buildable[N], N any]( if berr != nil { return nil, berr } + if isReference { + SetReference(n, referenceValue) + } + valueMap[KeyReference[string]{ Value: currentKey.Value, KeyNode: currentKey, }] = ValueReference[PT]{ - Value: n, - ValueNode: node, - IsReference: isReference, - Reference: referenceValue, + Value: n, + ValueNode: node, + //IsReference: isReference, + Reference: referenceValue, } } } @@ -427,7 +446,7 @@ func ExtractMap[PT Buildable[N], N any]( root *yaml.Node, idx *index.SpecIndex, ) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) { - var isReference bool + //var isReference bool var referenceValue string var labelNode, valueNode *yaml.Node var circError error @@ -437,7 +456,7 @@ func ExtractMap[PT Buildable[N], N any]( if ref != nil { valueNode = ref labelNode = rl - isReference = true + //isReference = true referenceValue = rv if err != nil { circError = err @@ -449,12 +468,12 @@ func ExtractMap[PT Buildable[N], N any]( } else { _, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content) if valueNode != nil { - if h, _, rv := utils.IsNodeRefValue(valueNode); h { + if h, _, rvt := utils.IsNodeRefValue(valueNode); h { ref, err := LocateRefNode(valueNode, idx) if ref != nil { valueNode = ref - isReference = true - referenceValue = rv + //isReference = true + referenceValue = rvt if err != nil { circError = err } @@ -474,7 +493,7 @@ func ExtractMap[PT Buildable[N], N any]( bChan := make(chan mappingResult[PT]) eChan := make(chan error) - buildMap := func(label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error) { + buildMap := func(label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error, ref string) { var n PT = new(N) _ = BuildModel(value, n) err := n.Build(value, idx) @@ -482,31 +501,40 @@ func ExtractMap[PT Buildable[N], N any]( ec <- err return } + + //isRef := false + if ref != "" { + //isRef = true + SetReference(n, ref) + } + c <- mappingResult[PT]{ k: KeyReference[string]{ KeyNode: label, Value: label.Value, }, v: ValueReference[PT]{ - Value: n, - ValueNode: value, - IsReference: isReference, - Reference: referenceValue, + Value: n, + ValueNode: value, + //IsReference: isRef, + Reference: ref, }, } } totalKeys := 0 for i, en := range valueNode.Content { + referenceValue = "" if i%2 == 0 { currentLabelNode = en continue } // check our valueNode isn't a reference still. - if h, _, _ := utils.IsNodeRefValue(en); h { + if h, _, refVal := utils.IsNodeRefValue(en); h { ref, err := LocateRefNode(en, idx) if ref != nil { en = ref + referenceValue = refVal if err != nil { circError = err } @@ -522,7 +550,7 @@ func ExtractMap[PT Buildable[N], N any]( continue // yo, don't pay any attention to extensions, not here anyway. } totalKeys++ - go buildMap(currentLabelNode, en, bChan, eChan) + go buildMap(currentLabelNode, en, bChan, eChan, referenceValue) } completedKeys := 0 diff --git a/datamodel/low/reference.go b/datamodel/low/reference.go index d4c6034..b03377c 100644 --- a/datamodel/low/reference.go +++ b/datamodel/low/reference.go @@ -11,6 +11,28 @@ const ( HASH = "%x" ) +type Reference struct { + Reference string `json:"-" yaml:"-"` +} + +func (r *Reference) GetReference() string { + return r.Reference +} + +func (r *Reference) IsReference() bool { + return r.Reference != "" +} + +func (r *Reference) SetReference(ref string) { + r.Reference = ref +} + +type IsReferenced interface { + IsReference() bool + GetReference() string + SetReference(string) +} + // Buildable is an interface for any struct that can be 'built out'. This means that a struct can accept // a root node and a reference to the index that carries data about any references used. // @@ -29,6 +51,7 @@ type HasValueNode[T any] interface { // HasValueNodeUntyped is implemented by NodeReference and ValueReference to return the yaml.Node backing the value. type HasValueNodeUntyped interface { GetValueNode() *yaml.Node + IsReferenced } // Hashable defines any struct that implements a Hash function that returns a 256SHA hash of the state of the @@ -85,7 +108,7 @@ type NodeReference[T any] struct { KeyNode *yaml.Node // Is this value actually a reference in the original tree? - IsReference bool + ReferenceNode bool // If HasReference is true, then Reference contains the original $ref value. Reference string @@ -113,7 +136,7 @@ type ValueReference[T any] struct { ValueNode *yaml.Node // Is this value actually a reference in the original tree? - IsReference bool + ReferenceNode bool // If HasReference is true, then Reference contains the original $ref value. Reference string @@ -132,12 +155,24 @@ func (n NodeReference[T]) NodeLineNumber() int { } } -// IsReferenceNode will return true if the key node contains a $ref key. -func (n NodeReference[T]) IsReferenceNode() bool { +func (n NodeReference[T]) GetReference() string { + return n.Reference +} + +func (n NodeReference[T]) SetReference(ref string) { + n.Reference = ref +} + +// IsReference will return true if the key node contains a $ref key. +func (n NodeReference[T]) IsReference() bool { + if n.ReferenceNode { + return true + } if n.KeyNode != nil { for k := range n.KeyNode.Content { if k%2 == 0 { if n.KeyNode.Content[k].Value == "$ref" { + n.ReferenceNode = true return true } } @@ -164,6 +199,11 @@ func (n NodeReference[T]) GetValueNode() *yaml.Node { return n.ValueNode } +// GetKeyNode will return the yaml.Node containing the reference key node +func (n NodeReference[T]) GetKeyNode() *yaml.Node { + return n.KeyNode +} + // GetValue will return the raw value of the node func (n NodeReference[T]) GetValue() T { return n.Value @@ -208,6 +248,22 @@ func (n ValueReference[T]) GetValueUntyped() any { return n.Value } +func (n ValueReference[T]) GetReference() string { + return n.Reference +} + +func (n ValueReference[T]) SetReference(ref string) { + n.Reference = ref +} + +// IsReference will return true if the key node contains a $ref +func (n ValueReference[T]) IsReference() bool { + if n.Reference != "" { + return true + } + return false +} + // IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored) func (n KeyReference[T]) IsEmpty() bool { return n.KeyNode == nil diff --git a/datamodel/low/reference_test.go b/datamodel/low/reference_test.go index 4942d4c..8fba042 100644 --- a/datamodel/low/reference_test.go +++ b/datamodel/low/reference_test.go @@ -40,7 +40,7 @@ func TestNodeReference_Mutate(t *testing.T) { n := nr.Mutate("nice one!") assert.NotNil(t, nr.GetValueNode()) assert.Empty(t, nr.GetValue()) - assert.False(t, nr.IsReferenceNode()) + assert.False(t, nr.IsReference()) assert.Equal(t, "nice one!", n.Value) assert.Equal(t, "nice one!", nr.ValueNode.Value) } @@ -52,7 +52,7 @@ func TestNodeReference_RefNode(t *testing.T) { Value: "$ref", }}, } - assert.True(t, nr.IsReferenceNode()) + assert.True(t, nr.IsReference()) } func TestValueReference_Mutate(t *testing.T) { diff --git a/datamodel/low/v2/definitions.go b/datamodel/low/v2/definitions.go index c1558cc..226a9c4 100644 --- a/datamodel/low/v2/definitions.go +++ b/datamodel/low/v2/definitions.go @@ -84,12 +84,12 @@ func (d *Definitions) Build(root *yaml.Node, idx *index.SpecIndex) error { var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*base.SchemaProxy], e chan error) { - obj, err, isRef, rv := low.ExtractObjectRaw[*base.SchemaProxy](value, idx) + obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](value, idx) if err != nil { e <- err } r <- definitionResult[*base.SchemaProxy]{k: label, v: low.ValueReference[*base.SchemaProxy]{ - Value: obj, ValueNode: value, IsReference: isRef, Reference: rv, + Value: obj, ValueNode: value, Reference: rv, }} } go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan) @@ -144,12 +144,12 @@ func (pd *ParameterDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) err var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*Parameter], e chan error) { - obj, err, isRef, rv := low.ExtractObjectRaw[*Parameter](value, idx) + obj, err, _, rv := low.ExtractObjectRaw[*Parameter](value, idx) if err != nil { e <- err } r <- definitionResult[*Parameter]{k: label, v: low.ValueReference[*Parameter]{Value: obj, - ValueNode: value, IsReference: isRef, Reference: rv}} + ValueNode: value, Reference: rv}} } go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan) } @@ -193,12 +193,12 @@ func (r *ResponsesDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) erro var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*Response], e chan error) { - obj, err, isRef, rv := low.ExtractObjectRaw[*Response](value, idx) + obj, err, _, rv := low.ExtractObjectRaw[*Response](value, idx) if err != nil { e <- err } r <- definitionResult[*Response]{k: label, v: low.ValueReference[*Response]{Value: obj, - ValueNode: value, IsReference: isRef, Reference: rv}} + ValueNode: value, Reference: rv}} } go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan) } @@ -236,12 +236,12 @@ func (s *SecurityDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) error var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*SecurityScheme], e chan error) { - obj, err, isRef, rv := low.ExtractObjectRaw[*SecurityScheme](value, idx) + obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](value, idx) if err != nil { e <- err } r <- definitionResult[*SecurityScheme]{k: label, v: low.ValueReference[*SecurityScheme]{ - Value: obj, ValueNode: value, IsReference: isRef, Reference: rv, + Value: obj, ValueNode: value, Reference: rv, }} } go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan) diff --git a/datamodel/low/v3/callback.go b/datamodel/low/v3/callback.go index 490f1e4..483ddbb 100644 --- a/datamodel/low/v3/callback.go +++ b/datamodel/low/v3/callback.go @@ -23,6 +23,7 @@ import ( type Callback struct { Expression low.ValueReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all Callback extensions and satisfies the low.HasExtensions interface. @@ -37,6 +38,7 @@ func (cb *Callback) FindExpression(exp string) *low.ValueReference[*PathItem] { // Build will extract extensions, expressions and PathItem objects for Callback func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error { + cb.Reference = new(low.Reference) cb.Extensions = low.ExtractExtensions(root) // handle callback @@ -51,7 +53,7 @@ func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error { if strings.HasPrefix(currentCB.Value, "x-") { continue // ignore extension. } - callback, eErr, isRef, rv := low.ExtractObjectRaw[*PathItem](callbackNode, idx) + callback, eErr, _, rv := low.ExtractObjectRaw[*PathItem](callbackNode, idx) if eErr != nil { return eErr } @@ -61,7 +63,6 @@ func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error { }] = low.ValueReference[*PathItem]{ Value: callback, ValueNode: callbackNode, - IsReference: isRef, Reference: rv, } } diff --git a/datamodel/low/v3/components.go b/datamodel/low/v3/components.go index ad8c603..05d5295 100644 --- a/datamodel/low/v3/components.go +++ b/datamodel/low/v3/components.go @@ -31,6 +31,7 @@ type Components struct { Links low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]] Callbacks low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all Components extensions and satisfies the low.HasExtensions interface. @@ -126,6 +127,7 @@ func (co *Components) FindCallback(callback string) *low.ValueReference[*Callbac } func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error { + co.Reference = new(low.Reference) co.Extensions = low.ExtractExtensions(root) // build out components asynchronously for speed. There could be some significant weight here. diff --git a/datamodel/low/v3/create_document.go b/datamodel/low/v3/create_document.go index 6ce7d45..9b6724c 100644 --- a/datamodel/low/v3/create_document.go +++ b/datamodel/low/v3/create_document.go @@ -127,10 +127,12 @@ func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInd if err != nil { return err } - doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{ - Value: sec, - KeyNode: ln, - ValueNode: vn, + if vn != nil && ln != nil { + doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{ + Value: sec, + KeyNode: ln, + ValueNode: vn, + } } return nil } diff --git a/datamodel/low/v3/encoding.go b/datamodel/low/v3/encoding.go index d294e7f..e9a5d08 100644 --- a/datamodel/low/v3/encoding.go +++ b/datamodel/low/v3/encoding.go @@ -20,6 +20,7 @@ type Encoding struct { Style low.NodeReference[string] Explode low.NodeReference[bool] AllowReserved low.NodeReference[bool] + *low.Reference } // FindHeader attempts to locate a Header with the supplied name @@ -57,6 +58,7 @@ func (en *Encoding) Hash() [32]byte { // Build will extract all Header objects from supplied node. func (en *Encoding) Build(root *yaml.Node, idx *index.SpecIndex) error { + en.Reference = new(low.Reference) headers, hL, hN, err := low.ExtractMap[*Header](HeadersLabel, root, idx) if err != nil { return err diff --git a/datamodel/low/v3/header.go b/datamodel/low/v3/header.go index d81dbf6..9d283d5 100644 --- a/datamodel/low/v3/header.go +++ b/datamodel/low/v3/header.go @@ -30,6 +30,7 @@ type Header struct { Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]] Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindExtension will attempt to locate an extension with the supplied name @@ -95,6 +96,7 @@ func (h *Header) Hash() [32]byte { // Build will extract extensions, examples, schema and content/media types from node. func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error { + h.Reference = new(low.Reference) h.Extensions = low.ExtractExtensions(root) // handle example if set. diff --git a/datamodel/low/v3/link.go b/datamodel/low/v3/link.go index 5e89c4e..e56ad0f 100644 --- a/datamodel/low/v3/link.go +++ b/datamodel/low/v3/link.go @@ -33,6 +33,7 @@ type Link struct { Description low.NodeReference[string] Server low.NodeReference[*Server] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all Link extensions and satisfies the low.HasExtensions interface. @@ -52,6 +53,7 @@ func (l *Link) FindExtension(ext string) *low.ValueReference[any] { // Build will extract extensions and servers from the node. func (l *Link) Build(root *yaml.Node, idx *index.SpecIndex) error { + l.Reference = new(low.Reference) l.Extensions = low.ExtractExtensions(root) // extract server. ser, sErr := low.ExtractObject[*Server](ServerLabel, root, idx) diff --git a/datamodel/low/v3/media_type.go b/datamodel/low/v3/media_type.go index 85ad2a5..a61e913 100644 --- a/datamodel/low/v3/media_type.go +++ b/datamodel/low/v3/media_type.go @@ -25,6 +25,7 @@ type MediaType struct { Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]] Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all MediaType extensions and satisfies the low.HasExtensions interface. @@ -54,6 +55,7 @@ func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueRefe // Build will extract examples, extensions, schema and encoding from node. func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error { + mt.Reference = new(low.Reference) mt.Extensions = low.ExtractExtensions(root) // handle example if set. diff --git a/datamodel/low/v3/oauth_flows.go b/datamodel/low/v3/oauth_flows.go index e91b5d3..5d91666 100644 --- a/datamodel/low/v3/oauth_flows.go +++ b/datamodel/low/v3/oauth_flows.go @@ -21,6 +21,7 @@ type OAuthFlows struct { ClientCredentials low.NodeReference[*OAuthFlow] AuthorizationCode low.NodeReference[*OAuthFlow] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all OAuthFlows extensions and satisfies the low.HasExtensions interface. @@ -35,6 +36,7 @@ func (o *OAuthFlows) FindExtension(ext string) *low.ValueReference[any] { // Build will extract extensions and all OAuthFlow types from the supplied node. func (o *OAuthFlows) Build(root *yaml.Node, idx *index.SpecIndex) error { + o.Reference = new(low.Reference) o.Extensions = low.ExtractExtensions(root) v, vErr := low.ExtractObject[*OAuthFlow](ImplicitLabel, root, idx) @@ -92,6 +94,7 @@ type OAuthFlow struct { RefreshUrl low.NodeReference[string] Scopes low.NodeReference[map[low.KeyReference[string]]low.ValueReference[string]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all OAuthFlow extensions and satisfies the low.HasExtensions interface. @@ -111,6 +114,7 @@ func (o *OAuthFlow) FindExtension(ext string) *low.ValueReference[any] { // Build will extract extensions from the node. func (o *OAuthFlow) Build(root *yaml.Node, idx *index.SpecIndex) error { + o.Reference = new(low.Reference) o.Extensions = low.ExtractExtensions(root) return nil } diff --git a/datamodel/low/v3/operation.go b/datamodel/low/v3/operation.go index 07d74ba..e3badf7 100644 --- a/datamodel/low/v3/operation.go +++ b/datamodel/low/v3/operation.go @@ -33,6 +33,7 @@ type Operation struct { Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]] Servers low.NodeReference[[]low.ValueReference[*Server]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindCallback will attempt to locate a Callback instance by the supplied name. @@ -54,6 +55,7 @@ func (o *Operation) FindSecurityRequirement(name string) []low.ValueReference[st // Build will extract external docs, parameters, request body, responses, callbacks, security and servers. func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error { + o.Reference = new(low.Reference) o.Extensions = low.ExtractExtensions(root) // extract externalDocs diff --git a/datamodel/low/v3/parameter.go b/datamodel/low/v3/parameter.go index 0ef9777..1a5bdd0 100644 --- a/datamodel/low/v3/parameter.go +++ b/datamodel/low/v3/parameter.go @@ -34,6 +34,7 @@ type Parameter struct { Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]] Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindContent will attempt to locate a MediaType instance using the specified name. @@ -58,6 +59,7 @@ func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueRefere // Build will extract examples, extensions and content/media types. func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error { + p.Reference = new(low.Reference) p.Extensions = low.ExtractExtensions(root) // handle example if set. diff --git a/datamodel/low/v3/path_item.go b/datamodel/low/v3/path_item.go index 6e9439c..1d7ebaa 100644 --- a/datamodel/low/v3/path_item.go +++ b/datamodel/low/v3/path_item.go @@ -35,6 +35,7 @@ type PathItem struct { Servers low.NodeReference[[]low.ValueReference[*Server]] Parameters low.NodeReference[[]low.ValueReference[*Parameter]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // Hash will return a consistent SHA256 Hash of the PathItem object @@ -107,6 +108,7 @@ func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReferen // Build extracts extensions, parameters, servers and each http method defined. // everything is extracted asynchronously for speed. func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error { + p.Reference = new(low.Reference) p.Extensions = low.ExtractExtensions(root) skip := false var currentNode *yaml.Node diff --git a/datamodel/low/v3/paths.go b/datamodel/low/v3/paths.go index a88ce8a..a88774d 100644 --- a/datamodel/low/v3/paths.go +++ b/datamodel/low/v3/paths.go @@ -23,6 +23,7 @@ import ( type Paths struct { PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindPath will attempt to locate a PathItem using the provided path string. @@ -57,6 +58,7 @@ func (p *Paths) GetExtensions() map[low.KeyReference[string]]low.ValueReference[ // Build will extract extensions and all PathItems. This happens asynchronously for speed. func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error { + p.Reference = new(low.Reference) p.Extensions = low.ExtractExtensions(root) skip := false var currentNode *yaml.Node diff --git a/datamodel/low/v3/request_body.go b/datamodel/low/v3/request_body.go index eed0e0c..d11c6d8 100644 --- a/datamodel/low/v3/request_body.go +++ b/datamodel/low/v3/request_body.go @@ -20,6 +20,7 @@ type RequestBody struct { Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] Required low.NodeReference[bool] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // FindExtension attempts to locate an extension using the provided name. @@ -39,6 +40,7 @@ func (rb *RequestBody) FindContent(cType string) *low.ValueReference[*MediaType] // Build will extract extensions and MediaType objects from the node. func (rb *RequestBody) Build(root *yaml.Node, idx *index.SpecIndex) error { + rb.Reference = new(low.Reference) rb.Extensions = low.ExtractExtensions(root) // handle content, if set. diff --git a/datamodel/low/v3/response.go b/datamodel/low/v3/response.go index e7396d3..7fe9690 100644 --- a/datamodel/low/v3/response.go +++ b/datamodel/low/v3/response.go @@ -24,6 +24,7 @@ type Response struct { Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] Extensions map[low.KeyReference[string]]low.ValueReference[any] Links low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]] + *low.Reference } // FindExtension will attempt to locate an extension using the supplied key @@ -53,6 +54,7 @@ func (r *Response) FindLink(hType string) *low.ValueReference[*Link] { // Build will extract headers, extensions, content and links from node. func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error { + r.Reference = new(low.Reference) r.Extensions = low.ExtractExtensions(root) //extract headers diff --git a/datamodel/low/v3/responses.go b/datamodel/low/v3/responses.go index b6bb301..d75fbe2 100644 --- a/datamodel/low/v3/responses.go +++ b/datamodel/low/v3/responses.go @@ -36,6 +36,7 @@ type Responses struct { Codes map[low.KeyReference[string]]low.ValueReference[*Response] Default low.NodeReference[*Response] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all Responses extensions and satisfies the low.HasExtensions interface. @@ -45,6 +46,7 @@ func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueRefere // Build will extract default response and all Response objects for each code func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error { + r.Reference = new(low.Reference) r.Extensions = low.ExtractExtensions(root) if utils.IsNodeMap(root) { codes, err := low.ExtractMapNoLookup[*Response](root, idx) diff --git a/datamodel/low/v3/security_scheme.go b/datamodel/low/v3/security_scheme.go index ae01969..468a8c8 100644 --- a/datamodel/low/v3/security_scheme.go +++ b/datamodel/low/v3/security_scheme.go @@ -33,26 +33,9 @@ type SecurityScheme struct { Flows low.NodeReference[*OAuthFlows] OpenIdConnectUrl low.NodeReference[string] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } -// SecurityRequirement is a low-level representation of an OpenAPI 3+ SecurityRequirement object. -// -// It lists the required security schemes to execute this operation. The name used for each property MUST correspond -// to a security scheme declared in the Security Schemes under the Components Object. -// -// Security Requirement Objects that contain multiple schemes require that all schemes MUST be satisfied for a -// request to be authorized. This enables support for scenarios where multiple query parameters or HTTP headers are -// required to convey security information. -// -// When a list of Security Requirement Objects is defined on the OpenAPI Object or Operation Object, only one of the -// Security Requirement Objects in the list needs to be satisfied to authorize the request. -// - https://spec.openapis.org/oas/v3.1.0#security-requirement-object -//type SecurityRequirement struct { -// -// // FYI, I hate this data structure. Even without the low level wrapping, it sucks. -// ValueRequirements []low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]] -//} - // FindExtension attempts to locate an extension using the supplied key. func (ss *SecurityScheme) FindExtension(ext string) *low.ValueReference[any] { return low.FindItemInMap[any](ext, ss.Extensions) @@ -65,6 +48,7 @@ func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.Value // Build will extract OAuthFlows and extensions from the node. func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error { + ss.Reference = new(low.Reference) ss.Extensions = low.ExtractExtensions(root) oa, oaErr := low.ExtractObject[*OAuthFlows](OAuthFlowsLabel, root, idx) diff --git a/datamodel/low/v3/server.go b/datamodel/low/v3/server.go index 67eda24..92e2210 100644 --- a/datamodel/low/v3/server.go +++ b/datamodel/low/v3/server.go @@ -20,6 +20,7 @@ type Server struct { Description low.NodeReference[string] Variables low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*ServerVariable]] Extensions map[low.KeyReference[string]]low.ValueReference[any] + *low.Reference } // GetExtensions returns all Paths extensions and satisfies the low.HasExtensions interface. @@ -34,6 +35,7 @@ func (s *Server) FindVariable(serverVar string) *low.ValueReference[*ServerVaria // Build will extract server variables from the supplied node. func (s *Server) Build(root *yaml.Node, idx *index.SpecIndex) error { + s.Reference = new(low.Reference) s.Extensions = low.ExtractExtensions(root) kn, vars := utils.FindKeyNode(VariablesLabel, root.Content) if vars == nil { @@ -50,6 +52,7 @@ func (s *Server) Build(root *yaml.Node, idx *index.SpecIndex) error { continue } variable := ServerVariable{} + variable.Reference = new(low.Reference) _ = low.BuildModel(varNode, &variable) variablesMap[low.KeyReference[string]{ Value: currentNode, diff --git a/datamodel/low/v3/server_variable.go b/datamodel/low/v3/server_variable.go index d08a8d3..e538a3c 100644 --- a/datamodel/low/v3/server_variable.go +++ b/datamodel/low/v3/server_variable.go @@ -19,6 +19,7 @@ type ServerVariable struct { Enum []low.NodeReference[string] Default low.NodeReference[string] Description low.NodeReference[string] + *low.Reference } // Hash will return a consistent SHA256 Hash of the ServerVariable object