Skip to content

Commit 3eda8b5

Browse files
committed
feat: Evaluate API
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
1 parent 83000f3 commit 3eda8b5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4600
-1972
lines changed

MIGRATION.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,32 @@ use jsonschema::primitive_type::{PrimitiveType, PrimitiveTypesBitMap};
8787
use jsonschema::types::{JsonType, JsonTypeSet};
8888
```
8989

90+
### Removal of `Validator::apply`, `Output`, and `BasicOutput`
91+
92+
The legacy `apply()` API and its `BasicOutput`/`OutputUnit` structures have been removed in favor of
93+
the richer [`Validator::evaluate`](https://docs.rs/jsonschema/latest/jsonschema/struct.Validator.html#method.evaluate)
94+
interface that exposes the JSON Schema output formats directly.
95+
96+
```rust
97+
// Old (0.34.x)
98+
let output = validator.apply(&instance).basic();
99+
match output {
100+
BasicOutput::Valid(units) => println!("valid: {units:?}"),
101+
BasicOutput::Invalid(errors) => println!("errors: {errors:?}"),
102+
}
103+
104+
// New (0.35.0)
105+
let evaluation = validator.evaluate(&instance);
106+
if evaluation.flag().valid {
107+
println!("valid");
108+
}
109+
let list = serde_json::to_value(evaluation.list())?;
110+
let hierarchical = serde_json::to_value(evaluation.hierarchical())?;
111+
```
112+
113+
`Annotations` and `ErrorDescription` continue to be available if you need to inspect the evaluation
114+
tree manually.
115+
90116
## Upgrading from 0.33.x to 0.34.0
91117

92118
### Removed `Validator::config()`

codecov.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ coverage:
66
project: off
77
patch: off
88

9-
# Ignore test/benchmark infrastructure from coverage
9+
# Ignore test/benchmark infrastructure & dev-only suite helpers from coverage
1010
ignore:
1111
- "crates/benchmark/"
1212
- "crates/benchmark-suite/"
1313
- "crates/jsonschema-testsuite/"
14+
- "crates/jsonschema-testsuite-codegen/"
15+
- "crates/jsonschema-testsuite-internal/"
1416
- "crates/jsonschema-referencing-testsuite/"
17+
- "crates/jsonschema-referencing-testsuite-codegen/"
18+
- "crates/jsonschema-referencing-testsuite-internal/"
19+
- "crates/testsuite-common/"

crates/jsonschema-py/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Added
6+
7+
- Python bindings for `evaluate`: a new `jsonschema_rs.evaluate()` helper, `Validator.evaluate()`, and the `Evaluation` class for accessing flag/list/hierarchical outputs plus collected annotations & errors without re-validating in Python.
8+
59
## [0.35.0] - 2025-11-16
610

711
### Added

crates/jsonschema-py/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,36 @@ On instance:
155155
"unknown"'''
156156
```
157157

158+
### Structured Output with `evaluate`
159+
160+
When you need more than a boolean result, use the `evaluate` helpers to access the JSON Schema output formats defined by the specification:
161+
162+
```python
163+
import jsonschema_rs
164+
165+
schema = {
166+
"type": "object",
167+
"required": ["name"],
168+
"properties": {"name": {"type": "string"}, "age": {"type": "number", "minimum": 0}},
169+
}
170+
instance = {"name": "Alice", "age": -3}
171+
172+
# One-off evaluation
173+
evaluation = jsonschema_rs.evaluate(schema, instance)
174+
175+
# Or reuse a validator
176+
validator = jsonschema_rs.validator_for(schema)
177+
evaluation = validator.evaluate(instance)
178+
179+
assert evaluation.valid is False
180+
print(evaluation.flag()) # {"valid": False}
181+
print(evaluation.list()) # Flat list of evaluation steps
182+
print(evaluation.hierarchical()) # Nested tree of evaluations
183+
184+
for error in evaluation.errors():
185+
print(error["instanceLocation"], error["error"])
186+
```
187+
158188
### Arbitrary-Precision Numbers
159189

160190
The Python bindings always include the `arbitrary-precision` support from the Rust validator, so numeric

crates/jsonschema-py/python/jsonschema_rs/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
Draft201909Validator,
1212
Draft202012,
1313
Draft202012Validator,
14+
Evaluation,
1415
FancyRegexOptions,
1516
RegexOptions,
1617
Registry,
1718
ValidationErrorKind,
19+
evaluate,
1820
is_valid,
1921
iter_errors,
2022
meta,
@@ -77,9 +79,11 @@ def __repr__(self) -> str:
7779
"ReferencingError",
7880
"ValidationError",
7981
"ValidationErrorKind",
82+
"Evaluation",
8083
"is_valid",
8184
"validate",
8285
"iter_errors",
86+
"evaluate",
8387
"validator_for",
8488
"Draft4",
8589
"Draft6",

crates/jsonschema-py/python/jsonschema_rs/__init__.pyi

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
11
from collections.abc import Iterator
2-
from typing import Any, Callable, Protocol, TypeAlias, TypeVar, Union
2+
from typing import Any, Callable, List, Protocol, TypeAlias, TypeVar, TypedDict, Union
33

44
_SchemaT = TypeVar("_SchemaT", bool, dict[str, Any])
55
_FormatFunc = TypeVar("_FormatFunc", bound=Callable[[str], bool])
66
JSONType: TypeAlias = dict[str, Any] | list | str | int | float | bool | None
77
JSONPrimitive: TypeAlias = str | int | float | bool | None
88

9+
class EvaluationAnnotation(TypedDict):
10+
schemaLocation: str
11+
absoluteKeywordLocation: str | None
12+
instanceLocation: str
13+
annotations: JSONType
14+
15+
class EvaluationErrorEntry(TypedDict):
16+
schemaLocation: str
17+
absoluteKeywordLocation: str | None
18+
instanceLocation: str
19+
error: str
20+
21+
class Evaluation:
22+
valid: bool
23+
def flag(self) -> JSONType: ...
24+
def list(self) -> JSONType: ...
25+
def hierarchical(self) -> JSONType: ...
26+
def annotations(self) -> List[EvaluationAnnotation]: ...
27+
def errors(self) -> List[EvaluationErrorEntry]: ...
28+
929
class FancyRegexOptions:
1030
def __init__(
1131
self, backtrack_limit: int | None = None, size_limit: int | None = None, dfa_size_limit: int | None = None
@@ -56,10 +76,23 @@ def iter_errors(
5676
validate_formats: bool | None = None,
5777
ignore_unknown_formats: bool = True,
5878
retriever: RetrieverProtocol | None = None,
79+
registry: Registry | None = None,
5980
mask: str | None = None,
6081
base_uri: str | None = None,
6182
pattern_options: PatternOptionsType | None = None,
6283
) -> Iterator[ValidationError]: ...
84+
def evaluate(
85+
schema: _SchemaT,
86+
instance: Any,
87+
draft: int | None = None,
88+
formats: dict[str, _FormatFunc] | None = None,
89+
validate_formats: bool | None = None,
90+
ignore_unknown_formats: bool = True,
91+
retriever: RetrieverProtocol | None = None,
92+
registry: Registry | None = None,
93+
base_uri: str | None = None,
94+
pattern_options: PatternOptionsType | None = None,
95+
) -> Evaluation: ...
6396

6497
class ReferencingError:
6598
message: str
@@ -195,6 +228,7 @@ class Draft4Validator:
195228
def is_valid(self, instance: Any) -> bool: ...
196229
def validate(self, instance: Any) -> None: ...
197230
def iter_errors(self, instance: Any) -> Iterator[ValidationError]: ...
231+
def evaluate(self, instance: Any) -> Evaluation: ...
198232

199233
class Draft6Validator:
200234
def __init__(
@@ -212,6 +246,7 @@ class Draft6Validator:
212246
def is_valid(self, instance: Any) -> bool: ...
213247
def validate(self, instance: Any) -> None: ...
214248
def iter_errors(self, instance: Any) -> Iterator[ValidationError]: ...
249+
def evaluate(self, instance: Any) -> Evaluation: ...
215250

216251
class Draft7Validator:
217252
def __init__(
@@ -229,6 +264,7 @@ class Draft7Validator:
229264
def is_valid(self, instance: Any) -> bool: ...
230265
def validate(self, instance: Any) -> None: ...
231266
def iter_errors(self, instance: Any) -> Iterator[ValidationError]: ...
267+
def evaluate(self, instance: Any) -> Evaluation: ...
232268

233269
class Draft201909Validator:
234270
def __init__(
@@ -246,6 +282,7 @@ class Draft201909Validator:
246282
def is_valid(self, instance: Any) -> bool: ...
247283
def validate(self, instance: Any) -> None: ...
248284
def iter_errors(self, instance: Any) -> Iterator[ValidationError]: ...
285+
def evaluate(self, instance: Any) -> Evaluation: ...
249286

250287
class Draft202012Validator:
251288
def __init__(
@@ -263,6 +300,7 @@ class Draft202012Validator:
263300
def is_valid(self, instance: Any) -> bool: ...
264301
def validate(self, instance: Any) -> None: ...
265302
def iter_errors(self, instance: Any) -> Iterator[ValidationError]: ...
303+
def evaluate(self, instance: Any) -> Evaluation: ...
266304

267305
def validator_for(
268306
schema: _SchemaT,

0 commit comments

Comments
 (0)