Skip to content

Commit 4b22642

Browse files
committed
Rust backend
1 parent a093f9c commit 4b22642

File tree

2 files changed

+226
-7
lines changed

2 files changed

+226
-7
lines changed

openapi_spec_validator/schemas/__init__.py

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
"""OpenAIP spec validator schemas module."""
2-
1+
# openapi_spec_validator/schemas/__init__.py (POC version)
2+
"""OpenAIP spec validator schemas module - POC with Rust backend support."""
3+
import os
34
from functools import partial
45

56
from jsonschema.validators import Draft4Validator
@@ -8,7 +9,39 @@
89

910
from openapi_spec_validator.schemas.utils import get_schema_content
1011

11-
__all__ = ["schema_v2", "schema_v3", "schema_v30", "schema_v31", "schema_v32"]
12+
# Import Rust adapters
13+
try:
14+
from openapi_spec_validator.schemas.rust_adapters import (
15+
has_rust_validators,
16+
create_rust_validator,
17+
get_validator_backend,
18+
)
19+
_USE_RUST = has_rust_validators()
20+
except ImportError:
21+
_USE_RUST = False
22+
has_rust_validators = lambda: False # type: ignore
23+
get_validator_backend = lambda: "python (jsonschema)" # type: ignore
24+
25+
# Allow override via environment variable for A/B testing
26+
_FORCE_PYTHON = os.getenv("OPENAPI_FORCE_PYTHON_VALIDATOR", "").lower() in ("1", "true", "yes")
27+
_FORCE_RUST = os.getenv("OPENAPI_FORCE_RUST_VALIDATOR", "").lower() in ("1", "true", "yes")
28+
29+
if _FORCE_PYTHON:
30+
_USE_RUST = False
31+
elif _FORCE_RUST and not _USE_RUST:
32+
raise ImportError(
33+
"OPENAPI_FORCE_RUST_VALIDATOR is set but jsonschema-rs is not available. "
34+
"Install it with: pip install jsonschema-rs"
35+
)
36+
37+
__all__ = [
38+
"schema_v2",
39+
"schema_v3",
40+
"schema_v30",
41+
"schema_v31",
42+
"schema_v32",
43+
"get_validator_backend",
44+
]
1245

1346
get_schema_content_v2 = partial(get_schema_content, "2.0")
1447
get_schema_content_v30 = partial(get_schema_content, "3.0")
@@ -23,10 +56,32 @@
2356
# alias to the latest v3 version
2457
schema_v3 = schema_v32
2558

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)
59+
# Validator factory functions with Rust/Python selection
60+
def get_openapi_v2_schema_validator():
61+
"""Create OpenAPI 2.0 schema validator (Draft4)."""
62+
if _USE_RUST:
63+
return create_rust_validator(dict(schema_v2), draft="draft4")
64+
return Draft4Validator(schema_v2)
65+
66+
67+
def get_openapi_v30_schema_validator():
68+
"""Create OpenAPI 3.0 schema validator (Draft4)."""
69+
if _USE_RUST:
70+
return create_rust_validator(dict(schema_v30), draft="draft4")
71+
return Draft4Validator(schema_v30)
72+
73+
74+
def get_openapi_v31_schema_validator():
75+
"""Create OpenAPI 3.1 schema validator (Draft 2020-12)."""
76+
if _USE_RUST:
77+
return create_rust_validator(dict(schema_v31), draft="draft202012")
78+
return Draft202012Validator(schema_v31)
79+
80+
def get_openapi_v32_schema_validator():
81+
"""Create OpenAPI 3.2 schema validator (Draft 2020-12)."""
82+
if _USE_RUST:
83+
return create_rust_validator(dict(schema_v32), draft="draft202012")
84+
return Draft202012Validator(schema_v32)
3085

3186
openapi_v2_schema_validator = Proxy(get_openapi_v2_schema_validator)
3287
openapi_v30_schema_validator = Proxy(get_openapi_v30_schema_validator)
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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], draft: str = "draft202012"):
38+
"""
39+
Initialize Rust validator wrapper.
40+
41+
Args:
42+
schema: JSON Schema to validate against
43+
draft: JSON Schema draft version ('draft4' or 'draft202012')
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.draft = draft
53+
54+
# Create appropriate Rust validator based on draft
55+
if draft == "draft4":
56+
self._rs_validator = jsonschema_rs.Draft4Validator(schema)
57+
elif draft == "draft7":
58+
self._rs_validator = jsonschema_rs.Draft7Validator(schema)
59+
elif draft == "draft201909":
60+
self._rs_validator = jsonschema_rs.Draft201909Validator(schema)
61+
elif draft == "draft202012":
62+
self._rs_validator = jsonschema_rs.Draft202012Validator(schema)
63+
else:
64+
raise ValueError(f"Unsupported draft: {draft}")
65+
66+
def iter_errors(self, instance: Any) -> Iterator[PyValidationError]:
67+
"""
68+
Validate instance and yield errors in jsonschema format.
69+
70+
This method converts jsonschema-rs errors to jsonschema ValidationError
71+
format for compatibility with existing code.
72+
"""
73+
# Try to validate - jsonschema-rs returns ValidationError on failure
74+
result = self._rs_validator.validate(instance)
75+
76+
if result is not None:
77+
# result contains validation errors
78+
# jsonschema-rs returns an iterator of errors
79+
for error in self._rs_validator.iter_errors(instance):
80+
yield self._convert_rust_error(error, instance)
81+
82+
def validate(self, instance: Any) -> None:
83+
"""
84+
Validate instance and raise ValidationError if invalid.
85+
86+
Compatible with jsonschema Validator.validate() method.
87+
"""
88+
try:
89+
self._rs_validator.validate(instance)
90+
except jsonschema_rs.ValidationError as e:
91+
# Convert and raise as Python ValidationError
92+
py_error = self._convert_rust_error_exception(e, instance)
93+
raise py_error from e
94+
95+
def is_valid(self, instance: Any) -> bool:
96+
"""Check if instance is valid against schema."""
97+
return self._rs_validator.is_valid(instance)
98+
99+
def _convert_rust_error(
100+
self, rust_error: Any, instance: Any
101+
) -> PyValidationError:
102+
"""
103+
Convert jsonschema-rs error format to jsonschema ValidationError.
104+
105+
jsonschema-rs error structure:
106+
- message: str
107+
- instance_path: list
108+
- schema_path: list (if available)
109+
"""
110+
message = str(rust_error)
111+
112+
# Extract path information if available
113+
# Note: jsonschema-rs error format may differ - adjust as needed
114+
instance_path = getattr(rust_error, "instance_path", [])
115+
schema_path = getattr(rust_error, "schema_path", [])
116+
117+
return RustValidatorError(
118+
message=message,
119+
path=list(instance_path) if instance_path else [],
120+
schema_path=list(schema_path) if schema_path else [],
121+
instance=instance,
122+
schema=self.schema,
123+
)
124+
125+
def _convert_rust_error_exception(
126+
self, rust_error: "jsonschema_rs.ValidationError", instance: Any
127+
) -> PyValidationError:
128+
"""Convert jsonschema-rs ValidationError exception to Python format."""
129+
message = str(rust_error)
130+
131+
return RustValidatorError(
132+
message=message,
133+
instance=instance,
134+
schema=self.schema,
135+
)
136+
137+
138+
def create_rust_validator(
139+
schema: dict[str, Any], draft: str = "draft202012"
140+
) -> RustValidatorWrapper:
141+
"""
142+
Factory function to create Rust-backed validator.
143+
144+
Args:
145+
schema: JSON Schema to validate against
146+
draft: JSON Schema draft version
147+
148+
Returns:
149+
RustValidatorWrapper instance
150+
"""
151+
return RustValidatorWrapper(schema, draft=draft)
152+
153+
154+
# Convenience function to check if Rust validators are available
155+
def has_rust_validators() -> bool:
156+
"""Check if jsonschema-rs is available."""
157+
return HAS_JSONSCHEMA_RS
158+
159+
160+
def get_validator_backend() -> str:
161+
"""Get current validator backend (rust or python)."""
162+
if HAS_JSONSCHEMA_RS:
163+
return "rust (jsonschema-rs)"
164+
return "python (jsonschema)"

0 commit comments

Comments
 (0)