From d808fd6d502721310120aa3773ad3e5e89110f9e Mon Sep 17 00:00:00 2001 From: "red-hat-konflux-kflux-prd-rh02[bot]" <190377777+red-hat-konflux-kflux-prd-rh02[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 00:34:18 +0000 Subject: [PATCH] Update module github.com/pb33f/libopenapi to v0.37.2 Signed-off-by: red-hat-konflux-kflux-prd-rh02 <190377777+red-hat-konflux-kflux-prd-rh02[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/pb33f/libopenapi/.gitignore | 3 +- vendor/github.com/pb33f/libopenapi/README.md | 2 + .../libopenapi/datamodel/document_config.go | 12 +- .../libopenapi/datamodel/high/base/schema.go | 76 +++-- .../datamodel/high/base/schema_proxy.go | 318 +++++++++++++++++- .../libopenapi/datamodel/high/node_builder.go | 33 ++ .../datamodel/low/base/discriminator.go | 64 ++++ .../libopenapi/datamodel/low/base/schema.go | 92 ++--- .../datamodel/low/base/schema_build.go | 3 + .../low/base/schema_build_helpers.go | 34 +- .../datamodel/low/base/schema_proxy.go | 74 +++- .../low/base/sibling_ref_transformer.go | 74 +++- .../libopenapi/datamodel/low/v2/swagger.go | 10 +- .../datamodel/low/v3/create_document.go | 4 +- .../pb33f/libopenapi/datamodel/spec_info.go | 154 +++++++-- .../github.com/pb33f/libopenapi/document.go | 2 +- .../libopenapi/index/extract_refs_ref.go | 2 + .../libopenapi/index/find_component_build.go | 1 + .../pb33f/libopenapi/index/index_model.go | 1 + .../libopenapi/index/resolver_relatives.go | 1 + vendor/modules.txt | 2 +- 23 files changed, 775 insertions(+), 193 deletions(-) diff --git a/go.mod b/go.mod index e3f660eb1..60c5affc4 100644 --- a/go.mod +++ b/go.mod @@ -144,7 +144,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.150.0 // indirect github.com/openai/openai-go/v3 v3.32.0 // indirect github.com/pb33f/jsonpath v0.8.2 // indirect - github.com/pb33f/libopenapi v0.36.1 // indirect + github.com/pb33f/libopenapi v0.37.2 // indirect github.com/pb33f/libopenapi-validator v0.13.4 // indirect github.com/pb33f/ordered-map/v2 v2.3.1 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect diff --git a/go.sum b/go.sum index 49f9dee7a..a07d25b9f 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,8 @@ github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pb33f/jsonpath v0.8.2 h1:Ou4C7zjYClBm97dfZjDCjdZGusJoynv/vrtiEKNfj2Y= github.com/pb33f/jsonpath v0.8.2/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= -github.com/pb33f/libopenapi v0.36.1 h1:CNZ52e+/W9fA1kAgL8EePDQQrKPfN9+HdLR6XAxUEpw= -github.com/pb33f/libopenapi v0.36.1/go.mod h1:MsDdUlQ1CdrIDO5v26JfgBxQs7kcaOUEpMP3EqU6bI4= +github.com/pb33f/libopenapi v0.37.2 h1:4Kb4w/h2BVKb099oYIZqeDxEBhUioWA+z6WJhBOk2r8= +github.com/pb33f/libopenapi v0.37.2/go.mod h1:MsDdUlQ1CdrIDO5v26JfgBxQs7kcaOUEpMP3EqU6bI4= github.com/pb33f/libopenapi-validator v0.13.4 h1:/6NJtmPIZYPT2mEbMAz+BfSoGoCNrwYL46ikEaFy0pM= github.com/pb33f/libopenapi-validator v0.13.4/go.mod h1:e6HLPJM493yfJmnNn81fb3Mt6Iyy8Knd1iuzyyW1A9Y= github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= diff --git a/vendor/github.com/pb33f/libopenapi/.gitignore b/vendor/github.com/pb33f/libopenapi/.gitignore index 069611e7d..d890c2ef0 100644 --- a/vendor/github.com/pb33f/libopenapi/.gitignore +++ b/vendor/github.com/pb33f/libopenapi/.gitignore @@ -1,3 +1,4 @@ test-operation.yaml .idea/ -*.iml \ No newline at end of file +*.iml +.zed/ diff --git a/vendor/github.com/pb33f/libopenapi/README.md b/vendor/github.com/pb33f/libopenapi/README.md index fa79a5711..649ea081a 100644 --- a/vendor/github.com/pb33f/libopenapi/README.md +++ b/vendor/github.com/pb33f/libopenapi/README.md @@ -72,6 +72,8 @@ See all the documentation at https://pb33f.io/libopenapi/ - [What Changed / Diff Engine](https://pb33f.io/libopenapi/what-changed/) - [Overlays](https://pb33f.io/libopenapi/overlays/) - [Arazzo](https://pb33f.io/libopenapi/arazzo/) +- [Generating Code](https://pb33f.io/libopenapi/generating-code/) +- [Parsing Code](https://pb33f.io/libopenapi/parsing-code/) - [FAQ](https://pb33f.io/libopenapi/faq/) - [About libopenapi](https://pb33f.io/libopenapi/about/) --- diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/document_config.go b/vendor/github.com/pb33f/libopenapi/datamodel/document_config.go index 5da94ef31..9c2df0c64 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/document_config.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/document_config.go @@ -39,8 +39,8 @@ type DocumentConfiguration struct { // RemoteURLHandler is a function that will be used to retrieve remote documents. If not set, the default // remote document getter will be used. // - // The remote handler is only used if the BaseURL is set. If the BaseURL is not set, then the remote handler - // will not be used, as there will be nothing to use it against. + // The remote handler is only used if AllowRemoteReferences is true. If AllowRemoteReferences is false, then + // the remote handler will not be used even when BaseURL is set. // // Resolves [#132]: https://github.com/pb33f/libopenapi/issues/132 RemoteURLHandler utils.RemoteURLHandler @@ -89,12 +89,8 @@ type DocumentConfiguration struct { // AllowRemoteReferences will allow the index to lookup remote references. This is disabled by default. // - // This behavior is now driven by the inclusion of a BaseURL. If a BaseURL is set, then the - // rolodex will look for remote references. If no BaseURL is set, then the rolodex will not look for - // remote references. This value has no effect as of version 0.13.0 and will be removed in a future release. - // - // This value when set, will force the creation of a remote file system even when the BaseURL has not been set. - // it will suck in every http link it finds, and recurse through all references located in each document. + // BaseURL is used to resolve relative references, but it does not enable remote fetching on its own. Remote + // lookup only occurs when this value is true. AllowRemoteReferences bool // AvoidIndexBuild will avoid building the index. This is disabled by default, only use if you are sure you don't need it. diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema.go b/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema.go index 16f1070a4..f65771057 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema.go @@ -56,8 +56,8 @@ type Schema struct { // 3.1 Specific properties Contains *SchemaProxy `json:"contains,omitempty" yaml:"contains,omitempty"` - MinContains *int64 `json:"minContains,omitempty" yaml:"minContains,omitempty"` - MaxContains *int64 `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` + MinContains *int64 `json:"minContains,renderZero,omitempty" yaml:"minContains,renderZero,omitempty"` + MaxContains *int64 `json:"maxContains,renderZero,omitempty" yaml:"maxContains,renderZero,omitempty"` If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"` Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"` Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"` @@ -102,15 +102,15 @@ type Schema struct { MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` Maximum *float64 `json:"maximum,renderZero,omitempty" yaml:"maximum,renderZero,omitempty"` Minimum *float64 `json:"minimum,renderZero,omitempty," yaml:"minimum,renderZero,omitempty"` - MaxLength *int64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` - MinLength *int64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength *int64 `json:"maxLength,renderZero,omitempty" yaml:"maxLength,renderZero,omitempty"` + MinLength *int64 `json:"minLength,renderZero,omitempty" yaml:"minLength,renderZero,omitempty"` Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` Format string `json:"format,omitempty" yaml:"format,omitempty"` - MaxItems *int64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` - MinItems *int64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + MaxItems *int64 `json:"maxItems,renderZero,omitempty" yaml:"maxItems,renderZero,omitempty"` + MinItems *int64 `json:"minItems,renderZero,omitempty" yaml:"minItems,renderZero,omitempty"` UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` - MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` - MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + MaxProperties *int64 `json:"maxProperties,renderZero,omitempty" yaml:"maxProperties,renderZero,omitempty"` + MinProperties *int64 `json:"minProperties,renderZero,omitempty" yaml:"minProperties,renderZero,omitempty"` Required []string `json:"required,omitempty" yaml:"required,omitempty"` Enum []*yaml.Node `json:"enum,omitempty" yaml:"enum,omitempty"` AdditionalProperties *DynamicValue[*SchemaProxy, bool] `json:"additionalProperties,renderZero,omitempty" yaml:"additionalProperties,renderZero,omitempty"` @@ -431,11 +431,17 @@ func NewSchema(schema *base.Schema) *Schema { } props := orderedmap.New[string, *SchemaProxy]() + if !schema.Properties.IsEmpty() { + s.Properties = props + } for name, schemaProxy := range schema.Properties.Value.FromOldest() { buildProps(name, schemaProxy, props, 0) } dependents := orderedmap.New[string, *SchemaProxy]() + if !schema.DependentSchemas.IsEmpty() { + s.DependentSchemas = dependents + } for name, schemaProxy := range schema.DependentSchemas.Value.FromOldest() { buildProps(name, schemaProxy, dependents, 1) } @@ -450,6 +456,9 @@ func NewSchema(schema *base.Schema) *Schema { } patternProps := orderedmap.New[string, *SchemaProxy]() + if !schema.PatternProperties.IsEmpty() { + s.PatternProperties = patternProps + } for name, schemaProxy := range schema.PatternProperties.Value.FromOldest() { buildProps(name, schemaProxy, patternProps, 2) } @@ -559,6 +568,12 @@ func (s *Schema) RenderInline() ([]byte, error) { // MarshalYAML will create a ready to render YAML representation of the Schema object. func (s *Schema) MarshalYAML() (interface{}, error) { + if s.ParentProxy != nil { + if node, ok, err := s.ParentProxy.renderTransformedRefWithSiblings(s); ok || err != nil { + return node, err + } + } + nb := high.NewNodeBuilder(s, s.low) // determine index version @@ -573,6 +588,14 @@ func (s *Schema) MarshalYAML() (interface{}, error) { // MarshalJSON will create a ready to render JSON representation of the Schema object. func (s *Schema) MarshalJSON() ([]byte, error) { + if s.ParentProxy != nil && s.ParentProxy.isParsedRefWithSiblings() { + node, err := s.ParentProxy.referenceYAMLNodeForSchema(s) + if err != nil { + return nil, err + } + return marshalYAMLNodeJSON(node) + } + nb := high.NewNodeBuilder(s, s.low) // determine index version @@ -584,13 +607,7 @@ func (s *Schema) MarshalJSON() ([]byte, error) { } // render node node := nb.Render() - var renderedJSON map[string]interface{} - - // marshal into struct - _ = node.Decode(&renderedJSON) - - // return JSON bytes - return json.Marshal(renderedJSON) + return marshalYAMLNodeJSON(node) } // MarshalYAMLInlineWithContext will render out the Schema pointer as YAML using the provided @@ -606,6 +623,9 @@ func (s *Schema) MarshalYAMLInlineWithContext(ctx any) (interface{}, error) { renderCtx = NewInlineRenderContext() ctx = renderCtx } + if s.ParentProxy != nil && s.ParentProxy.isParsedRefWithSiblings() { + return s.ParentProxy.marshalParsedRefWithSiblingsInline(renderCtx, s) + } // determine if we should preserve discriminator refs based on rendering mode. // in validation mode, we need to fully inline all refs for the JSON schema compiler. @@ -647,6 +667,14 @@ func (s *Schema) MarshalYAMLInline() (interface{}, error) { // MarshalJSONInline will render out the Schema pointer as JSON, and all refs will be inlined fully func (s *Schema) MarshalJSONInline() ([]byte, error) { + if s.ParentProxy != nil && s.ParentProxy.isParsedRefWithSiblings() { + rendered, err := s.MarshalYAMLInline() + if err != nil { + return nil, err + } + return marshalYAMLRenderJSON(rendered) + } + nb := high.NewNodeBuilder(s, s.low) nb.Resolve = true // determine index version @@ -658,11 +686,21 @@ func (s *Schema) MarshalJSONInline() ([]byte, error) { } // render node node := nb.Render() - var renderedJSON map[string]interface{} + return marshalYAMLNodeJSON(node) +} - // marshal into struct - _ = node.Decode(&renderedJSON) +func marshalYAMLRenderJSON(rendered interface{}) ([]byte, error) { + node, ok := yamlNodeFromRender(rendered) + if !ok { + return nil, errors.New("unable to render schema as JSON: YAML render was not a node") + } + return marshalYAMLNodeJSON(node) +} - // return JSON bytes +func marshalYAMLNodeJSON(node *yaml.Node) ([]byte, error) { + var renderedJSON map[string]interface{} + if err := node.Decode(&renderedJSON); err != nil { + return nil, err + } return json.Marshal(renderedJSON) } diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema_proxy.go b/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema_proxy.go index 84b4ce2e2..adc9afb5a 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema_proxy.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/high/base/schema_proxy.go @@ -243,6 +243,11 @@ func (sp *SchemaProxy) Schema() *Schema { return nil } + if sp.isParsedRefWithSiblings() { + sp.rendered = sp.buildSiblingOnlySchemaView() + return sp.rendered + } + // check the high-level cache first. idx := sp.schema.Value.GetIndex() if idx != nil && sp.schema.Value != nil { @@ -294,6 +299,8 @@ func (sp *SchemaProxy) Schema() *Schema { } // IsReference returns true if the SchemaProxy is a reference to another Schema. +// For parsed OpenAPI 3.1 $ref-with-siblings schemas, the low proxy is backed by +// an internal allOf node, but the high-level API reflects the authored $ref. func (sp *SchemaProxy) IsReference() bool { if sp == nil { return false @@ -302,6 +309,9 @@ func (sp *SchemaProxy) IsReference() bool { if sp.refStr != "" { return true } + if sp.isParsedRefWithSiblings() { + return true + } if sp.schema != nil && sp.schema.Value != nil { return sp.schema.Value.IsReference() } @@ -313,11 +323,17 @@ func (sp *SchemaProxy) GetReference() string { if sp.refStr != "" { return sp.refStr } + if sp.isParsedRefWithSiblings() { + return sp.schema.Value.GetTransformedRefReference() + } if refNode := sp.GetReferenceNode(); refNode != nil { if refValNode := utils.GetRefValueNode(refNode); refValNode != nil { return refValNode.Value } } + if sp.schema == nil || sp.schema.Value == nil { + return "" + } return sp.schema.GetValue().GetReference() } @@ -332,6 +348,12 @@ func (sp *SchemaProxy) GetReferenceNode() *yaml.Node { if sp.refStr != "" { return utils.CreateRefNode(sp.refStr) } + if sp.isParsedRefWithSiblings() { + return sp.schema.Value.TransformedRef + } + if sp.schema == nil || sp.schema.Value == nil { + return nil + } return sp.schema.GetValue().GetReferenceNode() } @@ -397,6 +419,69 @@ func (sp *SchemaProxy) isRefWithSiblings() bool { return sp.refStr != "" && sp.rendered != nil && sp.schema == nil } +// IsTransformedRefWithSiblings reports whether this high-level proxy represents +// an authored OpenAPI 3.1 $ref with sibling schema keywords. +func (sp *SchemaProxy) IsTransformedRefWithSiblings() bool { + return sp != nil && + sp.schema != nil && + sp.schema.Value != nil && + sp.schema.Value.IsTransformedRefWithSiblings() && + sp.shouldCollapseTransformedRefWithSiblings() +} + +func (sp *SchemaProxy) isParsedRefWithSiblings() bool { + return sp.IsTransformedRefWithSiblings() +} + +func (sp *SchemaProxy) buildSiblingOnlySchemaView() *Schema { + if sp == nil || sp.schema == nil || sp.schema.Value == nil { + return nil + } + lowProxy := sp.schema.Value + siblingNode := lowProxy.GetTransformedRefSiblingSchema() + if siblingNode == nil { + return nil + } + + lowSchema := new(base.Schema) + if err := lowSchema.Build(lowProxy.GetContext(), siblingNode, lowProxy.GetIndex()); err != nil { + sp.buildError = err + return nil + } + lowSchema.ParentProxy = lowProxy + + schema := NewSchema(lowSchema) + schema.ParentProxy = sp + return schema +} + +// BuildTransformedRefSemanticSchema returns the internal semantic allOf view for +// an authored $ref-with-siblings proxy, using current high-level sibling values. +func (sp *SchemaProxy) BuildTransformedRefSemanticSchema(currentSibling *Schema) (*Schema, error) { + return sp.buildSemanticAllOfSchemaView(currentSibling) +} + +func (sp *SchemaProxy) buildSemanticAllOfSchemaView(currentSibling *Schema) (*Schema, error) { + if sp == nil || sp.schema == nil || sp.schema.Value == nil { + return nil, nil + } + lowSchema := sp.schema.Value.Schema() + if lowSchema == nil { + return nil, sp.schema.Value.GetBuildError() + } + schema := NewSchema(lowSchema) + schema.ParentProxy = nil + if currentSibling == nil { + currentSibling = sp.Schema() + } + if currentSibling != nil && len(schema.AllOf) == 2 { + siblingCopy := *currentSibling + siblingCopy.ParentProxy = nil + schema.AllOf[0] = CreateSchemaProxy(&siblingCopy) + } + return schema, nil +} + // renderRefWithSiblings builds a YAML mapping node containing $ref as the // first key followed by all rendered schema sibling properties. func (sp *SchemaProxy) renderRefWithSiblings() *yaml.Node { @@ -412,6 +497,150 @@ func (sp *SchemaProxy) renderRefWithSiblings() *yaml.Node { return node } +func (sp *SchemaProxy) renderTransformedRefWithSiblings(s *Schema) (*yaml.Node, bool, error) { + if sp == nil || sp.schema == nil || sp.schema.Value == nil || sp.schema.Value.TransformedRef == nil || s == nil { + return nil, false, nil + } + if !sp.shouldCollapseTransformedRefWithSiblings() { + return nil, false, nil + } + + var siblingNode *yaml.Node + ref := sp.schema.Value.GetTransformedRefReference() + + if !sp.schemaIsTransformedSiblingView(s) { + if len(s.AllOf) != 2 || s.AllOf[0] == nil || s.AllOf[1] == nil || !s.AllOf[1].IsReference() { + return nil, false, nil + } + + // Only collapse the synthetic allOf created by the sibling-ref transformer. + // If callers add fields to the outer schema or change its composition, keep + // the explicit allOf so no mutations are hidden. + outerNode := high.NewNodeBuilder(s, s.low).Render() + if len(outerNode.Content) != 2 || outerNode.Content[0].Value != "allOf" { + return nil, false, nil + } + + siblingRender, err := s.AllOf[0].MarshalYAML() + if err != nil { + return nil, true, err + } + var ok bool + siblingNode, ok = yamlNodeFromRender(siblingRender) + if !ok || !utils.IsNodeMap(siblingNode) { + return nil, false, nil + } + ref = s.AllOf[1].GetReference() + } else { + siblingNode = high.NewNodeBuilder(s, s.low).Render() + } + + original := sp.schema.Value.TransformedRef + result := utils.CreateEmptyMapNode() + consumed := make(map[string]struct{}, len(siblingNode.Content)/2) + + for i := 0; i+1 < len(original.Content); i += 2 { + keyNode := original.Content[i] + valueNode := original.Content[i+1] + if keyNode == nil { + continue + } + if keyNode.Value == "$ref" { + refKey := cloneYAMLNode(keyNode) + refValue := cloneYAMLNode(valueNode) + if refValue == nil { + refValue = utils.CreateStringNode(ref) + } + refValue.Value = ref + result.Content = append(result.Content, refKey, refValue) + continue + } + if _, siblingValue := findYAMLPair(siblingNode, keyNode.Value); siblingValue != nil { + renderKey := cloneYAMLNode(keyNode) + result.Content = append(result.Content, renderKey, cloneYAMLNode(siblingValue)) + consumed[keyNode.Value] = struct{}{} + } + } + + for i := 0; i+1 < len(siblingNode.Content); i += 2 { + keyNode := siblingNode.Content[i] + valueNode := siblingNode.Content[i+1] + if _, ok := consumed[keyNode.Value]; ok { + continue + } + result.Content = append(result.Content, cloneYAMLNode(keyNode), cloneYAMLNode(valueNode)) + } + + return result, true, nil +} + +func (sp *SchemaProxy) schemaIsTransformedSiblingView(s *Schema) bool { + if sp == nil || sp.schema == nil || sp.schema.Value == nil || s == nil { + return false + } + return s.low != nil && s.low.RootNode == sp.schema.Value.GetTransformedRefSiblingSchema() +} + +func (sp *SchemaProxy) shouldCollapseTransformedRefWithSiblings() bool { + if sp == nil || sp.schema == nil || sp.schema.Value == nil { + return false + } + idx := sp.schema.Value.GetIndex() + if idx == nil || idx.GetConfig() == nil || idx.GetConfig().SpecInfo == nil { + return true + } + return idx.GetConfig().SpecInfo.VersionNumeric >= 3.1 +} + +func yamlNodeFromRender(rendered interface{}) (*yaml.Node, bool) { + switch node := rendered.(type) { + case *yaml.Node: + return node, node != nil + case yaml.Node: + return &node, true + default: + return nil, false + } +} + +func findYAMLPair(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) { + if node == nil || !utils.IsNodeMap(node) { + return nil, nil + } + for i := 0; i+1 < len(node.Content); i += 2 { + if node.Content[i] != nil && node.Content[i].Value == key { + return node.Content[i], node.Content[i+1] + } + } + return nil, nil +} + +func cloneYAMLNode(node *yaml.Node) *yaml.Node { + if node == nil { + return nil + } + clone := &yaml.Node{ + Kind: node.Kind, + Style: node.Style, + Tag: node.Tag, + Value: node.Value, + Anchor: node.Anchor, + Alias: node.Alias, + Line: node.Line, + Column: node.Column, + HeadComment: node.HeadComment, + LineComment: node.LineComment, + FootComment: node.FootComment, + } + if len(node.Content) > 0 { + clone.Content = make([]*yaml.Node, len(node.Content)) + for i, child := range node.Content { + clone.Content[i] = cloneYAMLNode(child) + } + } + return clone +} + // Render will return a YAML representation of the Schema object as a byte slice. func (sp *SchemaProxy) Render() ([]byte, error) { return yaml.Marshal(sp) @@ -419,11 +648,17 @@ func (sp *SchemaProxy) Render() ([]byte, error) { // MarshalYAML will create a ready to render YAML representation of the SchemaProxy object. func (sp *SchemaProxy) MarshalYAML() (interface{}, error) { + if sp.isParsedRefWithSiblings() { + return sp.referenceYAMLNodeForSchema(nil) + } if !sp.IsReference() { s, err := sp.BuildSchema() if err != nil { return nil, err } + if node, ok, renderErr := sp.renderTransformedRefWithSiblings(s); ok || renderErr != nil { + return node, renderErr + } nb := high.NewNodeBuilder(s, s.low) return nb.Render(), nil } @@ -433,6 +668,29 @@ func (sp *SchemaProxy) MarshalYAML() (interface{}, error) { return sp.GetReferenceNode(), nil } +func (sp *SchemaProxy) referenceYAMLNode() (*yaml.Node, error) { + return sp.referenceYAMLNodeForSchema(nil) +} + +func (sp *SchemaProxy) referenceYAMLNodeForSchema(s *Schema) (*yaml.Node, error) { + if sp.isRefWithSiblings() { + return sp.renderRefWithSiblings(), nil + } + if sp.isParsedRefWithSiblings() { + if s == nil { + var err error + s, err = sp.BuildSchema() + if err != nil { + return nil, err + } + } + if node, ok, renderErr := sp.renderTransformedRefWithSiblings(s); ok || renderErr != nil { + return node, renderErr + } + } + return sp.GetReferenceNode(), nil +} + // getInlineRenderKey generates a unique key for tracking this schema during inline rendering. // This prevents infinite recursion when schemas reference each other circularly. func (sp *SchemaProxy) getInlineRenderKey() string { @@ -444,6 +702,22 @@ func (sp *SchemaProxy) getInlineRenderKey() string { } return "" } + if sp.isParsedRefWithSiblings() && sp.schema.ValueNode != nil { + node := sp.schema.ValueNode + idx := sp.schema.Value.GetIndex() + if node.Line > 0 && node.Column > 0 { + source := "inline" + if idx != nil { + source = idx.GetSpecAbsolutePath() + } + return fmt.Sprintf("%s:%d:%d", source, node.Line, node.Column) + } + source := "inline" + if idx != nil { + source = fmt.Sprintf("%s:inline", idx.GetSpecAbsolutePath()) + } + return fmt.Sprintf("%s:%p", source, node) + } // Use the reference string if available if sp.IsReference() { ref := sp.GetReference() @@ -501,13 +775,8 @@ func (sp *SchemaProxy) MarshalYAMLInline() (interface{}, error) { } func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (interface{}, error) { - // refNode returns the correct reference YAML node — with sibling - // properties when this proxy carries both a $ref and schema data. - refNode := func() *yaml.Node { - if sp.isRefWithSiblings() { - return sp.renderRefWithSiblings() - } - return sp.GetReferenceNode() + refNode := func() (*yaml.Node, error) { + return sp.referenceYAMLNode() } // check if this reference should be preserved (set via context by discriminator handling). @@ -516,7 +785,7 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte if sp.IsReference() { ref := sp.GetReference() if ref != "" && ctx.ShouldPreserveRef(ref) { - return refNode(), nil + return refNode() } } @@ -537,7 +806,7 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte rootIdx := rolodex.GetRootIndex() // If the schema is in the root index, preserve the ref if rootIdx != nil && schemaIdx == rootIdx { - return refNode(), nil + return refNode() } } } @@ -553,8 +822,9 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte if ctx.StartRendering(renderKey) { // We're already rendering this schema in THIS call chain - return ref to break the cycle if sp.IsReference() { - return refNode(), - fmt.Errorf("schema render failure, circular reference: `%s`", sp.GetReference()) + node, refErr := refNode() + return node, errors.Join(refErr, + fmt.Errorf("schema render failure, circular reference: `%s`", sp.GetReference())) } // For inline schemas, return an empty map to avoid infinite recursion return &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}, @@ -585,7 +855,8 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte for _, c := range circ { if sp.IsReference() { if sp.GetReference() == c.LoopPoint.Definition { - return refNode(), cirError(c.LoopPoint.Definition) + node, refErr := refNode() + return node, errors.Join(refErr, cirError(c.LoopPoint.Definition)) } basePath := idx.GetSpecAbsolutePath() @@ -594,7 +865,8 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte } if basePath == c.LoopPoint.FullDefinition { - return refNode(), cirError(c.LoopPoint.Definition) + node, refErr := refNode() + return node, errors.Join(refErr, cirError(c.LoopPoint.Definition)) } a := utils.ReplaceWindowsDriveWithLinuxPath(strings.Replace(c.LoopPoint.FullDefinition, basePath, "", 1)) b := sp.GetReference() @@ -616,14 +888,16 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte bBase, bFragment := index.SplitRefFragment(b) if aFragment != "" && bFragment != "" && aFragment == bFragment { - return refNode(), cirError(c.LoopPoint.Definition) + node, refErr := refNode() + return node, errors.Join(refErr, cirError(c.LoopPoint.Definition)) } if aFragment == "" && bFragment == "" { aNorm := strings.TrimPrefix(strings.TrimPrefix(aBase, "./"), "/") bNorm := strings.TrimPrefix(strings.TrimPrefix(bBase, "./"), "/") if aNorm != "" && bNorm != "" && aNorm == bNorm { - return refNode(), cirError(c.LoopPoint.Definition) + node, refErr := refNode() + return node, errors.Join(refErr, cirError(c.LoopPoint.Definition)) } } } @@ -634,6 +908,9 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte return nil, err } if s != nil { + if sp.isParsedRefWithSiblings() { + return sp.marshalParsedRefWithSiblingsInline(ctx, s) + } // For programmatic ref+siblings proxies, render directly to avoid nil-deref // in Schema.MarshalYAMLInlineWithContext which assumes s.GoLow() is non-nil. if sp.isRefWithSiblings() { @@ -647,3 +924,14 @@ func (sp *SchemaProxy) marshalYAMLInlineInternal(ctx *InlineRenderContext) (inte } return nil, errors.New("unable to render schema") } + +func (sp *SchemaProxy) marshalParsedRefWithSiblingsInline(ctx *InlineRenderContext, currentSibling *Schema) (interface{}, error) { + s, err := sp.buildSemanticAllOfSchemaView(currentSibling) + if err != nil { + return nil, err + } + if s == nil { + return nil, errors.New("unable to render transformed schema reference") + } + return s.MarshalYAMLInlineWithContext(ctx) +} diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/high/node_builder.go b/vendor/github.com/pb33f/libopenapi/datamodel/high/node_builder.go index dc1a991cb..04f78cda4 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/high/node_builder.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/high/node_builder.go @@ -5,6 +5,7 @@ package high import ( "fmt" + "math" "reflect" "sort" "strconv" @@ -39,6 +40,32 @@ type RenderableInlineWithContext interface { const renderZero = "renderZero" +func originalFloatLexeme(value float64, lowValue any) (string, bool) { + vnut, ok := lowValue.(low.HasValueNodeUntyped) + if !ok { + return "", false + } + + valueNode := vnut.GetValueNode() + if valueNode == nil || !utils.IsNodeNumberValue(valueNode) { + return "", false + } + + parsed, err := strconv.ParseFloat(valueNode.Value, 64) + if err != nil { + return "", false + } + + if parsed != value { + return "", false + } + if value == 0 && math.Signbit(parsed) != math.Signbit(value) { + return "", false + } + + return valueNode.Value, true +} + // NewNodeBuilder will create a new NodeBuilder instance, this is the only way to create a NodeBuilder. // The function accepts a high level object and a low level object (need to be siblings/same type). // @@ -384,6 +411,9 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *nodes.NodeEntry) *ya precision = len(strings.Split(fmt.Sprint(entry.StringValue), ".")[1]) } val := strconv.FormatFloat(value.(float64), 'f', precision, 64) + if original, ok := originalFloatLexeme(value.(float64), entry.LowValue); ok { + val = original + } // Always create float node for float64 values, even if they don't contain decimal points // This handles cases like negative zero (-0.0) which formats as "-0" but should remain float valueNode = utils.CreateFloatNode(val) @@ -589,6 +619,9 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *nodes.NodeEntry) *ya encodeSkip = true if *b != 0 || entry.RenderZero { formatFloat := strconv.FormatFloat(*b, 'f', -1, 64) + if original, ok := originalFloatLexeme(*b, entry.LowValue); ok { + formatFloat = original + } // Always create float node for float64 values, even if they're whole numbers // This handles cases like negative zero (-0.0) and ensures type consistency diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/discriminator.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/discriminator.go index 77157492b..936af1471 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/discriminator.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/discriminator.go @@ -4,12 +4,14 @@ package base import ( + "fmt" "hash/maphash" "go.yaml.in/yaml/v4" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/orderedmap" + "github.com/pb33f/libopenapi/utils" ) // Discriminator is only used by OpenAPI 3+ documents, it represents a polymorphic discriminator used for schemas @@ -31,6 +33,68 @@ type Discriminator struct { low.NodeMap } +// ValidateDiscriminatorMappingValueNodes checks that discriminator mapping values are scalar strings. +func ValidateDiscriminatorMappingValueNodes(discriminatorNode *yaml.Node) error { + discriminatorNode = utils.NodeAlias(discriminatorNode) + if discriminatorNode == nil || discriminatorNode.Kind != yaml.MappingNode { + return nil + } + utils.CheckForMergeNodes(discriminatorNode) + + for i := 0; i < len(discriminatorNode.Content); i += 2 { + keyNode := utils.NodeAlias(discriminatorNode.Content[i]) + if keyNode == nil { + continue + } + if keyNode.Value != "mapping" { + continue + } + + mappingNode := utils.NodeAlias(discriminatorNode.Content[i+1]) + if mappingNode == nil || mappingNode.Kind != yaml.MappingNode { + return fmt.Errorf("discriminator.mapping must be an object") + } + utils.CheckForMergeNodes(mappingNode) + + for j := 0; j < len(mappingNode.Content); j += 2 { + keyNode := utils.NodeAlias(mappingNode.Content[j]) + if keyNode == nil { + continue + } + mappingName := keyNode.Value + valueNode := utils.NodeAlias(mappingNode.Content[j+1]) + if valueNode == nil || valueNode.Kind != yaml.ScalarNode || valueNode.Tag != "!!str" { + return fmt.Errorf("discriminator.mapping.%s must be a string, found %s", mappingName, describeDiscriminatorMappingNode(valueNode)) + } + } + return nil + } + + return nil +} + +func describeDiscriminatorMappingNode(node *yaml.Node) string { + if node == nil { + return "nil" + } + if node.Kind == yaml.ScalarNode { + return node.Tag + } + + switch node.Kind { + case yaml.MappingNode: + return "object" + case yaml.SequenceNode: + return "array" + case yaml.DocumentNode: + return "document" + case yaml.AliasNode: + return "alias" + default: + return fmt.Sprintf("kind %d", node.Kind) + } +} + // GetRootNode will return the root yaml node of the Discriminator object func (d *Discriminator) GetRootNode() *yaml.Node { return d.RootNode diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema.go index 6f81c0171..325cde4ad 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema.go @@ -2,8 +2,6 @@ package base import ( "context" - "errors" - "fmt" "sync" "github.com/pb33f/libopenapi/datamodel/low" @@ -153,81 +151,33 @@ type Schema struct { // will specifically look for a key node named 'schema' and extract the value mapped to that key. If the operation // fails then no NodeReference is returned and an error is returned instead. func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*SchemaProxy], error) { - var schLabel, schNode *yaml.Node errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d" - refLocation := "" - var refNode *yaml.Node - - foundIndex := idx - foundCtx := ctx - if rf, rl, rv := utils.IsNodeRefValue(root); rf { - // locate reference in index. - ref, fIdx, err, nCtx := low.LocateRefNodeWithContext(ctx, root, idx) - if ref != nil { - schNode = ref - schLabel = rl - foundCtx = nCtx - foundIndex = fIdx - } else if errors.Is(err, low.ErrExternalRefSkipped) { - refLocation = rv - schema := &SchemaProxy{kn: root, vn: root, idx: idx, ctx: ctx} - _ = schema.Build(ctx, root, root, idx) - n := &low.NodeReference[*SchemaProxy]{Value: schema, KeyNode: root, ValueNode: root} - n.SetReference(refLocation, root) - schema.SetReference(refLocation, root) - return n, nil - } else { - v := root.Content[1].Value - if root.Content[1].Value == "" { - v = "[empty]" - } - return nil, fmt.Errorf(errStr, - v, root.Content[1].Line, root.Content[1].Column) - } - } else { - _, schLabel, schNode = utils.FindKeyNodeFull(SchemaLabel, root.Content) - if schNode != nil { - h := false - if h, _, refLocation = utils.IsNodeRefValue(schNode); h { - ref, fIdx, lerr, nCtx := low.LocateRefNodeWithContext(foundCtx, schNode, foundIndex) - if ref != nil { - refNode = schNode - schNode = ref - if fIdx != nil { - foundIndex = fIdx - } - foundCtx = nCtx - } else if errors.Is(lerr, low.ErrExternalRefSkipped) { - refNode = schNode - } else { - v := schNode.Content[1].Value - if schNode.Content[1].Value == "" { - v = "[empty]" - } - return nil, fmt.Errorf(errStr, - v, schNode.Content[1].Line, schNode.Content[1].Column) - } - } - } + if rf, refLabel, _ := utils.IsNodeRefValue(root); rf { + return extractSchemaProxy(ctx, idx, refLabel, root, errStr) } + _, schLabel, schNode := utils.FindKeyNodeFull(SchemaLabel, root.Content) if schNode != nil { - // check if schema has already been built. - schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: foundIndex, ctx: foundCtx} - - // call Build to ensure transformation happens - _ = schema.Build(foundCtx, schLabel, schNode, foundIndex) + return extractSchemaProxy(ctx, idx, schLabel, schNode, errStr) + } + return nil, nil +} - schema.SetReference(refLocation, refNode) +func extractSchemaProxy(ctx context.Context, idx *index.SpecIndex, keyNode, valueNode *yaml.Node, errFormat string) (*low.NodeReference[*SchemaProxy], error) { + resolved, err := resolveSchemaBuildInput(ctx, valueNode, idx, errFormat) + if err != nil { + return nil, err + } - n := &low.NodeReference[*SchemaProxy]{ - Value: schema, - KeyNode: schLabel, - ValueNode: schema.vn, // use transformed node - } - n.SetReference(refLocation, refNode) - return n, nil + built := buildSchemaProxy(resolved.ctx, resolved.idx, keyNode, resolved.valueNode, resolved.scopeNode, resolved.refNode, resolved.transformed, resolved.refLocation) + n := &low.NodeReference[*SchemaProxy]{ + Value: built.Value, + KeyNode: keyNode, + ValueNode: built.ValueNode, } - return nil, nil + if resolved.refLocation != "" { + n.SetReference(resolved.refLocation, resolved.refNode) + } + return n, nil } diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build.go index 6e944f58f..8068ddb03 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build.go @@ -318,6 +318,9 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde _, discLabel, discNode := utils.FindKeyNodeFullTop(DiscriminatorLabel, root.Content) if discNode != nil { + if err := ValidateDiscriminatorMappingValueNodes(discNode); err != nil { + return err + } var discriminator Discriminator _ = low.BuildModel(discNode, &discriminator) discriminator.KeyNode = discLabel diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build_helpers.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build_helpers.go index 661a1c23a..03328453f 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build_helpers.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_build_helpers.go @@ -19,7 +19,9 @@ type resolvedSchemaBuildInput struct { ctx context.Context idx *index.SpecIndex valueNode *yaml.Node + scopeNode *yaml.Node refNode *yaml.Node + transformed *transformedSiblingRef refLocation string } @@ -41,7 +43,7 @@ func buildPropertyMap(ctx context.Context, parent *Schema, root *yaml.Node, idx propertyMap.Set(low.KeyReference[string]{ KeyNode: currentProp, Value: currentProp.Value, - }, buildSchemaProxy(resolved.ctx, resolved.idx, currentProp, resolved.valueNode, resolved.refNode, resolved.refLocation != "", resolved.refLocation)) + }, buildSchemaProxy(resolved.ctx, resolved.idx, currentProp, resolved.valueNode, resolved.scopeNode, resolved.refNode, resolved.transformed, resolved.refLocation)) } return &low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]{ @@ -100,13 +102,9 @@ func (s *Schema) extractExtensions(root *yaml.Node) { } // buildSchemaProxy builds out a SchemaProxy for a single node. -func buildSchemaProxy(ctx context.Context, idx *index.SpecIndex, kn, vn *yaml.Node, rf *yaml.Node, isRef bool, refLocation string) low.ValueReference[*SchemaProxy] { +func buildSchemaProxy(ctx context.Context, idx *index.SpecIndex, kn, vn, scopeNode, rf *yaml.Node, transformed *transformedSiblingRef, refLocation string) low.ValueReference[*SchemaProxy] { sp := new(SchemaProxy) - if isRef { - sp.prepareForResolvedBuild(ctx, kn, vn, idx, refLocation, rf) - } else { - sp.prepareForResolvedBuild(ctx, kn, vn, idx, "", nil) - } + sp.prepareForResolvedBuild(ctx, kn, vn, scopeNode, idx, refLocation, rf, transformed) return low.ValueReference[*SchemaProxy]{ Value: sp, ValueNode: sp.vn, @@ -130,7 +128,7 @@ func buildSchema(ctx context.Context, labelNode, valueNode *yaml.Node, idx *inde return low.ValueReference[*SchemaProxy]{}, err } - return buildSchemaProxy(resolved.ctx, resolved.idx, labelNode, resolved.valueNode, resolved.refNode, resolved.refLocation != "", resolved.refLocation), nil + return buildSchemaProxy(resolved.ctx, resolved.idx, labelNode, resolved.valueNode, resolved.scopeNode, resolved.refNode, resolved.transformed, resolved.refLocation), nil } // buildSchemaList builds out child schemas for a parent schema. Expected to be an array of schema objects. @@ -152,7 +150,7 @@ func buildSchemaList(ctx context.Context, labelNode, valueNode *yaml.Node, idx * if err != nil { return nil, err } - r := buildSchemaProxy(resolved.ctx, resolved.idx, resolved.valueNode, resolved.valueNode, resolved.refNode, resolved.refLocation != "", resolved.refLocation) + r := buildSchemaProxy(resolved.ctx, resolved.idx, resolved.valueNode, resolved.valueNode, resolved.scopeNode, resolved.refNode, resolved.transformed, resolved.refLocation) results = append(results, r) } @@ -190,17 +188,25 @@ func resolveSchemaBuildInput(ctx context.Context, valueNode *yaml.Node, idx *ind ctx: ctx, idx: idx, valueNode: valueNode, + scopeNode: valueNode, } if valueNode == nil { return resolved, nil } + if transformedValue, transformedRef, wasTransformed := transformSiblingRefNode(valueNode, idx); wasTransformed { + resolved.valueNode = transformedValue + resolved.transformed = transformedRef + return resolved, nil + } + if hasRef, _, refLocation := utils.IsNodeRefValue(valueNode); hasRef { ref, foundIdx, err, foundCtx := low.LocateRefNodeWithContext(ctx, valueNode, idx) if ref != nil { resolved.refNode = valueNode resolved.valueNode = ref + resolved.scopeNode = ref resolved.refLocation = refLocation resolved.ctx = foundCtx resolved.idx = foundIdx @@ -211,8 +217,16 @@ func resolveSchemaBuildInput(ctx context.Context, valueNode *yaml.Node, idx *ind resolved.refLocation = refLocation return resolved, nil } - return resolved, fmt.Errorf(errFormat, valueNode.Content[1].Value, valueNode.Content[1].Line, valueNode.Content[1].Column) + return resolved, schemaReferenceBuildError(errFormat, valueNode) } return resolved, nil } + +func schemaReferenceBuildError(errFormat string, valueNode *yaml.Node) error { + refValue := valueNode.Content[1].Value + if refValue == "" { + refValue = "[empty]" + } + return fmt.Errorf(errFormat, refValue, valueNode.Content[1].Line, valueNode.Content[1].Column) +} diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_proxy.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_proxy.go index 1f3d76733..17f1fbeda 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_proxy.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/schema_proxy.go @@ -70,6 +70,7 @@ type SchemaProxy struct { nodeStore sync.Map nodeMap low.NodeMap TransformedRef *yaml.Node // Original node that contained the ref before transformation + transformedRef *transformedSiblingRef *low.NodeMap } @@ -85,18 +86,9 @@ func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *in // transform sibling refs to allOf structure if enabled and applicable // this ensures sp.vn contains the pre-transformed YAML as the source of truth - transformedValue := value - wasTransformed := false - if idx != nil && idx.GetConfig() != nil && idx.GetConfig().TransformSiblingRefs { - transformer := NewSiblingRefTransformer(idx) - if transformer.ShouldTransform(value) { - transformed, _ := transformer.TransformSiblingRef(value) - if transformed != nil { - transformedValue = transformed - wasTransformed = true - sp.TransformedRef = value // store original node that had the ref - } - } + transformedValue, transformedRef, wasTransformed := transformSiblingRefNode(value, idx) + if wasTransformed { + sp.setTransformedRef(transformedRef) } sp.vn = transformedValue @@ -117,14 +109,27 @@ func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *in return nil } +func transformSiblingRefNode(value *yaml.Node, idx *index.SpecIndex) (*yaml.Node, *transformedSiblingRef, bool) { + if idx == nil || idx.GetConfig() == nil || !idx.GetConfig().TransformSiblingRefs { + return value, nil, false + } + transformer := NewSiblingRefTransformer(idx) + transformed := transformer.transformSiblingRefWithMetadata(value) + if transformed == nil { + return value, nil, false + } + return transformed.allOfNode, transformed, true +} + // prepareForResolvedBuild initializes proxy state when the caller has already resolved any reference metadata. // This avoids re-running the full Build ref-detection path for child-schema helpers that already did that work. -func (sp *SchemaProxy) prepareForResolvedBuild(ctx context.Context, key, value *yaml.Node, idx *index.SpecIndex, refLocation string, refNode *yaml.Node) { +func (sp *SchemaProxy) prepareForResolvedBuild(ctx context.Context, key, value, scopeNode *yaml.Node, idx *index.SpecIndex, refLocation string, refNode *yaml.Node, transformed *transformedSiblingRef) { sp.kn = key sp.idx = idx sp.vn = value - sp.ctx = applySchemaIdScope(ctx, value, idx) + sp.ctx = applySchemaIdScope(ctx, scopeNode, idx) sp.Reference = low.Reference{} + sp.setTransformedRef(transformed) if refLocation != "" { sp.SetReference(refLocation, refNode) } @@ -133,6 +138,47 @@ func (sp *SchemaProxy) prepareForResolvedBuild(ctx context.Context, key, value * sp.NodeMap = &sp.nodeMap } +func (sp *SchemaProxy) setTransformedRef(transformed *transformedSiblingRef) { + sp.transformedRef = transformed + sp.TransformedRef = nil + if transformed != nil { + sp.TransformedRef = transformed.referenceNode + } +} + +// IsTransformedRefWithSiblings reports whether this proxy was authored as a +// schema-level $ref with sibling keywords and internally normalized to allOf. +func (sp *SchemaProxy) IsTransformedRefWithSiblings() bool { + return sp != nil && sp.transformedRef != nil && sp.transformedRef.reference != "" +} + +// GetTransformedRefSiblingSchema returns the sibling-only schema for an +// internally transformed $ref-with-siblings node. +func (sp *SchemaProxy) GetTransformedRefSiblingSchema() *yaml.Node { + if !sp.IsTransformedRefWithSiblings() { + return nil + } + return sp.transformedRef.siblingNode +} + +// GetTransformedRefReference returns the original reference value for an +// internally transformed $ref-with-siblings node. +func (sp *SchemaProxy) GetTransformedRefReference() string { + if !sp.IsTransformedRefWithSiblings() { + return "" + } + return sp.transformedRef.reference +} + +// GetTransformedRefAllOfSchema returns the internal allOf schema for an +// authored $ref-with-siblings node. +func (sp *SchemaProxy) GetTransformedRefAllOfSchema() *yaml.Node { + if !sp.IsTransformedRefWithSiblings() { + return nil + } + return sp.transformedRef.allOfNode +} + func applySchemaIdScope(ctx context.Context, node *yaml.Node, idx *index.SpecIndex) context.Context { if node == nil { return ctx diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/sibling_ref_transformer.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/sibling_ref_transformer.go index 5173becd8..69c9cd48e 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/base/sibling_ref_transformer.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/base/sibling_ref_transformer.go @@ -4,6 +4,8 @@ package base import ( + "sort" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "go.yaml.in/yaml/v4" @@ -15,6 +17,13 @@ type SiblingRefTransformer struct { index *index.SpecIndex } +type transformedSiblingRef struct { + allOfNode *yaml.Node + siblingNode *yaml.Node + referenceNode *yaml.Node + reference string +} + // NewSiblingRefTransformer creates a new transformer instance func NewSiblingRefTransformer(idx *index.SpecIndex) *SiblingRefTransformer { return &SiblingRefTransformer{ @@ -28,17 +37,51 @@ func NewSiblingRefTransformer(idx *index.SpecIndex) *SiblingRefTransformer { // Input: {title: "MySchema", $ref: "#/components/schemas/Base"} // Output: {allOf: [{title: "MySchema"}, {$ref: "#/components/schemas/Base"}]} func (srt *SiblingRefTransformer) TransformSiblingRef(node *yaml.Node) (*yaml.Node, error) { - if !srt.ShouldTransform(node) { + transformed := srt.transformSiblingRefWithMetadata(node) + if transformed == nil { return node, nil // no transformation needed } + return transformed.allOfNode, nil +} +func (srt *SiblingRefTransformer) transformSiblingRefWithMetadata(node *yaml.Node) *transformedSiblingRef { + if srt.index == nil || srt.index.GetConfig() == nil || !srt.index.GetConfig().TransformSiblingRefs { + return nil + } siblings, refValue := srt.ExtractSiblingProperties(node) - return srt.CreateAllOfStructure(refValue, siblings), nil + if len(siblings) == 0 || refValue == "" { + return nil + } + siblingNode := srt.createSiblingSchemaNode(node) + return &transformedSiblingRef{ + allOfNode: srt.createAllOfStructureWithSiblingNode(refValue, siblingNode), + siblingNode: siblingNode, + referenceNode: node, + reference: refValue, + } } // CreateAllOfStructure creates an allOf node structure from ref value and sibling properties func (srt *SiblingRefTransformer) CreateAllOfStructure(refValue string, siblings map[string]*yaml.Node) *yaml.Node { + var siblingSchemaNode *yaml.Node + if len(siblings) > 0 { + siblingSchemaNode = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + keys := make([]string, 0, len(siblings)) + for key := range siblings { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + valueNode := siblings[key] + keyNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: key} + copiedValueNode := srt.copyNode(valueNode) + siblingSchemaNode.Content = append(siblingSchemaNode.Content, keyNode, copiedValueNode) + } + } + return srt.createAllOfStructureWithSiblingNode(refValue, siblingSchemaNode) +} +func (srt *SiblingRefTransformer) createAllOfStructureWithSiblingNode(refValue string, siblingSchemaNode *yaml.Node) *yaml.Node { allOfNode := &yaml.Node{ Kind: yaml.MappingNode, Tag: "!!map", @@ -50,19 +93,10 @@ func (srt *SiblingRefTransformer) CreateAllOfStructure(refValue string, siblings allOfArrayNode := allOfNode.Content[1] - // first element: schema with sibling properties (excluding $ref) - if len(siblings) > 0 { - siblingSchemaNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - for key, valueNode := range siblings { - keyNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: key} - // create a copy of the value node to avoid modifying original - copiedValueNode := srt.copyNode(valueNode) - siblingSchemaNode.Content = append(siblingSchemaNode.Content, keyNode, copiedValueNode) - } + if siblingSchemaNode != nil && len(siblingSchemaNode.Content) > 0 { allOfArrayNode.Content = append(allOfArrayNode.Content, siblingSchemaNode) } - // second element: the reference schema refSchemaNode := &yaml.Node{ Kind: yaml.MappingNode, Tag: "!!map", @@ -76,6 +110,22 @@ func (srt *SiblingRefTransformer) CreateAllOfStructure(refValue string, siblings return allOfNode } +func (srt *SiblingRefTransformer) createSiblingSchemaNode(node *yaml.Node) *yaml.Node { + if !utils.IsNodeMap(node) { + return nil + } + siblingNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + for i := 0; i+1 < len(node.Content); i += 2 { + keyNode := node.Content[i] + valueNode := node.Content[i+1] + if keyNode == nil || keyNode.Value == "$ref" { + continue + } + siblingNode.Content = append(siblingNode.Content, srt.copyNode(keyNode), srt.copyNode(valueNode)) + } + return siblingNode +} + // ExtractSiblingProperties extracts sibling properties from a node containing $ref // returns a map of sibling properties and the $ref value func (srt *SiblingRefTransformer) ExtractSiblingProperties(node *yaml.Node) (map[string]*yaml.Node, string) { diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/v2/swagger.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/v2/swagger.go index 6d079cb9b..0d5f668e5 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/v2/swagger.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/v2/swagger.go @@ -195,8 +195,8 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur } } - // if base url is provided, add a remote filesystem to the rolodex. - if idxConfig.BaseURL != nil { + // Only create a remote filesystem when the caller explicitly allows remote references. + if config.AllowRemoteReferences { // create a remote filesystem remoteFS, _ := index.NewRemoteFSWithConfig(idxConfig) @@ -206,7 +206,11 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur idxConfig.AllowRemoteLookup = true // add to the rolodex - rolodex.AddRemoteFS(config.BaseURL.String(), remoteFS) + u := "default" + if config.BaseURL != nil { + u = config.BaseURL.String() + } + rolodex.AddRemoteFS(u, remoteFS) } diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/low/v3/create_document.go b/vendor/github.com/pb33f/libopenapi/datamodel/low/v3/create_document.go index 6d037846a..7ae8f838e 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/low/v3/create_document.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/low/v3/create_document.go @@ -247,8 +247,8 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur rolodex.AddLocalFS(cwd, fileFS) } } - // if base url is provided, add a remote filesystem to the rolodex. - if idxConfig.BaseURL != nil || config.AllowRemoteReferences { + // Only create a remote filesystem when the caller explicitly allows remote references. + if config.AllowRemoteReferences { // create a remote filesystem remoteFS, _ := index.NewRemoteFSWithConfig(idxConfig) diff --git a/vendor/github.com/pb33f/libopenapi/datamodel/spec_info.go b/vendor/github.com/pb33f/libopenapi/datamodel/spec_info.go index f5200f042..8a2e7e0b4 100644 --- a/vendor/github.com/pb33f/libopenapi/datamodel/spec_info.go +++ b/vendor/github.com/pb33f/libopenapi/datamodel/spec_info.go @@ -10,6 +10,8 @@ import ( "fmt" "strings" "time" + "unicode/utf16" + "unicode/utf8" "github.com/pb33f/libopenapi/utils" "go.yaml.in/yaml/v4" @@ -97,12 +99,11 @@ func extractSpecInfoInternal(spec []byte, bypass bool, skipJSON bool) (*SpecInfo specInfo.NumLines = bytes.Count(spec, []byte{'\n'}) + 1 - // Pre-process JSON to handle \/ escape sequences that YAML parser doesn't recognize. - // JSON (RFC 8259) allows \/ as an optional escape for forward slash, but YAML does not. - // See: https://github.com/pb33f/libopenapi/issues/479 + // Pre-process JSON escapes that YAML parsers do not accept even though + // they are valid JSON, while preserving the existing YAML-node parse path. parseBytes := spec if specInfo.SpecFileType == JSONFileType { - parseBytes = unescapeJSONSlashes(spec) + parseBytes = normalizeJSONForYAMLParser(spec) } err := yaml.Unmarshal(parseBytes, &parsedSpec) @@ -324,39 +325,126 @@ func parseVersionTypeData(d interface{}) (string, int, error) { return string(r), int(r[0]) - '0', nil } -// unescapeJSONSlashes replaces the optional \/ escape sequence in JSON with / -// JSON (RFC 8259) allows \/ as an optional escape for forward slash, but YAML -// parsers (including go.yaml.in/yaml/v4) do not recognize it. -// This handles escaped backslashes correctly: \\/ becomes \/ not // -// Returns the original slice if no transformation is needed (zero allocation). -func unescapeJSONSlashes(jsonBytes []byte) []byte { - // fast path: check if transformation is needed - if !bytes.Contains(jsonBytes, []byte(`\/`)) { +// normalizeJSONForYAMLParser rewrites the small set of JSON escapes accepted by +// RFC 8259 but rejected by go.yaml.in/yaml/v4. It returns the original slice +// without allocation unless a rewrite is required. +func normalizeJSONForYAMLParser(jsonBytes []byte) []byte { + if bytes.IndexByte(jsonBytes, '\\') < 0 { return jsonBytes } - result := make([]byte, 0, len(jsonBytes)) - i := 0 - for i < len(jsonBytes) { - if jsonBytes[i] == '\\' && i+1 < len(jsonBytes) { - switch jsonBytes[i+1] { - case '/': - // \/ -> / (json optional escape for solidus) - result = append(result, '/') - i += 2 - case '\\': - // preserve escaped backslash to prevent \\/ becoming // - result = append(result, '\\', '\\') - i += 2 - default: - // preserve other escape sequences (\n, \t, \", etc.) - result = append(result, jsonBytes[i]) - i++ - } - } else { - result = append(result, jsonBytes[i]) - i++ + var result []byte + var runeBytes [utf8.UTFMax]byte + last := 0 + scan := 0 + + for scan < len(jsonBytes) { + rel := bytes.IndexByte(jsonBytes[scan:], '\\') + if rel < 0 { + break + } + + escape := scan + rel + replacement, consumed, ok := jsonEscapeReplacement(jsonBytes, escape, &runeBytes) + if !ok { + scan = nextJSONEscapeScanOffset(jsonBytes, escape) + continue } + + if result == nil { + result = make([]byte, 0, len(jsonBytes)) + } + result = append(result, jsonBytes[last:escape]...) + result = append(result, replacement...) + scan = escape + consumed + last = scan + } + + if result == nil { + return jsonBytes } + + result = append(result, jsonBytes[last:]...) return result } + +func jsonEscapeReplacement(jsonBytes []byte, escape int, runeBytes *[utf8.UTFMax]byte) ([]byte, int, bool) { + if escape+1 >= len(jsonBytes) { + return nil, 0, false + } + + switch jsonBytes[escape+1] { + case '/': + runeBytes[0] = '/' + return runeBytes[:1], 2, true + case 'u': + if escape+12 > len(jsonBytes) { + return nil, 0, false + } + + high, ok := decodeJSONUnicodeEscape(jsonBytes[escape+2 : escape+6]) + if !ok || !isHighSurrogate(high) { + return nil, 0, false + } + + lowEscape := escape + 6 + if jsonBytes[lowEscape] != '\\' || jsonBytes[lowEscape+1] != 'u' { + return nil, 0, false + } + + low, ok := decodeJSONUnicodeEscape(jsonBytes[lowEscape+2 : lowEscape+6]) + if !ok || !isLowSurrogate(low) { + return nil, 0, false + } + + r := utf16.DecodeRune(rune(high), rune(low)) + n := utf8.EncodeRune(runeBytes[:], r) + return runeBytes[:n], 12, true + default: + return nil, 0, false + } +} + +func nextJSONEscapeScanOffset(jsonBytes []byte, escape int) int { + if escape+1 >= len(jsonBytes) { + return escape + 1 + } + return escape + 2 +} + +func decodeJSONUnicodeEscape(hexBytes []byte) (uint16, bool) { + if len(hexBytes) != 4 { + return 0, false + } + + var value uint16 + for _, b := range hexBytes { + hex, ok := jsonHexValue(b) + if !ok { + return 0, false + } + value = value<<4 | uint16(hex) + } + return value, true +} + +func jsonHexValue(b byte) (byte, bool) { + switch { + case b >= '0' && b <= '9': + return b - '0', true + case b >= 'a' && b <= 'f': + return b - 'a' + 10, true + case b >= 'A' && b <= 'F': + return b - 'A' + 10, true + default: + return 0, false + } +} + +func isHighSurrogate(value uint16) bool { + return value >= 0xD800 && value <= 0xDBFF +} + +func isLowSurrogate(value uint16) bool { + return value >= 0xDC00 && value <= 0xDFFF +} diff --git a/vendor/github.com/pb33f/libopenapi/document.go b/vendor/github.com/pb33f/libopenapi/document.go index 65b571a77..9c9bf3e40 100644 --- a/vendor/github.com/pb33f/libopenapi/document.go +++ b/vendor/github.com/pb33f/libopenapi/document.go @@ -137,7 +137,7 @@ type DocumentModel[T v2high.Swagger | v3high.Document] struct { // This function will NOT automatically follow (meaning load) any file or remote references that are found. // // If this isn't the behavior you want, then you can use the NewDocumentWithConfiguration() function instead, which allows you to set a configuration that -// will allow you to control if file or remote references are allowed. In particular the `AllowFileReferences` and `FollowRemoteReferences` +// will allow you to control if file or remote references are allowed. In particular the `AllowFileReferences` and `AllowRemoteReferences` // properties. func NewDocument(specByteArray []byte) (Document, error) { return NewDocumentWithTypeCheck(specByteArray, false) diff --git a/vendor/github.com/pb33f/libopenapi/index/extract_refs_ref.go b/vendor/github.com/pb33f/libopenapi/index/extract_refs_ref.go index 850c120e4..2aeef7f0d 100644 --- a/vendor/github.com/pb33f/libopenapi/index/extract_refs_ref.go +++ b/vendor/github.com/pb33f/libopenapi/index/extract_refs_ref.go @@ -80,6 +80,7 @@ func (index *SpecIndex) extractReferenceAt( Node: node, KeyNode: node.Content[keyIndex+1], Path: path, + SourcePath: append([]string(nil), seenPath...), Index: index, IsExtensionRef: isExtensionPath, HasSiblingProperties: len(siblingProps) > 0, @@ -334,6 +335,7 @@ func (index *SpecIndex) storeReferenceWithSiblings( Node: &copiedNode, KeyNode: node.Content[keyIndex], Path: path, + SourcePath: append([]string(nil), ref.SourcePath...), Index: index, IsExtensionRef: isExtensionPath, HasSiblingProperties: len(siblingProps) > 0, diff --git a/vendor/github.com/pb33f/libopenapi/index/find_component_build.go b/vendor/github.com/pb33f/libopenapi/index/find_component_build.go index 5c027c28f..7c537d2a1 100644 --- a/vendor/github.com/pb33f/libopenapi/index/find_component_build.go +++ b/vendor/github.com/pb33f/libopenapi/index/find_component_build.go @@ -47,6 +47,7 @@ func buildResolvedComponentReference( HasSiblingProperties: source.HasSiblingProperties, In: source.In, } + ref.SourcePath = append([]string(nil), source.SourcePath...) ref.ParentNodeTypes = append([]string(nil), source.ParentNodeTypes...) ref.SiblingKeys = append([]*yaml.Node(nil), source.SiblingKeys...) ref.SiblingProperties = cloneSiblingProperties(source.SiblingProperties) diff --git a/vendor/github.com/pb33f/libopenapi/index/index_model.go b/vendor/github.com/pb33f/libopenapi/index/index_model.go index 46a5c386f..32eb02905 100644 --- a/vendor/github.com/pb33f/libopenapi/index/index_model.go +++ b/vendor/github.com/pb33f/libopenapi/index/index_model.go @@ -40,6 +40,7 @@ type Reference struct { Index *SpecIndex `json:"-"` // index that contains this reference. RemoteLocation string `json:"remoteLocation,omitempty"` Path string `json:"path,omitempty"` // this won't always be available. + SourcePath []string `json:"-"` // OpenAPI path to the source $ref location. RequiredRefProperties map[string][]string `json:"requiredProperties,omitempty"` // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition HasSiblingProperties bool `json:"-"` // indicates if ref has sibling properties SiblingProperties map[string]*yaml.Node `json:"-"` // stores sibling property nodes diff --git a/vendor/github.com/pb33f/libopenapi/index/resolver_relatives.go b/vendor/github.com/pb33f/libopenapi/index/resolver_relatives.go index ca33a60d9..4fee92f3a 100644 --- a/vendor/github.com/pb33f/libopenapi/index/resolver_relatives.go +++ b/vendor/github.com/pb33f/libopenapi/index/resolver_relatives.go @@ -144,6 +144,7 @@ func (resolver *Resolver) extractRelativeReference( RemoteLocation: ref.RemoteLocation, IsRemote: true, Index: ref.Index, + SourcePath: append([]string(nil), ref.SourcePath...), } locatedRef, _, _ := resolver.searchReferenceWithContext(ref, searchRef) diff --git a/vendor/modules.txt b/vendor/modules.txt index 5c32211cd..fb25914a6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -647,7 +647,7 @@ github.com/openshift/machine-config-operator/pkg/daemon/constants github.com/pb33f/jsonpath/pkg/jsonpath github.com/pb33f/jsonpath/pkg/jsonpath/config github.com/pb33f/jsonpath/pkg/jsonpath/token -# github.com/pb33f/libopenapi v0.36.1 +# github.com/pb33f/libopenapi v0.37.2 ## explicit; go 1.25.0 github.com/pb33f/libopenapi github.com/pb33f/libopenapi/datamodel