From 0356b51cb75626d1c269f8b3a121d59d1bc2a77f Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 7 Jan 2023 03:20:30 +0000 Subject: [PATCH] nullable implementation based on clarified OAS 3.0.3 definition --- openapi_schema_validator/_validators.py | 43 +++++-------------------- openapi_schema_validator/validators.py | 2 -- tests/integration/test_validators.py | 27 ++++++++++++++++ 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/openapi_schema_validator/_validators.py b/openapi_schema_validator/_validators.py index 6aa4d9e..34f82df 100644 --- a/openapi_schema_validator/_validators.py +++ b/openapi_schema_validator/_validators.py @@ -18,30 +18,6 @@ from jsonschema.protocols import Validator -def include_nullable_validator( - schema: Dict[Hashable, Any] -) -> ItemsView[Hashable, Any]: - """ - Include ``nullable`` validator always. - Suitable for use with `create`'s ``applicable_validators`` argument. - """ - _schema = deepcopy(schema) - - # append defaults to trigger nullable validator, except where $ref in the schema checks null - if "nullable" not in _schema \ - and "$ref" not in _schema \ - and "oneOf" not in _schema \ - and "anyOf" not in _schema \ - and "allOf" not in _schema: - _schema.update( - { - "nullable": False, - } - ) - - return _schema.items() - - def handle_discriminator( validator: Validator, _: Any, instance: Any, schema: Mapping[Hashable, Any] ) -> Iterator[ValidationError]: @@ -131,7 +107,14 @@ def type( schema: Mapping[Hashable, Any], ) -> Iterator[ValidationError]: if instance is None: - return + # nullable implementation based on OAS 3.0.3l + # * nullable is only meaningful if its value is true + # * nullable: true is only meaningful in combination with a type + # assertion specified in the same Schema Object. + # * nullable: true operates within a single Schema Object + if "nullable" in schema and schema["nullable"] == True: + return + yield ValidationError("None for not nullable") if not validator.is_type(instance, data_type): data_repr = repr(data_type) @@ -167,16 +150,6 @@ def items( yield from validator.descend(item, items, path=index) -def nullable( - validator: Validator, - is_nullable: bool, - instance: Any, - schema: Mapping[Hashable, Any], -) -> Iterator[ValidationError]: - if instance is None and not is_nullable: - yield ValidationError("None for not nullable") - - def required( validator: Validator, required: List[str], diff --git a/openapi_schema_validator/validators.py b/openapi_schema_validator/validators.py index c8d68b4..f4b12ea 100644 --- a/openapi_schema_validator/validators.py +++ b/openapi_schema_validator/validators.py @@ -45,7 +45,6 @@ # TODO: adjust default "$ref": _validators.ref, # fixed OAS fields - "nullable": oas_validators.nullable, "discriminator": oas_validators.not_implemented, "readOnly": oas_validators.readOnly, "writeOnly": oas_validators.writeOnly, @@ -59,7 +58,6 @@ # See https://github.com/p1c2u/openapi-schema-validator/pull/12 # version="oas30", id_of=lambda schema: schema.get("id", ""), - applicable_validators=oas_validators.include_nullable_validator, ) OAS31Validator = extend( diff --git a/tests/integration/test_validators.py b/tests/integration/test_validators.py index 8817829..1bbc9f6 100644 --- a/tests/integration/test_validators.py +++ b/tests/integration/test_validators.py @@ -31,6 +31,16 @@ def test_null(self, schema_type): with pytest.raises(ValidationError): validator.validate(value) + @pytest.mark.parametrize("is_nullable", [True, False]) + def test_nullable_untyped(self, is_nullable): + schema = {"nullable": is_nullable} + validator = OAS30Validator(schema) + value = None + + result = validator.validate(value) + + assert result is None + @pytest.mark.parametrize( "schema_type", [ @@ -50,6 +60,23 @@ def test_nullable(self, schema_type): assert result is None + def test_nullable_enum_without_none(self): + schema = {"type": "integer", "nullable": True, "enum": [1, 2, 3]} + validator = OAS30Validator(schema) + value = None + + with pytest.raises(ValidationError): + validator.validate(value) + + def test_nullable_enum_with_none(self): + schema = {"type": "integer", "nullable": True, "enum": [1, 2, 3, None]} + validator = OAS30Validator(schema) + value = None + + result = validator.validate(value) + + assert result is None + @pytest.mark.parametrize( "value", [