composite validators (all/any/pipeline)

Implement AllOf and AnyOf for combining multiple checks into
composite validation rules.
This commit is contained in:
2025-03-26 18:54:51 +00:00
parent 067cd74566
commit 62d78ab699

View File

@@ -0,0 +1,100 @@
"""Composite validators for combining multiple checks.
Note: CompositeCheck classes (AllOf, AnyOf) intentionally return ValidationResult
rather than CheckResult. This allows callers to inspect individual check results
for detailed error reporting. They implement a compatible interface but are not
substitutable where Check is expected as a type constraint.
"""
from veritext.core.types import CheckResult, ValidationContext, ValidationResult
from veritext.validators.base import Check
class AllOf:
"""Passes only if all checks pass.
Note: Returns ValidationResult (not CheckResult) to expose child results.
"""
def __init__(self, checks: list[Check]) -> None:
"""
Initialise the AllOf composite validator.
Args:
checks: List of checks that must all pass.
Raises:
ValueError: If checks list is empty.
"""
if not checks:
raise ValueError("checks list cannot be empty")
self._checks = list(checks)
@property
def name(self) -> str:
return "all_of"
def check(self, text: str, context: ValidationContext) -> ValidationResult:
"""
Run all checks and return aggregate result.
Args:
text: The text to validate.
context: Validation context containing reference text and metadata.
Returns:
ValidationResult that passes only if all checks pass.
"""
results: list[CheckResult] = []
for check in self._checks:
results.append(check.check(text, context))
all_passed = all(r.passed for r in results)
return ValidationResult(passed=all_passed, checks=results)
class AnyOf:
"""Passes if any check passes.
Note: Returns ValidationResult (not CheckResult) to expose child results.
"""
def __init__(self, checks: list[Check]) -> None:
"""
Initialise the AnyOf composite validator.
Args:
checks: List of checks where at least one must pass.
Raises:
ValueError: If checks list is empty.
"""
if not checks:
raise ValueError("checks list cannot be empty")
self._checks = list(checks)
@property
def name(self) -> str:
return "any_of"
def check(self, text: str, context: ValidationContext) -> ValidationResult:
"""
Run all checks and return aggregate result.
Args:
text: The text to validate.
context: Validation context containing reference text and metadata.
Returns:
ValidationResult that passes if any check passes.
"""
results: list[CheckResult] = []
for check in self._checks:
results.append(check.check(text, context))
any_passed = any(r.passed for r in results)
return ValidationResult(passed=any_passed, checks=results)