Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perform a strict check on object properties #33

Merged
11 changes: 11 additions & 0 deletions lib/openapi_parser/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ def message
end
end

class NotExistPropertyDefinition < OpenAPIError
def initialize(keys, reference)
super(reference)
@keys = keys
end

def message
"properties #{@keys.join(",")} are not defined in #{@reference}"
end
end

class NotExistDiscriminatorMappingTarget < OpenAPIError
def initialize(key, reference)
super(reference)
Expand Down
10 changes: 7 additions & 3 deletions lib/openapi_parser/schema_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class OpenAPIParser::SchemaValidator
# @param [Object] value
# @param [OpenAPIParser::Schemas::Schema] schema
module Validatable
def validate_schema(value, schema)
def validate_schema(value, schema, **keyword_args)
raise 'implement'
end

Expand Down Expand Up @@ -66,11 +66,15 @@ def validate_data
# validate value eby schema
# @param [Object] value
# @param [OpenAPIParser::Schemas::Schema] schema
def validate_schema(value, schema)
def validate_schema(value, schema, **keyword_args)
return [value, nil] unless schema

if (v = validator(value, schema))
return v.coerce_and_validate(value, schema)
if keyword_args.empty?
return v.coerce_and_validate(value, schema)
else
return v.coerce_and_validate(value, schema, **keyword_args)
end
end

# unknown return error
Expand Down
21 changes: 20 additions & 1 deletion lib/openapi_parser/schema_validators/all_of_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,29 @@ class AllOfValidator < Base
# @param [OpenAPIParser::Schemas::Schema] schema
def coerce_and_validate(value, schema)
# 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|
_coerced, err = validatable.validate_schema(value, 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)

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
ota42y marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_parser/schema_validators/any_of_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class OpenAPIParser::SchemaValidator
class AnyOfValidator < Base
# @param [Object] value
# @param [OpenAPIParser::Schemas::Schema] schema
def coerce_and_validate(value, schema)
def coerce_and_validate(value, schema, **_keyword_args)
if schema.discriminator
return validate_discriminator_schema(schema.discriminator, value)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_parser/schema_validators/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(validatable, coerce_value)
# need override
# @param [Array] _value
# @param [OpenAPIParser::Schemas::Schema] _schema
def coerce_and_validate(_value, _schema)
def coerce_and_validate(_value, _schema, **_keyword_args)
raise 'need implement'
end

Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_parser/schema_validators/nil_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class OpenAPIParser::SchemaValidator
class NilValidator < Base
# @param [Object] value
# @param [OpenAPIParser::Schemas::Schema] schema
def coerce_and_validate(value, schema)
def coerce_and_validate(value, schema, **_keyword_args)
return [value, nil] if schema.nullable

[nil, OpenAPIParser::NotNullError.new(schema.object_reference)]
Expand Down
19 changes: 17 additions & 2 deletions lib/openapi_parser/schema_validators/object_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,37 @@ class OpenAPIParser::SchemaValidator
class ObjectValidator < Base
# @param [Hash] value
# @param [OpenAPIParser::Schemas::Schema] schema
def coerce_and_validate(value, schema)
# @param [Boolean] parent_all_of true if component is nested under allOf
def coerce_and_validate(value, schema, parent_all_of: false)
return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(Hash)

return [value, nil] unless schema.properties

required_set = schema.required ? schema.required.to_set : Set.new
remaining_keys = value.keys

coerced_values = value.map do |name, v|
s = schema.properties[name]
coerced, err = validatable.validate_schema(v, s)
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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

# additionalProperties are defined
[v, nil]
end

return [nil, err] if err

required_set.delete(name)
[name, coerced]
end

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
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_parser/schema_validators/one_of_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class OpenAPIParser::SchemaValidator
class OneOfValidator < Base
# @param [Object] value
# @param [OpenAPIParser::Schemas::Schema] schema
def coerce_and_validate(value, schema)
def coerce_and_validate(value, schema, **_keyword_args)
if schema.discriminator
return validate_discriminator_schema(schema.discriminator, value)
end
Expand Down
2 changes: 2 additions & 0 deletions spec/data/petstore-expanded.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ paths:
properties:
id:
type: integer
message:
type: string
default:
description: unexpected error
content:
Expand Down
58 changes: 58 additions & 0 deletions spec/data/petstore-with-discriminator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ components:
anyOf:
- "$ref": "#/components/schemas/SquirrelBasket"
- "$ref": "#/components/schemas/CatBasket"
- "$ref": "#/components/schemas/TurtleBasket"
discriminator:
propertyName: name
mapping:
cats: "#/components/schemas/CatBasket"
squirrels: "#/components/schemas/SquirrelBasket"
turtles: "#/components/schemas/TurtleBasket"
SquirrelBasket:
type: object
required:
Expand Down Expand Up @@ -77,6 +79,7 @@ components:
nut_stock:
nullable: true
type: integer
additionalProperties: true
CatBasket:
type: object
required:
Expand Down Expand Up @@ -106,4 +109,59 @@ components:
milk_stock:
nullable: true
type: integer
TurtleBasket:
type: object
required:
- name
properties:
name:
type: string
content:
type: array
items:
"$ref": "#/components/schemas/NinjaTurtle"
NinjaTurtle:
type: object
required:
- name
properties:
name:
type: string
born_at:
format: date-time
nullable: true
type: string
description:
nullable: true
type: string
required_combat_style:
allOf:
- anyOf:
- "$ref": "#/components/schemas/DonatelloStyle"
- "$ref": "#/components/schemas/MichelangeloStyle"
- nullable: false
type: object
optional_combat_style:
anyOf:
- "$ref": "#/components/schemas/DonatelloStyle"
- "$ref": "#/components/schemas/MichelangeloStyle"
DonatelloStyle:
type: object
nullable: true
properties:
bo_color:
type: string
shuriken_count:
type: integer
MichelangeloStyle:
type: object
nullable: true
properties:
nunchaku_color:
type: string
grappling_hook_length:
type: number
format: double



Loading