Skip to content

Commit

Permalink
feat(python): Extend ValidationErrorKind with error-specific context
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
  • Loading branch information
Stranger6667 committed Dec 24, 2024
1 parent cfd6702 commit 4806e5c
Show file tree
Hide file tree
Showing 7 changed files with 461 additions and 130 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- Implement `ExactSizeIterator` for `PrimitiveTypesBitMapIterator`.

## [0.27.0] - 2024-12-23

### Added
Expand Down
5 changes: 5 additions & 0 deletions crates/jsonschema-py/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [Unreleased]

### Added

- Extend `ValidationErrorKind` with error-specific context data.
- Missing type annotations for `retriever` & `mask` arguments.

## [0.27.0] - 2024-12-23

### Added
Expand Down
27 changes: 26 additions & 1 deletion crates/jsonschema-py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,32 @@ validator.is_valid({
}) # False
```

## Error Message Masking
## Error Handling

`jsonschema-rs` provides detailed validation errors through the `ValidationError` class, which includes both basic error information and specific details about what caused the validation to fail:

```python
import jsonschema_rs

schema = {"type": "string", "maxLength": 5}

try:
jsonschema_rs.validate(schema, "too long")
except jsonschema_rs.ValidationError as error:
# Basic error information
print(error.message) # '"too long" is longer than 5 characters'
print(error.instance_path) # Location in the instance that failed
print(error.schema_path) # Location in the schema that failed

# Detailed error information via `kind`
if isinstance(error.kind, jsonschema_rs.ValidationErrorKind.MaxLength):
assert error.kind.limit == 5
print(f"Exceeded maximum length of {error.kind.limit}")
```

For a complete list of all error kinds and their attributes, see the [type definitions file](https://github.com/Stranger6667/jsonschema/blob/master/crates/jsonschema-py/python/jsonschema_rs/__init__.pyi)

### Error Message Masking

When working with sensitive data, you might want to hide actual values from error messages.
You can mask instance values in error messages by providing a placeholder:
Expand Down
129 changes: 126 additions & 3 deletions crates/jsonschema-py/python/jsonschema_rs/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from collections.abc import Iterator
from typing import Any, Callable, TypeVar
from typing import Any, Callable, Protocol, TypeAlias, TypeVar

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

class RetrieverProtocol(Protocol):
def __call__(self, uri: str) -> JSONType: ...

def is_valid(
schema: _SchemaT,
Expand All @@ -12,6 +17,8 @@ def is_valid(
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> bool: ...
def validate(
schema: _SchemaT,
Expand All @@ -21,6 +28,8 @@ def validate(
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> None: ...
def iter_errors(
schema: _SchemaT,
Expand All @@ -30,16 +39,118 @@ def iter_errors(
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> Iterator[ValidationError]: ...

class ValidationErrorKind: ...
class ReferencingError:
message: str

class ValidationErrorKind:
class AdditionalItems:
limit: int

class AdditionalProperties:
unexpected: list[str]

class AnyOf: ...

class BacktrackLimitExceeded:
error: str

class Constant:
expected_value: JSONType

class Contains: ...

class ContentEncoding:
content_encoding: str

class ContentMediaType:
content_media_type: str

class Custom:
message: str

class Enum:
options: list[JSONType]

class ExclusiveMaximum:
limit: JSONPrimitive

class ExclusiveMinimum:
limit: JSONPrimitive

class FalseSchema: ...

class Format:
format: str

class FromUtf8:
error: str

class MaxItems:
limit: int

class Maximum:
limit: JSONPrimitive

class MaxLength:
limit: int

class MaxProperties:
limit: int

class MinItems:
limit: int

class Minimum:
limit: JSONPrimitive

class MinLength:
limit: int

class MinProperties:
limit: int

class MultipleOf:
multiple_of: float

class Not:
schema: JSONType

class OneOfMultipleValid: ...
class OneOfNotValid: ...

class Pattern:
pattern: str

class PropertyNames:
error: "ValidationError"

class Required:
property: str

class Type:
types: list[str]

class UnevaluatedItems:
unexpected: list[int]

class UnevaluatedProperties:
unexpected: list[str]

class UniqueItems: ...

class Referencing:
error: ReferencingError

class ValidationError(ValueError):
message: str
schema_path: list[str | int]
instance_path: list[str | int]
kind: ValidationErrorKind
instance: list | dict | str | int | float | bool | None
instance: JSONType

Draft4: int
Draft6: int
Expand All @@ -54,6 +165,8 @@ class Draft4Validator:
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> None: ...
def is_valid(self, instance: Any) -> bool: ...
def validate(self, instance: Any) -> None: ...
Expand All @@ -66,6 +179,8 @@ class Draft6Validator:
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> None: ...
def is_valid(self, instance: Any) -> bool: ...
def validate(self, instance: Any) -> None: ...
Expand All @@ -78,6 +193,8 @@ class Draft7Validator:
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> None: ...
def is_valid(self, instance: Any) -> bool: ...
def validate(self, instance: Any) -> None: ...
Expand All @@ -90,6 +207,8 @@ class Draft201909Validator:
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> None: ...
def is_valid(self, instance: Any) -> bool: ...
def validate(self, instance: Any) -> None: ...
Expand All @@ -102,6 +221,8 @@ class Draft202012Validator:
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> None: ...
def is_valid(self, instance: Any) -> bool: ...
def validate(self, instance: Any) -> None: ...
Expand All @@ -112,4 +233,6 @@ def validator_for(
formats: dict[str, _FormatFunc] | None = None,
validate_formats: bool | None = None,
ignore_unknown_formats: bool = True,
retriever: RetrieverProtocol | None = None,
mask: str | None = None,
) -> Draft4Validator | Draft6Validator | Draft7Validator | Draft201909Validator | Draft202012Validator: ...
Loading

0 comments on commit 4806e5c

Please sign in to comment.