diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index 9ebd2f3..0b5fa21 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -5,9 +5,10 @@ package base import ( "fmt" - "gopkg.in/yaml.v3" "sync" + "gopkg.in/yaml.v3" + "github.com/pb33f/libopenapi/datamodel/high" lowmodel "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" @@ -68,6 +69,9 @@ type Schema struct { // in 3.1 Items can be a Schema or a boolean Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"` + // 3.1 only, part of the JSON Schema spec provides a way to identify a subschema + Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"` + // Compatible with all versions Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"` Properties map[string]*SchemaProxy `json:"properties,omitempty" yaml:"properties,omitempty"` @@ -77,26 +81,26 @@ type Schema struct { Minimum *int64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` MaxLength *int64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` MinLength *int64 `json:"minLength,omitempty" yaml:"minLength,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"` - UniqueItems *int64 `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` - MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` - MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` - Required []string `json:"required,omitempty" yaml:"required,omitempty"` - Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` - AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"` - Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` - ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 - WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 - XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` - ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` - Example any `json:"example,omitempty" yaml:"example,omitempty"` - Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + 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"` + UniqueItems *int64 `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"` + Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 + XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` + ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *base.Schema // Parent Proxy refers back to the low level SchemaProxy that is proxying this schema. @@ -282,6 +286,10 @@ func NewSchema(schema *base.Schema) *Schema { } s.Enum = enum + if !schema.Anchor.IsEmpty() { + s.Anchor = schema.Anchor.Value + } + // async work. // any polymorphic properties need to be handled in their own threads // any properties each need to be processed in their own thread. diff --git a/datamodel/high/base/schema_test.go b/datamodel/high/base/schema_test.go index f30df45..553bcc3 100644 --- a/datamodel/high/base/schema_test.go +++ b/datamodel/high/base/schema_test.go @@ -266,7 +266,8 @@ minLength: 1 maxItems: 20 minItems: 10 maxProperties: 30 -minProperties: 1` +minProperties: 1 +$anchor: anchor` var compNode yaml.Node _ = yaml.Unmarshal([]byte(testSpec), &compNode) @@ -310,6 +311,7 @@ minProperties: 1` assert.True(t, compiled.WriteOnly) assert.True(t, *compiled.Deprecated) assert.True(t, *compiled.Nullable) + assert.Equal(t, "anchor", compiled.Anchor) wentLow := compiled.GoLow() assert.Equal(t, 129, wentLow.AdditionalProperties.ValueNode.Line) @@ -317,8 +319,7 @@ minProperties: 1` // now render it out! schemaBytes, _ := compiled.Render() - assert.Len(t, schemaBytes, 3460) - + assert.Len(t, schemaBytes, 3476) } func TestSchemaObjectWithAllOfSequenceOrder(t *testing.T) { diff --git a/datamodel/low/base/constants.go b/datamodel/low/base/constants.go index ef171f0..dd48f40 100644 --- a/datamodel/low/base/constants.go +++ b/datamodel/low/base/constants.go @@ -39,15 +39,16 @@ const ( OneOfLabel = "oneOf" NotLabel = "not" TypeLabel = "type" - DiscriminatorLabel = "discriminator" - ExclusiveMinimumLabel = "exclusiveMinimum" - ExclusiveMaximumLabel = "exclusiveMaximum" - SchemaLabel = "schema" - SchemaTypeLabel = "$schema" + DiscriminatorLabel = "discriminator" + ExclusiveMinimumLabel = "exclusiveMinimum" + ExclusiveMaximumLabel = "exclusiveMaximum" + SchemaLabel = "schema" + SchemaTypeLabel = "$schema" + AnchorLabel = "$anchor" ) /* PropertyNames low.NodeReference[*SchemaProxy] UnevaluatedItems low.NodeReference[*SchemaProxy] UnevaluatedProperties low.NodeReference[*SchemaProxy] -*/ \ No newline at end of file +*/ diff --git a/datamodel/low/base/schema.go b/datamodel/low/base/schema.go index 4595f23..1b35e77 100644 --- a/datamodel/low/base/schema.go +++ b/datamodel/low/base/schema.go @@ -90,6 +90,7 @@ type Schema struct { PropertyNames low.NodeReference[*SchemaProxy] UnevaluatedItems low.NodeReference[*SchemaProxy] UnevaluatedProperties low.NodeReference[*SchemaProxy] + Anchor low.NodeReference[string] // Compatible with all versions Title low.NodeReference[string] @@ -394,6 +395,9 @@ func (s *Schema) Hash() [32]byte { if !s.UnevaluatedItems.IsEmpty() { d = append(d, low.GenerateHashString(s.UnevaluatedItems.Value)) } + if !s.Anchor.IsEmpty() { + d = append(d, fmt.Sprint(s.Anchor.Value)) + } depSchemasKeys := make([]string, len(s.DependentSchemas.Value)) z = 0 @@ -514,6 +518,7 @@ func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference // - PropertyNames // - UnevaluatedItems // - UnevaluatedProperties +// - Anchor func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { s.Reference = new(low.Reference) if h, _, _ := utils.IsNodeRefValue(root); h { @@ -615,6 +620,14 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { } } + // handle anchor if set. (3.1) + _, anchorLabel, anchorNode := utils.FindKeyNodeFullTop(AnchorLabel, root.Content) + if anchorNode != nil { + s.Anchor = low.NodeReference[string]{ + Value: anchorNode.Value, KeyNode: anchorLabel, ValueNode: anchorLabel, + } + } + // handle example if set. (3.0) _, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) if expNode != nil { diff --git a/datamodel/low/base/schema_test.go b/datamodel/low/base/schema_test.go index e7b0863..7b5f416 100644 --- a/datamodel/low/base/schema_test.go +++ b/datamodel/low/base/schema_test.go @@ -150,7 +150,8 @@ examples: contains: type: int maxContains: 10 -minContains: 1` +minContains: 1 +$anchor: anchor` } func Test_Schema(t *testing.T) { @@ -308,6 +309,7 @@ func Test_Schema(t *testing.T) { 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, "integer", sch.UnevaluatedProperties.Value.Schema().Type.Value.A) + assert.Equal(t, "anchor", sch.Anchor.Value) } func TestSchemaAllOfSequenceOrder(t *testing.T) { diff --git a/datamodel/low/v3/constants.go b/datamodel/low/v3/constants.go index ac2b666..14740d6 100644 --- a/datamodel/low/v3/constants.go +++ b/datamodel/low/v3/constants.go @@ -5,86 +5,86 @@ package v3 // Label definitions used to look up vales in yaml.Node tree. const ( - ComponentsLabel = "components" - SchemasLabel = "schemas" - EncodingLabel = "encoding" - HeadersLabel = "headers" - ExpressionLabel = "expression" - InfoLabel = "info" - SwaggerLabel = "swagger" - ParametersLabel = "parameters" - RequestBodyLabel = "requestBody" - RequestBodiesLabel = "requestBodies" - ResponsesLabel = "responses" - CallbacksLabel = "callbacks" - ContentLabel = "content" - PathsLabel = "paths" - PathLabel = "path" - WebhooksLabel = "webhooks" - JSONSchemaDialectLabel = "jsonSchemaDialect" - JSONSchemaLabel = "$schema" - GetLabel = "get" - PostLabel = "post" - PatchLabel = "patch" - PutLabel = "put" - DeleteLabel = "delete" - OptionsLabel = "options" - HeadLabel = "head" - TraceLabel = "trace" - LinksLabel = "links" - DefaultLabel = "default" - SecurityLabel = "security" - SecuritySchemesLabel = "securitySchemes" - OAuthFlowsLabel = "flows" - VariablesLabel = "variables" - ServersLabel = "servers" - ServerLabel = "server" - ImplicitLabel = "implicit" - PasswordLabel = "password" - ClientCredentialsLabel = "clientCredentials" - AuthorizationCodeLabel = "authorizationCode" - DescriptionLabel = "description" - URLLabel = "url" - NameLabel = "name" - EmailLabel = "email" - TitleLabel = "title" - TermsOfServiceLabel = "termsOfService" - VersionLabel = "version" - OpenAPILabel = "openapi" - HostLabel = "host" - BasePathLabel = "basePath" - LicenseLabel = "license" - ContactLabel = "contact" - NamespaceLabel = "namespace" - PrefixLabel = "prefix" - AttributeLabel = "attribute" - WrappedLabel = "wrapped" - PropertyNameLabel = "propertyName" - SummaryLabel = "summary" - ValueLabel = "value" - ExternalValue = "externalValue" - SchemaDialectLabel = "$schema" - ExclusiveMaximumLabel = "exclusiveMaximum" - ExclusiveMinimumLabel = "exclusiveMinimum" - TypeLabel = "type" - TagsLabel = "tags" - MultipleOfLabel = "multipleOf" - MaximumLabel = "maximum" - MinimumLabel = "minimum" - MaxLengthLabel = "maxLength" - MinLengthLabel = "minLength" - PatternLabel = "pattern" - FormatLabel = "format" - MaxItemsLabel = "maxItems" - ExamplesLabel = "examples" - MinItemsLabel = "minItems" - UniqueItemsLabel = "uniqueItems" - MaxPropertiesLabel = "maxProperties" - MinPropertiesLabel = "minProperties" - RequiredLabel = "required" - EnumLabel = "enum" - SchemaLabel = "schema" - NotLabel = "not" + ComponentsLabel = "components" + SchemasLabel = "schemas" + EncodingLabel = "encoding" + HeadersLabel = "headers" + ExpressionLabel = "expression" + InfoLabel = "info" + SwaggerLabel = "swagger" + ParametersLabel = "parameters" + RequestBodyLabel = "requestBody" + RequestBodiesLabel = "requestBodies" + ResponsesLabel = "responses" + CallbacksLabel = "callbacks" + ContentLabel = "content" + PathsLabel = "paths" + PathLabel = "path" + WebhooksLabel = "webhooks" + JSONSchemaDialectLabel = "jsonSchemaDialect" + JSONSchemaLabel = "$schema" + GetLabel = "get" + PostLabel = "post" + PatchLabel = "patch" + PutLabel = "put" + DeleteLabel = "delete" + OptionsLabel = "options" + HeadLabel = "head" + TraceLabel = "trace" + LinksLabel = "links" + DefaultLabel = "default" + SecurityLabel = "security" + SecuritySchemesLabel = "securitySchemes" + OAuthFlowsLabel = "flows" + VariablesLabel = "variables" + ServersLabel = "servers" + ServerLabel = "server" + ImplicitLabel = "implicit" + PasswordLabel = "password" + ClientCredentialsLabel = "clientCredentials" + AuthorizationCodeLabel = "authorizationCode" + DescriptionLabel = "description" + URLLabel = "url" + NameLabel = "name" + EmailLabel = "email" + TitleLabel = "title" + TermsOfServiceLabel = "termsOfService" + VersionLabel = "version" + OpenAPILabel = "openapi" + HostLabel = "host" + BasePathLabel = "basePath" + LicenseLabel = "license" + ContactLabel = "contact" + NamespaceLabel = "namespace" + PrefixLabel = "prefix" + AttributeLabel = "attribute" + WrappedLabel = "wrapped" + PropertyNameLabel = "propertyName" + SummaryLabel = "summary" + ValueLabel = "value" + ExternalValue = "externalValue" + SchemaDialectLabel = "$schema" + ExclusiveMaximumLabel = "exclusiveMaximum" + ExclusiveMinimumLabel = "exclusiveMinimum" + TypeLabel = "type" + TagsLabel = "tags" + MultipleOfLabel = "multipleOf" + MaximumLabel = "maximum" + MinimumLabel = "minimum" + MaxLengthLabel = "maxLength" + MinLengthLabel = "minLength" + PatternLabel = "pattern" + FormatLabel = "format" + MaxItemsLabel = "maxItems" + ExamplesLabel = "examples" + MinItemsLabel = "minItems" + UniqueItemsLabel = "uniqueItems" + MaxPropertiesLabel = "maxProperties" + MinPropertiesLabel = "minProperties" + RequiredLabel = "required" + EnumLabel = "enum" + SchemaLabel = "schema" + NotLabel = "not" ItemsLabel = "items" PropertiesLabel = "properties" AllOfLabel = "allOf" @@ -136,4 +136,5 @@ const ( UnevaluatedPropertiesLabel = "unevaluatedProperties" DependentSchemasLabel = "dependentSchemas" PatternPropertiesLabel = "patternProperties" + AnchorLabel = "$anchor" )