From b11986e479f0939da28a5fc9d455ba188f1b1188 Mon Sep 17 00:00:00 2001 From: Arnaud Monteils Date: Thu, 4 Jan 2024 11:48:10 +0100 Subject: [PATCH] Add the new modules --- lib/openapi_parser.rb | 12 +- .../schema_3_1_validator/all_of_validator.rb | 44 ++ .../schema_3_1_validator/any_of_validator.rb | 21 + .../schema_3_1_validator/array_validator.rb | 41 + .../schema_3_1_validator/base.rb | 39 + .../schema_3_1_validator/boolean_validator.rb | 29 + .../schema_3_1_validator/enumable.rb | 13 + .../schema_3_1_validator/float_validator.rb | 34 + .../schema_3_1_validator/integer_validator.rb | 32 + .../schema_3_1_validator/minimum_maximum.rb | 38 + .../schema_3_1_validator/nil_validator.rb | 11 + .../schema_3_1_validator/object_validator.rb | 56 ++ .../schema_3_1_validator/one_of_validator.rb | 25 + .../schema_3_1_validator/options.rb | 29 + .../schema_3_1_validator/string_validator.rb | 108 +++ .../unspecified_type_validator.rb | 8 + lib/openapi_parser/schemas_3_1.rb | 18 + lib/openapi_parser/schemas_3_1/base.rb | 28 + lib/openapi_parser/schemas_3_1/classes.rb | 20 + lib/openapi_parser/schemas_3_1/components.rb | 28 + .../schemas_3_1/discriminator.rb | 11 + lib/openapi_parser/schemas_3_1/header.rb | 18 + lib/openapi_parser/schemas_3_1/info.rb | 6 + lib/openapi_parser/schemas_3_1/media_type.rb | 18 + lib/openapi_parser/schemas_3_1/openapi.rb | 63 ++ lib/openapi_parser/schemas_3_1/operation.rb | 34 + lib/openapi_parser/schemas_3_1/parameter.rb | 20 + lib/openapi_parser/schemas_3_1/path_item.rb | 22 + lib/openapi_parser/schemas_3_1/paths.rb | 7 + lib/openapi_parser/schemas_3_1/reference.rb | 7 + .../schemas_3_1/request_body.rb | 34 + lib/openapi_parser/schemas_3_1/response.rb | 54 ++ lib/openapi_parser/schemas_3_1/responses.rb | 56 ++ lib/openapi_parser/schemas_3_1/schema.rb | 121 +++ spec/data/petstore.json | 1 + spec/data/petstore.json.unsupported_extension | 1 + spec/data/schema_3_1/cyclic-remote-ref1.yaml | 24 + spec/data/schema_3_1/cyclic-remote-ref2.yaml | 13 + spec/data/schema_3_1/normal.yml | 704 ++++++++++++++++++ .../schema_3_1/path-item-ref-relative.yaml | 21 + spec/data/schema_3_1/path-item-ref.yaml | 31 + spec/data/schema_3_1/petstore-expanded.yaml | 387 ++++++++++ .../petstore-with-discriminator.yaml | 245 ++++++ .../petstore-with-mapped-polymorphism.yaml | 109 +++ .../petstore-with-polymorphism.yaml | 106 +++ spec/data/schema_3_1/petstore.json | 154 ++++ .../petstore.json.unsupported_extension | 154 ++++ .../petstore.yaml.unsupported_extension | 351 +++++++++ spec/data/schema_3_1/reference-broken.yaml | 17 + .../schema_3_1/reference_in_responses.yaml | 19 + spec/data/schema_3_1/remote-file-ref.yaml | 22 + spec/data/schema_3_1/remote-http-ref.yaml | 20 + spec/data/schema_3_1/validate_test.yaml | 19 + 53 files changed, 3502 insertions(+), 1 deletion(-) create mode 100644 lib/openapi_parser/schema_3_1_validator/all_of_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/any_of_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/array_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/base.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/boolean_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/enumable.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/float_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/integer_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/minimum_maximum.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/nil_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/object_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/one_of_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/options.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/string_validator.rb create mode 100644 lib/openapi_parser/schema_3_1_validator/unspecified_type_validator.rb create mode 100644 lib/openapi_parser/schemas_3_1.rb create mode 100644 lib/openapi_parser/schemas_3_1/base.rb create mode 100644 lib/openapi_parser/schemas_3_1/classes.rb create mode 100644 lib/openapi_parser/schemas_3_1/components.rb create mode 100644 lib/openapi_parser/schemas_3_1/discriminator.rb create mode 100644 lib/openapi_parser/schemas_3_1/header.rb create mode 100644 lib/openapi_parser/schemas_3_1/info.rb create mode 100644 lib/openapi_parser/schemas_3_1/media_type.rb create mode 100644 lib/openapi_parser/schemas_3_1/openapi.rb create mode 100644 lib/openapi_parser/schemas_3_1/operation.rb create mode 100644 lib/openapi_parser/schemas_3_1/parameter.rb create mode 100644 lib/openapi_parser/schemas_3_1/path_item.rb create mode 100644 lib/openapi_parser/schemas_3_1/paths.rb create mode 100644 lib/openapi_parser/schemas_3_1/reference.rb create mode 100644 lib/openapi_parser/schemas_3_1/request_body.rb create mode 100644 lib/openapi_parser/schemas_3_1/response.rb create mode 100644 lib/openapi_parser/schemas_3_1/responses.rb create mode 100644 lib/openapi_parser/schemas_3_1/schema.rb create mode 100644 spec/data/schema_3_1/cyclic-remote-ref1.yaml create mode 100644 spec/data/schema_3_1/cyclic-remote-ref2.yaml create mode 100644 spec/data/schema_3_1/normal.yml create mode 100644 spec/data/schema_3_1/path-item-ref-relative.yaml create mode 100644 spec/data/schema_3_1/path-item-ref.yaml create mode 100644 spec/data/schema_3_1/petstore-expanded.yaml create mode 100644 spec/data/schema_3_1/petstore-with-discriminator.yaml create mode 100644 spec/data/schema_3_1/petstore-with-mapped-polymorphism.yaml create mode 100644 spec/data/schema_3_1/petstore-with-polymorphism.yaml create mode 100644 spec/data/schema_3_1/petstore.json create mode 100644 spec/data/schema_3_1/petstore.json.unsupported_extension create mode 100644 spec/data/schema_3_1/petstore.yaml.unsupported_extension create mode 100644 spec/data/schema_3_1/reference-broken.yaml create mode 100644 spec/data/schema_3_1/reference_in_responses.yaml create mode 100644 spec/data/schema_3_1/remote-file-ref.yaml create mode 100644 spec/data/schema_3_1/remote-http-ref.yaml create mode 100644 spec/data/schema_3_1/validate_test.yaml diff --git a/lib/openapi_parser.rb b/lib/openapi_parser.rb index 011d46a..dafc031 100644 --- a/lib/openapi_parser.rb +++ b/lib/openapi_parser.rb @@ -10,6 +10,7 @@ require 'openapi_parser/errors' require 'openapi_parser/concern' require 'openapi_parser/schemas' +require 'openapi_parser/schemas_3_1' require 'openapi_parser/path_item_finder' require 'openapi_parser/request_operation' require 'openapi_parser/schema_validator' @@ -93,7 +94,16 @@ def parse_json(content) end def load_hash(hash, config:, uri:, schema_registry:) - root = Schemas::OpenAPI.new(hash, config, uri: uri, schema_registry: schema_registry) + case hash["openapi"]&.split('.') + in ['3', '0', _] + root = Schemas::OpenAPI.new(hash, config, uri: uri, schema_registry: schema_registry) + in ['3', '1', _] + root = Schemas31::OpenAPI.new(hash, config, uri: uri, schema_registry: schema_registry) + else + # Too avoid breaking changes, we don't raise error here. + # raise NotSupportedSpecificationVersionError.new("Unsupported specification version: #{hash['openapi']}") + root = Schemas::OpenAPI.new(hash, config, uri: uri, schema_registry: schema_registry) + end OpenAPIParser::ReferenceExpander.expand(root, config.strict_reference_validation) if config.expand_reference diff --git a/lib/openapi_parser/schema_3_1_validator/all_of_validator.rb b/lib/openapi_parser/schema_3_1_validator/all_of_validator.rb new file mode 100644 index 0000000..79be13c --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/all_of_validator.rb @@ -0,0 +1,44 @@ +# validate AllOf schema +class OpenAPIParser::Schema31Validator + class AllOfValidator < Base + # coerce and validate value + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def coerce_and_validate(value, schema, **keyword_args) + if value.nil? && schema.nullable + return [value, nil] + end + + # if any schema return error, it's not valida all of value + remaining_keys = value.kind_of?(Hash) ? value.keys : [] + nested_additional_properties = false + schema.all_of.each do |s| + # We need to store the reference to all of, so we can perform strict check on allowed properties + _coerced, err = validatable.validate_schema( + value, + s, + :parent_all_of => true, + parent_discriminator_schemas: keyword_args[:parent_discriminator_schemas] + ) + + if s.type == "object" + remaining_keys -= (s.properties || {}).keys + nested_additional_properties = true if s.additional_properties + else + # If this is not allOf having array of objects inside, but e.g. having another anyOf/oneOf nested + remaining_keys.clear + end + + return [nil, err] if err + end + + # If there are nested additionalProperites, we allow not defined extra properties and lean on the specific + # additionalProperties validation + if !nested_additional_properties && !remaining_keys.empty? + return [nil, OpenAPIParser::NotExistPropertyDefinition.new(remaining_keys, schema.object_reference)] + end + + [value, nil] + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/any_of_validator.rb b/lib/openapi_parser/schema_3_1_validator/any_of_validator.rb new file mode 100644 index 0000000..5e94707 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/any_of_validator.rb @@ -0,0 +1,21 @@ +class OpenAPIParser::Schema31Validator + class AnyOfValidator < Base + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def coerce_and_validate(value, schema, **_keyword_args) + if value.nil? && schema.nullable + return [value, nil] + end + if schema.discriminator + return validate_discriminator_schema(schema.discriminator, value) + end + + # in all schema return error (=true) not any of data + schema.any_of.each do |s| + coerced, err = validatable.validate_schema(value, s) + return [coerced, nil] if err.nil? + end + [nil, OpenAPIParser::NotAnyOf.new(value, schema.object_reference)] + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/array_validator.rb b/lib/openapi_parser/schema_3_1_validator/array_validator.rb new file mode 100644 index 0000000..5dff0f8 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/array_validator.rb @@ -0,0 +1,41 @@ +class OpenAPIParser::Schema31Validator + class ArrayValidator < Base + # @param [Array] value + # @param [OpenAPIParser::Schemas31Schema] schema + def coerce_and_validate(value, schema, **_keyword_args) + return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(Array) + + value, err = validate_max_min_items(value, schema) + return [nil, err] if err + + value, err = validate_unique_items(value, schema) + return [nil, err] if err + + # array type have an schema in items property + items_schema = schema.items + coerced_values = value.map do |v| + coerced, err = validatable.validate_schema(v, items_schema) + return [nil, err] if err + + coerced + end + + value.each_index { |idx| value[idx] = coerced_values[idx] } if @coerce_value + + [value, nil] + end + + def validate_max_min_items(value, schema) + return [nil, OpenAPIParser::MoreThanMaxItems.new(value, schema.object_reference)] if schema.maxItems && value.length > schema.maxItems + return [nil, OpenAPIParser::LessThanMinItems.new(value, schema.object_reference)] if schema.minItems && value.length < schema.minItems + + [value, nil] + end + + def validate_unique_items(value, schema) + return [nil, OpenAPIParser::NotUniqueItems.new(value, schema.object_reference)] if schema.uniqueItems && value.length != value.uniq.length + + [value, nil] + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/base.rb b/lib/openapi_parser/schema_3_1_validator/base.rb new file mode 100644 index 0000000..f5e829c --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/base.rb @@ -0,0 +1,39 @@ +class OpenAPIParser::Schema31Validator + class Base + def initialize(validatable, coerce_value) + @validatable = validatable + @coerce_value = coerce_value + end + + attr_reader :validatable + + # need override + def coerce_and_validate(_value, _schema, **_keyword_args) + raise 'need implement' + end + + def validate_discriminator_schema(discriminator, value, parent_discriminator_schemas: []) + property_name = discriminator.property_name + if property_name.nil? || !value.key?(property_name) + return [nil, OpenAPIParser::NotExistDiscriminatorPropertyName.new(discriminator.property_name, value, discriminator.object_reference)] + end + mapping_key = value[property_name] + + # it's allowed to have discriminator without mapping, then we need to lookup discriminator.property_name + # but the format is not the full path, just model name in the components + mapping_target = discriminator.mapping&.[](mapping_key) || "#/components/schemas/#{mapping_key}" + + # Find object does O(n) search at worst, then caches the result, so this is ok for repeated search + resolved_schema = discriminator.root.find_object(mapping_target) + + unless resolved_schema + return [nil, OpenAPIParser::NotExistDiscriminatorMappedSchema.new(mapping_target, discriminator.object_reference)] + end + validatable.validate_schema( + value, + resolved_schema, + **{discriminator_property_name: discriminator.property_name, parent_discriminator_schemas: parent_discriminator_schemas} + ) + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/boolean_validator.rb b/lib/openapi_parser/schema_3_1_validator/boolean_validator.rb new file mode 100644 index 0000000..eef245f --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/boolean_validator.rb @@ -0,0 +1,29 @@ +class OpenAPIParser::Schema31Validator + class BooleanValidator < Base + include ::OpenAPIParser::Schema31Validator::Enumable + + TRUE_VALUES = ['true', '1'].freeze + FALSE_VALUES = ['false', '0'].freeze + + def coerce_and_validate(value, schema, **_keyword_args) + value = coerce(value) if @coerce_value + + return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(TrueClass) || value.kind_of?(FalseClass) + + value, err = check_enum_include(value, schema) + return [nil, err] if err + + [value, nil] + end + + private + + def coerce(value) + return true if TRUE_VALUES.include?(value) + + return false if FALSE_VALUES.include?(value) + + value + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/enumable.rb b/lib/openapi_parser/schema_3_1_validator/enumable.rb new file mode 100644 index 0000000..78aeebc --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/enumable.rb @@ -0,0 +1,13 @@ +class OpenAPIParser::Schema31Validator + module Enumable + # check enum value by schema + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def check_enum_include(value, schema) + return [value, nil] unless schema.enum + return [value, nil] if schema.enum.include?(value) + + [nil, OpenAPIParser::NotEnumInclude.new(value, schema.object_reference)] + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/float_validator.rb b/lib/openapi_parser/schema_3_1_validator/float_validator.rb new file mode 100644 index 0000000..26b4e65 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/float_validator.rb @@ -0,0 +1,34 @@ +class OpenAPIParser::Schema31Validator + class FloatValidator < Base + include ::OpenAPIParser::Schema31Validator::Enumable + include ::OpenAPIParser::Schema31Validator::MinimumMaximum + + # validate float value by schema + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def coerce_and_validate(value, schema, **_keyword_args) + value = coerce(value) if @coerce_value + + return validatable.validate_integer(value, schema) if value.kind_of?(Integer) + + coercer_and_validate_numeric(value, schema) + end + + private + + def coercer_and_validate_numeric(value, schema) + return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(Numeric) + + value, err = check_enum_include(value, schema) + return [nil, err] if err + + check_minimum_maximum(value, schema) + end + + def coerce(value) + Float(value) + rescue ArgumentError, TypeError + value + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/integer_validator.rb b/lib/openapi_parser/schema_3_1_validator/integer_validator.rb new file mode 100644 index 0000000..52f81f6 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/integer_validator.rb @@ -0,0 +1,32 @@ +class OpenAPIParser::Schema31Validator + class IntegerValidator < Base + include ::OpenAPIParser::Schema31Validator::Enumable + include ::OpenAPIParser::Schema31Validator::MinimumMaximum + + # validate integer value by schema + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def coerce_and_validate(value, schema, **_keyword_args) + value = coerce(value) if @coerce_value + + return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(Integer) + + value, err = check_enum_include(value, schema) + return [nil, err] if err + + check_minimum_maximum(value, schema) + end + + private + + def coerce(value) + return value if value.kind_of?(Integer) + + begin + Integer(value) + rescue ArgumentError, TypeError + value + end + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/minimum_maximum.rb b/lib/openapi_parser/schema_3_1_validator/minimum_maximum.rb new file mode 100644 index 0000000..d6df380 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/minimum_maximum.rb @@ -0,0 +1,38 @@ +class OpenAPIParser::Schema31Validator + module MinimumMaximum + # check minimum and maximum value by schema + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def check_minimum_maximum(value, schema) + include_min_max = schema.minimum || schema.maximum + return [value, nil] unless include_min_max + + validate(value, schema) + [value, nil] + rescue OpenAPIParser::OpenAPIError => e + return [nil, e] + end + + private + + def validate(value, schema) + reference = schema.object_reference + + if schema.minimum + if schema.exclusiveMinimum.present? && value <= schema.exclusiveMinimum + raise OpenAPIParser::LessThanExclusiveMinimum.new(value, reference) + elsif value < schema.minimum + raise OpenAPIParser::LessThanMinimum.new(value, reference) + end + end + + if schema.maximum + if schema.exclusiveMaximum.present? && value >= schema.exclusiveMinimum + raise OpenAPIParser::MoreThanExclusiveMaximum.new(value, reference) + elsif value > schema.maximum + raise OpenAPIParser::MoreThanMaximum.new(value, reference) + end + end + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/nil_validator.rb b/lib/openapi_parser/schema_3_1_validator/nil_validator.rb new file mode 100644 index 0000000..bef1ad8 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/nil_validator.rb @@ -0,0 +1,11 @@ +class OpenAPIParser::Schema31Validator + class NilValidator < Base + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def coerce_and_validate(value, schema, **_keyword_args) + return [value, nil] if schema.nullable + + [nil, OpenAPIParser::NotNullError.new(schema.object_reference)] + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/object_validator.rb b/lib/openapi_parser/schema_3_1_validator/object_validator.rb new file mode 100644 index 0000000..fc42b86 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/object_validator.rb @@ -0,0 +1,56 @@ +class OpenAPIParser::Schema31Validator + class ObjectValidator < Base + # @param [Hash] value + # @param [OpenAPIParser::Schemas31Schema] schema + # @param [Boolean] parent_all_of true if component is nested under allOf + # @param [String, nil] discriminator_property_name discriminator.property_name to ignore checking additional_properties + def coerce_and_validate(value, schema, parent_all_of: false, parent_discriminator_schemas: [], discriminator_property_name: nil) + return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(Hash) + + properties = schema.properties || {} + + required_set = schema.required ? schema.required.to_set : Set.new + remaining_keys = value.keys + + if schema.discriminator && !parent_discriminator_schemas.include?(schema) + return validate_discriminator_schema( + schema.discriminator, + value, + parent_discriminator_schemas: parent_discriminator_schemas + [schema] + ) + else + remaining_keys.delete('discriminator') + end + + coerced_values = value.map do |name, v| + s = properties[name] + coerced, err = if s + remaining_keys.delete(name) + validatable.validate_schema(v, s) + else + # TODO: we need to perform a validation based on schema.additional_properties here, if + # additionalProperties are defined + [v, nil] + end + + return [nil, err] if err + + required_set.delete(name) + [name, coerced] + end + + remaining_keys.delete(discriminator_property_name) if discriminator_property_name + + if !remaining_keys.empty? && !parent_all_of && !schema.additional_properties + # If object is nested in all of, the validation is already done in allOf validator. Or if + # additionalProperties are defined, we will validate using that + return [nil, OpenAPIParser::NotExistPropertyDefinition.new(remaining_keys, schema.object_reference)] + end + return [nil, OpenAPIParser::NotExistRequiredKey.new(required_set.to_a, schema.object_reference)] unless required_set.empty? + + value.merge!(coerced_values.to_h) if @coerce_value + + [value, nil] + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/one_of_validator.rb b/lib/openapi_parser/schema_3_1_validator/one_of_validator.rb new file mode 100644 index 0000000..a02c06e --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/one_of_validator.rb @@ -0,0 +1,25 @@ +class OpenAPIParser::Schema31Validator + class OneOfValidator < Base + # @param [Object] value + # @param [OpenAPIParser::Schemas31Schema] schema + def coerce_and_validate(value, schema, **_keyword_args) + if value.nil? && schema.nullable + return [value, nil] + end + if schema.discriminator + return validate_discriminator_schema(schema.discriminator, value) + end + + # if multiple schemas are satisfied, it's not valid + result = schema.one_of.one? do |s| + _coerced, err = validatable.validate_schema(value, s) + err.nil? + end + if result + [value, nil] + else + [nil, OpenAPIParser::NotOneOf.new(value, schema.object_reference)] + end + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/options.rb b/lib/openapi_parser/schema_3_1_validator/options.rb new file mode 100644 index 0000000..0b5f787 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/options.rb @@ -0,0 +1,29 @@ +class OpenAPIParser::Schema31Validator + class Options + # @!attribute [r] coerce_value + # @return [Boolean] coerce value option on/off + # @!attribute [r] datetime_coerce_class + # @return [Object, nil] coerce datetime string by this Object class + # @!attribute [r] validate_header + # @return [Boolean] validate header or not + attr_reader :coerce_value, :datetime_coerce_class, :validate_header + + def initialize(coerce_value: nil, datetime_coerce_class: nil, validate_header: true) + @coerce_value = coerce_value + @datetime_coerce_class = datetime_coerce_class + @validate_header = validate_header + end + end + + # response body validation option + class ResponseValidateOptions + # @!attribute [r] strict + # @return [Boolean] validate by strict (when not exist definition, raise error) + attr_reader :strict, :validate_header + + def initialize(strict: false, validate_header: true) + @strict = strict + @validate_header = validate_header + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/string_validator.rb b/lib/openapi_parser/schema_3_1_validator/string_validator.rb new file mode 100644 index 0000000..0d04aa9 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/string_validator.rb @@ -0,0 +1,108 @@ +class OpenAPIParser::Schema31Validator + class StringValidator < Base + include ::OpenAPIParser::Schema31Validator::Enumable + + def initialize(validator, coerce_value, datetime_coerce_class) + super(validator, coerce_value) + @datetime_coerce_class = datetime_coerce_class + end + + def coerce_and_validate(value, schema, **_keyword_args) + return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(String) + + value, err = check_enum_include(value, schema) + return [nil, err] if err + + value, err = pattern_validate(value, schema) + return [nil, err] if err + + value, err = validate_max_min_length(value, schema) + return [nil, err] if err + + value, err = validate_email_format(value, schema) + return [nil, err] if err + + value, err = validate_uuid_format(value, schema) + return [nil, err] if err + + value, err = validate_date_format(value, schema) + return [nil, err] if err + + value, err = validate_datetime_format(value, schema) + return [nil, err] if err + + [value, nil] + end + + private + + # @param [OpenAPIParser::Schemas31Schema] schema + def pattern_validate(value, schema) + # pattern support string only so put this + return [value, nil] unless schema.pattern + return [value, nil] if value =~ /#{schema.pattern}/ + + [nil, OpenAPIParser::InvalidPattern.new(value, schema.pattern, schema.object_reference, schema.example)] + end + + def validate_max_min_length(value, schema) + return [nil, OpenAPIParser::MoreThanMaxLength.new(value, schema.object_reference)] if schema.maxLength && value.size > schema.maxLength + return [nil, OpenAPIParser::LessThanMinLength.new(value, schema.object_reference)] if schema.minLength && value.size < schema.minLength + + [value, nil] + end + + def validate_email_format(value, schema) + return [value, nil] unless schema.format == 'email' + + # match? method is good performance. + # So when we drop ruby 2.3 support we use match? method because this method add ruby 2.4 + #return [value, nil] if value.match?(URI::MailTo::EMAIL_REGEXP) + return [value, nil] if value.match(URI::MailTo::EMAIL_REGEXP) + + return [nil, OpenAPIParser::InvalidEmailFormat.new(value, schema.object_reference)] + end + + def validate_uuid_format(value, schema) + return [value, nil] unless schema.format == 'uuid' + + return [value, nil] if value.match(/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}/) + + return [nil, OpenAPIParser::InvalidUUIDFormat.new(value, schema.object_reference)] + end + + def validate_date_format(value, schema) + return [value, nil] unless schema.format == 'date' + + begin + Date.strptime(value, "%Y-%m-%d") + rescue ArgumentError + return [nil, OpenAPIParser::InvalidDateFormat.new(value, schema.object_reference)] + end + + return [value, nil] + end + + def validate_datetime_format(value, schema) + return [value, nil] unless schema.format == 'date-time' + + begin + if @datetime_coerce_class.nil? + # validate only + DateTime.rfc3339(value) + [value, nil] + else + # validate and coerce + if @datetime_coerce_class == Time + [DateTime.rfc3339(value).to_time, nil] + else + [@datetime_coerce_class.rfc3339(value), nil] + end + end + rescue ArgumentError + # when rfc3339(value) failed + [nil, OpenAPIParser::InvalidDateTimeFormat.new(value, schema.object_reference)] + end + end + end +end diff --git a/lib/openapi_parser/schema_3_1_validator/unspecified_type_validator.rb b/lib/openapi_parser/schema_3_1_validator/unspecified_type_validator.rb new file mode 100644 index 0000000..7540816 --- /dev/null +++ b/lib/openapi_parser/schema_3_1_validator/unspecified_type_validator.rb @@ -0,0 +1,8 @@ +class OpenAPIParser::Schema31Validator + class UnspecifiedTypeValidator < Base + # @param [Object] value + def coerce_and_validate(value, _schema, **_keyword_args) + [value, nil] + end + end +end diff --git a/lib/openapi_parser/schemas_3_1.rb b/lib/openapi_parser/schemas_3_1.rb new file mode 100644 index 0000000..c0dcd37 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1.rb @@ -0,0 +1,18 @@ +require_relative 'schemas_3_1/classes' + +require_relative 'schemas_3_1/base' +require_relative 'schemas_3_1/discriminator' +require_relative 'schemas_3_1/openapi' +require_relative 'schemas_3_1/paths' +require_relative 'schemas_3_1/path_item' +require_relative 'schemas_3_1/operation' +require_relative 'schemas_3_1/parameter' +require_relative 'schemas_3_1/reference' +require_relative 'schemas_3_1/request_body' +require_relative 'schemas_3_1/response' +require_relative 'schemas_3_1/responses' +require_relative 'schemas_3_1/components' +require_relative 'schemas_3_1/media_type' +require_relative 'schemas_3_1/schema' +require_relative 'schemas_3_1/header' +require_relative 'schemas_3_1/info' diff --git a/lib/openapi_parser/schemas_3_1/base.rb b/lib/openapi_parser/schemas_3_1/base.rb new file mode 100644 index 0000000..b7d9e84 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/base.rb @@ -0,0 +1,28 @@ +module OpenAPIParser::Schemas31 + class Base + include OpenAPIParser::Parser + include OpenAPIParser::Findable + include OpenAPIParser::Expandable + + attr_reader :parent, :raw_schema, :object_reference, :root + + # @param [OpenAPIParser::Schemas31::Base] + def initialize(object_reference, parent, root, raw_schema) + @raw_schema = raw_schema + @parent = parent + @root = root + @object_reference = object_reference + + load_data + after_init + end + + # override + def after_init + end + + def inspect + @object_reference + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/classes.rb b/lib/openapi_parser/schemas_3_1/classes.rb new file mode 100644 index 0000000..c06205c --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/classes.rb @@ -0,0 +1,20 @@ +# We want to use class name for DSL in class definition so we should define first... + +module OpenAPIParser::Schemas31 + class Base; end + class Discriminator < Base; end + class OpenAPI < Base; end + class Operation < Base; end + class Parameter < Base; end + class PathItem < Base; end + class Paths < Base; end + class Reference < Base; end + class RequestBody < Base; end + class Responses < Base; end + class Response < Base; end + class MediaType < Base; end + class Schema < Base; end + class Components < Base; end + class Header < Base; end + class Info < Base; end +end diff --git a/lib/openapi_parser/schemas_3_1/components.rb b/lib/openapi_parser/schemas_3_1/components.rb new file mode 100644 index 0000000..50f025b --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/components.rb @@ -0,0 +1,28 @@ +# TODO: examples +# TODO: securitySchemes +# TODO: links +# TODO: callbacks + +module OpenAPIParser::Schemas31 + class Components < Base + # @!attribute [r] parameters + # @return [Hash{String => Parameter}, nil] + openapi_attr_hash_object :parameters, Parameter, reference: true + + # @!attribute [r] parameters + # @return [Hash{String => Parameter}, nil] + openapi_attr_hash_object :schemas, Schema, reference: true + + # @!attribute [r] responses + # @return [Hash{String => Response}, nil] + openapi_attr_hash_object :responses, Response, reference: true + + # @!attribute [r] request_bodies + # @return [Hash{String => RequestBody}, nil] + openapi_attr_hash_object :request_bodies, RequestBody, reference: true, schema_key: :requestBodies + + # @!attribute [r] headers + # @return [Hash{String => Header}, nil] header objects + openapi_attr_hash_object :headers, Header, reference: true + end +end diff --git a/lib/openapi_parser/schemas_3_1/discriminator.rb b/lib/openapi_parser/schemas_3_1/discriminator.rb new file mode 100644 index 0000000..60283ea --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/discriminator.rb @@ -0,0 +1,11 @@ +module OpenAPIParser::Schemas31 + class Discriminator < Base + # @!attribute [r] property_name + # @return [String, nil] + openapi_attr_value :property_name, schema_key: :propertyName + + # @!attribute [r] mapping + # @return [Hash{String => String] + openapi_attr_value :mapping + end +end diff --git a/lib/openapi_parser/schemas_3_1/header.rb b/lib/openapi_parser/schemas_3_1/header.rb new file mode 100644 index 0000000..7ee89b4 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/header.rb @@ -0,0 +1,18 @@ +module OpenAPIParser::Schemas31 + class Header < Base + openapi_attr_values :description, :required, :deprecated, :style, :explode, :example + + openapi_attr_value :allow_empty_value, schema_key: :allowEmptyValue + openapi_attr_value :allow_reserved, schema_key: :allowReserved + + # @!attribute [r] schema + # @return [Schema, Reference, nil] + openapi_attr_object :schema, Schema, reference: true + + # validate by schema + # @param [Object] value + def validate(value) + OpenAPIParser::SchemaValidator.validate(value, schema, OpenAPIParser::SchemaValidator::Options.new) + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/info.rb b/lib/openapi_parser/schemas_3_1/info.rb new file mode 100644 index 0000000..ffacdf0 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/info.rb @@ -0,0 +1,6 @@ +module OpenAPIParser::Schemas31 + class Info < Base + + openapi_attr_values :title, :version + end +end diff --git a/lib/openapi_parser/schemas_3_1/media_type.rb b/lib/openapi_parser/schemas_3_1/media_type.rb new file mode 100644 index 0000000..ca23e31 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/media_type.rb @@ -0,0 +1,18 @@ +# TODO: example +# TODO: examples +# TODO: encoding + +module OpenAPIParser::Schemas31 + class MediaType < Base + # @!attribute [r] schema + # @return [Schema, nil] OpenAPI3 Schema object + openapi_attr_object :schema, Schema, reference: true + + # validate params by schema definitions + # @param [Hash] params + # @param [OpenAPIParser::SchemaValidator::Options] options + def validate_parameter(params, options) + OpenAPIParser::SchemaValidator.validate(params, schema, options) + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/openapi.rb b/lib/openapi_parser/schemas_3_1/openapi.rb new file mode 100644 index 0000000..d5a98ac --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/openapi.rb @@ -0,0 +1,63 @@ +# TODO: info object +# TODO: servers object +# TODO: tags object +# TODO: externalDocs object + +module OpenAPIParser::Schemas31 + class OpenAPI < Base + def initialize(raw_schema, config, uri: nil, schema_registry: {}) + super('#', nil, self, raw_schema) + @find_object_cache = {} + @path_item_finder = OpenAPIParser::PathItemFinder.new(paths) if paths # invalid definition + @config = config + @uri = uri + @schema_registry = schema_registry + + # schema_registery is shared among schemas, and prevents a schema from being loaded multiple times + schema_registry[uri] = self if uri + end + + # @!attribute [r] openapi + # @return [String, nil] + openapi_attr_values :openapi + + # @!attribute [r] paths + # @return [Paths, nil] + openapi_attr_object :paths, Paths, reference: false + + # @!attribute [r] components + # @return [Components, nil] + openapi_attr_object :components, Components, reference: false + + # @!attribute [r] info + # @return [Info, nil] + openapi_attr_object :info, Info, reference: false + + # @return [OpenAPIParser::RequestOperation, nil] + def request_operation(http_method, request_path) + OpenAPIParser::RequestOperation.create(http_method, request_path, @path_item_finder, @config) + end + + # load another schema with shared config and schema_registry + # @return [OpenAPIParser::Schemas31::OpenAPI] + def load_another_schema(uri) + resolved_uri = resolve_uri(uri) + return if resolved_uri.nil? + + loaded = @schema_registry[resolved_uri] + return loaded if loaded + + OpenAPIParser.load_uri(resolved_uri, config: @config, schema_registry: @schema_registry) + end + + private + + def resolve_uri(uri) + if uri.absolute? + uri + else + @uri&.merge(uri) + end + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/operation.rb b/lib/openapi_parser/schemas_3_1/operation.rb new file mode 100644 index 0000000..1833156 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/operation.rb @@ -0,0 +1,34 @@ +# TODO: externalDocs +# TODO: callbacks +# TODO: security +# TODO: servers + +module OpenAPIParser::Schemas31 + class Operation < Base + include OpenAPIParser::ParameterValidatable + + openapi_attr_values :tags, :summary, :description, :deprecated + + openapi_attr_value :operation_id, schema_key: :operationId + + openapi_attr_list_object :parameters, Parameter, reference: true + + # @!attribute [r] request_body + # @return [OpenAPIParser::Schemas31::RequestBody, nil] return OpenAPI3 object + openapi_attr_object :request_body, RequestBody, reference: true, schema_key: :requestBody + + # @!attribute [r] responses + # @return [OpenAPIParser::Schemas31::Responses, nil] return OpenAPI3 object + openapi_attr_object :responses, Responses, reference: false + + def validate_request_body(content_type, params, options) + request_body&.validate_request_body(content_type, params, options) + end + + # @param [OpenAPIParser::RequestOperation::ValidatableResponseBody] response_body + # @param [OpenAPIParser::SchemaValidator::ResponseValidateOptions] response_validate_options + def validate_response(response_body, response_validate_options) + responses&.validate(response_body, response_validate_options) + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/parameter.rb b/lib/openapi_parser/schemas_3_1/parameter.rb new file mode 100644 index 0000000..84f995a --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/parameter.rb @@ -0,0 +1,20 @@ +# TODO: support examples + +module OpenAPIParser::Schemas31 + class Parameter < Base + openapi_attr_values :name, :in, :description, :required, :deprecated, :style, :explode, :example + + openapi_attr_value :allow_empty_value, schema_key: :allowEmptyValue + openapi_attr_value :allow_reserved, schema_key: :allowReserved + + # @!attribute [r] schema + # @return [Schema, Reference, nil] + openapi_attr_object :schema, Schema, reference: true + + # @return [Object] coerced or original params + # @param [OpenAPIParser::SchemaValidator::Options] options + def validate_params(params, options) + ::OpenAPIParser::SchemaValidator.validate(params, schema, options) + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/path_item.rb b/lib/openapi_parser/schemas_3_1/path_item.rb new file mode 100644 index 0000000..7113447 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/path_item.rb @@ -0,0 +1,22 @@ +# TODO: support servers +# TODO: support reference + +module OpenAPIParser::Schemas31 + class PathItem < Base + openapi_attr_values :summary, :description + + openapi_attr_objects :get, :put, :post, :delete, :options, :head, :patch, :trace, Operation + openapi_attr_list_object :parameters, Parameter, reference: true + + # @return [Operation] + def operation(method) + public_send(method) + rescue NoMethodError + nil + end + + def set_path_item_to_operation + [:get, :put, :post, :delete, :options, :head, :patch, :trace].each{ |method| operation(method)&.set_parent_path_item(self)} + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/paths.rb b/lib/openapi_parser/schemas_3_1/paths.rb new file mode 100644 index 0000000..83895c1 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/paths.rb @@ -0,0 +1,7 @@ +module OpenAPIParser::Schemas31 + class Paths < Base + # @!attribute [r] path + # @return [Hash{String => PathItem, Reference}, nil] + openapi_attr_hash_body_objects 'path', PathItem, reference: true, allow_data_type: false + end +end diff --git a/lib/openapi_parser/schemas_3_1/reference.rb b/lib/openapi_parser/schemas_3_1/reference.rb new file mode 100644 index 0000000..607d2ec --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/reference.rb @@ -0,0 +1,7 @@ +module OpenAPIParser::Schemas31 + class Reference < Base + # @!attribute [r] ref + # @return [Base] + openapi_attr_value :ref, schema_key: '$ref' + end +end diff --git a/lib/openapi_parser/schemas_3_1/request_body.rb b/lib/openapi_parser/schemas_3_1/request_body.rb new file mode 100644 index 0000000..76a0cac --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/request_body.rb @@ -0,0 +1,34 @@ +# TODO: support extended property + +module OpenAPIParser::Schemas31 + class RequestBody < Base + include OpenAPIParser::MediaTypeSelectable + + # @!attribute [r] description + # @return [String] description data + # @!attribute [r] required + # @return [Boolean] required bool data + openapi_attr_values :description, :required + + # @!attribute [r] content + # @return [Hash{String => MediaType}, nil] content type to MediaType object + openapi_attr_hash_object :content, MediaType, reference: false + + # @param [String] content_type + # @param [Hash] params + # @param [OpenAPIParser::SchemaValidator::Options] options + def validate_request_body(content_type, params, options) + media_type = select_media_type(content_type) + return params unless media_type + + media_type.validate_parameter(params, options) + end + + # select media type by content_type (consider wild card definition) + # @param [String] content_type + # @return [OpenAPIParser::Schemas31::MediaType, nil] + def select_media_type(content_type) + select_media_type_from_content(content_type, content) + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/response.rb b/lib/openapi_parser/schemas_3_1/response.rb new file mode 100644 index 0000000..182b17a --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/response.rb @@ -0,0 +1,54 @@ +# TODO: links + +module OpenAPIParser::Schemas31 + class Response < Base + include OpenAPIParser::MediaTypeSelectable + + openapi_attr_values :description + + # @!attribute [r] content + # @return [Hash{String => MediaType}, nil] content_type to MediaType hash + openapi_attr_hash_object :content, MediaType, reference: false + + # @!attribute [r] headers + # @return [Hash{String => Header}, nil] header string to Header + openapi_attr_hash_object :headers, Header, reference: true + + # @param [OpenAPIParser::RequestOperation::ValidatableResponseBody] response_body + # @param [OpenAPIParser::SchemaValidator::ResponseValidateOptions] response_validate_options + def validate(response_body, response_validate_options) + validate_header(response_body.headers) if response_validate_options.validate_header + + media_type = select_media_type(response_body.content_type) + unless media_type + raise ::OpenAPIParser::NotExistContentTypeDefinition, object_reference if response_validate_options.strict + + return nil + end + + options = ::OpenAPIParser::SchemaValidator::Options.new # response validator not support any options + media_type.validate_parameter(response_body.response_data, options) + end + + # select media type by content_type (consider wild card definition) + # @param [String] content_type + # @return [OpenAPIParser::Schemas31::MediaType, nil] + def select_media_type(content_type) + select_media_type_from_content(content_type, content) + end + + private + + # @param [Hash] response_headers + def validate_header(response_headers) + return unless headers + + headers.each do |name, schema| + next unless response_headers.key?(name) + + value = response_headers[name] + schema.validate(value) + end + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/responses.rb b/lib/openapi_parser/schemas_3_1/responses.rb new file mode 100644 index 0000000..eebe6df --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/responses.rb @@ -0,0 +1,56 @@ +# TODO: support extended property + +module OpenAPIParser::Schemas31 + class Responses < Base + # @!attribute [r] default + # @return [Response, Reference, nil] default response object + openapi_attr_object :default, Response, reference: true + + # @!attribute [r] response + # @return [Hash{String => Response, Reference}, nil] response object indexed by status code. see: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#patterned-fields-1 + openapi_attr_hash_body_objects 'response', Response, reject_keys: [:default], reference: true, allow_data_type: false + + # validate params data by definition + # find response object by status_code and content_type + # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#patterned-fields-1 + # @param [OpenAPIParser::RequestOperation::ValidatableResponseBody] response_body + # @param [OpenAPIParser::SchemaValidator::ResponseValidateOptions] response_validate_options + def validate(response_body, response_validate_options) + return nil unless response + + if (res = find_response_object(response_body.status_code)) + + return res.validate(response_body, response_validate_options) + end + + raise ::OpenAPIParser::NotExistStatusCodeDefinition, object_reference if response_validate_options.strict + + nil + end + + private + + # @param [Integer] status_code + # @return [Response] + def find_response_object(status_code) + if (res = response[status_code.to_s]) + return res + end + + wild_card = status_code_to_wild_card(status_code) + if (res = response[wild_card]) + return res + end + + default + end + + # parse 400 -> 4xx + # OpenAPI3 allow 1xx, 2xx, 3xx... only, don't allow 41x + # @param [Integer] status_code + def status_code_to_wild_card(status_code) + top = status_code / 100 + "#{top}XX" + end + end +end diff --git a/lib/openapi_parser/schemas_3_1/schema.rb b/lib/openapi_parser/schemas_3_1/schema.rb new file mode 100644 index 0000000..9544714 --- /dev/null +++ b/lib/openapi_parser/schemas_3_1/schema.rb @@ -0,0 +1,121 @@ +# TODO: support 'not' because I need check reference... +# TODO: support 'xml', 'externalDocs' +# TODO: support extended property + +module OpenAPIParser::Schemas31 + class Schema < Base + # @!attribute [r] title + # @return [String, nil] + # @!attribute [r] pattern + # @return [String, nil] regexp + # @!attribute [r] pattern + # @return [String, nil] regexp + # @!attribute [r] description + # @return [String, nil] + # @!attribute [r] format + # @return [String, nil] + # @!attribute [r] type + # @return [String, nil] multiple types doesn't supported in OpenAPI3 + + # @!attribute [r] maximum + # @return [Float, nil] + # @!attribute [r] multipleOf + # @return [Float, nil] + + # @!attribute [r] maxLength + # @return [Integer, nil] + # @!attribute [r] minLength + # @return [Integer, nil] + # @!attribute [r] maxItems + # @return [Integer, nil] + # @!attribute [r] minItems + # @return [Integer, nil] + # @!attribute [r] maxProperties + # @return [Integer, nil] + # @!attribute [r] minProperties + # @return [Integer, nil] + + # @!attribute [r] exclusiveMaximum + # @return [Float, nil] + # @!attribute [r] exclusiveMinimum + # @return [Float, nil] + # @!attribute [r] uniqueItems + # @return [Boolean, nil] + # @!attribute [r] nullable + # @return [Boolean, nil] + # @!attribute [r] deprecated + # @return [Boolean, nil] + + # @!attribute [r] required + # @return [Array, nil] at least one item included + + # @!attribute [r] enum + # @return [Array, nil] any type array + + # @!attribute [r] default + # @return [Object, nil] + + # @!attribute [r] example + # @return [Object, nil] + + openapi_attr_values :title, :multipleOf, + :maximum, :exclusiveMaximum, :minimum, :exclusiveMinimum, + :maxLength, :minLength, + :pattern, + :maxItems, :minItems, :uniqueItems, + :maxProperties, :minProperties, + :required, :enum, + :description, + :format, + :default, + :type, + :example, + :deprecated + + # @!attribute [r] read_only + # @return [Boolean, nil] + openapi_attr_value :read_only, schema_key: :readOnly + + # @!attribute [r] write_only + # @return [Boolean, nil] + openapi_attr_value :write_only, schema_key: :writeOnly + + # @!attribute [r] all_of + # @return [Array, nil] + openapi_attr_list_object :all_of, Schema, reference: true, schema_key: :allOf + + # @!attribute [r] one_of + # @return [Array, nil] + openapi_attr_list_object :one_of, Schema, reference: true, schema_key: :oneOf + + # @!attribute [r] any_of + # @return [Array, nil] + openapi_attr_list_object :any_of, Schema, reference: true, schema_key: :anyOf + + # @!attribute [r] items + # @return [Schema, nil] + openapi_attr_object :items, Schema, reference: true + + # @!attribute [r] properties + # @return [Hash{String => Schema}, nil] + openapi_attr_hash_object :properties, Schema, reference: true + + # @!attribute [r] discriminator + # @return [Discriminator, nil] + openapi_attr_object :discriminator, Discriminator + + # @!attribute [r] additional_properties + # @return [Boolean, Schema, Reference, nil] + openapi_attr_object :additional_properties, Schema, reference: true, allow_data_type: true, schema_key: :additionalProperties + # additional_properties have default value + # we should add default value feature in openapi_attr_object method, but we need temporary fix so override attr_reader + def additional_properties + @additional_properties.nil? ? true : @additional_properties + end + # additional_properties have default value + # we should add default value feature in openapi_attr_object method, but we need temporary fix so override attr_reader + def nullable + type&.include?("null") + end + end +end diff --git a/spec/data/petstore.json b/spec/data/petstore.json index 92fcf82..0e7ac62 100644 --- a/spec/data/petstore.json +++ b/spec/data/petstore.json @@ -1,4 +1,5 @@ { + "openapi": "3.1.0", "swagger": "2.0", "info": { "version": "1.0.0", diff --git a/spec/data/petstore.json.unsupported_extension b/spec/data/petstore.json.unsupported_extension index 92fcf82..df7d8f1 100644 --- a/spec/data/petstore.json.unsupported_extension +++ b/spec/data/petstore.json.unsupported_extension @@ -1,4 +1,5 @@ { + "openapi": "3.0.0", "swagger": "2.0", "info": { "version": "1.0.0", diff --git a/spec/data/schema_3_1/cyclic-remote-ref1.yaml b/spec/data/schema_3_1/cyclic-remote-ref1.yaml new file mode 100644 index 0000000..6ae4dc3 --- /dev/null +++ b/spec/data/schema_3_1/cyclic-remote-ref1.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /cyclic_reference: + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: cyclic-remote-ref2.yaml#/components/schemas/outer + responses: + '200': + description: correct + content: + application/json: + schema: + type: object +components: + schemas: + inner: + type: integer diff --git a/spec/data/schema_3_1/cyclic-remote-ref2.yaml b/spec/data/schema_3_1/cyclic-remote-ref2.yaml new file mode 100644 index 0000000..50c6807 --- /dev/null +++ b/spec/data/schema_3_1/cyclic-remote-ref2.yaml @@ -0,0 +1,13 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +components: + schemas: + outer: + type: object + properties: + content: + $ref: cyclic-remote-ref1.yaml#/components/schemas/inner + required: + - content diff --git a/spec/data/schema_3_1/normal.yml b/spec/data/schema_3_1/normal.yml new file mode 100644 index 0000000..bb02bcd --- /dev/null +++ b/spec/data/schema_3_1/normal.yml @@ -0,0 +1,704 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: OpenAPI3 Test + description: A Sample file +servers: + - url: https://github.com/interagent/committee/ +paths: + /characters: + summary: summary_text + description: desc + get: + description: get characters + parameters: + - name: school_name + in: query + description: school name to filter by + required: false + style: form + schema: + type: array + items: + type: string + - name: limit + in: query + description: maximum number of characters + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: success + content: + application/json: + schema: + type: object + properties: + string_1: + type: string + array_1: + type: array + items: + type: string + post: + description: new characters + responses: + '200': + description: correct + content: + application/json: + schema: + type: object + properties: + response_1: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + string_post_1: + type: string + delete: + description: new characters + responses: + '200': + description: correct + content: + application/json: + schema: + type: object + properties: + response_1: + type: string + parameters: + - name: limit + in: query + description: maximum number of characters + required: false + schema: + type: integer + format: int32 + + /string_params_coercer: + get: + description: string_params_coercer test data + parameters: + - name: integer_1 + in: query + description: integer 32 + required: false + schema: + type: integer + format: int32 + - name: string_1 + in: query + description: string data + required: false + schema: + type: string + - name: boolean_1 + in: query + description: boolean data + required: false + schema: + type: boolean + - name: number_1 + in: query + description: number data + required: false + schema: + type: + - "number" + - "null" + - name: datetime_string + in: query + required: false + schema: + type: string + format: date-time + - name: normal_array + in: query + description: number data + required: false + schema: + type: array + items: + type: integer + - name: nested_array + in: query + description: nested_array + required: false + schema: + "$ref": '#/components/schemas/nested_array' + - name: any_of + in: query + description: coercing to any of types + required: false + schema: + anyOf: + - type: integer + - type: number + - type: boolean + responses: + '200': + description: success + content: + application/json: + schema: + type: array + items: + type: string + post: + description: string_params_coercer test data + requestBody: + content: + application/json: + schema: + type: object + properties: + any_of: + anyOf: + - type: number + - type: integer + - type: boolean + - type: string + nested_array: + "$ref": '#/components/schemas/nested_array' + responses: + '200': + description: success + content: + application/json: + schema: + type: array + items: + type: string + /validate: + get: + description: get characters + parameters: + - name: query_string + in: query + required: true + style: form + schema: + type: string + - name: query_string + in: path + required: true + schema: + type: integer + - name: query_integer_list + in: query + required: true + schema: + type: array + items: + type: integer + - name: optional_integer + in: query + required: false + schema: + type: integer + - name: queryString + in: query + required: true + schema: + type: string + responses: + '204': + description: no content + post: + description: validate test data + requestBody: + content: + application/json: + schema: + additionalProperties: false + type: object + properties: + string: + type: string + datetime: + type: string + format: date-time + integer: + type: integer + boolean: + type: boolean + number: + type: number + array: + type: array + items: + type: integer + all_of_data: + allOf: + - $ref: '#/components/schemas/all_of_base' + - type: object + required: + - id + properties: + id: + type: integer + format: int64 + all_of_with_nullable: + allOf: + - type: + - "integer" + - "null" + one_of_data: + oneOf: + - $ref: '#/components/schemas/one_of_object1' + - $ref: '#/components/schemas/one_of_object2' + one_of_with_discriminator: + oneOf: + - $ref: '#/components/schemas/one_of_object1' + - $ref: '#/components/schemas/one_of_object2' + discriminator: + propertyName: objType + mapping: + obj1: '#/components/schemas/one_of_object1' + obj2: '#/components/schemas/one_of_object2' + one_of_with_nullable: + oneOf: + - type: + - "integer" + - "null" + object_1: + type: object + properties: + string_1: + type: + - "string" + - "null" + integer_1: + type: + - "integer" + - "null" + + boolean_1: + type: + - "boolean" + - "null" + number_1: + type: + - "number" + - "null" + object_2: + type: object + required: + - string_2 + - integer_2 + - boolean_2 + - number_2 + properties: + string_2: + type: string + integer_2: + type: integer + boolean_2: + type: boolean + number_2: + type: number + required_object: + type: object + required: + - need_object + properties: + need_object: + type: object + required: + - string + properties: + string: + type: string + no_need_object: + type: object + required: + - integer + properties: + integer: + type: integer + any_of: + type: array + items: + anyOf: + - type: string + - type: boolean + any_of_with_nullable: + anyOf: + - type: + - "integer" + - "null" + unspecified_type: {} + enum_string: + type: string + enum: + - a + - b + enum_integer: + type: integer + enum: + - 1 + - 2 + enum_number: + type: number + enum: + - 1.0 + - 2.1 + enum_boolean: + type: boolean + enum: + - true + + responses: + '200': + description: success + content: + application/json: + schema: + type: object + properties: + string: + type: string + '204': + description: no content + default: + description: success + content: + application/json: + schema: + type: object + properties: + integer: + type: integer + put: + description: validate put metodhd + requestBody: + content: + application/json: + schema: + type: object + properties: + string: + type: string + responses: + '200': + description: success + content: + application/json: + schema: + type: object + properties: + string: + type: string + patch: + description: validate patch metodhd + requestBody: + content: + application/json: + schema: + type: object + properties: + integer: + type: integer + responses: + '200': + description: success + content: + application/json: + schema: + type: object + properties: + string: + type: string + + /validate_no_parameter: + patch: + description: validate no body + responses: + '200': + description: success + content: + application/json: + schema: + type: object + properties: + integer: + type: integer + + /validate_response_array: + get: + responses: + '200': + description: success + content: + application/json: + schema: + type: array + items: + type: string + + /path_template_test/no_template: + get: + responses: + '204': + description: no content + /path_template_test/{template_name}: + parameters: + - name: template_name + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: no content + /path_template_test/{template_name}/nested: + parameters: + - name: template_name + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: no content + /path_template_test/{template_name}/nested/{nested_parameter}: + parameters: + - name: template_name + in: path + required: true + schema: + type: string + - name: nested_parameter + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: no content + /path_template_test/{template_name}/{nested_parameter}: + parameters: + - name: template_name + in: path + required: true + schema: + type: string + - name: nested_parameter + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: no content + /path_template_test/{template_name}/{nested_parameter}/finish: + parameters: + - name: template_name + in: path + required: true + schema: + type: string + - name: nested_parameter + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: no content + /{ambiguous}/no_template: + parameters: + - name: ambiguous + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: no content + /coerce_query_prams_in_operation_and_path_item: + parameters: + - name: path_item_integer + in: query + required: true + schema: + type: integer + get: + parameters: + - name: operation_integer + in: query + required: true + schema: + type: integer + description: get characters + responses: + '204': + description: no content + /coerce_path_params/{integer}: + parameters: + - name: integer + in: path + required: true + schema: + type: integer + get: + description: get characters + responses: + '204': + description: no content + /coerce_path_params_in_path_item/{integer}: + get: + description: get characters + parameters: + - name: integer + in: path + required: true + schema: + type: integer + responses: + '204': + description: no content + /reference: + get: + parameters: + - "$ref": '#/components/parameters/ref1' + responses: + default: + "$ref": '#/components/responses/ref1' + + post: + requestBody: + "$ref": '#/components/requestBodies/ref1' + responses: + default: + "$ref": '#/components/responses/ref2' + + /date_time: + get: + responses: + '200': + description: success + content: + application/json: + schema: + type: object + properties: + date: + type: string + format: date + date-time: + type: string + format: date-time + example: + date: 2020-05-12 + date-time: 2020-05-12T00:00:00.00Z + + +components: + parameters: + ref1: + name: integer + in: path + required: true + schema: + type: integer + requestBodies: + ref1: + content: + application/json: + schema: + type: object + properties: + integer: + type: integer + responses: + ref1: + description: no content + content: + application/json: + schema: + "$ref": '#/components/schemas/nested_array' + ref2: + "$ref": '#/components/responses/ref1' + schemas: + all_of_base: + type: object + required: + - name + properties: + name: + type: string + tag: + type: string + nested_array: + type: array + items: + type: object + properties: + update_time: + type: string + format: date-time + per_page: + type: integer + threshold: + type: number + nested_coercer_object: + type: object + properties: + update_time: + type: string + format: date-time + threshold: + type: number + nested_no_coercer_object: + type: object + nested_coercer_array: + type: array + items: + type: object + properties: + update_time: + type: string + format: date-time + threshold: + type: number + nested_no_coercer_array: + type: array + items: + type: object + one_of_object1: + type: object + required: + - name + - integer_1 + properties: + name: + type: string + integer_1: + type: integer + additionalProperties: false + one_of_object2: + type: object + required: + - name + - string_1 + properties: + name: + type: string + string_1: + type: string + additionalProperties: false diff --git a/spec/data/schema_3_1/path-item-ref-relative.yaml b/spec/data/schema_3_1/path-item-ref-relative.yaml new file mode 100644 index 0000000..152a425 --- /dev/null +++ b/spec/data/schema_3_1/path-item-ref-relative.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /ref-sample/relative: + post: + description: override here + requestBody: + content: + application/json: + schema: + type: object + properties: + test: + type: string + required: + - test + responses: + "204": + description: empty diff --git a/spec/data/schema_3_1/path-item-ref.yaml b/spec/data/schema_3_1/path-item-ref.yaml new file mode 100644 index 0000000..5c6f91a --- /dev/null +++ b/spec/data/schema_3_1/path-item-ref.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /sample/{sample_id}: + parameters: + - name: sample_id + in: path + required: true + schema: + type: string + post: + description: override here + requestBody: + content: + application/json: + schema: + type: object + properties: + test: + type: string + required: + - test + responses: + '204': + description: empty + /ref-sample: + $ref: '#/paths/~1sample~1%7Bsample_id%7D' + /ref-sample/relative: + $ref: 'path-item-ref-relative.yaml#/paths/~1ref-sample~1relative' diff --git a/spec/data/schema_3_1/petstore-expanded.yaml b/spec/data/schema_3_1/petstore-expanded.yaml new file mode 100644 index 0000000..a7174b8 --- /dev/null +++ b/spec/data/schema_3_1/petstore-expanded.yaml @@ -0,0 +1,387 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification + termsOfService: http://swagger.io/terms/ + contact: + name: Swagger API Team + email: apiteam@swagger.io + url: http://swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: +- url: http://petstore.swagger.io/api +paths: + /pets: + get: + description: | + Returns all pets from the system that the user has access to + Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + + Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + operationId: findPets + tags: + - tag_1 + - tag_2 + summary: sum + deprecated: true + parameters: + - name: tags + in: query + description: tags to filter by + required: false + style: form + allowEmptyValue: true + schema: + type: array + items: + type: string + additionalProperties: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + default: 1 + - $ref: '#/components/parameters/test' + - name: all_of_check + in: query + required: false + schema: + type: + - "object" + - "null" + allOf: + - $ref: '#/components/parameters/test' + - + type: object + required: + - name + properties: + nmae: + type: string + properties: + pop: + type: string + readOnly: true + example: 'test' + deprecated: true + int: + type: integer + writeOnly: true + additionalProperties: true + description: desc + responses: + '200': + description: pet response + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + x-limit: + $ref: '#/components/headers/X-Rate-Limit-Limit' + non-nullable-x-limit: + $ref: '#/components/headers/Non-Nullable-X-Rate-Limit-Limit' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 'application/unknown': + schema: + type: object + 'application/*': + schema: + type: object + '*/*': + schema: + type: object + '4XX': + description: error response + content: + application/json: + schema: + type: object + required: + - message + properties: + message: + type: string + '404': + description: 404 response + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + message: + type: string + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + description: Creates a new pet in the store. Duplicates are allowed + operationId: addPet + requestBody: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + '201': + $ref: '#/components/responses/normal' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{id}: + get: + description: Returns a user based on a single ID, if the user does not have access to the pet + operationId: find pet by id + parameters: + - name: id + in: path + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + parameters: + - name: id + in: path + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + requestBody: + $ref: '#/components/requestBodies/test_body' + responses: + '200': + $ref: '#/components/responses/normal' + delete: + description: deletes a single pet based on the ID supplied + operationId: deletePet + parameters: + - name: id + in: path + description: ID of pet to delete + required: true + schema: + type: integer + format: int64 + responses: + '204': + description: pet deleted + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{nickname}/adopt/{param_2}: + post: + description: Adopt a pet + operationId: adoptPet + parameters: + - name: nickname + in: path + description: Name of pet to adopt + required: true + schema: + type: string + - name: param_2 + in: path + description: Sample parameter + required: true + schema: + type: string + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /animals/{id}: + parameters: + - $ref: '#/components/parameters/petId' + - name: token + in: header + description: token to be passed as a header + required: true + schema: + type: integer + format: int64 + style: simple + get: + parameters: + - name: header_2 + in: header + required: true + schema: + type: string + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /animals/{groupId}/{id}.json: + parameters: + - $ref: '#/components/parameters/petId' + - name: groupId + in: path + description: group ID of the animal + required: true + schema: + type: integer + format: int64 + style: simple + - name: token + in: header + description: token to be passed as a header + required: true + schema: + type: integer + format: int64 + style: simple + get: + parameters: + - name: header_2 + in: header + required: true + schema: + type: string + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + +components: + parameters: + test: + name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + test_ref: + $ref: '#/components/parameters/test' + petId: + in: path + name: id + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + schemas: + Pet: + allOf: + - $ref: '#/components/schemas/NewPet' + - type: object + required: + - id + properties: + id: + type: integer + format: int64 + + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: string + + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + responses: + normal: + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + requestBodies: + test_body: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + headers: + X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + Non-Nullable-X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + schema: + type: integer diff --git a/spec/data/schema_3_1/petstore-with-discriminator.yaml b/spec/data/schema_3_1/petstore-with-discriminator.yaml new file mode 100644 index 0000000..6d4b6dc --- /dev/null +++ b/spec/data/schema_3_1/petstore-with-discriminator.yaml @@ -0,0 +1,245 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification + termsOfService: http://swagger.io/terms/ + contact: + name: Swagger API Team + email: apiteam@swagger.io + url: http://swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: + - url: http://petstore.swagger.io/api +paths: + /save_the_pets: + post: + description: Creates a new pet in the store. Duplicates are allowed + operationId: addPet + requestBody: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PetBaskets' + responses: + '200': + description: pet response + content: + application/json: + schema: + type: object + /save_the_pets_without_mapping: + post: + description: Creates a new pet in the store. Duplicates are allowed + operationId: addPet + requestBody: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PetBasketsWithoutMapping' + responses: + '200': + description: pet response + content: + application/json: + schema: + type: object +components: + schemas: + PetBaskets: + type: object + properties: + baskets: + type: array + items: + anyOf: + - "$ref": "#/components/schemas/SquirrelBasket" + - "$ref": "#/components/schemas/CatBasket" + - "$ref": "#/components/schemas/TurtleBasket" + - "$ref": "#/components/schemas/Dragon" + - "$ref": "#/components/schemas/Hydra" + discriminator: + propertyName: name + mapping: + cats: "#/components/schemas/CatBasket" + squirrels: "#/components/schemas/SquirrelBasket" + turtles: "#/components/schemas/TurtleBasket" + dragon: "#/components/schemas/Dragon" + hydra: "#/components/schemas/Hydra" + PetBasketsWithoutMapping: + type: object + properties: + baskets: + type: array + items: + anyOf: + - "$ref": "#/components/schemas/SquirrelBasket" + - "$ref": "#/components/schemas/CatBasket" + discriminator: + propertyName: name + SquirrelBasket: + type: object + required: + - name + properties: + name: + type: string + content: + type: array + items: + "$ref": "#/components/schemas/Squirrel" + additionalProperties: false + Squirrel: + type: object + required: + - name + - nut_stock + properties: + name: + type: string + born_at: + format: date-time + type: + - "string" + - "null" + description: + type: string + nut_stock: + type: + - "integer" + - "null" + additionalProperties: true + CatBasket: + type: object + required: + - name + properties: + name: + type: string + content: + type: array + items: + "$ref": "#/components/schemas/Cat" + additionalProperties: false + Cat: + type: object + required: + - name + - milk_stock + properties: + name: + type: string + born_at: + format: date-time + type: + - "string" + - "null" + description: + type: string + - "string" + - "null" + milk_stock: + type: + - "integer" + - "null" + additionalProperties: false + TurtleBasket: + type: object + required: + - name + properties: + name: + type: string + content: + type: array + items: + "$ref": "#/components/schemas/NinjaTurtle" + additionalProperties: false + NinjaTurtle: + type: object + required: + - name + properties: + name: + type: string + born_at: + format: date-time + type: + - "string" + - "null" + description: + type: + - "string" + - "null" + required_combat_style: + allOf: + - anyOf: + - "$ref": "#/components/schemas/DonatelloStyle" + - "$ref": "#/components/schemas/MichelangeloStyle" + type: + - "object" + - "null" + optional_combat_style: + anyOf: + - "$ref": "#/components/schemas/DonatelloStyle" + - "$ref": "#/components/schemas/MichelangeloStyle" + DonatelloStyle: + type: + - "object" + - "null" + properties: + bo_color: + type: string + shuriken_count: + type: integer + additionalProperties: false + MichelangeloStyle: + type: + - "object" + - "null" + properties: + nunchaku_color: + type: string + grappling_hook_length: + type: number + format: double + additionalProperties: false + Dragon: + allOf: + - $ref: '#/components/schemas/DragonBody' + - type: object + required: + - fire_range + properties: + fire_range: + type: integer + format: int64 + additionalProperties: false + Hydra: + allOf: + - $ref: '#/components/schemas/DragonBody' + - type: object + required: + - head_count + properties: + head_count: + type: integer + format: int64 + additionalProperties: + type: string + DragonBody: + type: object + required: + - name + properties: + name: + type: string + mass: + type: integer + additionalProperties: false diff --git a/spec/data/schema_3_1/petstore-with-mapped-polymorphism.yaml b/spec/data/schema_3_1/petstore-with-mapped-polymorphism.yaml new file mode 100644 index 0000000..376cc71 --- /dev/null +++ b/spec/data/schema_3_1/petstore-with-mapped-polymorphism.yaml @@ -0,0 +1,109 @@ +openapi: '3.0.3' +info: + description: This is a sample server Petstore server. + version: 1.0.5 + title: Swagger Petstore + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + "/pet": + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + requestBody: + description: Pet object that needs to be added to the store + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + '405': + description: Invalid input + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + requestBody: + description: Pet object that needs to be added to the store + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception +components: + schemas: + Pet: + type: object + discriminator: + propertyName: petType + mapping: + tinyLion: '#/components/schemas/Cat' + docileWolf: '#/components/schemas/Dog' + properties: + name: + type: string + petType: + type: string + required: + - name + - petType + Cat: ## "Cat" will be used as the discriminator value + description: A representation of a cat + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + huntingSkill: + type: string + description: The measured skill for hunting + enum: + - clueless + - lazy + - adventurous + - aggressive + required: + - huntingSkill + Dog: ## "Dog" will be used as the discriminator value + description: A representation of a dog + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + packSize: + type: integer + format: int32 + description: the size of the pack the dog is from + default: 0 + minimum: 0 + required: + - packSize diff --git a/spec/data/schema_3_1/petstore-with-polymorphism.yaml b/spec/data/schema_3_1/petstore-with-polymorphism.yaml new file mode 100644 index 0000000..b4e706b --- /dev/null +++ b/spec/data/schema_3_1/petstore-with-polymorphism.yaml @@ -0,0 +1,106 @@ +openapi: '3.0.3' +info: + description: This is a sample server Petstore server. + version: 1.0.5 + title: Swagger Petstore + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + "/pet": + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + requestBody: + description: Pet object that needs to be added to the store + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + '405': + description: Invalid input + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + requestBody: + description: Pet object that needs to be added to the store + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception +components: + schemas: + Pet: + type: object + discriminator: + propertyName: petType + properties: + name: + type: string + petType: + type: string + required: + - name + - petType + Cat: ## "Cat" will be used as the discriminator value + description: A representation of a cat + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + huntingSkill: + type: string + description: The measured skill for hunting + enum: + - clueless + - lazy + - adventurous + - aggressive + required: + - huntingSkill + Dog: ## "Dog" will be used as the discriminator value + description: A representation of a dog + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + packSize: + type: integer + format: int32 + description: the size of the pack the dog is from + default: 0 + minimum: 0 + required: + - packSize diff --git a/spec/data/schema_3_1/petstore.json b/spec/data/schema_3_1/petstore.json new file mode 100644 index 0000000..0e7ac62 --- /dev/null +++ b/spec/data/schema_3_1/petstore.json @@ -0,0 +1,154 @@ +{ + "openapi": "3.1.0", + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "license": { + "name": "MIT" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v1", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "responses": { + "200": { + "description": "An paged array of pets", + "headers": { + "x-next": { + "type": "string", + "description": "A link to the next page of responses" + } + }, + "schema": { + "$ref": "#/definitions/Pets" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "tags": [ + "pets" + ], + "responses": { + "201": { + "description": "Null response" + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, + "/pets/{petId}": { + "get": { + "summary": "Info for a specific pet", + "operationId": "showPetById", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "description": "The id of the pet to retrieve", + "type": "string" + } + ], + "responses": { + "200": { + "description": "Expected response to a valid request", + "schema": { + "$ref": "#/definitions/Pets" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + } + }, + "definitions": { + "Pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "Pets": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + }, + "Error": { + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } +} diff --git a/spec/data/schema_3_1/petstore.json.unsupported_extension b/spec/data/schema_3_1/petstore.json.unsupported_extension new file mode 100644 index 0000000..df7d8f1 --- /dev/null +++ b/spec/data/schema_3_1/petstore.json.unsupported_extension @@ -0,0 +1,154 @@ +{ + "openapi": "3.0.0", + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "license": { + "name": "MIT" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v1", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "responses": { + "200": { + "description": "An paged array of pets", + "headers": { + "x-next": { + "type": "string", + "description": "A link to the next page of responses" + } + }, + "schema": { + "$ref": "#/definitions/Pets" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "tags": [ + "pets" + ], + "responses": { + "201": { + "description": "Null response" + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, + "/pets/{petId}": { + "get": { + "summary": "Info for a specific pet", + "operationId": "showPetById", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "description": "The id of the pet to retrieve", + "type": "string" + } + ], + "responses": { + "200": { + "description": "Expected response to a valid request", + "schema": { + "$ref": "#/definitions/Pets" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + } + }, + "definitions": { + "Pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "Pets": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + }, + "Error": { + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } +} diff --git a/spec/data/schema_3_1/petstore.yaml.unsupported_extension b/spec/data/schema_3_1/petstore.yaml.unsupported_extension new file mode 100644 index 0000000..72685cd --- /dev/null +++ b/spec/data/schema_3_1/petstore.yaml.unsupported_extension @@ -0,0 +1,351 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification + termsOfService: http://swagger.io/terms/ + contact: + name: Swagger API Team + email: apiteam@swagger.io + url: http://swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: +- url: http://petstore.swagger.io/api +paths: + /pets: + get: + description: | + Returns all pets from the system that the user has access to + Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + + Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + operationId: findPets + tags: + - tag_1 + - tag_2 + summary: sum + deprecated: true + parameters: + - name: tags + in: query + description: tags to filter by + required: false + style: form + allowEmptyValue: true + schema: + type: array + items: + type: string + additionalProperties: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + default: 1 + - $ref: '#/components/parameters/test' + - name: all_of_check + in: query + required: false + schema: + type: object + nullable: true + allOf: + - $ref: '#/components/parameters/test' + - + type: object + required: + - name + properties: + nmae: + type: string + properties: + pop: + type: string + readOnly: true + example: 'test' + deprecated: true + int: + type: integer + writeOnly: true + additionalProperties: true + description: desc + responses: + '200': + description: pet response + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + x-limit: + $ref: '#/components/headers/X-Rate-Limit-Limit' + non-nullable-x-limit: + $ref: '#/components/headers/Non-Nullable-X-Rate-Limit-Limit' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 'application/unknown': + schema: + type: object + 'application/*': + schema: + type: object + '*/*': + schema: + type: object + '4XX': + description: error response + content: + application/json: + schema: + type: object + required: + - message + properties: + message: + type: string + '404': + description: 404 response + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + message: + type: string + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + description: Creates a new pet in the store. Duplicates are allowed + operationId: addPet + requestBody: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + '201': + $ref: '#/components/responses/normal' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{id}: + get: + description: Returns a user based on a single ID, if the user does not have access to the pet + operationId: find pet by id + parameters: + - name: id + in: path + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + parameters: + - name: id + in: path + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + requestBody: + $ref: '#/components/requestBodies/test_body' + responses: + '200': + $ref: '#/components/responses/normal' + delete: + description: deletes a single pet based on the ID supplied + operationId: deletePet + parameters: + - name: id + in: path + description: ID of pet to delete + required: true + schema: + type: integer + format: int64 + responses: + '204': + description: pet deleted + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{nickname}/adopt/{param_2}: + post: + description: Adopt a pet + operationId: adoptPet + parameters: + - name: nickname + in: path + description: Name of pet to adopt + required: true + schema: + type: string + - name: param_2 + in: path + description: Sample parameter + required: true + schema: + type: string + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /animals/{id}: + parameters: + - name: id + in: path + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + - name: token + in: header + description: token to be passed as a header + required: true + schema: + type: integer + format: int64 + style: simple + get: + parameters: + - name: header_2 + in: header + required: true + schema: + type: string + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' +components: + parameters: + test: + name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + test_ref: + $ref: '#/components/parameters/test' + schemas: + Pet: + allOf: + - $ref: '#/components/schemas/NewPet' + - type: object + required: + - id + properties: + id: + type: integer + format: int64 + + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: string + + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + responses: + normal: + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + requestBodies: + test_body: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + headers: + X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + Non-Nullable-X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + nullable: false diff --git a/spec/data/schema_3_1/reference-broken.yaml b/spec/data/schema_3_1/reference-broken.yaml new file mode 100644 index 0000000..06541bc --- /dev/null +++ b/spec/data/schema_3_1/reference-broken.yaml @@ -0,0 +1,17 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /ref-sample/broken_reference: + post: + description: Broken Reference in YAML + requestBody: + $ref: '#/components/requestBodies/foobar' + responses: + "204": + description: empty +requestBodies: + hoge: + type: object + description: This object is defined, but `foobar` is not diff --git a/spec/data/schema_3_1/reference_in_responses.yaml b/spec/data/schema_3_1/reference_in_responses.yaml new file mode 100644 index 0000000..cf855b0 --- /dev/null +++ b/spec/data/schema_3_1/reference_in_responses.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.2 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /info: + get: + responses: + 200: + $ref: '#/components/responses/Response' + +components: + responses: + Response: + description: reference response + content: + application/json: + schema: + type: object diff --git a/spec/data/schema_3_1/remote-file-ref.yaml b/spec/data/schema_3_1/remote-file-ref.yaml new file mode 100644 index 0000000..be2f28c --- /dev/null +++ b/spec/data/schema_3_1/remote-file-ref.yaml @@ -0,0 +1,22 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /local_file_reference: + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: normal.yml#/components/schemas/all_of_base + responses: + '200': + description: correct + content: + application/json: + schema: + type: object + /reference_to_path_item_with_path_param: + $ref: 'path-item-ref.yaml#/paths/~1sample~1%7Bsample_id%7D' diff --git a/spec/data/schema_3_1/remote-http-ref.yaml b/spec/data/schema_3_1/remote-http-ref.yaml new file mode 100644 index 0000000..61fc37b --- /dev/null +++ b/spec/data/schema_3_1/remote-http-ref.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /http_reference: + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: https://raw.githubusercontent.com/ota42y/openapi_parser/091b20838cf0bfbc7bed2c9968b10bb0b9e8a789/spec/data/normal.yml#/components/schemas/all_of_base + responses: + '200': + description: correct + content: + application/json: + schema: + type: object diff --git a/spec/data/schema_3_1/validate_test.yaml b/spec/data/schema_3_1/validate_test.yaml new file mode 100644 index 0000000..28487fc --- /dev/null +++ b/spec/data/schema_3_1/validate_test.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: OpenAPI3 Test +paths: + /validate_test: + post: + description: override here + requestBody: + content: + 'application/json': + schema: + type: object + properties: + test: + type: string + responses: + '204': + description: empty