Entity checklist
Every decision one #[expose]d entity asks of you, one line each, each pointing at the page that explains it.
One #[expose] declaration feeds the REST body, the GraphQL type, the OpenAPI
schema, the CRUD inputs and the response mask. That is the framework’s densest
payoff and its densest set of choices — and they are documented across four
pages, because each belongs beside the mechanism it drives.
This page is the list. One line per decision, and a link to where it is explained. Read it when you declare your second entity; the first one you copy from Entities.
On the entity
Section titled “On the entity”#[expose(name = "Post", service = …, graphql, timestamps, soft_delete)]| Argument | Ask yourself | Reference |
|---|---|---|
name | What is this type called on the wire? Bare, no suffix | Entities |
service | Which CrudService owns it? Omit for a read-only exposure with none | CRUD |
graphql | Does it appear in the schema, or is it HTTP-only? | GraphQL |
timestamps | Should created_at / updated_at be maintained for you? | Entities |
soft_delete | Does DELETE tombstone the row instead of erasing it? | CRUD |
complex | Does the wire type need a hand-written #[ComplexObject] field resolver? | Field resolvers |
On each field
Section titled “On each field”| Argument | Ask yourself | Reference |
|---|---|---|
#[expose] | Does this column cross the wire at all? Silence means hidden | Entities |
input(create, update) | May a client write it, and on which operation? | CRUD |
validate(…) | What makes a value of it invalid at the edge? | Pipes |
via = "…" | On a has_many, which of the child’s foreign keys does it follow? | Entities |
complexity = … | Does resolving it cost more than a column read? | Query limits |
An enum column takes #[wire_enum] — it
carries the wire derives, so the field can be #[expose]d like any scalar.
What fails on its own, and how loudly
Section titled “What fails on its own, and how loudly”Two of the traps on this page announce themselves. Neither is a compile error, and the difference matters when you are deciding what to test:
- A hidden column with no default surfaces as a runtime 500 on the first
masked response, not at build time. Reconstructing the model for masking needs
a value for every unexposed column; the macro supplies one for safe scalars
and deliberately emits nothing for a hidden
Uuid, timestamp,Decimalor enum withoutDefault, sowire_to_modelfails closed. Give those an explicit#[wire_default(…)]. - A half-wired soft delete fails the boot, naming the entity and the
service — in one direction. The entity flag without
CrudService::soft_delete_columnis caught; the service opting in without the entity flag registers nothing and is not. See CRUD.
The two you carry yourself
Section titled “The two you carry yourself”Is every column that should be hidden actually unexposed? Exposure is
opt-in, so a column added by a later migration never leaks by omission — the
failure direction is the safe one. The remaining risk is the reverse: an
#[expose] written by habit on a column that should not travel.
Does any ability rule predicate on a column you did not expose? Masking rebuilds the row from the wire body and fills unexposed columns with a placeholder, so such a rule is evaluated against the placeholder rather than the value and masks the wrong fields. Nothing catches this — see Response masking.
Going further
Section titled “Going further”- Entities — the full
#[expose]grammar. - CRUD — what the service half owes each argument.
- Response masking — what the exposed set means once a policy is in play.