From 173982855949bea4cb1f04b5bda46bbcf756f630 Mon Sep 17 00:00:00 2001 From: Artur Maciag Date: Mon, 6 Nov 2017 16:50:00 +0000 Subject: [PATCH] AllOf support --- openapi_core/schemas.py | 30 ++++++++++++---- tests/integration/data/v3.0/petstore.yaml | 21 ++++++++--- tests/integration/test_petstore.py | 44 ++++++++++++++++++++--- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/openapi_core/schemas.py b/openapi_core/schemas.py index 47f1c05..d51640a 100644 --- a/openapi_core/schemas.py +++ b/openapi_core/schemas.py @@ -28,22 +28,32 @@ class Schema(object): def __init__( self, schema_type, model=None, properties=None, items=None, - spec_format=None, required=False, default=None, nullable=False, - enum=None, deprecated=False): + spec_format=None, required=None, default=None, nullable=False, + enum=None, deprecated=False, all_of=None): self.type = schema_type self.model = model self.properties = properties and dict(properties) or {} self.items = items self.format = spec_format - self.required = required + self.required = required or [] self.default = default self.nullable = nullable self.enum = enum self.deprecated = deprecated + self.all_of = all_of and list(all_of) or [] def __getitem__(self, name): return self.properties[name] + def get_all_properties(self): + properties = self.properties.copy() + + for subschema in self.all_of: + subschema_props = subschema.get_all_properties() + properties.update(subschema_props) + + return properties + def get_cast_mapping(self): mapping = DEFAULT_CAST_CALLABLE_GETTER.copy() mapping.update({ @@ -101,17 +111,18 @@ class Schema(object): if isinstance(value, (str, bytes)): value = loads(value) - properties_keys = self.properties.keys() + all_properties = self.get_all_properties() + all_properties_keys = all_properties.keys() value_keys = value.keys() - extra_props = set(value_keys) - set(properties_keys) + extra_props = set(value_keys) - set(all_properties_keys) if extra_props: raise UndefinedSchemaProperty( "Undefined properties in schema: {0}".format(extra_props)) properties = {} - for prop_name, prop in iteritems(self.properties): + for prop_name, prop in iteritems(all_properties): try: prop_value = value[prop_name] except KeyError: @@ -156,11 +167,16 @@ class SchemaFactory(object): nullable = schema_deref.get('nullable', False) enum = schema_deref.get('enum', None) deprecated = schema_deref.get('deprecated', False) + all_of_spec = schema_deref.get('allOf', None) properties = None if properties_spec: properties = self.properties_generator.generate(properties_spec) + all_of = [] + if all_of_spec: + all_of = map(self.create, all_of_spec) + items = None if items_spec: items = self._create_items(items_spec) @@ -168,7 +184,7 @@ class SchemaFactory(object): return Schema( schema_type, model=model, properties=properties, items=items, required=required, default=default, nullable=nullable, enum=enum, - deprecated=deprecated, + deprecated=deprecated, all_of=all_of, ) @property diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml index b2e6ff4..abc7d5b 100644 --- a/tests/integration/data/v3.0/petstore.yaml +++ b/tests/integration/data/v3.0/petstore.yaml @@ -60,7 +60,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Pets" + $ref: "#/components/schemas/PetsData" post: summary: Create a pet operationId: createPets @@ -101,7 +101,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Pets" + $ref: "#/components/schemas/PetData" default: $ref: "#/components/responses/ErrorResponse" components: @@ -154,12 +154,23 @@ components: position: $ref: "#/components/schemas/Position" Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + PetsData: type: object + required: + - data properties: data: - type: array - items: - $ref: "#/components/schemas/Pet" + $ref: "#/components/schemas/Pets" + PetData: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Pet" Error: type: object required: diff --git a/tests/integration/test_petstore.py b/tests/integration/test_petstore.py index 98d8e46..4da3ba1 100644 --- a/tests/integration/test_petstore.py +++ b/tests/integration/test_petstore.py @@ -16,7 +16,8 @@ from openapi_core.responses import Response from openapi_core.schemas import Schema from openapi_core.servers import Server, ServerVariable from openapi_core.shortcuts import create_spec -from openapi_core.wrappers import MockRequest +from openapi_core.validators import RequestValidator, ResponseValidator +from openapi_core.wrappers import MockRequest, MockResponse class TestPetstore(object): @@ -29,6 +30,14 @@ class TestPetstore(object): def spec(self, spec_dict): return create_spec(spec_dict) + @pytest.fixture + def request_validator(self, spec): + return RequestValidator(spec) + + @pytest.fixture + def response_validator(self, spec): + return ResponseValidator(spec) + def test_spec(self, spec, spec_dict): url = 'http://petstore.swagger.io/v1' assert spec.info.title == spec_dict['info']['title'] @@ -99,7 +108,7 @@ class TestPetstore(object): assert type(media_type.schema) == Schema assert media_type.schema.type == schema_spec['type'] assert media_type.schema.required == schema_spec.get( - 'required', False) + 'required', []) for parameter_name, parameter in iteritems( response.headers): @@ -121,7 +130,7 @@ class TestPetstore(object): assert type(parameter.schema) == Schema assert parameter.schema.type == schema_spec['type'] assert parameter.schema.required == schema_spec.get( - 'required', False) + 'required', []) request_body_spec = operation_spec.get('requestBody') @@ -161,7 +170,7 @@ class TestPetstore(object): for schema_name, schema in iteritems(spec.components.schemas): assert type(schema) == Schema - def test_get_pets(self, spec): + def test_get_pets(self, spec, response_validator): host_url = 'http://petstore.swagger.io/v1' path_pattern = '/v1/pets' query_params = { @@ -187,6 +196,17 @@ class TestPetstore(object): } assert body is None + data_json = { + 'data': [], + } + data = json.dumps(data_json) + response = MockResponse(data) + + response_result = response_validator.validate(request, response) + + assert response_result.errors == [] + assert response_result.data == data_json + def test_get_pets_wrong_parameter_type(self, spec): host_url = 'http://petstore.swagger.io/v1' path_pattern = '/v1/pets' @@ -439,7 +459,7 @@ class TestPetstore(object): with pytest.raises(InvalidServer): request.get_body(spec) - def test_get_pet(self, spec): + def test_get_pet(self, spec, response_validator): host_url = 'http://petstore.swagger.io/v1' path_pattern = '/v1/pets/{petId}' view_args = { @@ -461,3 +481,17 @@ class TestPetstore(object): body = request.get_body(spec) assert body is None + + data_json = { + 'data': { + 'id': 1, + 'name': 'test', + }, + } + data = json.dumps(data_json) + response = MockResponse(data) + + response_result = response_validator.validate(request, response) + + assert response_result.errors == [] + assert response_result.data == data_json