From 9c9213ad91cf7518cbca8bcd57d762558f160ddd Mon Sep 17 00:00:00 2001 From: avisat Date: Tue, 24 May 2022 15:14:46 +0200 Subject: [PATCH] feat: handle allof when required linked to parent object (#97) --- openapi_spec_validator/validators.py | 16 ++++++++-- tests/integration/test_validators.py | 46 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/openapi_spec_validator/validators.py b/openapi_spec_validator/validators.py index 58ffc7f..862ccec 100644 --- a/openapi_spec_validator/validators.py +++ b/openapi_spec_validator/validators.py @@ -120,23 +120,33 @@ class SchemaValidator(object): def __init__(self, dereferencer): self.dereferencer = dereferencer + def _nested_properties(self, schema): + schema_deref = self.dereferencer.dereference(schema) + return schema_deref.get("properties", {}).keys() + @wraps_errors def iter_errors(self, schema, require_properties=True): schema_deref = self.dereferencer.dereference(schema) if not isinstance(schema_deref, dict): return + nested_properties = [] if 'allOf' in schema_deref: for inner_schema in schema_deref['allOf']: for err in self.iter_errors( - inner_schema, - require_properties=False + inner_schema, + require_properties=False ): yield err + nested_properties = nested_properties + list(self._nested_properties(inner_schema)) required = schema_deref.get('required', []) properties = schema_deref.get('properties', {}).keys() - extra_properties = list(set(required) - set(properties)) + if 'allOf' in schema_deref: + extra_properties = list(set(required) - set(properties) - set(nested_properties)) + else: + extra_properties = list(set(required) - set(properties)) + if extra_properties and require_properties: yield ExtraParametersError( "Required list has not defined properties: {0}".format( diff --git a/tests/integration/test_validators.py b/tests/integration/test_validators.py index 46e9716..03d3ef2 100644 --- a/tests/integration/test_validators.py +++ b/tests/integration/test_validators.py @@ -162,6 +162,52 @@ def test_allow_allof_required_no_properties(self, validator_v30): errors_list = list(errors) assert errors_list == [] + def test_allow_allof_when_required_is_linked_to_the_parent_object(self, validator_v30): + spec = { + 'openapi': '3.0.1', + 'info': { + 'title': 'Test Api', + 'version': '0.0.1', + }, + 'paths': {}, + 'components': { + 'schemas': { + 'Address': { + 'type': 'object', + 'properties': { + 'SubdivisionCode': { + 'type': 'string', + 'description': 'State or region' + }, + 'Town': { + 'type': 'string', + 'description': 'Town or city' + }, + 'CountryCode': { + 'type': 'string', + } + } + }, + 'AddressCreation': { + 'required': [ + 'CountryCode', + 'Town' + ], + 'type': 'object', + 'allOf': [ + { + '$ref': '#/components/schemas/Address' + } + ] + } + } + } + } + + errors = validator_v30.iter_errors(spec) + errors_list = list(errors) + assert errors_list == [] + def test_extra_parameters_in_required(self, validator_v30): spec = { 'openapi': '3.0.0',