From be45f04e515df7da9eb0d9694bf3b1db3a7fbfdd Mon Sep 17 00:00:00 2001 From: David Harsha Date: Sun, 24 Sep 2023 14:07:49 -0700 Subject: [PATCH] Require discriminator `propertyName` property https://spec.openapis.org/oas/v3.1.0#discriminator-object > The expectation now is that a property with name petType MUST be present in the response payload It's a little unclear because the specification examples mostly specify the `propertyName` property under `required` as well, which seems redundant. It's probably safer this way, though, and the spec says "MUST" so we must. --- CHANGELOG.md | 4 ++++ lib/json_schemer/openapi31/vocab/base.rb | 3 ++- test/open_api_test.rb | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9d2b37c..d66d6f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [2.1.0] - XXXX-XX-XX +### Bug Fixes + +- Require discriminator `propertyName` property + [2.1.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.0 ## [2.0.0] - 2023-08-20 diff --git a/lib/json_schemer/openapi31/vocab/base.rb b/lib/json_schemer/openapi31/vocab/base.rb index 937fbcfc..dae66117 100644 --- a/lib/json_schemer/openapi31/vocab/base.rb +++ b/lib/json_schemer/openapi31/vocab/base.rb @@ -46,7 +46,8 @@ def validate(instance, instance_location, keyword_location, context) property_name = value.fetch('propertyName') mapping = value['mapping'] || {} - return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) && instance.key?(property_name) + return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) + return result(instance, instance_location, keyword_location, false) unless instance.key?(property_name) property = instance.fetch(property_name) ref = mapping.fetch(property, property) diff --git a/test/open_api_test.rb b/test/open_api_test.rb index 14423441..c24f2327 100644 --- a/test/open_api_test.rb +++ b/test/open_api_test.rb @@ -205,7 +205,7 @@ def test_discriminator_specification_example assert_equal([['enum', '/components/schemas/Cat/allOf/1/properties/huntingSkill']], schemer.validate(invalid_hunting_skill).map { |error| error.values_at('type', 'schema_pointer') }) assert_equal([['required', '/components/schemas/Dog/allOf/1']], schemer.validate(missing_pack_size).map { |error| error.values_at('type', 'schema_pointer') }) assert_equal([['format', '/components/schemas/Dog/allOf/1/properties/packSize']], schemer.validate(invalid_pack_size).map { |error| error.values_at('type', 'schema_pointer') }) - assert_equal([['required', '/components/schemas/Pet']], schemer.validate(missing_pet_type).map { |error| error.values_at('type', 'schema_pointer') }) + assert_equal([['required', '/components/schemas/Pet'], ['discriminator', '/components/schemas/Pet']], schemer.validate(missing_pet_type).map { |error| error.values_at('type', 'schema_pointer') }) assert_equal([['required', '/components/schemas/Pet'], ['required', '/components/schemas/Cat/allOf/1']], schemer.validate(missing_name).map { |error| error.values_at('type', 'schema_pointer') }) assert_raises(JSONSchemer::UnknownRef) { schemer.validate(invalid_pet_type) } end @@ -404,7 +404,7 @@ def test_all_of_discriminator_subclass_schemas_work_on_their_own assert(schemer.valid?(CAT)) assert(schemer.valid?(MISTY)) assert_equal(['/components/schemas/Cat/allOf/1/properties/name'], schemer.validate(INVALID_CAT).map { |error| error.fetch('schema_pointer') }) - assert_equal([['required', '/components/schemas/Pet']], schemer.validate({}).map { |error| error.values_at('type', 'schema_pointer') }) + assert_equal([['required', '/components/schemas/Pet'], ['discriminator', '/components/schemas/Pet']], schemer.validate({}).map { |error| error.values_at('type', 'schema_pointer') }) end def test_all_of_discriminator_with_non_discriminator_ref @@ -444,7 +444,7 @@ def test_all_of_discriminator_with_non_discriminator_ref refute(schemer.valid?(CAT)) assert(schemer.valid?(CAT.merge('other' => 'y'))) assert_equal(['/components/schemas/Other', '/components/schemas/Cat/allOf/2/properties/name'], schemer.validate(INVALID_CAT).map { |error| error.fetch('schema_pointer') }) - assert_equal([['required', '/components/schemas/Pet'], ['required', '/components/schemas/Other']], schemer.validate({}).map { |error| error.values_at('type', 'schema_pointer') }) + assert_equal([['required', '/components/schemas/Pet'], ['discriminator', '/components/schemas/Pet'], ['required', '/components/schemas/Other']], schemer.validate({}).map { |error| error.values_at('type', 'schema_pointer') }) end def test_any_of_discriminator_without_matching_schema @@ -597,7 +597,7 @@ def test_non_json_pointer_discriminator def test_discriminator_non_object_and_missing_property_name schemer = JSONSchemer.schema({ 'discriminator' => { 'propertyName' => 'x' } }, :meta_schema => JSONSchemer.openapi31) assert(schemer.valid?(1)) - assert(schemer.valid?({ 'y' => 'z' })) + refute(schemer.valid?({ 'y' => 'z' })) end def test_openapi31_formats