Validation (validator / IValidator / ValidationError)¶
SpecStar supports custom validation on write operations (create/update/patch/modify), in addition to msgspec's own type-level validation.
Two layers of validation¶
A) Type-level validation (msgspec)¶
- Happens when decoding / constructing your
msgspec.Struct - Errors are typically
msgspec.ValidationError
B) Domain/business validation (SpecStar)¶
- Your custom rule checks (e.g. cross-field constraints, invariants)
- Should raise
specstar.types.ValidationError(orValueError, which SpecStar will wrap)
Validator forms accepted¶
Depending on where you attach it, SpecStar accepts validators in these forms:
1) Callable¶
A simple function:
2) IValidator implementation¶
from specstar.types import IValidator
class PriceValidator(IValidator):
def validate(self, data) -> None:
if data.price < 0:
raise ValueError("Price must be non-negative")
3) Pydantic model (bridge)¶
If you register a Pydantic BaseModel as the model type, SpecStar can use it as a validator
(by converting it and validating through Pydantic), when no validator is provided elsewhere.
Practical example¶
A useful mental model is:
- let msgspec check whether the payload shape and field types are valid
- let SpecStar validators check whether the data makes sense for your business rules
For example:
from msgspec import Struct
class User(Struct):
name: str
age: int
def validate_user(u: User) -> None:
if u.age < 18:
raise ValueError("user must be an adult")
In this example:
{"age": "abc"}fails at the msgspec/type-validation layer{"age": 12}passes type validation but fails the domain validation rule
Where to attach validators¶
Attach via add_model(...)¶
Attach via Schema(...)¶
Errors¶
ValidationError¶
SpecStar uses ValidationError (a ValueError subclass) for domain validation failures.
This is intentionally distinct from msgspec.ValidationError.
Practical rule:
- in validators, raise
ValueErrorwith a clear message - SpecStar will surface it as
ValidationError(or pass through if alreadyValidationError)
Common debugging pattern¶
If a write is rejected and you are not sure why, check the failure in this order:
- did the payload fail type validation before your validator even ran?
- did your custom validator reject a business rule?
- is the real problem actually a uniqueness or schema-migration issue instead?
See also: