Added support for unevaluatedProperties as Schema and bool #118

Also ran `gofmt` across the entire project. Things need cleaning up.

Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
Dave Shanley
2023-06-15 08:58:20 -04:00
committed by quobix
parent 148822fa2a
commit c3cf5f1e38
142 changed files with 11751 additions and 11577 deletions

View File

@@ -32,14 +32,17 @@ const (
) )
// OpenAPI3SchemaData is an embedded version of the OpenAPI 3 Schema // OpenAPI3SchemaData is an embedded version of the OpenAPI 3 Schema
//
//go:embed schemas/oas3-schema.json //go:embed schemas/oas3-schema.json
var OpenAPI3SchemaData string // embedded OAS3 schema var OpenAPI3SchemaData string // embedded OAS3 schema
// OpenAPI31SchemaData is an embedded version of the OpenAPI 3.1 Schema // OpenAPI31SchemaData is an embedded version of the OpenAPI 3.1 Schema
//
//go:embed schemas/oas31-schema.json //go:embed schemas/oas31-schema.json
var OpenAPI31SchemaData string // embedded OAS31 schema var OpenAPI31SchemaData string // embedded OAS31 schema
// OpenAPI2SchemaData is an embedded version of the OpenAPI 2 (Swagger) Schema // OpenAPI2SchemaData is an embedded version of the OpenAPI 2 (Swagger) Schema
//
//go:embed schemas/swagger2-schema.json //go:embed schemas/swagger2-schema.json
var OpenAPI2SchemaData string // embedded OAS3 schema var OpenAPI2SchemaData string // embedded OAS3 schema

View File

@@ -9,4 +9,3 @@
// beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure // beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure
// that all the latest features are collected, without damaging backwards compatibility. // that all the latest features are collected, without damaging backwards compatibility.
package base package base

View File

@@ -10,6 +10,7 @@ import (
) )
// Contact represents a high-level representation of the Contact definitions found at // Contact represents a high-level representation of the Contact definitions found at
//
// v2 - https://swagger.io/specification/v2/#contactObject // v2 - https://swagger.io/specification/v2/#contactObject
// v3 - https://spec.openapis.org/oas/v3.1.0#contact-object // v3 - https://spec.openapis.org/oas/v3.1.0#contact-object
type Contact struct { type Contact struct {
@@ -47,6 +48,3 @@ func (c *Contact) MarshalYAML() (interface{}, error) {
nb := low2.NewNodeBuilder(c, c.low) nb := low2.NewNodeBuilder(c, c.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -16,6 +16,7 @@ import (
// which is used to inform the consumer of the document of an alternative schema based on the value associated with it. // which is used to inform the consumer of the document of an alternative schema based on the value associated with it.
// //
// When using the discriminator, inline schemas will not be considered. // When using the discriminator, inline schemas will not be considered.
//
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object // v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"` PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"`

View File

@@ -44,7 +44,6 @@ func (d *DynamicValue[A, B]) RenderInline() ([]byte, error) {
return yaml.Marshal(d) return yaml.Marshal(d)
} }
// MarshalYAML will create a ready to render YAML representation of the DynamicValue object. // MarshalYAML will create a ready to render YAML representation of the DynamicValue object.
func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) { func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) {
// this is a custom renderer, we can't use the NodeBuilder out of the gate. // this is a custom renderer, we can't use the NodeBuilder out of the gate.

View File

@@ -11,6 +11,7 @@ import (
) )
// Example represents a high-level Example object as defined by OpenAPI 3+ // Example represents a high-level Example object as defined by OpenAPI 3+
//
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object // v3 - https://spec.openapis.org/oas/v3.1.0#example-object
type Example struct { type Example struct {
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`

View File

@@ -12,6 +12,7 @@ import (
// ExternalDoc represents a high-level External Documentation object as defined by OpenAPI 2 and 3 // ExternalDoc represents a high-level External Documentation object as defined by OpenAPI 2 and 3
// //
// Allows referencing an external resource for extended documentation. // Allows referencing an external resource for extended documentation.
//
// v2 - https://swagger.io/specification/v2/#externalDocumentationObject // v2 - https://swagger.io/specification/v2/#externalDocumentationObject
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object // v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object
type ExternalDoc struct { type ExternalDoc struct {

View File

@@ -200,5 +200,3 @@ x-cake:
bytes, _ := highInfo.Render() bytes, _ := highInfo.Render()
assert.Len(t, bytes, 275) assert.Len(t, bytes, 275)
} }

View File

@@ -10,6 +10,7 @@ import (
) )
// License is a high-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3 // License is a high-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3
//
// v2 - https://swagger.io/specification/v2/#licenseObject // v2 - https://swagger.io/specification/v2/#licenseObject
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object // v3 - https://spec.openapis.org/oas/v3.1.0#license-object
type License struct { type License struct {

View File

@@ -62,7 +62,10 @@ type Schema struct {
PatternProperties map[string]*SchemaProxy `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` PatternProperties map[string]*SchemaProxy `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"`
PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"`
UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"`
UnevaluatedProperties *SchemaProxy `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"`
// in 3.1 UnevaluatedProperties can be a Schema or a boolean
// https://github.com/pb33f/libopenapi/issues/118
UnevaluatedProperties *DynamicValue[*SchemaProxy, *bool] `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"`
// in 3.1 Items can be a Schema or a boolean // in 3.1 Items can be a Schema or a boolean
Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"` Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"`
@@ -209,11 +212,27 @@ func NewSchema(schema *base.Schema) *Schema {
Value: schema.UnevaluatedItems.Value, Value: schema.UnevaluatedItems.Value,
}} }}
} }
if !schema.UnevaluatedProperties.IsEmpty() { // check if unevaluated properties is a schema
s.UnevaluatedProperties = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ if !schema.UnevaluatedProperties.IsEmpty() && schema.UnevaluatedProperties.Value.IsA() {
s.UnevaluatedProperties = &DynamicValue[*SchemaProxy, *bool]{
A: &SchemaProxy{
schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.UnevaluatedProperties.ValueNode, ValueNode: schema.UnevaluatedProperties.ValueNode,
Value: schema.UnevaluatedProperties.Value, Value: schema.UnevaluatedProperties.Value.A,
}} },
},
}
}
// check if unevaluated properties is a bool
if !schema.UnevaluatedProperties.IsEmpty() && schema.UnevaluatedProperties.Value.IsB() {
s.UnevaluatedProperties = &DynamicValue[*SchemaProxy, *bool]{
B: schema.UnevaluatedProperties.Value.B,
}
}
if !schema.UnevaluatedProperties.IsEmpty() {
} }
s.Pattern = schema.Pattern.Value s.Pattern = schema.Pattern.Value

View File

@@ -65,4 +65,3 @@ func TestCreateSchemaProxyRef(t *testing.T) {
assert.Equal(t, "#/components/schemas/MySchema", sp.GetReference()) assert.Equal(t, "#/components/schemas/MySchema", sp.GetReference())
assert.True(t, sp.IsReference()) assert.True(t, sp.IsReference())
} }

View File

@@ -307,7 +307,7 @@ $anchor: anchor`
assert.Equal(t, "string", compiled.DependentSchemas["schemaOne"].Schema().Type[0]) assert.Equal(t, "string", compiled.DependentSchemas["schemaOne"].Schema().Type[0])
assert.Equal(t, "string", compiled.PropertyNames.Schema().Type[0]) assert.Equal(t, "string", compiled.PropertyNames.Schema().Type[0])
assert.Equal(t, "boolean", compiled.UnevaluatedItems.Schema().Type[0]) assert.Equal(t, "boolean", compiled.UnevaluatedItems.Schema().Type[0])
assert.Equal(t, "integer", compiled.UnevaluatedProperties.Schema().Type[0]) assert.Equal(t, "integer", compiled.UnevaluatedProperties.A.Schema().Type[0])
assert.True(t, compiled.ReadOnly) assert.True(t, compiled.ReadOnly)
assert.True(t, compiled.WriteOnly) assert.True(t, compiled.WriteOnly)
assert.True(t, *compiled.Deprecated) assert.True(t, *compiled.Deprecated)
@@ -1184,3 +1184,25 @@ components:
schemaBytes, _ := compiled.RenderInline() schemaBytes, _ := compiled.RenderInline()
assert.Len(t, schemaBytes, 585) assert.Len(t, schemaBytes, 585)
} }
func TestUnevaluatedPropertiesBoolean_True(t *testing.T) {
yml := `
type: number
unevaluatedProperties: true
`
highSchema := getHighSchema(t, yml)
value := true
assert.EqualValues(t, &value, highSchema.UnevaluatedProperties.B)
}
func TestUnevaluatedPropertiesBoolean_False(t *testing.T) {
yml := `
type: number
unevaluatedProperties: false
`
highSchema := getHighSchema(t, yml)
value := false
assert.EqualValues(t, &value, highSchema.UnevaluatedProperties.B)
}

View File

@@ -108,7 +108,7 @@ func (s *SecurityRequirement) MarshalYAML() (interface{}, error) {
for t := range keys[k].val { for t := range keys[k].val {
reqs[t] = &req{val: keys[k].val[t], line: 9999 + t} reqs[t] = &req{val: keys[k].val[t], line: 9999 + t}
if keys[k].lowVal != nil { if keys[k].lowVal != nil {
for _ = range keys[k].lowVal.Value[t].Value { for range keys[k].lowVal.Value[t].Value {
fh := keys[k].val[t] fh := keys[k].val[t]
df := keys[k].lowVal.Value[t].Value df := keys[k].lowVal.Value[t].Value
if fh == df { if fh == df {

View File

@@ -16,6 +16,7 @@ import (
// //
// When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be // When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be
// used to add that information. See examples for expected behavior. // used to add that information. See examples for expected behavior.
//
// v2 - https://swagger.io/specification/v2/#xmlObject // v2 - https://swagger.io/specification/v2/#xmlObject
// v3 - https://swagger.io/specification/#xml-object // v3 - https://swagger.io/specification/#xml-object
type XML struct { type XML struct {
@@ -61,4 +62,3 @@ func (x *XML) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(x, x.low) nb := high.NewNodeBuilder(x, x.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -58,6 +58,7 @@ func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReferenc
// `low` represents the HIGH type of the object that contains the extensions. // `low` represents the HIGH type of the object that contains the extensions.
// //
// to use: // to use:
//
// schema := schemaProxy.Schema() // any high-level object that has extensions // schema := schemaProxy.Schema() // any high-level object that has extensions
// extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema) // extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema)
func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string]*T, error) { func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string]*T, error) {
@@ -75,4 +76,3 @@ func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string
} }
return m, nil return m, nil
} }

View File

@@ -16,17 +16,26 @@ import (
// There are five possible parameter types. // There are five possible parameter types.
// //
// Path // Path
//
// Used together with Path Templating, where the parameter value is actually part of the operation's URL. // Used together with Path Templating, where the parameter value is actually part of the operation's URL.
// This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId. // This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId.
//
// Query // Query
//
// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id. // Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id.
//
// Header // Header
//
// Custom headers that are expected as part of the request. // Custom headers that are expected as part of the request.
//
// Body // Body
//
// The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter. // The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter.
// The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. // The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only.
// Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation. // Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation.
//
// Form // Form
//
// Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data // Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data
// or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation). // or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation).
// This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters // This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters
@@ -39,6 +48,7 @@ import (
// multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for // multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for
// the header Content-Disposition: form-data; name="submit-name" the name of the parameter is // the header Content-Disposition: form-data; name="submit-name" the name of the parameter is
// submit-name. This type of form parameters is more commonly used for file transfers // submit-name. This type of form parameters is more commonly used for file transfers
//
// https://swagger.io/specification/v2/#parameterObject // https://swagger.io/specification/v2/#parameterObject
type Parameter struct { type Parameter struct {
Name string Name string

View File

@@ -63,4 +63,3 @@ func (o *OAuthFlows) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(o, o.low) nb := high.NewNodeBuilder(o, o.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -65,7 +65,7 @@ func TestOperation_MarshalYAML(t *testing.T) {
}, },
OperationId: "slice", OperationId: "slice",
Parameters: []*Parameter{ Parameters: []*Parameter{
&Parameter{ {
Name: "mice", Name: "mice",
}, },
}, },
@@ -103,7 +103,7 @@ func TestOperation_MarshalYAMLInline(t *testing.T) {
}, },
OperationId: "slice", OperationId: "slice",
Parameters: []*Parameter{ Parameters: []*Parameter{
&Parameter{ {
Name: "mice", Name: "mice",
}, },
}, },

View File

@@ -9,4 +9,3 @@
// beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure // beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure
// that all the latest features are collected, without damaging backwards compatibility. // that all the latest features are collected, without damaging backwards compatibility.
package base package base

View File

@@ -12,6 +12,7 @@ import (
) )
// Contact represents a low-level representation of the Contact definitions found at // Contact represents a low-level representation of the Contact definitions found at
//
// v2 - https://swagger.io/specification/v2/#contactObject // v2 - https://swagger.io/specification/v2/#contactObject
// v3 - https://spec.openapis.org/oas/v3.1.0#contact-object // v3 - https://spec.openapis.org/oas/v3.1.0#contact-object
type Contact struct { type Contact struct {
@@ -42,4 +43,3 @@ func (c *Contact) Hash() [32]byte {
} }
return sha256.Sum256([]byte(strings.Join(f, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -17,6 +17,7 @@ import (
// which is used to inform the consumer of the document of an alternative schema based on the value associated with it. // which is used to inform the consumer of the document of an alternative schema based on the value associated with it.
// //
// When using the discriminator, inline schemas will not be considered. // When using the discriminator, inline schemas will not be considered.
//
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object // v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName low.NodeReference[string] PropertyName low.NodeReference[string]

View File

@@ -16,6 +16,7 @@ import (
) )
// Example represents a low-level Example object as defined by OpenAPI 3+ // Example represents a low-level Example object as defined by OpenAPI 3+
//
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object // v3 - https://spec.openapis.org/oas/v3.1.0#example-object
type Example struct { type Example struct {
Summary low.NodeReference[string] Summary low.NodeReference[string]

View File

@@ -16,6 +16,7 @@ import (
// ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3 // ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3
// //
// Allows referencing an external resource for extended documentation. // Allows referencing an external resource for extended documentation.
//
// v2 - https://swagger.io/specification/v2/#externalDocumentationObject // v2 - https://swagger.io/specification/v2/#externalDocumentationObject
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object // v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object
type ExternalDoc struct { type ExternalDoc struct {

View File

@@ -12,6 +12,7 @@ import (
) )
// License is a low-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3 // License is a low-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3
//
// v2 - https://swagger.io/specification/v2/#licenseObject // v2 - https://swagger.io/specification/v2/#licenseObject
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object // v3 - https://spec.openapis.org/oas/v3.1.0#license-object
type License struct { type License struct {

View File

@@ -29,15 +29,26 @@ type SchemaDynamicValue[A any, B any] struct {
} }
// IsA will return true if the 'A' or left value is set. (OpenAPI 3) // IsA will return true if the 'A' or left value is set. (OpenAPI 3)
func (s SchemaDynamicValue[A, B]) IsA() bool { func (s *SchemaDynamicValue[A, B]) IsA() bool {
return s.N == 0 return s.N == 0
} }
// IsB will return true if the 'B' or right value is set (OpenAPI 3.1) // IsB will return true if the 'B' or right value is set (OpenAPI 3.1)
func (s SchemaDynamicValue[A, B]) IsB() bool { func (s *SchemaDynamicValue[A, B]) IsB() bool {
return s.N == 1 return s.N == 1
} }
// Hash will generate a stable hash of the SchemaDynamicValue
func (s *SchemaDynamicValue[A, B]) Hash() [32]byte {
var hash string
if s.IsA() {
hash = low.GenerateHashString(s.A)
} else {
hash = low.GenerateHashString(s.B)
}
return sha256.Sum256([]byte(hash))
}
// Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1 // Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1
// //
// Until 3.1 OpenAPI had a strange relationship with JSON Schema. It's been a super-set/sub-set // Until 3.1 OpenAPI had a strange relationship with JSON Schema. It's been a super-set/sub-set
@@ -89,7 +100,7 @@ type Schema struct {
PatternProperties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]] PatternProperties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
PropertyNames low.NodeReference[*SchemaProxy] PropertyNames low.NodeReference[*SchemaProxy]
UnevaluatedItems low.NodeReference[*SchemaProxy] UnevaluatedItems low.NodeReference[*SchemaProxy]
UnevaluatedProperties low.NodeReference[*SchemaProxy] UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]]
Anchor low.NodeReference[string] Anchor low.NodeReference[string]
// Compatible with all versions // Compatible with all versions
@@ -706,7 +717,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
for i := range addPNode.Content { for i := range addPNode.Content {
if utils.IsNodeMap(addPNode.Content[i]) { if utils.IsNodeMap(addPNode.Content[i]) {
var prop map[string]any var prop map[string]any
addPNode.Content[i].Decode(&prop) _ = addPNode.Content[i].Decode(&prop)
addProps = append(addProps, addProps = append(addProps,
low.ValueReference[any]{Value: prop, ValueNode: addPNode.Content[i]}) low.ValueReference[any]{Value: prop, ValueNode: addPNode.Content[i]})
} else { } else {
@@ -801,6 +812,27 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
} }
// check unevaluatedProperties type for schema or bool (3.1 only)
unevalIsBool := false
unevalBoolValue := true
_, unevalLabel, unevalValue := utils.FindKeyNodeFullTop(UnevaluatedPropertiesLabel, root.Content)
if unevalValue != nil {
if utils.IsNodeBoolValue(unevalValue) {
unevalIsBool = true
unevalBoolValue, _ = strconv.ParseBool(unevalValue.Value)
}
}
if unevalIsBool {
s.UnevaluatedProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]]{
Value: &SchemaDynamicValue[*SchemaProxy, *bool]{
B: &unevalBoolValue,
N: 1,
},
KeyNode: unevalLabel,
ValueNode: unevalValue,
}
}
var allOf, anyOf, oneOf, prefixItems []low.ValueReference[*SchemaProxy] var allOf, anyOf, oneOf, prefixItems []low.ValueReference[*SchemaProxy]
var items, not, contains, sif, selse, sthen, propertyNames, unevalItems, unevalProperties low.ValueReference[*SchemaProxy] var items, not, contains, sif, selse, sthen, propertyNames, unevalItems, unevalProperties low.ValueReference[*SchemaProxy]
@@ -814,7 +846,6 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
_, selseLabel, selseValue := utils.FindKeyNodeFullTop(ElseLabel, root.Content) _, selseLabel, selseValue := utils.FindKeyNodeFullTop(ElseLabel, root.Content)
_, sthenLabel, sthenValue := utils.FindKeyNodeFullTop(ThenLabel, root.Content) _, sthenLabel, sthenValue := utils.FindKeyNodeFullTop(ThenLabel, root.Content)
_, propNamesLabel, propNamesValue := utils.FindKeyNodeFullTop(PropertyNamesLabel, root.Content) _, propNamesLabel, propNamesValue := utils.FindKeyNodeFullTop(PropertyNamesLabel, root.Content)
_, unevalItemsLabel, unevalItemsValue := utils.FindKeyNodeFullTop(UnevaluatedItemsLabel, root.Content) _, unevalItemsLabel, unevalItemsValue := utils.FindKeyNodeFullTop(UnevaluatedItemsLabel, root.Content)
_, unevalPropsLabel, unevalPropsValue := utils.FindKeyNodeFullTop(UnevaluatedPropertiesLabel, root.Content) _, unevalPropsLabel, unevalPropsValue := utils.FindKeyNodeFullTop(UnevaluatedPropertiesLabel, root.Content)
@@ -882,7 +913,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
totalBuilds++ totalBuilds++
go buildSchema(unevalItemsChan, unevalItemsLabel, unevalItemsValue, errorChan, idx) go buildSchema(unevalItemsChan, unevalItemsLabel, unevalItemsValue, errorChan, idx)
} }
if unevalPropsValue != nil { if !unevalIsBool && unevalPropsValue != nil {
totalBuilds++ totalBuilds++
go buildSchema(unevalPropsChan, unevalPropsLabel, unevalPropsValue, errorChan, idx) go buildSchema(unevalPropsChan, unevalPropsLabel, unevalPropsValue, errorChan, idx)
} }
@@ -1020,9 +1051,11 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
ValueNode: unevalItemsValue, ValueNode: unevalItemsValue,
} }
} }
if !unevalProperties.IsEmpty() { if !unevalIsBool && !unevalProperties.IsEmpty() {
s.UnevaluatedProperties = low.NodeReference[*SchemaProxy]{ s.UnevaluatedProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]]{
Value: unevalProperties.Value, Value: &SchemaDynamicValue[*SchemaProxy, *bool]{
A: unevalProperties.Value,
},
KeyNode: unevalPropsLabel, KeyNode: unevalPropsLabel,
ValueNode: unevalPropsValue, ValueNode: unevalPropsValue,
} }

View File

@@ -309,7 +309,7 @@ func Test_Schema(t *testing.T) {
assert.Equal(t, "string", sch.FindPatternProperty("patternOne").Value.Schema().Type.Value.A) assert.Equal(t, "string", sch.FindPatternProperty("patternOne").Value.Schema().Type.Value.A)
assert.Equal(t, "string", sch.PropertyNames.Value.Schema().Type.Value.A) assert.Equal(t, "string", sch.PropertyNames.Value.Schema().Type.Value.A)
assert.Equal(t, "boolean", sch.UnevaluatedItems.Value.Schema().Type.Value.A) assert.Equal(t, "boolean", sch.UnevaluatedItems.Value.Schema().Type.Value.A)
assert.Equal(t, "integer", sch.UnevaluatedProperties.Value.Schema().Type.Value.A) assert.Equal(t, "integer", sch.UnevaluatedProperties.Value.A.Schema().Type.Value.A)
assert.Equal(t, "anchor", sch.Anchor.Value) assert.Equal(t, "anchor", sch.Anchor.Value)
} }
@@ -1575,3 +1575,71 @@ allOf:
- description: allOf sequence check 4 - description: allOf sequence check 4
` `
} }
func TestSchema_UnevaluatedPropertiesAsBool_DefinedAsTrue(t *testing.T) {
yml := `components:
schemas:
Something:
unevaluatedProperties: true`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndexWithConfig(&iNode, index.CreateOpenAPIIndexConfig())
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(idxNode.Content[0], idx)
assert.True(t, res.Value.Schema().UnevaluatedProperties.Value.IsB())
assert.True(t, *res.Value.Schema().UnevaluatedProperties.Value.B)
}
func TestSchema_UnevaluatedPropertiesAsBool_DefinedAsFalse(t *testing.T) {
yml := `components:
schemas:
Something:
unevaluatedProperties: false`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndexWithConfig(&iNode, index.CreateOpenAPIIndexConfig())
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(idxNode.Content[0], idx)
assert.True(t, res.Value.Schema().UnevaluatedProperties.Value.IsB())
assert.False(t, *res.Value.Schema().UnevaluatedProperties.Value.B)
}
func TestSchema_UnevaluatedPropertiesAsBool_Undefined(t *testing.T) {
yml := `components:
schemas:
Something:
description: I have not defined unevaluatedProperties`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndexWithConfig(&iNode, index.CreateOpenAPIIndexConfig())
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(idxNode.Content[0], idx)
assert.Nil(t, res.Value.Schema().UnevaluatedProperties.Value)
}

View File

@@ -16,6 +16,7 @@ import (
// //
// When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be // When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be
// used to add that information. See examples for expected behavior. // used to add that information. See examples for expected behavior.
//
// v2 - https://swagger.io/specification/v2/#xmlObject // v2 - https://swagger.io/specification/v2/#xmlObject
// v3 - https://swagger.io/specification/#xml-object // v3 - https://swagger.io/specification/#xml-object
type XML struct { type XML struct {

View File

@@ -1024,7 +1024,7 @@ one:
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
for k, _ := range things { for k := range things {
assert.Equal(t, "one", k.Value) assert.Equal(t, "one", k.Value)
} }
} }

View File

@@ -51,7 +51,7 @@ func TestNodeReference_Mutate(t *testing.T) {
func TestNodeReference_RefNode(t *testing.T) { func TestNodeReference_RefNode(t *testing.T) {
nr := new(NodeReference[string]) nr := new(NodeReference[string])
nr.KeyNode = &yaml.Node{ nr.KeyNode = &yaml.Node{
Content: []*yaml.Node{&yaml.Node{ Content: []*yaml.Node{{
Value: "$ref", Value: "$ref",
}}, }},
} }
@@ -749,8 +749,3 @@ func TestKeyReference_GetKeyNode(t *testing.T) {
assert.Equal(t, 3, nr.GetKeyNode().Line) assert.Equal(t, 3, nr.GetKeyNode().Line)
assert.Equal(t, "pizza", nr.GetKeyNode().Value) assert.Equal(t, "pizza", nr.GetKeyNode().Value)
} }

View File

@@ -22,17 +22,26 @@ import (
// There are five possible parameter types. // There are five possible parameter types.
// //
// Path // Path
//
// Used together with Path Templating, where the parameter value is actually part of the operation's URL. // Used together with Path Templating, where the parameter value is actually part of the operation's URL.
// This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId. // This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId.
//
// Query // Query
//
// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id. // Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id.
//
// Header // Header
//
// Custom headers that are expected as part of the request. // Custom headers that are expected as part of the request.
//
// Body // Body
//
// The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter. // The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter.
// The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. // The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only.
// Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation. // Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation.
//
// Form // Form
//
// Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data // Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data
// or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation). // or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation).
// This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters // This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters
@@ -45,6 +54,7 @@ import (
// multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for // multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for
// the header Content-Disposition: form-data; name="submit-name" the name of the parameter is // the header Content-Disposition: form-data; name="submit-name" the name of the parameter is
// submit-name. This type of form parameters is more commonly used for file transfers // submit-name. This type of form parameters is more commonly used for file transfers
//
// https://swagger.io/specification/v2/#parameterObject // https://swagger.io/specification/v2/#parameterObject
type Parameter struct { type Parameter struct {
Name low.NodeReference[string] Name low.NodeReference[string]

View File

@@ -446,5 +446,3 @@ func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bo
return p return p
} }

View File

@@ -29,7 +29,7 @@ func (a *ExamplesChanges) TotalBreakingChanges() int {
} }
// CompareExamplesV2 compares two Swagger Examples objects, returning a pointer to // CompareExamplesV2 compares two Swagger Examples objects, returning a pointer to
//ExamplesChanges if anything was found. // ExamplesChanges if anything was found.
func CompareExamplesV2(l, r *v2.Examples) *ExamplesChanges { func CompareExamplesV2(l, r *v2.Examples) *ExamplesChanges {
lHashes := make(map[string]string) lHashes := make(map[string]string)

View File

@@ -1012,12 +1012,28 @@ func checkSchemaPropertyChanges(
CreateChange(changes, ObjectRemoved, v3.UnevaluatedItemsLabel, CreateChange(changes, ObjectRemoved, v3.UnevaluatedItemsLabel,
lSchema.UnevaluatedItems.ValueNode, nil, true, lSchema.UnevaluatedItems.Value, nil) lSchema.UnevaluatedItems.ValueNode, nil, true, lSchema.UnevaluatedItems.Value, nil)
} }
// UnevaluatedProperties // UnevaluatedProperties
if lSchema.UnevaluatedProperties.Value != nil && rSchema.UnevaluatedProperties.Value != nil { if lSchema.UnevaluatedProperties.Value != nil && rSchema.UnevaluatedProperties.Value != nil {
if !low.AreEqual(lSchema.UnevaluatedProperties.Value, rSchema.UnevaluatedProperties.Value) { if lSchema.UnevaluatedProperties.Value.IsA() && rSchema.UnevaluatedProperties.Value.IsA() {
sc.UnevaluatedPropertiesChanges = CompareSchemas(lSchema.UnevaluatedProperties.Value, rSchema.UnevaluatedProperties.Value) if !low.AreEqual(lSchema.UnevaluatedProperties.Value.A, rSchema.UnevaluatedProperties.Value.A) {
sc.UnevaluatedPropertiesChanges = CompareSchemas(lSchema.UnevaluatedProperties.Value.A, rSchema.UnevaluatedProperties.Value.A)
}
} else {
if lSchema.UnevaluatedProperties.Value.IsB() && rSchema.UnevaluatedProperties.Value.IsB() {
if lSchema.UnevaluatedProperties.Value.B != rSchema.UnevaluatedProperties.Value.B {
CreateChange(changes, Modified, v3.UnevaluatedPropertiesLabel,
lSchema.UnevaluatedProperties.ValueNode, rSchema.UnevaluatedProperties.ValueNode, true,
lSchema.UnevaluatedProperties.Value.B, rSchema.UnevaluatedProperties.Value.B)
}
} else {
CreateChange(changes, Modified, v3.UnevaluatedPropertiesLabel,
lSchema.UnevaluatedProperties.ValueNode, rSchema.UnevaluatedProperties.ValueNode, true,
lSchema.UnevaluatedProperties.Value.B, rSchema.UnevaluatedProperties.Value.B)
} }
} }
}
// added UnevaluatedProperties // added UnevaluatedProperties
if lSchema.UnevaluatedProperties.Value == nil && rSchema.UnevaluatedProperties.Value != nil { if lSchema.UnevaluatedProperties.Value == nil && rSchema.UnevaluatedProperties.Value != nil {
CreateChange(changes, ObjectAdded, v3.UnevaluatedPropertiesLabel, CreateChange(changes, ObjectAdded, v3.UnevaluatedPropertiesLabel,