From 97eef1f94d4c9f7cc871373e089413e3971b427f Mon Sep 17 00:00:00 2001 From: Sergey Vilgelm Date: Thu, 23 Dec 2021 22:45:45 -0800 Subject: [PATCH] Use govet (#3) * use go vet * Use go vet and fieldalignment --- .github/workflows/checks.yaml | 6 + Makefile | 10 +- spec/bool_or_schema.go | 2 +- spec/bool_or_schema_test.go | 58 ++-- spec/discriminator.go | 4 +- spec/header.go | 60 ++-- spec/json_schema.go | 258 ++++++++++++++++++ spec/link.go | 20 +- spec/{ouath-flow.go => oauth-flow.go} | 14 +- spec/{ouath-flows.go => oauth-flows.go} | 0 spec/openapi.go | 30 +- spec/operation.go | 48 ++-- spec/parameter.go | 78 +++--- spec/ref_test.go | 30 +- spec/{request-body.go => request_body.go} | 8 +- spec/response.go | 8 +- spec/schema.go | 254 +---------------- spec/server.go | 6 +- ...{server-variable.go => server_variable.go} | 6 +- spec/tag.go | 4 +- 20 files changed, 472 insertions(+), 432 deletions(-) create mode 100644 spec/json_schema.go rename spec/{ouath-flow.go => oauth-flow.go} (100%) rename spec/{ouath-flows.go => oauth-flows.go} (100%) rename spec/{request-body.go => request_body.go} (100%) rename spec/{server-variable.go => server_variable.go} (100%) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 90835a0..1d868a6 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -44,6 +44,12 @@ jobs: with: stable: false go-version: ${{ env.GO }} + - name: gofmt + run: diff -u <(echo -n) <(gofmt -l . ) + - name: go vet + run: go vet ./... + - name: fieldalignment + run: go vet -vettool=$(which fieldalignment) ./... - name: Run Unit Tests run: go test -race -cover -coverprofile=coverage.out -covermode=atomic ./... - name: Codecov diff --git a/Makefile b/Makefile index 98d8481..455d32a 100644 --- a/Makefile +++ b/Makefile @@ -4,19 +4,23 @@ ifeq ($(shell uname), Darwin) all: brew-install endif -# +lint -all: tidy test +all: go-install tidy lint test brew-install: @brew bundle --file $(BREWFILE) +go-install: + @go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest + run-test: @go test -cover -race ./... test: run-test +# @golangci-lint run --fix ./... lint: - @golangci-lint run --fix ./... + @go vet ./... + @go vet -vettool=$(which fieldalignment) ./... tidy: @go mod tidy diff --git a/spec/bool_or_schema.go b/spec/bool_or_schema.go index 95e8e89..1f96abf 100644 --- a/spec/bool_or_schema.go +++ b/spec/bool_or_schema.go @@ -11,8 +11,8 @@ import ( // It MUST be used as a pointer, // otherwise the `false` can be omitted by json or yaml encoders in case of `omitempty` tag is set. type BoolOrSchema struct { - Allowed bool Schema *RefOrSpec[Schema] + Allowed bool } // NewBoolOrSchema creates BoolOrSchema object. diff --git a/spec/bool_or_schema_test.go b/spec/bool_or_schema_test.go index 4fd640a..c0894c2 100644 --- a/spec/bool_or_schema_test.go +++ b/spec/bool_or_schema_test.go @@ -11,8 +11,8 @@ import ( ) type testAD struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` AP *spec.BoolOrSchema `json:"ap,omitempty" yaml:"ap,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` } func TestAdditionalPropertiesJSON(t *testing.T) { @@ -51,33 +51,37 @@ func TestAdditionalPropertiesJSON(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - var j testAD - require.NoError(t, json.Unmarshal([]byte(tt.data), &j)) - require.Equal(t, "foo", j.Name) - if tt.nilAP { - require.Nil(t, j.AP) - } else { - require.NotNil(t, j.AP) - require.Equal(t, tt.allowed, j.AP.Allowed) - require.Equal(t, tt.nilSchema, j.AP.Schema == nil) - } - newJson, err := json.Marshal(&j) - require.NoError(t, err) - require.JSONEq(t, tt.data, string(newJson)) + t.Run("json", func(t *testing.T) { + var v testAD + require.NoError(t, json.Unmarshal([]byte(tt.data), &v)) + require.Equal(t, "foo", v.Name) + if tt.nilAP { + require.Nil(t, v.AP) + } else { + require.NotNil(t, v.AP) + require.Equal(t, tt.allowed, v.AP.Allowed) + require.Equal(t, tt.nilSchema, v.AP.Schema == nil) + } + newJson, err := json.Marshal(&v) + require.NoError(t, err) + require.JSONEq(t, tt.data, string(newJson)) + }) - var y testAD - require.NoError(t, yaml.Unmarshal([]byte(tt.data), &y)) - require.Equal(t, "foo", y.Name) - if tt.nilAP { - require.Nil(t, y.AP) - } else { - require.NotNil(t, y.AP) - require.Equal(t, tt.allowed, y.AP.Allowed) - require.Equal(t, tt.nilSchema, y.AP.Schema == nil) - } - newYaml, err := yaml.Marshal(&y) - require.NoError(t, err) - require.YAMLEq(t, tt.data, string(newYaml)) + t.Run("yaml", func(t *testing.T) { + var v testAD + require.NoError(t, yaml.Unmarshal([]byte(tt.data), &v)) + require.Equal(t, "foo", v.Name) + if tt.nilAP { + require.Nil(t, v.AP) + } else { + require.NotNil(t, v.AP) + require.Equal(t, tt.allowed, v.AP.Allowed) + require.Equal(t, tt.nilSchema, v.AP.Schema == nil) + } + newYaml, err := yaml.Marshal(&v) + require.NoError(t, err) + require.YAMLEq(t, tt.data, string(newYaml)) + }) }) } diff --git a/spec/discriminator.go b/spec/discriminator.go index f36a8a7..acccc14 100644 --- a/spec/discriminator.go +++ b/spec/discriminator.go @@ -21,11 +21,11 @@ package spec // dog: '#/components/schemas/Dog' // monster: 'https://gigantic-server.com/schemas/Monster/schema.json' type Discriminator struct { + // An object to hold mappings between payload values and schema names or references. + Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` // REQUIRED. // The name of the property in the payload that will hold the discriminator value. PropertyName string `json:"propertyName" yaml:"propertyName"` - // An object to hold mappings between payload values and schema names or references. - Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` } // NewDiscriminator creates Discriminator object. diff --git a/spec/header.go b/spec/header.go index a05c419..6612e44 100644 --- a/spec/header.go +++ b/spec/header.go @@ -11,22 +11,28 @@ package spec // // All fields are copied from Parameter Object as is, except name and in fields. type Header struct { + // Example of the parameter’s potential value. + // The example SHOULD match the specified schema and encoding properties if present. + // The example field is mutually exclusive of the examples field. + // Furthermore, if referencing a schema that contains an example, the example value SHALL override the example provided by the schema. + // To represent examples of media types that cannot naturally be represented in JSON or YAML, + // a string value can contain the example with escaping where necessary. + Example any `json:"example,omitempty" yaml:"example,omitempty"` + // The schema defining the type used for the parameter. + Schema *RefOrSpec[Schema] `json:"schema,omitempty" yaml:"schema,omitempty"` + // Examples of the parameter’s potential value. + // Each example SHOULD contain a value in the correct format as specified in the parameter encoding. + // The examples field is mutually exclusive of the example field. + // Furthermore, if referencing a schema that contains an example, the examples value SHALL override the example provided by the schema. + Examples map[string]*RefOrSpec[Extendable[Example]] `json:"examples,omitempty" yaml:"examples,omitempty"` + // A map containing the representations for the parameter. + // The key is the media type and the value describes it. + // The map MUST only contain one entry. + Content map[string]*Extendable[MediaType] `json:"content,omitempty" yaml:"content,omitempty"` // A brief description of the header. // This could contain examples of use. // CommonMark syntax MAY be used for rich text representation. Description string `json:"description,omitempty" yaml:"description,omitempty"` - // Determines whether this header is mandatory. - // The property MAY be included and its default value is false. - Required bool `json:"required,omitempty" yaml:"required,omitempty"` - // Specifies that a header is deprecated and SHOULD be transitioned out of usage. - // Default value is false. - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - // Sets the ability to pass empty-valued headers. - // This is valid only for query parameters and allows sending a parameter with an empty value. - // Default value is false. - // If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored. - // Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision. - AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` // Describes how the parameter value will be serialized depending on the type of the parameter value. // Default values (based on value of in): // for query - form; @@ -46,24 +52,18 @@ type Header struct { // This property only applies to parameters with an in value of query. // The default value is false. AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` - // The schema defining the type used for the parameter. - Schema *RefOrSpec[Schema] `json:"schema,omitempty" yaml:"schema,omitempty"` - // Example of the parameter’s potential value. - // The example SHOULD match the specified schema and encoding properties if present. - // The example field is mutually exclusive of the examples field. - // Furthermore, if referencing a schema that contains an example, the example value SHALL override the example provided by the schema. - // To represent examples of media types that cannot naturally be represented in JSON or YAML, - // a string value can contain the example with escaping where necessary. - Example any `json:"example,omitempty" yaml:"example,omitempty"` - // Examples of the parameter’s potential value. - // Each example SHOULD contain a value in the correct format as specified in the parameter encoding. - // The examples field is mutually exclusive of the example field. - // Furthermore, if referencing a schema that contains an example, the examples value SHALL override the example provided by the schema. - Examples map[string]*RefOrSpec[Extendable[Example]] `json:"examples,omitempty" yaml:"examples,omitempty"` - // A map containing the representations for the parameter. - // The key is the media type and the value describes it. - // The map MUST only contain one entry. - Content map[string]*Extendable[MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + // Determines whether this header is mandatory. + // The property MAY be included and its default value is false. + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + // Specifies that a header is deprecated and SHOULD be transitioned out of usage. + // Default value is false. + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + // Sets the ability to pass empty-valued headers. + // This is valid only for query parameters and allows sending a parameter with an empty value. + // Default value is false. + // If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored. + // Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision. + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` } // NewHeaderSpec creates Header object. diff --git a/spec/json_schema.go b/spec/json_schema.go new file mode 100644 index 0000000..cc6a315 --- /dev/null +++ b/spec/json_schema.go @@ -0,0 +1,258 @@ +package spec + +// JSONSchemaTypeString +// +// https://json-schema.org/understanding-json-schema/reference/string.html#string +type JsonSchemaTypeString struct { + MinLength *int `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength *int `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` +} + +// JsonSchemaTypeNumber +// +// https://json-schema.org/understanding-json-schema/reference/numeric.html#numeric-types +type JsonSchemaTypeNumber struct { + // MultipleOf restricts the numbers to a multiple of a given number, using the multipleOf keyword. + // It may be set to any positive number. + // + // https://json-schema.org/understanding-json-schema/reference/numeric.html#multiples + MultipleOf *int `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + // x ≥ minimum + Minimum *int `json:"minimum,omitempty" yaml:"minimum,omitempty"` + // x > exclusiveMinimum + ExclusiveMinimum *int `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + // x ≤ maximum + Maximum *int `json:"maximum,omitempty" yaml:"maximum,omitempty"` + // x < exclusiveMaximum + ExclusiveMaximum *int `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` +} + +// JsonSchemaTypeObject +// +// https://json-schema.org/understanding-json-schema/reference/object.html#object +type JsonSchemaTypeObject struct { + // The properties (key-value pairs) on an object are defined using the properties keyword. + // The value of properties is an object, where each key is the name of a property and each value is + // a schema used to validate that property. + // Any property that doesn't match any of the property names in the properties keyword is ignored by this keyword. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#properties + Properties map[string]*RefOrSpec[Schema] `json:"properties,omitempty" yaml:"properties,omitempty"` + // Sometimes you want to say that, given a particular kind of property name, the value should match a particular schema. + // That’s where patternProperties comes in: it maps regular expressions to schemas. + // If a property name matches the given regular expression, the property value must validate against the corresponding schema. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#pattern-properties + PatternProperties map[string]*RefOrSpec[Schema] `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` + // The additionalProperties keyword is used to control the handling of extra stuff, that is, + // properties whose names are not listed in the properties keyword or match any of the regular expressions + // in the patternProperties keyword. + // By default any additional properties are allowed. + // + // The value of the additionalProperties keyword is a schema that will be used to validate any properties in the instance + // that are not matched by properties or patternProperties. + // Setting the additionalProperties schema to false means no additional properties will be allowed. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties + AdditionalProperties *BoolOrSchema `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` + // The unevaluatedProperties keyword is similar to additionalProperties except that it can recognize properties declared in subschemas. + // So, the example from the previous section can be rewritten without the need to redeclare properties. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#unevaluated-properties + UnevaluatedProperties *BoolOrSchema `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"` + // The names of properties can be validated against a schema, irrespective of their values. + // This can be useful if you don’t want to enforce specific properties, but you want to make sure that + // the names of those properties follow a specific convention. + // You might, for example, want to enforce that all names are valid ASCII tokens so they can be used + // as attributes in a particular programming language. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#property-names + PropertyNames *RefOrSpec[Schema] `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` + // The min number of properties on an object. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#size + MinProperties *int `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + // The max number of properties on an object. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#size + MaxProperties *int `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + // The required keyword takes an array of zero or more strings. + // Each of these strings must be unique. + // + // https://json-schema.org/understanding-json-schema/reference/object.html#required-properties + Required []string `json:"required,omitempty" yaml:"required,omitempty"` +} + +// JsonSchemaTypeArray +// +// https://json-schema.org/understanding-json-schema/reference/array.html#array +type JsonSchemaTypeArray struct { + // List validation is useful for arrays of arbitrary length where each item matches the same schema. + // For this kind of array, set the items keyword to a single schema that will be used to validate all of the items in the array. + // + // https://json-schema.org/understanding-json-schema/reference/array.html#items + Items *BoolOrSchema `json:"items,omitempty" yaml:"items,omitempty"` + // https://json-schema.org/understanding-json-schema/reference/array.html#length + MaxItems *int `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + // The unevaluatedItems keyword is similar to unevaluatedProperties, but for items. + // + // https://json-schema.org/understanding-json-schema/reference/array.html#unevaluated-items + UnevaluatedItems *BoolOrSchema `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` + // While the items schema must be valid for every item in the array, the contains schema only needs + // to validate against one or more items in the array. + // + // https://json-schema.org/understanding-json-schema/reference/array.html#contains + Contains *RefOrSpec[Schema] `json:"contains,omitempty" yaml:"contains,omitempty"` + MinContains *int `json:"minContains,omitempty" yaml:"minContains,omitempty"` + MaxContains *int `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` + // https://json-schema.org/understanding-json-schema/reference/array.html#length + MinItems *int `json:"minItems,omitempty" yaml:"minItems,omitempty"` + // A schema can ensure that each of the items in an array is unique. + // Simply set the uniqueItems keyword to true. + // + // https://json-schema.org/understanding-json-schema/reference/array.html#uniqueness + UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + // The prefixItems is an array, where each item is a schema that corresponds to each index of the document’s array. + // That is, an array where the first element validates the first element of the input array, + // the second element validates the second element of the input array, etc. + // + // https://json-schema.org/understanding-json-schema/reference/array.html#tuple-validation + PrefixItems []*RefOrSpec[Schema] `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"` +} + +// JsonSchemaGeneric +// +// https://json-schema.org/understanding-json-schema/reference/generic.html +type JsonSchemaGeneric struct { + Default any `json:"default,omitempty" yaml:"default,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + // The const keyword is used to restrict a value to a single value. + // + // https://json-schema.org/understanding-json-schema/reference/generic.html#constant-values + Const string `json:"const,omitempty" yaml:"const,omitempty"` + // The $comment keyword is strictly intended for adding comments to a schema. + // Its value must always be a string. + // Unlike the annotations title, description, and examples, JSON schema implementations aren’t allowed + // to attach any meaning or behavior to it whatsoever, and may even strip them at any time. + // Therefore, they are useful for leaving notes to future editors of a JSON schema, + // but should not be used to communicate to users of the schema. + // + // https://json-schema.org/understanding-json-schema/reference/generic.html#comments + Comment string `json:"$comment,omitempty" yaml:"$comment,omitempty"` + // The enum keyword is used to restrict a value to a fixed set of values. + // It must be an array with at least one element, where each element is unique. + // + // https://json-schema.org/understanding-json-schema/reference/generic.html#enumerated-values + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` + // The deprecated keyword is a boolean that indicates that the instance value the keyword applies to + // should not be used and may be removed in the future. + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` +} + +// JsonSchemaMedia string-encoding non-JSON data +// +// https://json-schema.org/understanding-json-schema/reference/non_json_data.html +type JsonSchemaMedia struct { + // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentschema + ContentSchema *RefOrSpec[Schema] `json:"contentSchema,omitempty" yaml:"contentSchema,omitempty"` + // The contentMediaType keyword specifies the MIME type of the contents of a string, as described in RFC 2046. + // There is a list of MIME types officially registered by the IANA, but the set of types supported will be + // application and operating system dependent. + // + // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentmediatype + ContentMediaType string `json:"contentMediaType,omitempty" yaml:"contentMediaType,omitempty"` + // The contentEncoding keyword specifies the encoding used to store the contents, as specified in RFC 2054, part 6.1 and RFC 4648. + // + // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentencoding + ContentEncoding string `json:"contentEncoding,omitempty" yaml:"contentEncoding,omitempty"` +} + +// JsonSchemaComposition +// +// https://json-schema.org/understanding-json-schema/reference/combining.html +type JsonSchemaComposition struct { + // The not keyword declares that an instance validates if it doesn’t validate against the given subschema. + // + // https://json-schema.org/understanding-json-schema/reference/combining.html#not + Not *RefOrSpec[Schema] `json:"not,omitempty" yaml:"not,omitempty"` + // To validate against allOf, the given data must be valid against all of the given subschemas. + // + // https://json-schema.org/understanding-json-schema/reference/combining.html#allof + AllOf []*RefOrSpec[Schema] `json:"allOf,omitempty" yaml:"allOf,omitempty"` + // To validate against anyOf, the given data must be valid against any (one or more) of the given subschemas. + // + // https://json-schema.org/understanding-json-schema/reference/combining.html#anyof + AnyOf []*RefOrSpec[Schema] `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` + // To validate against oneOf, the given data must be valid against exactly one of the given subschemas. + // + // https://json-schema.org/understanding-json-schema/reference/combining.html#oneof + OneOf []*RefOrSpec[Schema] `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` +} + +// JsonSchemaConditionals Applying Subschemas Conditionally +// +// https://json-schema.org/understanding-json-schema/reference/conditionals.html +type JsonSchemaConditionals struct { + // The dependentRequired keyword conditionally requires that certain properties must be present if + // a given property is present in an object. + // For example, suppose we have a schema representing a customer. + // If you have their credit card number, you also want to ensure you have a billing address. + // If you don’t have their credit card number, a billing address would not be required. + // We represent this dependency of one property on another using the dependentRequired keyword. + // The value of the dependentRequired keyword is an object. + // Each entry in the object maps from the name of a property, p, to an array of strings listing properties that + // are required if p is present. + // + // https://json-schema.org/understanding-json-schema/reference/conditionals.html#dependentrequired + DependentRequired map[string][]string `json:"dependentRequired,omitempty" yaml:"dependentRequired,omitempty"` + // The dependentSchemas keyword conditionally applies a subschema when a given property is present. + // This schema is applied in the same way allOf applies schemas. + // Nothing is merged or extended. + // Both schemas apply independently. + // + // https://json-schema.org/understanding-json-schema/reference/conditionals.html#dependentschemas + DependentSchemas map[string]*RefOrSpec[Schema] `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` + + // https://json-schema.org/understanding-json-schema/reference/conditionals.html#if-then-else + If *RefOrSpec[Schema] `json:"if,omitempty" yaml:"if,omitempty"` + Then *RefOrSpec[Schema] `json:"then,omitempty" yaml:"then,omitempty"` + Else *RefOrSpec[Schema] `json:"else,omitempty" yaml:"else,omitempty"` +} + +type JsonSchemaCore struct { + // https://json-schema.org/understanding-json-schema/reference/schema.html#schema + Schema string `json:"$schema,omitempty" yaml:"$schema,omitempty"` + // https://json-schema.org/understanding-json-schema/structuring.html#id + ID string `json:"$id,omitempty" yaml:"$id,omitempty"` + // https://json-schema.org/understanding-json-schema/structuring.html#defs + Defs map[string]*RefOrSpec[Schema] `json:"$defs,omitempty" yaml:"$defs,omitempty"` + //Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` is part of RefOrSpec object + DynamicRef string `json:"$dynamicRef,omitempty" yaml:"$dynamicRef,omitempty"` + Vocabulary map[string]bool `json:"$vocabulary,omitempty" yaml:"$vocabulary,omitempty"` + DynamicAnchor string `json:"$dynamicAnchor,omitempty" yaml:"dynamicAnchor,omitempty"` + // https://json-schema.org/understanding-json-schema/reference/type.html + Type SingleOrArray[string] `json:"type,omitempty" yaml:"type,omitempty"` +} + +// JsonSchema fields +// +// https://json-schema.org/understanding-json-schema/index.html +// +// NOTE: all the other fields are available via Extensions property +type JsonSchema struct { + JsonSchemaTypeNumber `yaml:",inline"` + JsonSchemaConditionals `yaml:",inline"` + JsonSchemaTypeString `yaml:",inline"` + JsonSchemaMedia `yaml:",inline"` + JsonSchemaCore `yaml:",inline"` + JsonSchemaTypeArray `yaml:",inline"` + JsonSchemaTypeObject `yaml:",inline"` + JsonSchemaComposition `yaml:",inline"` + JsonSchemaGeneric `yaml:",inline"` +} diff --git a/spec/link.go b/spec/link.go index 114f721..724bb50 100644 --- a/spec/link.go +++ b/spec/link.go @@ -55,6 +55,16 @@ package spec // '200': // description: the user's address type Link struct { + // A literal value or {expression} to use as a request body when calling the target operation. + RequestBody any `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + // A map representing parameters to pass to an operation as specified with operationId or identified via operationRef. + // The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and + // passed to the linked operation. + // The parameter name can be qualified using the parameter location [{in}.]{name} for operations that use + // the same parameter name in different locations (e.g. path.id). + Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"` + // A server object to be used by the target operation. + Server *Extendable[Server] `json:"server,omitempty" yaml:"server,omitempty"` // A relative or absolute URI reference to an OAS operation. // This field is mutually exclusive of the operationId field, and MUST point to an Operation Object. // Relative operationRef values MAY be used to locate an existing Operation Object in the OpenAPI definition. @@ -63,19 +73,9 @@ type Link struct { // The name of an existing, resolvable OAS operation, as defined with a unique operationId. // This field is mutually exclusive of the operationRef field. OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` - // A map representing parameters to pass to an operation as specified with operationId or identified via operationRef. - // The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and - // passed to the linked operation. - // The parameter name can be qualified using the parameter location [{in}.]{name} for operations that use - // the same parameter name in different locations (e.g. path.id). - Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"` - // A literal value or {expression} to use as a request body when calling the target operation. - RequestBody any `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` // A description of the link. // CommonMark syntax MAY be used for rich text representation. Description string `json:"description,omitempty" yaml:"description,omitempty"` - // A server object to be used by the target operation. - Server *Extendable[Server] `json:"server,omitempty" yaml:"server,omitempty"` } // NewLinkSpec creates Link object. diff --git a/spec/ouath-flow.go b/spec/oauth-flow.go similarity index 100% rename from spec/ouath-flow.go rename to spec/oauth-flow.go index dc072a9..2812c59 100644 --- a/spec/ouath-flow.go +++ b/spec/oauth-flow.go @@ -16,6 +16,13 @@ package spec // write:pets: modify pets in your account // read:pets: read your pets type OAuthFlow struct { + // REQUIRED. + // The available scopes for the OAuth2 security scheme. + // A map between the scope name and a short description for it. + // The map MAY be empty. + // + // Applies To: oauth2 + Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"` // REQUIRED. // The authorization URL to be used for this flow. // This MUST be in the form of a URL. @@ -36,13 +43,6 @@ type OAuthFlow struct { // // Applies To: oauth2 RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` - // REQUIRED. - // The available scopes for the OAuth2 security scheme. - // A map between the scope name and a short description for it. - // The map MAY be empty. - // - // Applies To: oauth2 - Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"` } // NewOAuthFlow creates OAuthFlow object diff --git a/spec/ouath-flows.go b/spec/oauth-flows.go similarity index 100% rename from spec/ouath-flows.go rename to spec/oauth-flows.go diff --git a/spec/openapi.go b/spec/openapi.go index 5281bf3..a9731fd 100644 --- a/spec/openapi.go +++ b/spec/openapi.go @@ -11,20 +11,13 @@ package spec // version: 1.0.0 // paths: { } type OpenAPI struct { - // REQUIRED - // This string MUST be the version number of the OpenAPI Specification that the OpenAPI document uses. - // The openapi field SHOULD be used by tooling to interpret the OpenAPI document. - // This is not related to the API info.version string. - OpenAPI string `json:"openapi" yaml:"openapi"` + // An element to hold various schemas for the document. + Components *Extendable[Components] `json:"components,omitempty" yaml:"components,omitempty"` // REQUIRED // Provides metadata about the API. The metadata MAY be used by tooling as required. Info *Extendable[Info] `json:"info" yaml:"info"` - // The default value for the $schema keyword within Schema Objects contained within this OAS document. - // This MUST be in the form of a URI. - JsonSchemaDialect string `json:"jsonSchemaDialect,omitempty" yaml:"jsonSchemaDialect,omitempty"` - // An array of Server Objects, which provide connectivity information to a target server. - // If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /. - Servers []*Extendable[Server] `json:"servers,omitempty" yaml:"servers,omitempty"` + // Additional external documentation. + ExternalDocs *Extendable[ExternalDocs] `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` // Holds the relative paths to the individual endpoints and their operations. // The path is appended to the URL from the Server Object in order to construct the full URL. // The Paths MAY be empty, due to Access Control List (ACL) constraints. @@ -35,8 +28,14 @@ type OpenAPI struct { // The key name is a unique string to refer to each webhook, while the (optionally referenced) PathItem Object describes // a request that may be initiated by the API provider and the expected responses. WebHooks map[string]*RefOrSpec[Extendable[PathItem]] `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` - // An element to hold various schemas for the document. - Components *Extendable[Components] `json:"components,omitempty" yaml:"components,omitempty"` + // The default value for the $schema keyword within Schema Objects contained within this OAS document. + // This MUST be in the form of a URI. + JsonSchemaDialect string `json:"jsonSchemaDialect,omitempty" yaml:"jsonSchemaDialect,omitempty"` + // REQUIRED + // This string MUST be the version number of the OpenAPI Specification that the OpenAPI document uses. + // The openapi field SHOULD be used by tooling to interpret the OpenAPI document. + // This is not related to the API info.version string. + OpenAPI string `json:"openapi" yaml:"openapi"` // A declaration of which security mechanisms can be used across the API. // The list of values includes alternative security requirement objects that can be used. // Only one of the security requirement objects need to be satisfied to authorize a request. @@ -49,8 +48,9 @@ type OpenAPI struct { // The tags that are not declared MAY be organized randomly or based on the tools’ logic. // Each tag name in the list MUST be unique. Tags []*Extendable[Tag] `json:"tags,omitempty" yaml:"tags,omitempty"` - // Additional external documentation. - ExternalDocs *Extendable[ExternalDocs] `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + // An array of Server Objects, which provide connectivity information to a target server. + // If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /. + Servers []*Extendable[Server] `json:"servers,omitempty" yaml:"servers,omitempty"` } // NewOpenAPI creates OpenAPI object. diff --git a/spec/operation.go b/spec/operation.go index f7ef5fe..9a863fc 100644 --- a/spec/operation.go +++ b/spec/operation.go @@ -46,14 +46,18 @@ package spec // - write:pets // - read:pets type Operation struct { - // A list of tags for API documentation control. - // Tags can be used for logical grouping of operations by resources or any other qualifier. - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` - // A short summary of what the operation does. - Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` - // A verbose explanation of the operation behavior. - // CommonMark syntax MAY be used for rich text representation. - Description string `json:"description,omitempty" yaml:"description,omitempty"` + // The request body applicable for this operation. + // The requestBody is fully supported in HTTP methods where the HTTP 1.1 specification [RFC7231] has + // explicitly defined semantics for request bodies. + // In other cases where the HTTP spec is vague (such as [GET]section-4.3.1), [HEAD]section-4.3.2) and + // [DELETE]section-4.3.5)), requestBody is permitted but does not have well-defined semantics and SHOULD be avoided if possible. + RequestBody *RefOrSpec[Extendable[RequestBody]] `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + // The list of possible responses as they are returned from executing this operation. + Responses *Extendable[Responses] `json:"responses,omitempty" yaml:"responses,omitempty"` + // A map of possible out-of band callbacks related to the parent operation. + // The key is a unique identifier for the Callback Object. + // Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses. + Callbacks map[string]*RefOrSpec[Extendable[Callback]] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` // Additional external documentation for this operation. ExternalDocs *Extendable[ExternalDocs] `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` // Unique string used to identify the operation. @@ -62,28 +66,20 @@ type Operation struct { // Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, // it is RECOMMENDED to follow common programming naming conventions. OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + // A short summary of what the operation does. + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + // A verbose explanation of the operation behavior. + // CommonMark syntax MAY be used for rich text representation. + Description string `json:"description,omitempty" yaml:"description,omitempty"` // A list of parameters that are applicable for this operation. // If a parameter is already defined at the Path Item, the new definition will override it but can never remove it. // The list MUST NOT include duplicated parameters. // A unique parameter is defined by a combination of a name and location. // The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters. Parameters []*RefOrSpec[Extendable[Parameter]] `json:"parameters,omitempty" yaml:"parameters,omitempty"` - // The request body applicable for this operation. - // The requestBody is fully supported in HTTP methods where the HTTP 1.1 specification [RFC7231] has - // explicitly defined semantics for request bodies. - // In other cases where the HTTP spec is vague (such as [GET]section-4.3.1), [HEAD]section-4.3.2) and - // [DELETE]section-4.3.5)), requestBody is permitted but does not have well-defined semantics and SHOULD be avoided if possible. - RequestBody *RefOrSpec[Extendable[RequestBody]] `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` - // The list of possible responses as they are returned from executing this operation. - Responses *Extendable[Responses] `json:"responses,omitempty" yaml:"responses,omitempty"` - // A map of possible out-of band callbacks related to the parent operation. - // The key is a unique identifier for the Callback Object. - // Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses. - Callbacks map[string]*RefOrSpec[Extendable[Callback]] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` - // Declares this operation to be deprecated. - // Consumers SHOULD refrain from usage of the declared operation. - // Default value is false. - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + // A list of tags for API documentation control. + // Tags can be used for logical grouping of operations by resources or any other qualifier. + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` // A declaration of which security mechanisms can be used for this operation. // The list of values includes alternative security requirement objects that can be used. // Only one of the security requirement objects need to be satisfied to authorize a request. @@ -94,6 +90,10 @@ type Operation struct { // An alternative server array to service this operation. // If an alternative server object is specified at the Path Item Object or Root level, it will be overridden by this value. Servers []*Extendable[Server] `json:"servers,omitempty" yaml:"servers,omitempty"` + // Declares this operation to be deprecated. + // Consumers SHOULD refrain from usage of the declared operation. + // Default value is false. + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` } // NewOperation creates Operation object. diff --git a/spec/parameter.go b/spec/parameter.go index f16e8a1..3a902bc 100644 --- a/spec/parameter.go +++ b/spec/parameter.go @@ -32,14 +32,24 @@ const ( // name: pet // description: Pets operations type Parameter struct { - // REQUIRED. - // The name of the parameter. - // Parameter names are case sensitive. - // If in is "path", the name field MUST correspond to a template expression occurring within the path field in the Paths Object. - // See Path Templating for further information. - // If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the parameter definition SHALL be ignored. - // For all other cases, the name corresponds to the parameter name used by the in property. - Name string `json:"name" yaml:"name"` + // Example of the parameter’s potential value. + // The example SHOULD match the specified schema and encoding properties if present. + // The example field is mutually exclusive of the examples field. + // Furthermore, if referencing a schema that contains an example, the example value SHALL override the example provided by the schema. + // To represent examples of media types that cannot naturally be represented in JSON or YAML, + // a string value can contain the example with escaping where necessary. + Example any `json:"example,omitempty" yaml:"example,omitempty"` + // A map containing the representations for the parameter. + // The key is the media type and the value describes it. + // The map MUST only contain one entry. + Content map[string]*Extendable[MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + // Examples of the parameter’s potential value. + // Each example SHOULD contain a value in the correct format as specified in the parameter encoding. + // The examples field is mutually exclusive of the example field. + // Furthermore, if referencing a schema that contains an example, the examples value SHALL override the example provided by the schema. + Examples map[string]*RefOrSpec[Extendable[Example]] `json:"examples,omitempty" yaml:"examples,omitempty"` + // The schema defining the type used for the parameter. + Schema *RefOrSpec[Schema] `json:"schema,omitempty" yaml:"schema,omitempty"` // REQUIRED. // The location of the parameter. // Possible values are "query", "header", "path" or "cookie". @@ -48,19 +58,6 @@ type Parameter struct { // This could contain examples of use. // CommonMark syntax MAY be used for rich text representation. Description string `json:"description,omitempty" yaml:"description,omitempty"` - // Determines whether this parameter is mandatory. - // If the parameter location is "path", this property is REQUIRED and its value MUST be true. - // Otherwise, the property MAY be included and its default value is false. - Required bool `json:"required,omitempty" yaml:"required,omitempty"` - // Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. - // Default value is false. - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - // Sets the ability to pass empty-valued parameters. - // This is valid only for query parameters and allows sending a parameter with an empty value. - // Default value is false. - // If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored. - // Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision. - AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` // Describes how the parameter value will be serialized depending on the type of the parameter value. // Default values (based on value of in): // for query - form; @@ -68,6 +65,14 @@ type Parameter struct { // for header - simple; // for cookie - form. Style string `json:"style,omitempty" yaml:"style,omitempty"` + // REQUIRED. + // The name of the parameter. + // Parameter names are case sensitive. + // If in is "path", the name field MUST correspond to a template expression occurring within the path field in the Paths Object. + // See Path Templating for further information. + // If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the parameter definition SHALL be ignored. + // For all other cases, the name corresponds to the parameter name used by the in property. + Name string `json:"name" yaml:"name"` // When this is true, parameter values of type array or object generate separate parameters // for each value of the array or key-value pair of the map. // For other types of parameters this property has no effect. @@ -80,24 +85,19 @@ type Parameter struct { // This property only applies to parameters with an in value of query. // The default value is false. AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` - // The schema defining the type used for the parameter. - Schema *RefOrSpec[Schema] `json:"schema,omitempty" yaml:"schema,omitempty"` - // Example of the parameter’s potential value. - // The example SHOULD match the specified schema and encoding properties if present. - // The example field is mutually exclusive of the examples field. - // Furthermore, if referencing a schema that contains an example, the example value SHALL override the example provided by the schema. - // To represent examples of media types that cannot naturally be represented in JSON or YAML, - // a string value can contain the example with escaping where necessary. - Example any `json:"example,omitempty" yaml:"example,omitempty"` - // Examples of the parameter’s potential value. - // Each example SHOULD contain a value in the correct format as specified in the parameter encoding. - // The examples field is mutually exclusive of the example field. - // Furthermore, if referencing a schema that contains an example, the examples value SHALL override the example provided by the schema. - Examples map[string]*RefOrSpec[Extendable[Example]] `json:"examples,omitempty" yaml:"examples,omitempty"` - // A map containing the representations for the parameter. - // The key is the media type and the value describes it. - // The map MUST only contain one entry. - Content map[string]*Extendable[MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + // Sets the ability to pass empty-valued parameters. + // This is valid only for query parameters and allows sending a parameter with an empty value. + // Default value is false. + // If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored. + // Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision. + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + // Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. + // Default value is false. + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + // Determines whether this parameter is mandatory. + // If the parameter location is "path", this property is REQUIRED and its value MUST be true. + // Otherwise, the property MAY be included and its default value is false. + Required bool `json:"required,omitempty" yaml:"required,omitempty"` } // NewParameterSpec creates Parameter object. diff --git a/spec/ref_test.go b/spec/ref_test.go index 3a1a34a..e9151e8 100644 --- a/spec/ref_test.go +++ b/spec/ref_test.go @@ -19,9 +19,9 @@ func (o testRefOrSpec) OpenAPIConstraint() {} func TestNewRefOrSpec(t *testing.T) { for _, tt := range []struct { - name string ref *spec.Ref spec *testRefOrSpec + name string nilRef bool nilSpec bool }{ @@ -97,19 +97,19 @@ func TestRefOrSpec_Marshal_Unmarshal(t *testing.T) { } { t.Run(tt.name, func(t *testing.T) { t.Run("json", func(t *testing.T) { - var j *spec.RefOrSpec[testRefOrSpec] - require.NoError(t, json.Unmarshal([]byte(tt.data), &j)) + var v *spec.RefOrSpec[testRefOrSpec] + require.NoError(t, json.Unmarshal([]byte(tt.data), &v)) if tt.nilRef { - require.Nil(t, j.Ref) + require.Nil(t, v.Ref) } else { - require.NotNil(t, j.Ref) + require.NotNil(t, v.Ref) } if tt.nilSpec { - require.Nil(t, j.Spec) + require.Nil(t, v.Spec) } else { - require.NotNil(t, j.Spec) + require.NotNil(t, v.Spec) } - data, err := json.Marshal(&j) + data, err := json.Marshal(&v) require.NoError(t, err) if tt.expected == "" { tt.expected = tt.data @@ -118,19 +118,19 @@ func TestRefOrSpec_Marshal_Unmarshal(t *testing.T) { }) t.Run("yaml", func(t *testing.T) { - var y *spec.RefOrSpec[testRefOrSpec] - require.NoError(t, yaml.Unmarshal([]byte(tt.data), &y)) + var v *spec.RefOrSpec[testRefOrSpec] + require.NoError(t, yaml.Unmarshal([]byte(tt.data), &v)) if tt.nilRef { - require.Nil(t, y.Ref) + require.Nil(t, v.Ref) } else { - require.NotNil(t, y.Ref) + require.NotNil(t, v.Ref) } if tt.nilSpec { - require.Nil(t, y.Spec) + require.Nil(t, v.Spec) } else { - require.NotNil(t, y.Spec) + require.NotNil(t, v.Spec) } - data, err := yaml.Marshal(&y) + data, err := yaml.Marshal(&v) require.NoError(t, err) if tt.expected == "" { tt.expected = tt.data diff --git a/spec/request-body.go b/spec/request_body.go similarity index 100% rename from spec/request-body.go rename to spec/request_body.go index 966b25c..9e6b006 100644 --- a/spec/request-body.go +++ b/spec/request_body.go @@ -32,15 +32,15 @@ package spec // summary: User example in other format // externalValue: 'https://foo.bar/examples/user-example.whatever' type RequestBody struct { - // A brief description of the request body. - // This could contain examples of use. - // CommonMark syntax MAY be used for rich text representation. - Description string `json:"description,omitempty" yaml:"description,omitempty"` // REQUIRED. // The content of the request body. // The key is a media type or [media type range]appendix-D) and the value describes it. // For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* Content map[string]*Extendable[MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + // A brief description of the request body. + // This could contain examples of use. + // CommonMark syntax MAY be used for rich text representation. + Description string `json:"description,omitempty" yaml:"description,omitempty"` // Determines if the request body is required in the request. // Defaults to false. Required bool `json:"required,omitempty" yaml:"required,omitempty"` diff --git a/spec/response.go b/spec/response.go index e296f38..0127542 100644 --- a/spec/response.go +++ b/spec/response.go @@ -13,10 +13,6 @@ package spec // items: // $ref: '#/components/schemas/VeryComplexType' type Response struct { - // REQUIRED. - // A description of the response. - // CommonMark syntax MAY be used for rich text representation. - Description string `json:"description,omitempty" yaml:"description,omitempty"` // Maps a header name to its definition. // [RFC7230] states header names are case insensitive. // If a response header is defined with the name "Content-Type", it SHALL be ignored. @@ -28,6 +24,10 @@ type Response struct { // A map of operations links that can be followed from the response. // The key of the map is a short name for the link, following the naming constraints of the names for Component Objects. Links map[string]*RefOrSpec[Extendable[Link]] `json:"links,omitempty" yaml:"links,omitempty"` + // REQUIRED. + // A description of the response. + // CommonMark syntax MAY be used for rich text representation. + Description string `json:"description,omitempty" yaml:"description,omitempty"` } // NewResponseSpec creates Response object. diff --git a/spec/schema.go b/spec/schema.go index 9950184..171e8bf 100644 --- a/spec/schema.go +++ b/spec/schema.go @@ -19,9 +19,7 @@ import ( // // https://spec.openapis.org/oas/v3.1.0#schema-object type Schema struct { - // ******* OpenAPI Fixed Fields ******* - // - // https://spec.openapis.org/oas/v3.1.0#fixed-fields-19 + JsonSchema `yaml:",inline"` // Adds support for polymorphism. // The discriminator is an object name that is used to differentiate between other schemas which may satisfy the payload description. @@ -41,242 +39,6 @@ type Schema struct { Example any `json:"example,omitempty" yaml:"example,omitempty"` Extensions map[string]any `json:"-" yaml:"-"` - - // ******* JSON Schema Fields ******* - // - // https://json-schema.org/understanding-json-schema/index.html - // - // NOTE: all the other fields are available via Extensions API (GetExt and SetExt) - - // https://json-schema.org/understanding-json-schema/reference/schema.html#schema - Schema string `json:"$schema,omitempty" yaml:"$schema,omitempty"` - // https://json-schema.org/understanding-json-schema/structuring.html#id - ID string `json:"$id,omitempty" yaml:"$id,omitempty"` - // https://json-schema.org/understanding-json-schema/structuring.html#defs - Defs map[string]*RefOrSpec[Schema] `json:"$defs,omitempty" yaml:"$defs,omitempty"` - //Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` is part of RefOrSpec object - DynamicRef string `json:"$dynamicRef,omitempty" yaml:"$dynamicRef,omitempty"` - Vocabulary map[string]bool `json:"$vocabulary,omitempty" yaml:"$vocabulary,omitempty"` - DynamicAnchor string `json:"$dynamicAnchor,omitempty" yaml:"dynamicAnchor,omitempty"` - // https://json-schema.org/understanding-json-schema/reference/type.html - Type SingleOrArray[string] `json:"type,omitempty" yaml:"type,omitempty"` - - // Type:string - // - // https://json-schema.org/understanding-json-schema/reference/string.html#string - - MinLength *int `json:"minLength,omitempty" yaml:"minLength,omitempty"` - MaxLength *int `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` - Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` - Format string `json:"format,omitempty" yaml:"format,omitempty"` - - // Type: number,integer - // - // https://json-schema.org/understanding-json-schema/reference/numeric.html#numeric-types - - // MultipleOf restricts the numbers to a multiple of a given number, using the multipleOf keyword. - // It may be set to any positive number. - // - // https://json-schema.org/understanding-json-schema/reference/numeric.html#multiples - MultipleOf *int `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` - // x ≥ minimum - Minimum *int `json:"minimum,omitempty" yaml:"minimum,omitempty"` - // x > exclusiveMinimum - ExclusiveMinimum *int `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` - // x ≤ maximum - Maximum *int `json:"maximum,omitempty" yaml:"maximum,omitempty"` - // x < exclusiveMaximum - ExclusiveMaximum *int `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` - - // Type: object - // - // https://json-schema.org/understanding-json-schema/reference/object.html#object - - // The properties (key-value pairs) on an object are defined using the properties keyword. - // The value of properties is an object, where each key is the name of a property and each value is - // a schema used to validate that property. - // Any property that doesn't match any of the property names in the properties keyword is ignored by this keyword. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#properties - Properties map[string]*RefOrSpec[Schema] `json:"properties,omitempty" yaml:"properties,omitempty"` - // Sometimes you want to say that, given a particular kind of property name, the value should match a particular schema. - // That’s where patternProperties comes in: it maps regular expressions to schemas. - // If a property name matches the given regular expression, the property value must validate against the corresponding schema. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#pattern-properties - PatternProperties map[string]*RefOrSpec[Schema] `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` - // The additionalProperties keyword is used to control the handling of extra stuff, that is, - // properties whose names are not listed in the properties keyword or match any of the regular expressions - // in the patternProperties keyword. - // By default any additional properties are allowed. - // - // The value of the additionalProperties keyword is a schema that will be used to validate any properties in the instance - // that are not matched by properties or patternProperties. - // Setting the additionalProperties schema to false means no additional properties will be allowed. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties - AdditionalProperties *BoolOrSchema `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` - // The unevaluatedProperties keyword is similar to additionalProperties except that it can recognize properties declared in subschemas. - // So, the example from the previous section can be rewritten without the need to redeclare properties. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#unevaluated-properties - UnevaluatedProperties *BoolOrSchema `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"` - // The required keyword takes an array of zero or more strings. - // Each of these strings must be unique. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#required-properties - Required []string `json:"required,omitempty" yaml:"required,omitempty"` - // The names of properties can be validated against a schema, irrespective of their values. - // This can be useful if you don’t want to enforce specific properties, but you want to make sure that - // the names of those properties follow a specific convention. - // You might, for example, want to enforce that all names are valid ASCII tokens so they can be used - // as attributes in a particular programming language. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#property-names - PropertyNames *RefOrSpec[Schema] `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` - // The min number of properties on an object. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#size - MinProperties *int `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` - // The max number of properties on an object. - // - // https://json-schema.org/understanding-json-schema/reference/object.html#size - MaxProperties *int `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` - - // Type: array - // - // https://json-schema.org/understanding-json-schema/reference/array.html#array - - // List validation is useful for arrays of arbitrary length where each item matches the same schema. - // For this kind of array, set the items keyword to a single schema that will be used to validate all of the items in the array. - // - // https://json-schema.org/understanding-json-schema/reference/array.html#items - Items *BoolOrSchema `json:"items,omitempty" yaml:"items,omitempty"` - // The prefixItems is an array, where each item is a schema that corresponds to each index of the document’s array. - // That is, an array where the first element validates the first element of the input array, - // the second element validates the second element of the input array, etc. - // - // https://json-schema.org/understanding-json-schema/reference/array.html#tuple-validation - PrefixItems []*RefOrSpec[Schema] `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"` - // The unevaluatedItems keyword is similar to unevaluatedProperties, but for items. - // - // https://json-schema.org/understanding-json-schema/reference/array.html#unevaluated-items - UnevaluatedItems *BoolOrSchema `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` - // While the items schema must be valid for every item in the array, the contains schema only needs - // to validate against one or more items in the array. - // - // https://json-schema.org/understanding-json-schema/reference/array.html#contains - Contains *RefOrSpec[Schema] `json:"contains,omitempty" yaml:"contains,omitempty"` - MinContains *int `json:"minContains,omitempty" yaml:"minContains,omitempty"` - MaxContains *int `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` - // https://json-schema.org/understanding-json-schema/reference/array.html#length - MinItems *int `json:"minItems,omitempty" yaml:"minItems,omitempty"` - // https://json-schema.org/understanding-json-schema/reference/array.html#length - MaxItems *int `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` - // A schema can ensure that each of the items in an array is unique. - // Simply set the uniqueItems keyword to true. - // - // https://json-schema.org/understanding-json-schema/reference/array.html#uniqueness - UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` - - // Generic keywords - // - // https://json-schema.org/understanding-json-schema/reference/generic.html - - Title string `json:"title,omitempty" yaml:"title,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Default any `json:"default,omitempty" yaml:"default,omitempty"` - Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"` - ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` - WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` - // The deprecated keyword is a boolean that indicates that the instance value the keyword applies to - // should not be used and may be removed in the future. - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - // The $comment keyword is strictly intended for adding comments to a schema. - // Its value must always be a string. - // Unlike the annotations title, description, and examples, JSON schema implementations aren’t allowed - // to attach any meaning or behavior to it whatsoever, and may even strip them at any time. - // Therefore, they are useful for leaving notes to future editors of a JSON schema, - // but should not be used to communicate to users of the schema. - // - // https://json-schema.org/understanding-json-schema/reference/generic.html#comments - Comment string `json:"$comment,omitempty" yaml:"$comment,omitempty"` - // The enum keyword is used to restrict a value to a fixed set of values. - // It must be an array with at least one element, where each element is unique. - // - // https://json-schema.org/understanding-json-schema/reference/generic.html#enumerated-values - Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` - // The const keyword is used to restrict a value to a single value. - // - // https://json-schema.org/understanding-json-schema/reference/generic.html#constant-values - Const string `json:"const,omitempty" yaml:"const,omitempty"` - - // Media: string-encoding non-JSON data - // - // https://json-schema.org/understanding-json-schema/reference/non_json_data.html - - // The contentMediaType keyword specifies the MIME type of the contents of a string, as described in RFC 2046. - // There is a list of MIME types officially registered by the IANA, but the set of types supported will be - // application and operating system dependent. - // - // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentmediatype - ContentMediaType string `json:"contentMediaType,omitempty" yaml:"contentMediaType,omitempty"` - // The contentEncoding keyword specifies the encoding used to store the contents, as specified in RFC 2054, part 6.1 and RFC 4648. - // - // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentencoding - ContentEncoding string `json:"contentEncoding,omitempty" yaml:"contentEncoding,omitempty"` - // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentschema - ContentSchema *RefOrSpec[Schema] `json:"contentSchema,omitempty" yaml:"contentSchema,omitempty"` - - // Schema Composition - // - // https://json-schema.org/understanding-json-schema/reference/combining.html - - // To validate against allOf, the given data must be valid against all of the given subschemas. - // - // https://json-schema.org/understanding-json-schema/reference/combining.html#allof - AllOf []*RefOrSpec[Schema] `json:"allOf,omitempty" yaml:"allOf,omitempty"` - // To validate against anyOf, the given data must be valid against any (one or more) of the given subschemas. - // - // https://json-schema.org/understanding-json-schema/reference/combining.html#anyof - AnyOf []*RefOrSpec[Schema] `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` - // To validate against oneOf, the given data must be valid against exactly one of the given subschemas. - // - // https://json-schema.org/understanding-json-schema/reference/combining.html#oneof - OneOf []*RefOrSpec[Schema] `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` - // The not keyword declares that an instance validates if it doesn’t validate against the given subschema. - // - // https://json-schema.org/understanding-json-schema/reference/combining.html#not - Not *RefOrSpec[Schema] `json:"not,omitempty" yaml:"not,omitempty"` - - // Applying Subschemas Conditionally - // - // https://json-schema.org/understanding-json-schema/reference/conditionals.html - - // The dependentRequired keyword conditionally requires that certain properties must be present if - // a given property is present in an object. - // For example, suppose we have a schema representing a customer. - // If you have their credit card number, you also want to ensure you have a billing address. - // If you don’t have their credit card number, a billing address would not be required. - // We represent this dependency of one property on another using the dependentRequired keyword. - // The value of the dependentRequired keyword is an object. - // Each entry in the object maps from the name of a property, p, to an array of strings listing properties that - // are required if p is present. - // - // https://json-schema.org/understanding-json-schema/reference/conditionals.html#dependentrequired - DependentRequired map[string][]string `json:"dependentRequired,omitempty" yaml:"dependentRequired,omitempty"` - // The dependentSchemas keyword conditionally applies a subschema when a given property is present. - // This schema is applied in the same way allOf applies schemas. - // Nothing is merged or extended. - // Both schemas apply independently. - // - // https://json-schema.org/understanding-json-schema/reference/conditionals.html#dependentschemas - DependentSchemas map[string]*RefOrSpec[Schema] `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` - - // https://json-schema.org/understanding-json-schema/reference/conditionals.html#if-then-else - If *RefOrSpec[Schema] `json:"if,omitempty" yaml:"if,omitempty"` - Then *RefOrSpec[Schema] `json:"then,omitempty" yaml:"then,omitempty"` - Else *RefOrSpec[Schema] `json:"else,omitempty" yaml:"else,omitempty"` } // NewSchemaSpec creates Schema object. @@ -292,8 +54,7 @@ func NewSchemaRef(ref *Ref) *RefOrSpec[Schema] { func (o Schema) OpenAPIConstraint() {} // returns the list of public fields for given tag and ignores `-` names -func getFields(v any, tag string) map[string]struct{} { - t := reflect.TypeOf(v) +func getFields(t reflect.Type, tag string) map[string]struct{} { if t.Kind() == reflect.Pointer { t = t.Elem() } @@ -307,6 +68,13 @@ func getFields(v any, tag string) map[string]struct{} { if !f.IsExported() { continue } + if f.Anonymous { + sub := getFields(f.Type, tag) + for n, v := range sub { + ret[n] = v + } + continue + } name, _, _ := strings.Cut(f.Tag.Get(tag), ",") if name == "-" { continue @@ -356,7 +124,7 @@ func (o *Schema) UnmarshalJSON(data []byte) error { return fmt.Errorf("%T: %w", o, err) } exts := make(map[string]any) - keys := getFields(o, "json") + keys := getFields(reflect.TypeOf(o), "json") for name, value := range raw { if _, ok := keys[name]; !ok { var v any @@ -408,7 +176,7 @@ func (o *Schema) UnmarshalYAML(node *yaml.Node) error { return fmt.Errorf("%T: %w", o, err) } exts := make(map[string]any) - keys := getFields(o, "json") + keys := getFields(reflect.TypeOf(o), "json") for name, value := range raw { if _, ok := keys[name]; !ok { exts[name] = value diff --git a/spec/server.go b/spec/server.go index 3f23808..bc77f26 100644 --- a/spec/server.go +++ b/spec/server.go @@ -13,6 +13,9 @@ package spec // - url: https://api.gigantic-server.com/v1 // description: Production server type Server struct { + // A map between a variable name and its value. + // The value is used for substitution in the server’s URL template. + Variables map[string]*Extendable[ServerVariable] `json:"variables,omitempty" yaml:"variables,omitempty"` // REQUIRED. // A URL to the target host. // This URL supports Server Variables and MAY be relative, to indicate that the host location is relative @@ -22,9 +25,6 @@ type Server struct { // An optional string describing the host designated by the URL. // CommonMark syntax MAY be used for rich text representation. Description string `json:"description,omitempty" yaml:"description,omitempty"` - // A map between a variable name and its value. - // The value is used for substitution in the server’s URL template. - Variables map[string]*Extendable[ServerVariable] `json:"variables,omitempty" yaml:"variables,omitempty"` } // NewServer creates Server object. diff --git a/spec/server-variable.go b/spec/server_variable.go similarity index 100% rename from spec/server-variable.go rename to spec/server_variable.go index 641dd9f..fa9e91c 100644 --- a/spec/server-variable.go +++ b/spec/server_variable.go @@ -4,9 +4,6 @@ package spec // // https://spec.openapis.org/oas/v3.1.0#server-variable-object type ServerVariable struct { - // An enumeration of string values to be used if the substitution options are from a limited set. - // The array MUST NOT be empty. - Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"` // REQUIRED. // The default value to use for substitution, which SHALL be sent if an alternate value is not supplied. // Note this behavior is different than the Schema Object’s treatment of default values, @@ -16,6 +13,9 @@ type ServerVariable struct { // An optional description for the server variable. // CommonMark syntax MAY be used for rich text representation. Description string `json:"description,omitempty" yaml:"description,omitempty"` + // An enumeration of string values to be used if the substitution options are from a limited set. + // The array MUST NOT be empty. + Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"` } // NewServerVariable creates ServerVariable object. diff --git a/spec/tag.go b/spec/tag.go index 581209e..6356df8 100644 --- a/spec/tag.go +++ b/spec/tag.go @@ -9,14 +9,14 @@ package spec // name: pet // description: Pets operations type Tag struct { + // Additional external documentation for this tag. + ExternalDocs *Extendable[ExternalDocs] `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` // REQUIRED. // The name of the tag. Name string `json:"name" yaml:"name"` // A description for the tag. // CommonMark syntax MAY be used for rich text representation. Description string `json:"description,omitempty" yaml:"description,omitempty"` - // Additional external documentation for this tag. - ExternalDocs *Extendable[ExternalDocs] `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` } // NewTag creates Tag object.