diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index 3dcd2966..089546a7 100644 --- a/jsonschema/exceptions.py +++ b/jsonschema/exceptions.py @@ -292,6 +292,21 @@ def __str__(self): ) +class MutatingValidator(Exception): + """ + A validator mutated the instance. + """ + + def __init__(self, validator: Any) -> None: + self.validator = validator + + def __str__(self) -> str: + return ( + f"Validator {self.validator!r} mutated the instance. " + f"This can cause failures for later validators." + ) + + class FormatError(Exception): """ Validating a format failed. diff --git a/jsonschema/validators.py b/jsonschema/validators.py index b8ca3bd4..a8e27056 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -12,6 +12,7 @@ from urllib.request import urlopen from warnings import warn import contextlib +import copy import json import reprlib import warnings @@ -380,7 +381,12 @@ def iter_errors(self, instance, _schema=None): return for validator, k, v in validators: + instance_clone = copy.deepcopy(instance) errors = validator(self, v, instance, _schema) or () + if instance != instance_clone: + yield exceptions.MutatingValidator(validator) + del instance_clone + for error in errors: # set details if not already set by the called fn error._set( @@ -428,7 +434,12 @@ def descend( if validator is None: continue + instance_clone = copy.deepcopy(instance) errors = validator(evolved, v, instance, schema) or () + if instance != instance_clone: + yield exceptions.MutatingValidator(validator) + del instance_clone + for error in errors: # set details if not already set by the called fn error._set(