pub trait EsRepo: Send {
type Entity: EsEntity;
type ConstraintViolation: Rejection;
type WriteError: Classify;
type EsQueryFlavor;
// Required methods
fn nested_tree_spec() -> TreeSpec;
fn hydrate_nested_from_rows(
rows_by_tag: &mut HashMap<i32, Vec<Row>>,
tag_cursor: &mut i32,
entities: &mut [Self::Entity],
) -> Result<(), RepoReadError>;
}Expand description
Required trait for all repositories to be compatible with es-entity and generate functions.
All repositories implement this trait to satisfy the basic requirements for
type-safe database operations with the associated entity. The trait ensures validation
that required fields (like entity) are present with compile-time errors.
Implemented by the EsRepo derive macro with #[es_repo] attributes.
§Example
// Would show error for missing entity field if not provided in the `es_repo` attribute
#[derive(EsRepo, Debug)]
#[es_repo(entity = "User", columns(name(ty = "String")))]
pub struct Users {
pool: PgPool, // Required field for database operations
}
impl Users {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}§in_op_only: making the operation mandatory
#[es_repo(in_op_only)] generates only the _in_op variants of every
repo fn. The standalone fns are exactly the ones that open (or borrow) the
pool on the caller’s behalf, so removing them makes passing an operation —
an AtomicOperation for writes, an
IntoOneTimeExecutor for reads — the only way
to reach the database.
With no standalone fn left to open one, the pool field becomes optional. A repo that holds no pool cannot begin its own operation, which is the point: the discipline is enforced by construction rather than by convention.
Calling a non-_in_op fn on such a repo does not compile — the method does
not exist (note the two tests below compile a real repo, so they need the
test database, like the book’s examples):
use es_entity::*;
use serde::{Deserialize, Serialize};
// This repo deliberately KEEPS its pool, so that `create`, were it ever
// generated again, would compile: that makes the E0599 below prove the fn
// is absent, rather than merely that its body could not build an operation.
#[derive(EsRepo)]
#[es_repo(entity = "User", in_op_only, columns(name(ty = "String")))]
pub struct Users {
pool: es_entity::db::Pool,
}
async fn reaches_the_db_without_an_op(repo: &Users, new_user: NewUser) {
// error[E0599]: no method named `create` found — `in_op_only` leaves
// only `create_in_op`, which demands an operation from the caller.
repo.create(new_user).await.unwrap();
}The _in_op twin of the very same call compiles:
use es_entity::*;
use serde::{Deserialize, Serialize};
#[derive(EsRepo)]
#[es_repo(entity = "User", in_op_only, columns(name(ty = "String")))]
pub struct Users {}
async fn takes_the_op_from_its_caller(
repo: &Users,
op: &mut impl AtomicOperation,
new_user: NewUser,
) {
repo.create_in_op(op, new_user).await.unwrap();
}Required Associated Types§
type Entity: EsEntity
Sourcetype ConstraintViolation: Rejection
type ConstraintViolation: Rejection
The one repo Rejection: a violated database constraint the domain
did not otherwise account for. Every generated write op returns
Result<T, crate::RepoWriteError<Self::ConstraintViolation>>; find/list ops
return Result<T, crate::RepoReadError> — reads never reject, so there is
no NotFound rejection, only Fatal.
Sourcetype WriteError: Classify
type WriteError: Classify
The repo’s write classification made public and reusable: a consumer
hand-writing a query against the repo’s own tables classifies its
sqlx::Error the same way a generated write op would, with
.classify::<Self::WriteError>()?.
type EsQueryFlavor
Required Methods§
fn nested_tree_spec() -> TreeSpec
fn hydrate_nested_from_rows( rows_by_tag: &mut HashMap<i32, Vec<Row>>, tag_cursor: &mut i32, entities: &mut [Self::Entity], ) -> Result<(), RepoReadError>
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".