Skip to main content

CrudService

Trait CrudService 

Source
pub trait CrudService<Entity, CreateDto, UpdateDto>: Send + Sync
where Entity: Send + Sync + 'static, CreateDto: Send + Sync + 'static, UpdateDto: Send + Sync + 'static,
{ type Error: Error + Send + Sync;
Show 28 methods // Required methods fn entity_name() -> &'static str; fn list<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, u64), Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn create<'life0, 'async_trait>( &'life0 self, dto: CreateDto, ) -> Pin<Box<dyn Future<Output = Result<Entity, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn get_by_id<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn update<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, dto: UpdateDto, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn partial_update<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, fields: HashMap<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn soft_delete<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<bool, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn bulk_create<'life0, 'async_trait>( &'life0 self, items: Vec<CreateDto>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn upsert<'life0, 'async_trait>( &'life0 self, dto: CreateDto, ) -> Pin<Box<dyn Future<Output = Result<Entity, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn list_deleted<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, u64), Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn restore<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn empty_trash<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn get_deleted_by_id<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn permanent_delete<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<bool, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn list_deleted_filtered<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, u64), Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn count_active<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn aggregate<'life0, 'life1, 'async_trait>( &'life0 self, spec: &'life1 AggregateSpec, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<AggregateResult, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn count_deleted<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; // Provided methods fn fetch_related_json<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _table: &'life1 str, _ids: &'life2 [String], ) -> Pin<Box<dyn Future<Output = Vec<Value>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait { ... } fn list_with_info<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, PaginationInfo), Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn table_name(&self) -> Option<&str> { ... } fn count_active_filtered<'life0, 'async_trait>( &'life0 self, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn bulk_soft_delete<'life0, 'async_trait>( &'life0 self, ids: Vec<String>, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn bulk_restore<'life0, 'async_trait>( &'life0 self, ids: Vec<String>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn bulk_permanent_delete<'life0, 'async_trait>( &'life0 self, ids: Vec<String>, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn restore_all<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn bulk_update<'life0, 'async_trait>( &'life0 self, items: Vec<(String, UpdateDto)>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn bulk_partial_update<'life0, 'async_trait>( &'life0 self, items: Vec<(String, HashMap<String, Value>)>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... }
}
Expand description

Core trait that application services must implement for generic CRUD operations.

This trait provides the contract for all 11 standard Backbone endpoints. Module-specific services implement this trait with their entity types.

§Type Parameters

  • Entity: The domain entity type
  • CreateDto: DTO for create operations
  • UpdateDto: DTO for update operations

§Example

ⓘ
#[async_trait]
impl CrudService<User, CreateUserDto, UpdateUserDto> for UserCrudService {
    type Error = ApplicationError;
    fn entity_name() -> &'static str { "User" }
    // ... implement all methods
}

Required Associated Types§

Source

type Error: Error + Send + Sync

Error type for service operations

Required Methods§

Source

fn entity_name() -> &'static str

Entity name for error messages (e.g., “User”, “Role”)

Source

fn list<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, u64), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. List entities with pagination and filters
Source

fn create<'life0, 'async_trait>( &'life0 self, dto: CreateDto, ) -> Pin<Box<dyn Future<Output = Result<Entity, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Create a new entity
Source

fn get_by_id<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

  1. Get entity by ID
Source

fn update<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, dto: UpdateDto, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

  1. Full update of entity
Source

fn partial_update<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, fields: HashMap<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

  1. Partial update with specific fields
Source

fn soft_delete<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<bool, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

  1. Soft delete entity
Source

fn bulk_create<'life0, 'async_trait>( &'life0 self, items: Vec<CreateDto>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Bulk create multiple entities
Source

fn upsert<'life0, 'async_trait>( &'life0 self, dto: CreateDto, ) -> Pin<Box<dyn Future<Output = Result<Entity, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Upsert (create or update)
Source

fn list_deleted<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, u64), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. List deleted entities (trash)
Source

fn restore<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

  1. Restore soft-deleted entity
Source

fn empty_trash<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Permanently delete all soft-deleted entities
Source

fn get_deleted_by_id<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

  1. Get deleted entity by ID (from trash)
Source

fn permanent_delete<'life0, 'life1, 'async_trait>( &'life0 self, id: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<bool, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

  1. Permanently delete a single soft-deleted entity by ID
Source

fn list_deleted_filtered<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, u64), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. List deleted entities with filters
Source

fn count_active<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Count active (non-deleted) entities
Source

fn aggregate<'life0, 'life1, 'async_trait>( &'life0 self, spec: &'life1 AggregateSpec, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<AggregateResult, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Group and reduce the rows list would return, under the same filters.

The default refuses rather than inventing an answer — see the repository trait for why a zero-filled default would be worse than an error.

Source

fn count_deleted<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Count deleted entities in trash

Provided Methods§

Hydrate ?include= relations: fetch rows from table by id list, as JSON. Default: no expansion. GenericCrudService delegates to its repository.

Source

fn list_with_info<'life0, 'async_trait>( &'life0 self, page: u32, limit: u32, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<(Vec<Entity>, PaginationInfo), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

list, carrying the pagination info (cursor positions included) so the HTTP layer can surface keyset paging. Default: the tuple form — generated services override this with the repository’s cursors.

Source

fn table_name(&self) -> Option<&str>

The schema-qualified table this service reads, when it has one.

Used to key a record’s history. Derived from the mount path instead, this would silently return an empty history whenever a route segment and a Postgres schema disagreed — so it is read from the repository, which is the same string the capture trigger writes.

Source

fn count_active_filtered<'life0, 'async_trait>( &'life0 self, filters: HashMap<String, String>, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Count active entities MATCHING the caller’s filters.

The generated client has always sent filters to /count; the handler never read them, so a filtered count silently answered with the whole table. Nothing caught it because the wrong number is a plausible one.

The default delegates to list, whose total is already built from the same where-clause as its data query — so the filtered count is correct by construction rather than by a second implementation of the same filter parsing, which is exactly how the two drifted apart. It asks for a single row because the rows are discarded; an implementor that can count without fetching may override.

Source

fn bulk_soft_delete<'life0, 'async_trait>( &'life0 self, ids: Vec<String>, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Soft-delete many entities by id. Returns the number affected.
Source

fn bulk_restore<'life0, 'async_trait>( &'life0 self, ids: Vec<String>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Restore many soft-deleted entities by id. Returns the restored rows.
Source

fn bulk_permanent_delete<'life0, 'async_trait>( &'life0 self, ids: Vec<String>, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Permanently delete many soft-deleted entities by id.
Source

fn restore_all<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Restore every soft-deleted entity. Returns the number restored.

Default is a no-op (0) — only the GenericCrudService blanket impl can perform this without an id list.

Source

fn bulk_update<'life0, 'async_trait>( &'life0 self, items: Vec<(String, UpdateDto)>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Full-update many entities. Each item is (id, UpdateDto).
Source

fn bulk_partial_update<'life0, 'async_trait>( &'life0 self, items: Vec<(String, HashMap<String, Value>)>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Entity>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

  1. Partial-update many entities. Each item is (id, field_map).

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl<E, C, U, R> CrudService<E, C, U> for GenericCrudService<E, C, U, R>
where E: PersistentEntity + Clone + Serialize + DeserializeOwned + FromCreateDto<C> + ApplyUpdateDto<U> + Send + Sync + 'static, C: Send + Sync + 'static, U: Send + Sync + 'static, R: CrudRepository<E> + Send + Sync + 'static,

Blanket impl so that any GenericCrudService<E, C, U, R> automatically satisfies CrudService<E, C, U> without generated adapter structs.

Generated code emits only a type alias: pub type UserService = GenericCrudService<User, CreateUserDto, UpdateUserDto, UserRepository>;

That alias now directly implements CrudService and can be passed to BackboneCrudHandler without any additional boilerplate.

Source§

impl<R, E, C, U> CrudService<E, C, U> for CrudServiceAdapter<R, E, C, U>
where R: CrudRepository<E> + Send + Sync + 'static, E: PersistentEntity + Clone + 'static, C: Send + Sync + 'static, U: Send + Sync + 'static,

Source§

impl<R, E, C, U> CrudService<E, C, U> for SearchableCrudServiceAdapter<R, E, C, U>
where R: SearchableRepository<E> + Send + Sync + 'static, E: PersistentEntity + Clone + 'static, C: Send + Sync + 'static, U: Send + Sync + 'static,

Source§

impl<R, E> CrudService<E, E, E> for SimpleCrudServiceAdapter<R, E>
where R: CrudRepository<E> + Send + Sync + 'static, E: PersistentEntity + Clone + 'static,