diff --git a/AGENTS.md b/AGENTS.md index 7e3f83d..1558910 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ Use this as the default operating playbook when making changes. ## Source Layout - `openapi_schema_validator/__init__.py`: public exports + package metadata. -- `openapi_schema_validator/validators.py`: validator class setup for OAS 3.0/3.1. +- `openapi_schema_validator/validators.py`: validator class setup for OAS 3.0/3.1/3.2. - `openapi_schema_validator/_keywords.py`: custom keyword handlers and ValidationError generation. - `openapi_schema_validator/_format.py`: format checker functions and registrations. - `openapi_schema_validator/_types.py`: custom type checker setup. @@ -128,7 +128,7 @@ Run commands from repository root. ### Behavioral constraints to preserve - Do not mutate incoming schema objects in helper APIs. -- Maintain compatibility for both OAS 3.0 and OAS 3.1 validators. +- Maintain compatibility for OAS 3.0, OAS 3.1, and OAS 3.2 validators. - Keep existing read/write behavior around `readOnly` and `writeOnly`. - Keep format and type checker semantics aligned with current tests. diff --git a/README.rst b/README.rst index bf4e5d9..71054b7 100644 --- a/README.rst +++ b/README.rst @@ -22,6 +22,7 @@ Openapi-schema-validator is a Python library that validates schema against: * `OpenAPI Schema Specification v3.0 `__ which is an extended subset of the `JSON Schema Specification Wright Draft 00 `__. * `OpenAPI Schema Specification v3.1 `__ which is an extended superset of the `JSON Schema Specification Draft 2020-12 `__. +* `OpenAPI Schema Specification v3.2 `__ which uses the same JSON Schema dialect as v3.1. Documentation diff --git a/docs/format.rst b/docs/format.rst index 3a79a17..44899a9 100644 --- a/docs/format.rst +++ b/docs/format.rst @@ -12,3 +12,5 @@ You can check format for predefined OAS primitive types Traceback (most recent call last): ... ValidationError: '-12' is not a 'date' + +For OpenAPI 3.2, use ``oas32_format_checker`` (behaves identically to ``oas31_format_checker``, since 3.2 uses the same JSON Schema dialect). diff --git a/docs/index.rst b/docs/index.rst index f89daba..35bb311 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Openapi-schema-validator is a Python library that validates schema against: * `OpenAPI Schema Specification v3.0 `__ which is an extended subset of the `JSON Schema Specification Wright Draft 00 `__. * `OpenAPI Schema Specification v3.1 `__ which is an extended superset of the `JSON Schema Specification Draft 2020-12 `__. +* `OpenAPI Schema Specification v3.2 `__ which uses the same JSON Schema dialect as v3.1. Installation ------------ diff --git a/docs/validation.rst b/docs/validation.rst index eb58d48..beddc18 100644 --- a/docs/validation.rst +++ b/docs/validation.rst @@ -67,6 +67,8 @@ if you want to disambiguate the expected schema version, import and use ``OAS31V validate({"name": "John", "age": 23}, schema, cls=OAS31Validator) +For OpenAPI 3.2, use ``OAS32Validator`` (behaves identically to ``OAS31Validator``, since 3.2 uses the same JSON Schema dialect). + In order to validate OpenAPI 3.0 schema, import and use ``OAS30Validator`` instead of ``OAS31Validator``. .. code-block:: python diff --git a/openapi_schema_validator/__init__.py b/openapi_schema_validator/__init__.py index 8864295..3d5a07d 100644 --- a/openapi_schema_validator/__init__.py +++ b/openapi_schema_validator/__init__.py @@ -1,12 +1,14 @@ from openapi_schema_validator._format import oas30_format_checker from openapi_schema_validator._format import oas30_strict_format_checker from openapi_schema_validator._format import oas31_format_checker +from openapi_schema_validator._format import oas32_format_checker from openapi_schema_validator.shortcuts import validate from openapi_schema_validator.validators import OAS30ReadValidator from openapi_schema_validator.validators import OAS30StrictValidator from openapi_schema_validator.validators import OAS30Validator from openapi_schema_validator.validators import OAS30WriteValidator from openapi_schema_validator.validators import OAS31Validator +from openapi_schema_validator.validators import OAS32Validator __author__ = "Artur Maciag" __email__ = "maciag.artur@gmail.com" @@ -24,4 +26,6 @@ "oas30_strict_format_checker", "OAS31Validator", "oas31_format_checker", + "OAS32Validator", + "oas32_format_checker", ] diff --git a/openapi_schema_validator/_format.py b/openapi_schema_validator/_format.py index 0b87dab..6653b81 100644 --- a/openapi_schema_validator/_format.py +++ b/openapi_schema_validator/_format.py @@ -105,3 +105,11 @@ def is_password(instance: object) -> bool: oas31_format_checker.checks("float")(is_float) oas31_format_checker.checks("double")(is_double) oas31_format_checker.checks("password")(is_password) + +# OAS 3.2 uses the same format checks as OAS 3.1 +oas32_format_checker = FormatChecker() +oas32_format_checker.checks("int32")(is_int32) +oas32_format_checker.checks("int64")(is_int64) +oas32_format_checker.checks("float")(is_float) +oas32_format_checker.checks("double")(is_double) +oas32_format_checker.checks("password")(is_password) diff --git a/openapi_schema_validator/validators.py b/openapi_schema_validator/validators.py index 2fae85d..a46f375 100644 --- a/openapi_schema_validator/validators.py +++ b/openapi_schema_validator/validators.py @@ -121,3 +121,13 @@ def _oas30_id_of(schema: Any) -> str: type_checker=oas31_type_checker, format_checker=oas_format.oas31_format_checker, ) + +# OAS 3.2 uses JSON Schema Draft 2020-12 as its base dialect, same as +# OAS 3.1. The OAS-specific vocabulary differs slightly (e.g. xml keyword +# changes), but since xml is not_implemented in the current validators, +# the behavior is equivalent. +OAS32Validator = extend( + OAS31Validator, + {}, + format_checker=oas_format.oas32_format_checker, +) diff --git a/tests/integration/test_validators.py b/tests/integration/test_validators.py index 0b4a372..90c6fa4 100644 --- a/tests/integration/test_validators.py +++ b/tests/integration/test_validators.py @@ -19,9 +19,11 @@ from openapi_schema_validator import OAS30Validator from openapi_schema_validator import OAS30WriteValidator from openapi_schema_validator import OAS31Validator +from openapi_schema_validator import OAS32Validator from openapi_schema_validator import oas30_format_checker from openapi_schema_validator import oas30_strict_format_checker from openapi_schema_validator import oas31_format_checker +from openapi_schema_validator import oas32_format_checker class TestOAS30ValidatorFormatChecker: @@ -1003,6 +1005,66 @@ def test_array_prefixitems_invalid(self, validator_class, value): assert any(error in str(excinfo.value) for error in errors) +class TestOAS32ValidatorValidate(TestOAS31ValidatorValidate): + """OAS 3.2 uses the same JSON Schema dialect as 3.1.""" + + @pytest.fixture + def validator_class(self): + return OAS32Validator + + @pytest.fixture + def format_checker(self): + return oas32_format_checker + + def test_validator_is_distinct_from_oas31(self): + assert OAS32Validator is not OAS31Validator + + def test_format_checker_is_distinct_from_oas31(self): + assert oas32_format_checker is not oas31_format_checker + + def test_validator_shares_oas31_behavior(self): + assert OAS32Validator.VALIDATORS == OAS31Validator.VALIDATORS + + def test_format_validation_int32(self, validator_class): + schema = {"type": "integer", "format": "int32"} + validator = validator_class( + schema, format_checker=oas32_format_checker + ) + + result = validator.validate(42) + assert result is None + + with pytest.raises(ValidationError): + validator.validate(9999999999) + + def test_format_validation_date(self, validator_class): + schema = {"type": "string", "format": "date"} + validator = validator_class( + schema, format_checker=oas32_format_checker + ) + + result = validator.validate("2024-01-15") + assert result is None + + with pytest.raises(ValidationError): + validator.validate("not-a-date") + + def test_schema_with_allof(self, validator_class): + schema = { + "allOf": [ + {"type": "object", "properties": {"id": {"type": "integer"}}}, + {"type": "object", "properties": {"name": {"type": "string"}}}, + ] + } + validator = validator_class(schema) + + result = validator.validate({"id": 1, "name": "test"}) + assert result is None + + with pytest.raises(ValidationError): + validator.validate({"id": "not-an-integer"}) + + class TestOAS30StrictValidator: """ Tests for OAS30StrictValidator which follows OAS spec strictly: