99from jsonschema .exceptions import SchemaError
1010from jsonschema .exceptions import ValidationError
1111from jsonschema .protocols import Validator
12+ from jsonschema .validators import Draft4Validator
13+ from jsonschema .validators import Draft7Validator
1214from jsonschema .validators import Draft202012Validator
1315from jsonschema .validators import create
1416from jsonschema .validators import extend
2830from openapi_schema_validator ._types import oas31_type_checker
2931
3032_CHECK_SCHEMA_UNSET = object ()
33+ _DRAFT4_META_SCHEMA = OPENAPI_SPECIFICATIONS .contents (
34+ "http://json-schema.org/draft-04/schema#" ,
35+ )
36+ _OAS30_SCHEMA_CHECKER = extend (
37+ Draft4Validator , {"if" : Draft7Validator .VALIDATORS ["if" ]}
38+ )
39+ # Keep the metaschema private and anonymous so its recursive references use
40+ # this conditional without switching back to the registered Draft 4 validator.
41+ _OAS30_CHECK_SCHEMA = {
42+ "definitions" : _DRAFT4_META_SCHEMA ["definitions" ],
43+ "type" : _DRAFT4_META_SCHEMA ["type" ],
44+ "if" : {"required" : ["$ref" ]},
45+ "then" : {"properties" : {"$ref" : {"type" : "string" }}},
46+ "else" : {
47+ "properties" : _DRAFT4_META_SCHEMA ["properties" ],
48+ "dependencies" : _DRAFT4_META_SCHEMA ["dependencies" ],
49+ },
50+ }
3151
3252
3353def check_openapi_schema (
@@ -46,12 +66,41 @@ def check_openapi_schema(
4666 registry = OPENAPI_SPECIFICATIONS ,
4767 )
4868
49- for error in validator_for_metaschema .iter_errors (schema ):
69+ _raise_first_schema_error (validator_for_metaschema , schema )
70+
71+
72+ def _check_oas30_schema (
73+ cls : Any ,
74+ schema : Any ,
75+ format_checker : Any = _CHECK_SCHEMA_UNSET ,
76+ ) -> None :
77+ if format_checker is _CHECK_SCHEMA_UNSET :
78+ format_checker = cls .FORMAT_CHECKER
79+
80+ validator = _OAS30_SCHEMA_CHECKER (
81+ _OAS30_CHECK_SCHEMA ,
82+ format_checker = format_checker ,
83+ registry = OPENAPI_SPECIFICATIONS ,
84+ )
85+ _raise_first_schema_error (validator , schema )
86+
87+
88+ def _check_oas30_variant_schema (
89+ cls : Any ,
90+ schema : Any ,
91+ format_checker : Any = Draft4Validator .FORMAT_CHECKER ,
92+ ) -> None :
93+ # These variants previously inherited jsonschema's Draft 4 format checks.
94+ _check_oas30_schema (cls , schema , format_checker )
95+
96+
97+ def _raise_first_schema_error (validator : Validator , schema : Any ) -> None :
98+ for error in validator .iter_errors (schema ):
5099 raise SchemaError .create_from (error )
51100
52101
53102def _oas30_id_of (schema : Any ) -> str :
54- if isinstance (schema , dict ):
103+ if isinstance (schema , dict ) and "$ref" not in schema :
55104 return schema .get ("id" , "" ) # type: ignore[no-any-return]
56105 return ""
57106
@@ -101,9 +150,7 @@ def _oas30_id_of(schema: Any) -> str:
101150
102151def _build_oas30_validator () -> Any :
103152 return create (
104- meta_schema = OPENAPI_SPECIFICATIONS .contents (
105- "http://json-schema.org/draft-04/schema#" ,
106- ),
153+ meta_schema = _DRAFT4_META_SCHEMA ,
107154 validators = OAS30_VALIDATORS ,
108155 type_checker = oas_types .oas30_type_checker ,
109156 format_checker = oas_format .oas30_format_checker ,
@@ -181,15 +228,40 @@ def _build_oas32_validator() -> Any:
181228 },
182229)
183230
231+ _jsonschema_evolve = OAS30Validator .evolve
232+
233+
234+ def _evolve_oas30 (validator : Any , ** changes : Any ) -> Any :
235+ schema = changes .get ("schema" , validator .schema )
236+ # jsonschema selects a validator from $schema before filtering keywords.
237+ if (
238+ isinstance (schema , Mapping )
239+ and "$ref" in schema
240+ and "$schema" in schema
241+ ):
242+ schema = dict (schema )
243+ del schema ["$schema" ]
244+ changes ["schema" ] = schema
245+ return _jsonschema_evolve (validator , ** changes )
246+
247+
248+ for _oas30_validator in (
249+ OAS30Validator ,
250+ OAS30StrictValidator ,
251+ OAS30ReadValidator ,
252+ OAS30WriteValidator ,
253+ ):
254+ _oas30_validator .evolve = _evolve_oas30
255+
184256OAS31Validator = _build_oas31_validator ()
185257OAS32Validator = _build_oas32_validator ()
186258
187- # These validator classes are generated via jsonschema create/extend, so there
188- # is no simpler hook to inject registry-aware schema checking while preserving
189- # each class's FORMAT_CHECKER. Override check_schema on each class to keep
190- # OpenAPI metaschema resolution local and to apply optional ecma-regex
191- # behavior consistently across OAS 3.0/3.1/3.2.
192- OAS30Validator .check_schema = classmethod (check_openapi_schema )
259+ # Override the generated methods to use the dialect-specific schema checks
260+ # and keep metaschema resolution local.
261+ OAS30Validator . check_schema = classmethod ( _check_oas30_schema )
262+ OAS30StrictValidator . check_schema = classmethod ( _check_oas30_variant_schema )
263+ OAS30ReadValidator . check_schema = classmethod ( _check_oas30_variant_schema )
264+ OAS30WriteValidator .check_schema = classmethod (_check_oas30_variant_schema )
193265OAS31Validator .check_schema = classmethod (check_openapi_schema )
194266OAS32Validator .check_schema = classmethod (check_openapi_schema )
195267
@@ -235,4 +307,6 @@ def enforce_properties(
235307 extended_validator .check_schema = classmethod (
236308 validator_class .check_schema .__func__
237309 )
310+ if validator_class .evolve is _evolve_oas30 :
311+ extended_validator .evolve = _evolve_oas30
238312 return cast (type [Validator ], extended_validator )
0 commit comments