Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

API Reference - Rust

This document is the API reference for Qleany-generated Rust code. It covers the APIs you interact with as a developer: Entity Controllers, Feature Controllers, and the Unit of Work proc macros you adapt when implementing custom use cases.

For general architecture and code structure, see Generated Code - Rust.


Entity Controller

File: crates/direct_access/src/{entity}/{entity}_controller.rs

Entity controllers are free functions (not methods on a struct) that provide CRUD and relationship operations on a single entity type. All operations are synchronous and return anyhow::Result<T>.

CRUD Functions

create_orphan

#![allow(unused)]
fn main() {
pub fn create_orphan(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    entity: &CreateCarDto,
) -> Result<CarDto>
}

Creates a single entity without an owner.

create_orphan_multi

#![allow(unused)]
fn main() {
pub fn create_orphan_multi(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    entities: &[CreateCarDto],
) -> Result<Vec<CarDto>>
}

Batch version of create_orphan.

create

#![allow(unused)]
fn main() {
// Only available if the entity has an owner (defined in the manifest)
pub fn create(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    entity: &CreateCarDto,
    owner_id: EntityId,
    index: i32,              // insertion position; -1 = append
) -> Result<CarDto>
}

Creates an entity and attaches it to its owner.

create_multi

#![allow(unused)]
fn main() {
pub fn create_multi(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    entities: &[CreateCarDto],
    owner_id: EntityId,
    index: i32,
) -> Result<Vec<CarDto>>
}

Batch version of create.

get

#![allow(unused)]
fn main() {
pub fn get(
    db_context: &DbContext,
    id: &EntityId,
) -> Result<Option<CarDto>>
}

Fetches a single entity by ID. Returns None if not found.

get_multi

#![allow(unused)]
fn main() {
pub fn get_multi(
    db_context: &DbContext,
    ids: &[EntityId],
) -> Result<Vec<Option<CarDto>>>
}

Fetches multiple entities. Each entry is None if the corresponding ID was not found.

get_all

#![allow(unused)]
fn main() {
pub fn get_all(
    db_context: &DbContext,
) -> Result<Vec<CarDto>>
}

Returns all entities of this type. Use with caution on large tables.

update

#![allow(unused)]
fn main() {
pub fn update(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    entity: &CarDto,
) -> Result<CarDto>
}

Updates a single entity.

update_multi

#![allow(unused)]
fn main() {
pub fn update_multi(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    entities: &[CarDto],
) -> Result<Vec<CarDto>>
}

Batch version of update.

remove

#![allow(unused)]
fn main() {
pub fn remove(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    id: &EntityId,
) -> Result<()>
}

Deletes a single entity. Strong (owned) children are cascade-deleted.

remove_multi

#![allow(unused)]
fn main() {
pub fn remove_multi(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    ids: &[EntityId],
) -> Result<()>
}

Batch version of remove.

Relationship Functions

Only available if the entity has forward relationships defined in the manifest.

get_relationship

#![allow(unused)]
fn main() {
pub fn get_relationship(
    db_context: &DbContext,
    id: &EntityId,
    field: &CarRelationshipField,
) -> Result<Vec<EntityId>>
}

Returns the IDs of related entities for a given relationship field.

get_relationship_many

#![allow(unused)]
fn main() {
pub fn get_relationship_many(
    db_context: &DbContext,
    ids: &[EntityId],
    field: &CarRelationshipField,
) -> Result<HashMap<EntityId, Vec<EntityId>>>
}

Batch version of get_relationship. Returns a map from each entity ID to its related IDs.

get_relationship_count

#![allow(unused)]
fn main() {
pub fn get_relationship_count(
    db_context: &DbContext,
    id: &EntityId,
    field: &CarRelationshipField,
) -> Result<usize>
}

Returns the number of related entities without loading them.

get_relationship_in_range

#![allow(unused)]
fn main() {
pub fn get_relationship_in_range(
    db_context: &DbContext,
    id: &EntityId,
    field: &CarRelationshipField,
    offset: usize,
    limit: usize,
) -> Result<Vec<EntityId>>
}

Returns a paginated slice of related entity IDs, starting at offset and returning at most limit entries.

set_relationship

#![allow(unused)]
fn main() {
pub fn set_relationship(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    // only if entity is undoable:
    undo_redo_manager: &mut UndoRedoManager,
    stack_id: Option<u64>,
    dto: &CarRelationshipDto,
) -> Result<()>
}

Replaces the relationship. The CarRelationshipDto contains the entity ID, the relationship field, and the new list of related IDs.

Usage Examples

#![allow(unused)]
fn main() {
use direct_access::car::controller;
use direct_access::car::dtos::{CreateCarDto, CarDto};
use common::types::EntityId;

// Read-only operations (no event_hub needed)
let car = controller::get(&db_context, &EntityId::new(1))?;
let all_cars = controller::get_all(&db_context)?;
let some_cars = controller::get_multi(&db_context, &[EntityId::new(1), EntityId::new(2)])?;

// Write operations (need event_hub for event emission)
let created = controller::create_orphan(&db_context, &event_hub, &create_dto)?;
let updated = controller::update(&db_context, &event_hub, &car_dto)?;
controller::remove(&db_context, &event_hub, &EntityId::new(1))?;

// Undoable write operations (need undo_redo_manager)
let created = controller::create_orphan(
    &db_context, &event_hub, &mut undo_redo_manager, Some(stack_id), &create_dto,
)?;

// Relationships
let passenger_ids = controller::get_relationship(
    &db_context, &EntityId::new(1), &CarRelationshipField::Passengers,
)?;
let many = controller::get_relationship_many(
    &db_context, &[EntityId::new(1), EntityId::new(2)], &CarRelationshipField::Passengers,
)?;
let count = controller::get_relationship_count(
    &db_context, &EntityId::new(1), &CarRelationshipField::Passengers,
)?;
let page = controller::get_relationship_in_range(
    &db_context, &EntityId::new(1), &CarRelationshipField::Passengers, 0, 10,
)?;
controller::set_relationship(
    &db_context, &event_hub, &relationship_dto,
)?;
}

Feature Controller

File: crates/{feature}/src/{feature}_controller.rs

Feature controllers are free functions for custom use cases grouped by feature. The controller is generated; you implement the use case logic.

Generated Functions

For each use case defined in the manifest, the controller generates a function. The shape depends on the use case configuration:

Standard use case (with input DTO, with output DTO)

#![allow(unused)]
fn main() {
pub fn save(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    dto: &SaveDto,
) -> Result<SaveResultDto>
}

Standard use case (no input DTO, no output DTO)

#![allow(unused)]
fn main() {
pub fn initialize(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
) -> Result<()>
}

Long operation use case

Long operations run on a background thread with progress tracking:

#![allow(unused)]
fn main() {
// Start the operation, returns an operation ID
pub fn generate_code(
    db_context: &DbContext,
    event_hub: &Arc<EventHub>,
    long_operation_manager: &mut LongOperationManager,
    dto: &GenerateCodeDto,
) -> Result<String>

// Poll progress
pub fn get_generate_code_progress(
    long_operation_manager: &LongOperationManager,
    operation_id: &str,
) -> Option<OperationProgress>

// Get result
pub fn get_generate_code_result(
    long_operation_manager: &LongOperationManager,
    operation_id: &str,
) -> Result<Option<GenerateCodeResultDto>>
}

Event Emission

After a successful use case execution, the controller automatically emits a feature event via the EventHub:

#![allow(unused)]
fn main() {
event_hub.send_event(Event {
    origin: Origin::HandlingManifest(Save),
    ids: vec![],
    data: None,
});
}

You can subscribe to these events to trigger UI updates or other reactions.

Usage Examples

#![allow(unused)]
fn main() {
use handling_manifest::controller;

// Standard use case
let result = controller::save(&db_context, &event_hub, &save_dto)?;

// Long operation
let op_id = controller::generate_code(
    &db_context, &event_hub, &mut long_op_manager, &dto,
)?;

// Check progress (e.g., in a loop or callback)
if let Some(progress) = controller::get_generate_code_progress(&long_op_manager, &op_id) {
    println!("{}% - {}", progress.percentage, progress.message.unwrap_or_default());
}

// Get result when done
if let Some(result) = controller::get_generate_code_result(&long_op_manager, &op_id)? {
    println!("Generated {} files", result.file_count);
}
}

Custom Unit of Work (Proc Macros)

Files you edit:

  • Use case + trait: crates/{feature}/src/use_cases/{use_case}_uc.rs
  • Implementation: crates/{feature}/src/units_of_work/{use_case}_uow.rs

When Qleany generates a custom feature use case, it scaffolds a UoW trait and implementation with TODO comments. Your job is to adapt the #[macros::uow_action] attributes to expose only the entity operations your use case needs.

How It Works

The #[macros::uow_action] proc macro decorates the impl Trait for UoW block. Each attribute generates a trait method (on the trait) or an implementation (on the impl block). The same set of attributes must appear on both the trait definition and the impl block.

The generated UoW implements either CommandUnitOfWork (read-write) or QueryUnitOfWork (read-only) for transaction management.

Available Actions

Read-write actions (use with CommandUnitOfWork):

ActionGenerated method signature
CreateOrphanfn create_orphan_name(&self, entity: &Name) -> Result<Name>
CreateOrphanMultifn create_orphan_name_multi(&self, entities: &[Name]) -> Result<Vec<Name>>
Createfn create_name(&self, entity: &Name, owner_id: EntityId, index: i32) -> Result<Name>
CreateMultifn create_name_multi(&self, entities: &[Name], owner_id: EntityId, index: i32) -> Result<Vec<Name>>
Getfn get_name(&self, id: &EntityId) -> Result<Option<Name>>
GetMultifn get_name_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Name>>>
GetAllfn get_all_name(&self) -> Result<Vec<Name>>
Updatefn update_name(&self, entity: &Name) -> Result<Name>
UpdateMultifn update_name_multi(&self, entities: &[Name]) -> Result<Vec<Name>>
Removefn remove_name(&self, id: &EntityId) -> Result<()>
RemoveMultifn remove_name_multi(&self, ids: &[EntityId]) -> Result<()>
GetRelationshipfn get_name_relationship(&self, id: &EntityId, field: &RF) -> Result<Vec<EntityId>>
GetRelationshipManyfn get_name_relationship_many(&self, ids: &[EntityId], field: &RF) -> Result<HashMap<EntityId, Vec<EntityId>>>
GetRelationshipCountfn get_name_relationship_count(&self, id: &EntityId, field: &RF) -> Result<usize>
GetRelationshipInRangefn get_name_relationship_in_range(&self, id: &EntityId, field: &RF, offset: usize, limit: usize) -> Result<Vec<EntityId>>
GetRelationshipsFromRightIdsfn get_name_relationships_from_right_ids(&self, field: &RF, right_ids: &[EntityId]) -> Result<Vec<(EntityId, Vec<EntityId>)>>
SetRelationshipfn set_name_relationship(&self, id: &EntityId, field: &RF, right_ids: &[EntityId]) -> Result<()>
SetRelationshipMultifn set_name_relationship_multi(&self, field: &RF, relationships: Vec<(EntityId, Vec<EntityId>)>) -> Result<()>
Snapshotfn snapshot_name(&self, ids: &[EntityId]) -> Result<EntityTreeSnapshot>
Restorefn restore_name(&self, snap: &EntityTreeSnapshot) -> Result<()>

Read-only actions (use with QueryUnitOfWork):

ActionGenerated method signature
GetROfn get_name(&self, id: &EntityId) -> Result<Option<Name>>
GetMultiROfn get_name_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Name>>>
GetAllROfn get_all_name(&self) -> Result<Vec<Name>>
GetRelationshipROfn get_name_relationship(&self, id: &EntityId, field: &RF) -> Result<Vec<EntityId>>
GetRelationshipManyROfn get_name_relationship_many(&self, ids: &[EntityId], field: &RF) -> Result<HashMap<EntityId, Vec<EntityId>>>
GetRelationshipCountROfn get_name_relationship_count(&self, id: &EntityId, field: &RF) -> Result<usize>
GetRelationshipInRangeROfn get_name_relationship_in_range(&self, id: &EntityId, field: &RF, offset: usize, limit: usize) -> Result<Vec<EntityId>>
GetRelationshipsFromRightIdsROfn get_name_relationships_from_right_ids(&self, field: &RF, right_ids: &[EntityId]) -> Result<Vec<(EntityId, Vec<EntityId>)>>

Do not mix read-only (*RO) and write actions in the same unit of work.

For long operations, add thread_safe = true to the implementation attributes (not the trait). This makes the generated code use Mutex instead of RefCell for thread safety.

Full Example

Given a “Save” use case in the “HandlingManifest” feature that needs to read and update Work and Setting entities:

Use case + trait (use_cases/save_uc.rs):

The developer only have to adapt the #[macros::uow_action] attributes to expose only the entity operations your use case needs. Then, implement the use case logic.

#![allow(unused)]
fn main() {
use common::database::CommandUnitOfWork;

pub trait SaveUnitOfWorkFactoryTrait {
    fn create(&self) -> Box<dyn SaveUnitOfWorkTrait>;
}

// Adapt these macros to your needs:
#[macros::uow_action(entity = "Work", action = "Get")]
#[macros::uow_action(entity = "Work", action = "Update")]
#[macros::uow_action(entity = "Setting", action = "Get")]
#[macros::uow_action(entity = "Setting", action = "GetMulti")]
pub trait SaveUnitOfWorkTrait: CommandUnitOfWork {}

pub struct SaveUseCase {
    uow_factory: Box<dyn SaveUnitOfWorkFactoryTrait>,
}

impl SaveUseCase {
    pub fn new(uow_factory: Box<dyn SaveUnitOfWorkFactoryTrait>) -> Self {
        SaveUseCase { uow_factory }
    }

    pub fn execute(&mut self, dto: &SaveDto) -> Result<SaveResultDto> {
        let mut uow = self.uow_factory.create();
        uow.begin_transaction()?;

        // Use the UoW methods you declared:
        let work = uow.get_work(&dto.work_id)?
            .ok_or_else(|| anyhow!("Work not found"))?;

        let setting = uow.get_setting(&dto.setting_id)?
            .ok_or_else(|| anyhow!("Setting not found"))?;

        // ... your business logic ...

        let updated_work = uow.update_work(&work)?;

        uow.commit()?;

        Ok(SaveResultDto { /* ... */ })
    }
}
}

Implementation (units_of_work/save_uow.rs):

The developer only have to adapt the #[macros::uow_action] attributes to expose only the entity operations your use case needs.

#![allow(unused)]
fn main() {
use crate::use_cases::save_uc::{SaveUnitOfWorkFactoryTrait, SaveUnitOfWorkTrait};
use common::database::CommandUnitOfWork;

pub struct SaveUnitOfWork {
    context: DbContext,
    transaction: Option<Transaction>,
    event_hub: Arc<EventHub>,
    event_buffer: RefCell<EventBuffer>,
}

impl SaveUnitOfWork {
    pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
        SaveUnitOfWork {
            context: db_context.clone(),
            transaction: None,
            event_hub: event_hub.clone(),
            event_buffer: RefCell::new(EventBuffer::new()),
        }
    }
}

impl CommandUnitOfWork for SaveUnitOfWork {
    fn begin_transaction(&mut self) -> Result<()> {
        self.transaction = Some(Transaction::begin_write_transaction(&self.context)?);
        self.event_buffer.get_mut().begin_buffering();
        Ok(())
    }

    fn commit(&mut self) -> Result<()> {
        self.transaction.take().unwrap().commit()?;
        for event in self.event_buffer.get_mut().flush() {
            self.event_hub.send_event(event);
        }
        Ok(())
    }

    fn rollback(&mut self) -> Result<()> {
        self.transaction.take().unwrap().rollback()?;
        self.event_buffer.get_mut().discard();
        Ok(())
    }

    fn create_savepoint(&self) -> Result<types::Savepoint> {
        self.transaction.as_ref().unwrap().create_savepoint()
    }

    fn restore_to_savepoint(&mut self, savepoint: types::Savepoint) -> Result<()> {
        let mut transaction = self.transaction.take().unwrap();
        transaction.restore_to_savepoint(savepoint)?;
        self.event_buffer.get_mut().discard();
        self.event_hub.send_event(Event {
            origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
            ids: vec![],
            data: None,
        });
        self.transaction = Some(transaction);
        Ok(())
    }
}

// Same macros as the trait, matching exactly:
#[macros::uow_action(entity = "Work", action = "Get")]
#[macros::uow_action(entity = "Work", action = "Update")]
#[macros::uow_action(entity = "Setting", action = "Get")]
#[macros::uow_action(entity = "Setting", action = "GetMulti")]
impl SaveUnitOfWorkTrait for SaveUnitOfWork {}

pub struct SaveUnitOfWorkFactory {
    context: DbContext,
    event_hub: Arc<EventHub>,
}

impl SaveUnitOfWorkFactory {
    pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
        SaveUnitOfWorkFactory {
            context: db_context.clone(),
            event_hub: event_hub.clone(),
        }
    }
}

impl SaveUnitOfWorkFactoryTrait for SaveUnitOfWorkFactory {
    fn create(&self) -> Box<dyn SaveUnitOfWorkTrait> {
        Box::new(SaveUnitOfWork::new(&self.context, &self.event_hub))
    }
}
}

Transaction Methods

These are available on every UoW via CommandUnitOfWork or QueryUnitOfWork.

CommandUnitOfWork (read-write):

MethodPurpose
begin_transaction(&mut self)Start a write transaction and begin event buffering
commit(&mut self)Commit; flush buffered events on success
rollback(&mut self)Roll back the transaction and discard buffered events
create_savepoint(&self)Create a savepoint within the current transaction
restore_to_savepoint(&mut self, sp)Restore to a savepoint, discard events, emit Reset

Do not use savepoint without understanding the implications: please read Undo-Redo Architecture # savepoints

QueryUnitOfWork (read-only):

MethodPurpose
begin_transaction(&self)Start a read transaction
end_transaction(&self)End the read transaction

The event buffering ensures that if a transaction fails, no events are emitted and the UI stays consistent.

Undoable Custom Use Cases

If a custom use case is marked undoable: true in the manifest, the generated scaffold includes an UndoRedoCommand impl:

#![allow(unused)]
fn main() {
impl UndoRedoCommand for SaveUseCase {
    fn undo(&mut self) -> Result<()> {
        // TODO: implement undo logic
        unimplemented!();
    }

    fn redo(&mut self) -> Result<()> {
        // TODO: implement redo logic
        unimplemented!();
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}
}

You implement the undo() and redo() methods. The controller will call undo_redo_manager.add_command_to_stack(Box::new(uc), stack_id) after a successful execute().