Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(python): Extend ValidationErrorKind with error-specific context #656

Merged
merged 1 commit into from
Dec 24, 2024
Merged
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
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