Error handling
Generated operations return rent::runtime::EntityError. Preserve the source with ? unless the application can
take a specific recovery action.
use rent::runtime::EntityError;
match client.user().email_eq(email).only().await {
Ok(user) => show_profile(user),
Err(EntityError::NotFound { .. }) => show_missing_profile(),
Err(error) => return Err(error.into()),
}Query and mutation invariants
| Error | Meaning | Typical response |
|---|---|---|
NotFound | only() found no row | Return absence or a domain-level not-found error |
NotSingular | only() found several rows | Fix the uniqueness or filter invariant |
MissingRequiredField | A create omitted required data | Create with create_<entity>_from(New<Entity> { .. }) so the compiler requires every field |
InvalidConflictTarget | An upsert target is empty or unavailable | Use a matching unique field or tuple |
InvalidPageSize | A page requested zero rows | Reject or clamp the input |
InvalidPageNumber | An offset page number was below one | Reject the input. Pages are one-indexed |
InvalidPageOffset | An offset page resolved past the largest offset the database accepts | Bound the accepted page number at the API edge |
InvalidCursor | A cursor is malformed or belongs to another key | Return an invalid-pagination response |
InvalidStreamBatchSize | A stream requested an empty batch | Choose a positive bounded batch |
SingleTargetRequired | A one-row operation has no explicit target | Use *_one(&entity) or .one(id) |
InvalidCursorOrdering | Cursor pagination was combined with a conflicting order or an offset | Remove the order or offset, or choose a matching cursor |
InvalidEagerBatch | A relationship load cannot be split within the database parameter budget | Load fewer parents per query or narrow the window |
InvalidExtensionValue | A generated extension field rejected an invalid typed value | Validate the value before building the mutation |
MissingGeneratedId | The backend did not return the generated identifier needed to reload a created entity | Check the identity column and backend configuration |
Builder | SQL builder validation failed before execution | Fix the query shape the wrapped BuilderError names |
FastPathMiddleware | A raw high-throughput path such as COPY cannot preserve configured policies, hooks, or codecs | Use the ordinary builders for that entity |
Model accessors such as post.author()? return a separate RelationNotLoaded error, not an EntityError, when the
relationship was not eagerly loaded. Add the corresponding with_* call to the query.
Unique violations
UniqueViolation is returned when a create, update, upsert, or PostgreSQL COPY would duplicate a value a unique
constraint protects. It is typed so an API can answer 409 Conflict without inspecting driver errors:
match client.create_agent_from(input).save().await {
Err(EntityError::UniqueViolation { fields, .. })
if fields.contains(&"key") =>
{
Err(ApiError::Conflict("this agent key is taken"))
}
result => result.map_err(ApiError::from),
}fields names the schema fields the violated constraint covers: ["owner_id", "key"] for @@unique([owner_id, key]),
["email"] for email String @unique, the identity fields for a duplicate primary key. It never contains the
duplicated values. Rent identifies the constraint from the name the database reports and, failing that, from the
columns it reports, so indexes created under an older naming convention still resolve. When a database reports
neither, as MySQL does for a unique index Rent did not create, fields is empty but the error is still a
UniqueViolation.
A policy-scoped upsert reports a conflict as a policy denial instead, so a caller cannot probe for protected rows.
Concurrency and transactions
OptimisticLock is an expected concurrency outcome: reload, merge, and retry only when the application's semantics
allow it. VersionOverflow indicates an invalid long-lived version counter and should be treated as an invariant
failure.
TransactionClosed, TransactionAlreadyActive, and TransactionsUnsupported report lifecycle misuse.
TransactionRollback preserves both the operation error and rollback failure so neither is lost.
TransactionError<E> is used when a transaction closure returns an application-defined error. Its variants keep
database and application failures distinct.
Driver and middleware errors
Driver wraps SQLx transport, server, decoding, cancellation, configuration, and argument errors. Check
is_retryable_transaction() only around a complete idempotent transaction. Do not retry arbitrary statements after
an unknown partial outcome.
Policy, Intercept, and Hook identify the middleware layer that rejected an operation. FieldCodec identifies
the entity, field, and encode/decode direction for encryption, blob, or custom storage failures.
NativeProjection identifies the model when a native selected value cannot be decoded. Inspect its source for
the column/type mismatch, and check the schema against the live database. Retrying does not fix incompatible
storage types. Dynamic projection decoding reports Decode or InvalidProjection instead.
Migration and extension commands expose their own typed plan, inspection, apply, directory, and registry errors. Their CLI messages include the failing file, object, extension, or operation whenever it is known.
