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):
| Action | Generated method signature |
|---|---|
CreateOrphan | fn create_orphan_name(&self, entity: &Name) -> Result<Name> |
CreateOrphanMulti | fn create_orphan_name_multi(&self, entities: &[Name]) -> Result<Vec<Name>> |
Create | fn create_name(&self, entity: &Name, owner_id: EntityId, index: i32) -> Result<Name> |
CreateMulti | fn create_name_multi(&self, entities: &[Name], owner_id: EntityId, index: i32) -> Result<Vec<Name>> |
Get | fn get_name(&self, id: &EntityId) -> Result<Option<Name>> |
GetMulti | fn get_name_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Name>>> |
GetAll | fn get_all_name(&self) -> Result<Vec<Name>> |
Update | fn update_name(&self, entity: &Name) -> Result<Name> |
UpdateMulti | fn update_name_multi(&self, entities: &[Name]) -> Result<Vec<Name>> |
Remove | fn remove_name(&self, id: &EntityId) -> Result<()> |
RemoveMulti | fn remove_name_multi(&self, ids: &[EntityId]) -> Result<()> |
GetRelationship | fn get_name_relationship(&self, id: &EntityId, field: &RF) -> Result<Vec<EntityId>> |
GetRelationshipMany | fn get_name_relationship_many(&self, ids: &[EntityId], field: &RF) -> Result<HashMap<EntityId, Vec<EntityId>>> |
GetRelationshipCount | fn get_name_relationship_count(&self, id: &EntityId, field: &RF) -> Result<usize> |
GetRelationshipInRange | fn get_name_relationship_in_range(&self, id: &EntityId, field: &RF, offset: usize, limit: usize) -> Result<Vec<EntityId>> |
GetRelationshipsFromRightIds | fn get_name_relationships_from_right_ids(&self, field: &RF, right_ids: &[EntityId]) -> Result<Vec<(EntityId, Vec<EntityId>)>> |
SetRelationship | fn set_name_relationship(&self, id: &EntityId, field: &RF, right_ids: &[EntityId]) -> Result<()> |
SetRelationshipMulti | fn set_name_relationship_multi(&self, field: &RF, relationships: Vec<(EntityId, Vec<EntityId>)>) -> Result<()> |
Snapshot | fn snapshot_name(&self, ids: &[EntityId]) -> Result<EntityTreeSnapshot> |
Restore | fn restore_name(&self, snap: &EntityTreeSnapshot) -> Result<()> |
Read-only actions (use with QueryUnitOfWork):
| Action | Generated method signature |
|---|---|
GetRO | fn get_name(&self, id: &EntityId) -> Result<Option<Name>> |
GetMultiRO | fn get_name_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Name>>> |
GetAllRO | fn get_all_name(&self) -> Result<Vec<Name>> |
GetRelationshipRO | fn get_name_relationship(&self, id: &EntityId, field: &RF) -> Result<Vec<EntityId>> |
GetRelationshipManyRO | fn get_name_relationship_many(&self, ids: &[EntityId], field: &RF) -> Result<HashMap<EntityId, Vec<EntityId>>> |
GetRelationshipCountRO | fn get_name_relationship_count(&self, id: &EntityId, field: &RF) -> Result<usize> |
GetRelationshipInRangeRO | fn get_name_relationship_in_range(&self, id: &EntityId, field: &RF, offset: usize, limit: usize) -> Result<Vec<EntityId>> |
GetRelationshipsFromRightIdsRO | fn 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):
| Method | Purpose |
|---|---|
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):
| Method | Purpose |
|---|---|
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().