Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 62 additions & 7 deletions openapi_spec_validator/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""OpenAIP spec validator schemas module."""

# openapi_spec_validator/schemas/__init__.py (POC version)
"""OpenAIP spec validator schemas module - POC with Rust backend support."""
import os
from functools import partial

from jsonschema.validators import Draft4Validator
Expand All @@ -8,7 +9,39 @@

from openapi_spec_validator.schemas.utils import get_schema_content

__all__ = ["schema_v2", "schema_v3", "schema_v30", "schema_v31", "schema_v32"]
# Import Rust adapters
try:
from openapi_spec_validator.schemas.rust_adapters import (
has_rust_validators,
create_rust_validator,
get_validator_backend,
)
_USE_RUST = has_rust_validators()
except ImportError:
_USE_RUST = False
has_rust_validators = lambda: False # type: ignore
get_validator_backend = lambda: "python (jsonschema)" # type: ignore

# Allow override via environment variable for A/B testing
_FORCE_PYTHON = os.getenv("OPENAPI_FORCE_PYTHON_VALIDATOR", "").lower() in ("1", "true", "yes")
_FORCE_RUST = os.getenv("OPENAPI_FORCE_RUST_VALIDATOR", "").lower() in ("1", "true", "yes")

if _FORCE_PYTHON:
_USE_RUST = False
elif _FORCE_RUST and not _USE_RUST:
raise ImportError(
"OPENAPI_FORCE_RUST_VALIDATOR is set but jsonschema-rs is not available. "
"Install it with: pip install jsonschema-rs"
)

__all__ = [
"schema_v2",
"schema_v3",
"schema_v30",
"schema_v31",
"schema_v32",
"get_validator_backend",
]

get_schema_content_v2 = partial(get_schema_content, "2.0")
get_schema_content_v30 = partial(get_schema_content, "3.0")
Expand All @@ -23,10 +56,32 @@
# alias to the latest v3 version
schema_v3 = schema_v32

get_openapi_v2_schema_validator = partial(Draft4Validator, schema_v2)
get_openapi_v30_schema_validator = partial(Draft4Validator, schema_v30)
get_openapi_v31_schema_validator = partial(Draft202012Validator, schema_v31)
get_openapi_v32_schema_validator = partial(Draft202012Validator, schema_v32)
# Validator factory functions with Rust/Python selection
def get_openapi_v2_schema_validator():
"""Create OpenAPI 2.0 schema validator (Draft4)."""
if _USE_RUST:
return create_rust_validator(dict(schema_v2), draft="draft4")
return Draft4Validator(schema_v2)


def get_openapi_v30_schema_validator():
"""Create OpenAPI 3.0 schema validator (Draft4)."""
if _USE_RUST:
return create_rust_validator(dict(schema_v30), draft="draft4")
return Draft4Validator(schema_v30)


def get_openapi_v31_schema_validator():
"""Create OpenAPI 3.1 schema validator (Draft 2020-12)."""
if _USE_RUST:
return create_rust_validator(dict(schema_v31), draft="draft202012")
return Draft202012Validator(schema_v31)

def get_openapi_v32_schema_validator():
"""Create OpenAPI 3.2 schema validator (Draft 2020-12)."""
if _USE_RUST:
return create_rust_validator(dict(schema_v32), draft="draft202012")
return Draft202012Validator(schema_v32)

openapi_v2_schema_validator = Proxy(get_openapi_v2_schema_validator)
openapi_v30_schema_validator = Proxy(get_openapi_v30_schema_validator)
Expand Down
164 changes: 164 additions & 0 deletions openapi_spec_validator/schemas/rust_adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# openapi_spec_validator/schemas/rust_adapters.py
"""
Proof-of-Concept: jsonschema-rs adapter for openapi-spec-validator.

This module provides a compatibility layer between jsonschema-rs (Rust)
and the existing jsonschema (Python) validator interface.
"""

from typing import Any
from typing import Iterator

from jsonschema.exceptions import ValidationError as PyValidationError

# Try to import jsonschema-rs
try:
import jsonschema_rs

HAS_JSONSCHEMA_RS = True
except ImportError:
HAS_JSONSCHEMA_RS = False
jsonschema_rs = None # type: ignore


class RustValidatorError(PyValidationError):
"""ValidationError compatible with jsonschema, but originating from Rust validator."""

pass


class RustValidatorWrapper:
"""
Wrapper that makes jsonschema-rs validator compatible with jsonschema interface.

This allows drop-in replacement while maintaining the same API surface.
"""

def __init__(self, schema: dict[str, Any], draft: str = "draft202012"):
"""
Initialize Rust validator wrapper.

Args:
schema: JSON Schema to validate against
draft: JSON Schema draft version ('draft4' or 'draft202012')
"""
if not HAS_JSONSCHEMA_RS:
raise ImportError(
"jsonschema-rs is not installed. Install it with: "
"pip install jsonschema-rs"
)

self.schema = schema
self.draft = draft

# Create appropriate Rust validator based on draft
if draft == "draft4":
self._rs_validator = jsonschema_rs.Draft4Validator(schema)
elif draft == "draft7":
self._rs_validator = jsonschema_rs.Draft7Validator(schema)
elif draft == "draft201909":
self._rs_validator = jsonschema_rs.Draft201909Validator(schema)
elif draft == "draft202012":
self._rs_validator = jsonschema_rs.Draft202012Validator(schema)
else:
raise ValueError(f"Unsupported draft: {draft}")

def iter_errors(self, instance: Any) -> Iterator[PyValidationError]:
"""
Validate instance and yield errors in jsonschema format.

This method converts jsonschema-rs errors to jsonschema ValidationError
format for compatibility with existing code.
"""
# Try to validate - jsonschema-rs returns ValidationError on failure
result = self._rs_validator.validate(instance)

if result is not None:
# result contains validation errors
# jsonschema-rs returns an iterator of errors
for error in self._rs_validator.iter_errors(instance):
yield self._convert_rust_error(error, instance)

def validate(self, instance: Any) -> None:
"""
Validate instance and raise ValidationError if invalid.

Compatible with jsonschema Validator.validate() method.
"""
try:
self._rs_validator.validate(instance)
except jsonschema_rs.ValidationError as e:
# Convert and raise as Python ValidationError
py_error = self._convert_rust_error_exception(e, instance)
raise py_error from e

def is_valid(self, instance: Any) -> bool:
"""Check if instance is valid against schema."""
return self._rs_validator.is_valid(instance)

def _convert_rust_error(
self, rust_error: Any, instance: Any
) -> PyValidationError:
"""
Convert jsonschema-rs error format to jsonschema ValidationError.

jsonschema-rs error structure:
- message: str
- instance_path: list
- schema_path: list (if available)
"""
message = str(rust_error)

# Extract path information if available
# Note: jsonschema-rs error format may differ - adjust as needed
instance_path = getattr(rust_error, "instance_path", [])
schema_path = getattr(rust_error, "schema_path", [])

return RustValidatorError(
message=message,
path=list(instance_path) if instance_path else [],
schema_path=list(schema_path) if schema_path else [],
instance=instance,
schema=self.schema,
)

def _convert_rust_error_exception(
self, rust_error: "jsonschema_rs.ValidationError", instance: Any
) -> PyValidationError:
"""Convert jsonschema-rs ValidationError exception to Python format."""
message = str(rust_error)

return RustValidatorError(
message=message,
instance=instance,
schema=self.schema,
)


def create_rust_validator(
schema: dict[str, Any], draft: str = "draft202012"
) -> RustValidatorWrapper:
"""
Factory function to create Rust-backed validator.

Args:
schema: JSON Schema to validate against
draft: JSON Schema draft version

Returns:
RustValidatorWrapper instance
"""
return RustValidatorWrapper(schema, draft=draft)


# Convenience function to check if Rust validators are available
def has_rust_validators() -> bool:
"""Check if jsonschema-rs is available."""
return HAS_JSONSCHEMA_RS


def get_validator_backend() -> str:
"""Get current validator backend (rust or python)."""
if HAS_JSONSCHEMA_RS:
return "rust (jsonschema-rs)"
return "python (jsonschema)"
Loading