For any validator (including |, &, ~ compositions), return a structured trace of pass/fail with nested attempts, reasons, and failure paths.
Proposed Solution:
Implement explain_validation(validator, value) -> ValidationExplanation with root_attempt: ValidationAttempt. Accept validator classes or instances; capture validator_repr before validation.
ValidationAttempt fields: validator_type (class name), validator_repr (repr), value, passed (bool), failure_reason (str|None), children (list[ValidationAttempt]), path (tuple of keys/indices).
Track paths through dict keys and list/tuple/tuple indices for all nesting.
OR (|): flatten branches left-to-right into root children; each child uses the underlying validator's class name; never expose DirtyOr.
AND (&): flatten branches left-to-right; never expose DirtyAnd.
NOT (~): add one child for the inner validator; parent passes iff the child fails; record both attempts.
Provide concrete failure reasons: type mismatches; numeric bounds (gt, ge, lt, le); regex mismatches; min/max length; case upper/lower; missing/extra dict keys; list/tuple length and per-item failures.
Capture exceptions from custom DirtyEquals subclasses; mark the attempt failed with the exception message.
ValidationExplanation.get_failure_path(): return attempts from root to deepest failing leaf; on full success return [] (or a single passed root).
ValidationExplanation.format_explanation(max_depth): print a tree with exactly two spaces per depth; each line includes validator_repr and, if present, the failure_reason immediately after; hard-cutoff at max_depth.
Implement explain_failure(validator, value): return None on pass; else a non-empty human-readable string.
For any validator (including |, &, ~ compositions), return a structured trace of pass/fail with nested attempts, reasons, and failure paths.
Proposed Solution:
Implement explain_validation(validator, value) -> ValidationExplanation with root_attempt: ValidationAttempt. Accept validator classes or instances; capture validator_repr before validation.
ValidationAttempt fields: validator_type (class name), validator_repr (repr), value, passed (bool), failure_reason (str|None), children (list[ValidationAttempt]), path (tuple of keys/indices).
Track paths through dict keys and list/tuple/tuple indices for all nesting.
OR (|): flatten branches left-to-right into root children; each child uses the underlying validator's class name; never expose DirtyOr.
AND (&): flatten branches left-to-right; never expose DirtyAnd.
NOT (~): add one child for the inner validator; parent passes iff the child fails; record both attempts.
Provide concrete failure reasons: type mismatches; numeric bounds (gt, ge, lt, le); regex mismatches; min/max length; case upper/lower; missing/extra dict keys; list/tuple length and per-item failures.
Capture exceptions from custom DirtyEquals subclasses; mark the attempt failed with the exception message.
ValidationExplanation.get_failure_path(): return attempts from root to deepest failing leaf; on full success return [] (or a single passed root).
ValidationExplanation.format_explanation(max_depth): print a tree with exactly two spaces per depth; each line includes validator_repr and, if present, the failure_reason immediately after; hard-cutoff at max_depth.
Implement explain_failure(validator, value): return None on pass; else a non-empty human-readable string.