Constraints (Unique)¶
SpecStar supports a built-in uniqueness constraint via Unique() metadata.
Declare unique fields¶
Use typing.Annotated:
from typing import Annotated
from msgspec import Struct
from specstar.types import Unique
class User(Struct):
username: Annotated[str, Unique()]
email: Annotated[str, Unique()]
age: int = 0
What Unique means (precise semantics)¶
A field marked with Unique() must be unique among non-deleted resources
of the same resource type.
- Soft-deleted resources are ignored when checking uniqueness.
Nonevalues are ignored (i.e.Nonecan repeat without violating uniqueness).
If a duplicate is detected, SpecStar raises UniqueConstraintError.
When does the check run?¶
Uniqueness is checked on write operations where unique-relevant data changes, such as:
- create (
POST) → returns409 Conflict - update / modify (
PUT) → returns409 Conflict - patch (
PATCH) → returns409 Conflict
All write endpoints return a consistent 409 Conflict response when a unique
constraint is violated.
How it works (implementation notes)¶
SpecStar uses a UniqueConstraintChecker:
- detects unique fields from
Unique()annotations (or accepts an explicit list) - ensures each unique field is present in
ResourceManagerindexed fields (auto-adds if missing) - queries storage for
is_deleted=Falseandfield == valueto find conflicts
Update behavior (exclude current resource)¶
When updating a resource, SpecStar excludes the current resource ID so that:
- updating a resource without changing the unique value does not fail
- changing to a value owned by another resource fails
Debugging uniqueness failures¶
If you see UniqueConstraintError:
- find the conflicting resource ID reported in the error
- verify that the conflicting resource is not deleted
- verify the field value being written is not
None - verify your storage backend supports searching indexed fields correctly
Practical example¶
Suppose you already created a user with username="alice".
If you try to create another non-deleted user with the same username, SpecStar will reject the write with 409 Conflict.
That means the uniqueness rule is enforced at the API layer in a predictable way, not left to ad-hoc application code.