33from collections .abc import Iterator
44from collections .abc import Mapping
55from collections .abc import Sequence
6+ from functools import partial
67from typing import TYPE_CHECKING
78from typing import Any
89from typing import cast
1112from jsonschema .exceptions import SchemaError
1213from jsonschema .exceptions import ValidationError
1314from jsonschema .protocols import Validator
15+ from jsonschema .validators import validator_for
1416from jsonschema_path .paths import SchemaPath
1517from openapi_schema_validator import oas30_format_checker
1618from openapi_schema_validator import oas31_format_checker
1719from openapi_schema_validator .validators import OAS30Validator
1820from openapi_schema_validator .validators import OAS31Validator
21+ from openapi_schema_validator .validators import check_openapi_schema
1922
2023from openapi_spec_validator .validation .exceptions import (
2124 DuplicateOperationIDError ,
3437 KeywordValidatorRegistry ,
3538 )
3639
40+ OAS31_BASE_DIALECT_URI = "https://spec.openapis.org/oas/3.1/dialect/base"
41+
3742
3843class KeywordValidator :
3944 def __init__ (self , registry : "KeywordValidatorRegistry" ):
@@ -101,6 +106,26 @@ def _collect_properties(self, schema: SchemaPath) -> set[str]:
101106
102107 return props
103108
109+ def _get_schema_checker (
110+ self , schema : SchemaPath , schema_value : Any
111+ ) -> Callable [[Any ], None ]:
112+ raise NotImplementedError
113+
114+ def _validate_schema_meta (
115+ self , schema : SchemaPath , schema_value : Any
116+ ) -> OpenAPIValidationError | None :
117+ try :
118+ schema_checker = self ._get_schema_checker (schema , schema_value )
119+ except ValueError as exc :
120+ return OpenAPIValidationError (str (exc ))
121+ try :
122+ schema_checker (schema_value )
123+ except (SchemaError , ValidationError ) as err :
124+ return cast (
125+ OpenAPIValidationError , OpenAPIValidationError .create_from (err )
126+ )
127+ return None
128+
104129 def __call__ (
105130 self ,
106131 schema : SchemaPath ,
@@ -114,23 +139,17 @@ def __call__(
114139 )
115140 return
116141
142+ schema_id = id (schema_value )
117143 if not meta_checked :
118144 assert self .meta_checked_schema_ids is not None
119- schema_id = id (schema_value )
120145 if schema_id not in self .meta_checked_schema_ids :
121- try :
122- schema_check = getattr (
123- self .default_validator .value_validator_cls ,
124- "check_schema" ,
125- )
126- schema_check (schema_value )
127- except (SchemaError , ValidationError ) as err :
128- yield OpenAPIValidationError .create_from (err )
129- return
130146 self .meta_checked_schema_ids .append (schema_id )
147+ err = self ._validate_schema_meta (schema , schema_value )
148+ if err is not None :
149+ yield err
150+ return
131151
132152 assert self .visited_schema_ids is not None
133- schema_id = id (schema_value )
134153 if schema_id in self .visited_schema_ids :
135154 return
136155 self .visited_schema_ids .append (schema_id )
@@ -218,6 +237,74 @@ def __call__(
218237 yield from self .default_validator (schema , default_value )
219238
220239
240+ class OpenAPIV2SchemaValidator (SchemaValidator ):
241+ def _get_schema_checker (
242+ self , schema : SchemaPath , schema_value : Any
243+ ) -> Callable [[Any ], None ]:
244+ return cast (
245+ Callable [[Any ], None ],
246+ getattr (
247+ self .default_validator .value_validator_cls ,
248+ "check_schema" ,
249+ ),
250+ )
251+
252+
253+ class OpenAPIV30SchemaValidator (SchemaValidator ):
254+ def _get_schema_checker (
255+ self , schema : SchemaPath , schema_value : Any
256+ ) -> Callable [[Any ], None ]:
257+ return cast (Callable [[Any ], None ], OAS30Validator .check_schema )
258+
259+
260+ class OpenAPIV31SchemaValidator (SchemaValidator ):
261+ default_jsonschema_dialect_id = OAS31_BASE_DIALECT_URI
262+
263+ def _get_schema_checker (
264+ self , schema : SchemaPath , schema_value : Any
265+ ) -> Callable [[Any ], None ]:
266+ if isinstance (schema_value , Mapping ):
267+ schema_to_check = dict (schema_value )
268+ if "$schema" in schema_to_check :
269+ dialect_source = schema_to_check
270+ else :
271+ jsonschema_dialect_id = self ._get_jsonschema_dialect_id (schema )
272+ dialect_source = {"$schema" : jsonschema_dialect_id }
273+ schema_to_check = {
274+ ** schema_to_check ,
275+ "$schema" : jsonschema_dialect_id ,
276+ }
277+ else :
278+ jsonschema_dialect_id = self ._get_jsonschema_dialect_id (schema )
279+ dialect_source = {"$schema" : jsonschema_dialect_id }
280+ schema_to_check = schema_value
281+
282+ validator_cls = validator_for (
283+ dialect_source ,
284+ default = cast (Any , None ),
285+ )
286+ if validator_cls is None :
287+ raise ValueError (
288+ f"Unknown JSON Schema dialect: { dialect_source ['$schema' ]!r} "
289+ )
290+ return partial (
291+ check_openapi_schema ,
292+ validator_cls ,
293+ format_checker = oas31_format_checker ,
294+ )
295+
296+ def _get_jsonschema_dialect_id (self , schema : SchemaPath ) -> str :
297+ schema_root = self ._get_schema_root (schema )
298+ try :
299+ return (schema_root // "jsonSchemaDialect" ).read_str ()
300+ except KeyError :
301+ return self .default_jsonschema_dialect_id
302+
303+ def _get_schema_root (self , schema : SchemaPath ) -> SchemaPath :
304+ # jsonschema-path currently has no public API for root traversal.
305+ return schema ._clone_with_parts (())
306+
307+
221308class SchemasValidator (KeywordValidator ):
222309 @property
223310 def schema_validator (self ) -> SchemaValidator :
0 commit comments