Skip to content

Commit a141235

Browse files
committed
Rust backend
1 parent a093f9c commit a141235

File tree

8 files changed

+396
-11
lines changed

8 files changed

+396
-11
lines changed

README.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ Rules:
131131
* Set ``0`` to disable the resolved cache.
132132
* Invalid values (non-integer or negative) fall back to ``128``.
133133

134+
You can also choose schema validator backend:
135+
136+
.. code-block:: bash
137+
138+
OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND=auto
139+
140+
Allowed values are ``auto`` (default), ``jsonschema``, and
141+
``jsonschema-rs``.
142+
Invalid values raise a warning and fall back to ``auto``.
143+
134144
Related projects
135145
################
136146

docs/cli.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,7 @@ Performance note:
7373
You can tune resolved-path caching with
7474
``OPENAPI_SPEC_VALIDATOR_RESOLVED_CACHE_MAXSIZE``.
7575
Default is ``128``; set ``0`` to disable.
76+
77+
You can also select schema validator backend with
78+
``OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND``
79+
(``auto``/``jsonschema``/``jsonschema-rs``).

docs/python.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,13 @@ Rules:
7575
* Default is ``128``.
7676
* Set ``0`` to disable the resolved cache.
7777
* Invalid values (non-integer or negative) fall back to ``128``.
78+
79+
Schema validator backend can be selected with:
80+
81+
.. code-block:: bash
82+
83+
OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND=auto
84+
85+
Allowed values are ``auto`` (default), ``jsonschema``, and
86+
``jsonschema-rs``.
87+
Invalid values raise a warning and fall back to ``auto``.

openapi_spec_validator/schemas/__init__.py

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,61 @@
1-
"""OpenAIP spec validator schemas module."""
1+
# openapi_spec_validator/schemas/__init__.py (POC version)
2+
"""OpenAIP spec validator schemas module - POC with Rust backend support."""
23

34
from functools import partial
5+
from typing import Any
46

57
from jsonschema.validators import Draft4Validator
68
from jsonschema.validators import Draft202012Validator
79
from lazy_object_proxy import Proxy
810

911
from openapi_spec_validator.schemas.utils import get_schema_content
12+
from openapi_spec_validator.settings import get_schema_validator_backend
1013

11-
__all__ = ["schema_v2", "schema_v3", "schema_v30", "schema_v31", "schema_v32"]
14+
_create_rust_validator_impl: Any = None
15+
16+
# Import Rust adapters
17+
try:
18+
from openapi_spec_validator.schemas.rust_adapters import (
19+
create_rust_validator as _create_rust_validator_impl,
20+
)
21+
from openapi_spec_validator.schemas.rust_adapters import (
22+
get_validator_backend,
23+
)
24+
from openapi_spec_validator.schemas.rust_adapters import (
25+
has_rust_validators,
26+
)
27+
28+
_USE_RUST = has_rust_validators()
29+
except ImportError:
30+
_create_rust_validator_impl = None
31+
_USE_RUST = False
32+
33+
def has_rust_validators() -> bool:
34+
return False
35+
36+
def get_validator_backend() -> str:
37+
return "python (jsonschema)"
38+
39+
40+
_BACKEND_MODE = get_schema_validator_backend()
41+
42+
if _BACKEND_MODE == "jsonschema":
43+
_USE_RUST = False
44+
elif _BACKEND_MODE == "jsonschema-rs" and not _USE_RUST:
45+
raise ImportError(
46+
"OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND=jsonschema-rs "
47+
"is set but jsonschema-rs is not available. "
48+
"Install it with: pip install jsonschema-rs"
49+
)
50+
51+
__all__ = [
52+
"schema_v2",
53+
"schema_v3",
54+
"schema_v30",
55+
"schema_v31",
56+
"schema_v32",
57+
"get_validator_backend",
58+
]
1259

1360
get_schema_content_v2 = partial(get_schema_content, "2.0")
1461
get_schema_content_v30 = partial(get_schema_content, "3.0")
@@ -23,10 +70,53 @@
2370
# alias to the latest v3 version
2471
schema_v3 = schema_v32
2572

26-
get_openapi_v2_schema_validator = partial(Draft4Validator, schema_v2)
27-
get_openapi_v30_schema_validator = partial(Draft4Validator, schema_v30)
28-
get_openapi_v31_schema_validator = partial(Draft202012Validator, schema_v31)
29-
get_openapi_v32_schema_validator = partial(Draft202012Validator, schema_v32)
73+
74+
def _create_rust_schema_validator(
75+
schema: dict[str, Any],
76+
draft: str,
77+
) -> Any:
78+
if _create_rust_validator_impl is None:
79+
raise ImportError(
80+
"jsonschema-rs is not available. "
81+
"Install it with: pip install jsonschema-rs"
82+
)
83+
return _create_rust_validator_impl(schema, draft)
84+
85+
86+
# Validator factory functions with Rust/Python selection
87+
def get_openapi_v2_schema_validator() -> Any:
88+
"""Create OpenAPI 2.0 schema validator (Draft4)."""
89+
if _USE_RUST:
90+
return _create_rust_schema_validator(dict(schema_v2), draft="draft4")
91+
return Draft4Validator(schema_v2)
92+
93+
94+
def get_openapi_v30_schema_validator() -> Any:
95+
"""Create OpenAPI 3.0 schema validator (Draft4)."""
96+
if _USE_RUST:
97+
return _create_rust_schema_validator(dict(schema_v30), draft="draft4")
98+
return Draft4Validator(schema_v30)
99+
100+
101+
def get_openapi_v31_schema_validator() -> Any:
102+
"""Create OpenAPI 3.1 schema validator (Draft 2020-12)."""
103+
if _USE_RUST:
104+
return _create_rust_schema_validator(
105+
dict(schema_v31),
106+
draft="draft202012",
107+
)
108+
return Draft202012Validator(schema_v31)
109+
110+
111+
def get_openapi_v32_schema_validator() -> Any:
112+
"""Create OpenAPI 3.2 schema validator (Draft 2020-12)."""
113+
if _USE_RUST:
114+
return _create_rust_schema_validator(
115+
dict(schema_v32),
116+
draft="draft202012",
117+
)
118+
return Draft202012Validator(schema_v32)
119+
30120

31121
openapi_v2_schema_validator = Proxy(get_openapi_v2_schema_validator)
32122
openapi_v30_schema_validator = Proxy(get_openapi_v30_schema_validator)
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# openapi_spec_validator/schemas/rust_adapters.py
2+
"""
3+
Proof-of-Concept: jsonschema-rs adapter for openapi-spec-validator.
4+
5+
This module provides a compatibility layer between jsonschema-rs (Rust)
6+
and the existing jsonschema (Python) validator interface.
7+
"""
8+
9+
from typing import Any
10+
from typing import Iterator
11+
12+
from jsonschema.exceptions import ValidationError as PyValidationError
13+
14+
# Try to import jsonschema-rs
15+
try:
16+
import jsonschema_rs
17+
18+
HAS_JSONSCHEMA_RS = True
19+
except ImportError:
20+
HAS_JSONSCHEMA_RS = False
21+
jsonschema_rs = None # type: ignore
22+
23+
24+
class RustValidatorError(PyValidationError):
25+
"""ValidationError compatible with jsonschema, but originating from Rust validator."""
26+
27+
pass
28+
29+
30+
class RustValidatorWrapper:
31+
"""
32+
Wrapper that makes jsonschema-rs validator compatible with jsonschema interface.
33+
34+
This allows drop-in replacement while maintaining the same API surface.
35+
"""
36+
37+
def __init__(self, schema: dict[str, Any], validator: Any):
38+
"""
39+
Initialize Rust validator wrapper.
40+
41+
Args:
42+
schema: JSON Schema to validate against
43+
cls: JSON Schema validator
44+
"""
45+
if not HAS_JSONSCHEMA_RS:
46+
raise ImportError(
47+
"jsonschema-rs is not installed. Install it with: "
48+
"pip install jsonschema-rs"
49+
)
50+
51+
self.schema = schema
52+
self._rs_validator = validator
53+
54+
def iter_errors(self, instance: Any) -> Iterator[PyValidationError]:
55+
"""
56+
Validate instance and yield errors in jsonschema format.
57+
58+
This method converts jsonschema-rs errors to jsonschema ValidationError
59+
format for compatibility with existing code.
60+
"""
61+
# Try to validate - jsonschema-rs returns ValidationError on failure
62+
result = self._rs_validator.validate(instance)
63+
64+
if result is not None:
65+
# result contains validation errors
66+
# jsonschema-rs returns an iterator of errors
67+
for error in self._rs_validator.iter_errors(instance):
68+
yield self._convert_rust_error(error, instance)
69+
70+
def validate(self, instance: Any) -> None:
71+
"""
72+
Validate instance and raise ValidationError if invalid.
73+
74+
Compatible with jsonschema Validator.validate() method.
75+
"""
76+
try:
77+
self._rs_validator.validate(instance)
78+
except jsonschema_rs.ValidationError as e:
79+
# Convert and raise as Python ValidationError
80+
py_error = self._convert_rust_error_exception(e, instance)
81+
raise py_error from e
82+
83+
def is_valid(self, instance: Any) -> bool:
84+
"""Check if instance is valid against schema."""
85+
return self._rs_validator.is_valid(instance)
86+
87+
def _convert_rust_error(
88+
self, rust_error: Any, instance: Any
89+
) -> PyValidationError:
90+
"""
91+
Convert jsonschema-rs error format to jsonschema ValidationError.
92+
93+
jsonschema-rs error structure:
94+
- message: str
95+
- instance_path: list
96+
- schema_path: list (if available)
97+
"""
98+
message = str(rust_error)
99+
100+
# Extract path information if available
101+
# Note: jsonschema-rs error format may differ - adjust as needed
102+
instance_path = getattr(rust_error, "instance_path", [])
103+
schema_path = getattr(rust_error, "schema_path", [])
104+
105+
return RustValidatorError(
106+
message=message,
107+
path=list(instance_path) if instance_path else [],
108+
schema_path=list(schema_path) if schema_path else [],
109+
instance=instance,
110+
schema=self.schema,
111+
)
112+
113+
def _convert_rust_error_exception(
114+
self, rust_error: "jsonschema_rs.ValidationError", instance: Any
115+
) -> PyValidationError:
116+
"""Convert jsonschema-rs ValidationError exception to Python format."""
117+
message = str(rust_error)
118+
119+
return RustValidatorError(
120+
message=message,
121+
instance=instance,
122+
schema=self.schema,
123+
)
124+
125+
126+
def create_rust_validator(
127+
schema: dict[str, Any], draft: str = "draft202012"
128+
) -> RustValidatorWrapper:
129+
"""
130+
Factory function to create Rust-backed validator.
131+
132+
Args:
133+
schema: JSON Schema to validate against
134+
draft: JSON Schema draft version
135+
136+
Returns:
137+
RustValidatorWrapper instance
138+
"""
139+
140+
# Create appropriate Rust validator based on draft
141+
if draft == "draft4":
142+
validator = jsonschema_rs.Draft4Validator(schema)
143+
elif draft == "draft7":
144+
validator = jsonschema_rs.Draft7Validator(schema)
145+
elif draft == "draft201909":
146+
validator = jsonschema_rs.Draft201909Validator(schema)
147+
elif draft == "draft202012":
148+
validator = jsonschema_rs.Draft202012Validator(schema)
149+
else:
150+
raise ValueError(f"Unsupported draft: {draft}")
151+
152+
return RustValidatorWrapper(schema, validator=validator)
153+
154+
155+
# Convenience function to check if Rust validators are available
156+
def has_rust_validators() -> bool:
157+
"""Check if jsonschema-rs is available."""
158+
return HAS_JSONSCHEMA_RS
159+
160+
161+
def get_validator_backend() -> str:
162+
"""Get current validator backend (rust or python)."""
163+
if HAS_JSONSCHEMA_RS:
164+
return "rust (jsonschema-rs)"
165+
return "python (jsonschema)"

0 commit comments

Comments
 (0)