Code generation

Rust

Generate a Rust library crate for the domain and behavior layers of a model, ready to put behind your own storage and HTTP code.

The @appling/codegen-rust extension writes a library crate with one Rust module per Appling module. Like the Go extension, it covers types, invariants, derived fields, actions with their state transitions, errors and events, and leaves storage, HTTP and scheduling to you as registry tasks.

Requirements

Rust 1.85 or later with Cargo. Setup adds serde, rust_decimal and chrono with cargo add.

Configuration

{
  "generate": [
    { "extension": "@appling/codegen-rust", "out": "build/shop" }
  ]
}

The crate name is the project name in snake case. The module sales is src/sales.rs, declared in src/lib.rs; nested modules a.b become src/a/b.rs, declared in src/a.rs.

Model to Rust

ModelRust
enuman enum deriving Copy, PartialEq and serde, with the Appling value names as serde names
value, eventa struct deriving Clone, PartialEq and serde; every event is also a variant of the module’s Event enum
entitya struct with id: i64, new() taking the fields that have no default and are neither optional nor lists, one method per derived field, and validate() returning the labels of the invariants that fail
field typesString, i64, bool, rust_decimal::Decimal, chrono::DateTime<chrono::Utc>, chrono::NaiveDate, chrono::TimeDelta; entities as Box<Entity>, lists as Vec<T>, optional fields as Option<T>
actiona function taking &mut the entity and the inputs, returning Result<Vec<Event>, Error>: preconditions return the declared error, the transition is a match on the state with guards as match guards and Error::InvalidTransition otherwise, effects change the record, emitted events are returned
machinetransitions on an event become <machine>_on_<event>(&mut event)
errorsvariants of the module’s Error enum
invariants, policies, transitions, preconditions#[ignore]d tests in the module’s tests

This is the generated place_order of the example:

/// The action sales.PlaceOrder. It returns the events it emits.
pub fn place_order(order: &mut Order) -> Result<Vec<Event>, Error> {
    let mut events = Vec::new();
    if !((order.lines.len() as i64) > 0) {
        return Err(Error::EmptyOrder);
    }
    match order.status {
        OrderStatus::Draft => {
            events.push(Event::OrderPlaced(OrderPlaced { order: Box::new(order.clone()) }));
            order.status = OrderStatus::Placed;
        }
        _ => return Err(Error::InvalidTransition { action: "PlaceOrder", from: format!("{:?}", order.status) }),
    }
    order.placed_at = Some(chrono::Utc::now());
    Ok(events)
}

What becomes a task

Queries, projections, interfaces, pages, flows, messages, use cases, external systems, jobs and policies, and derived fields, defaults, preconditions, guards and effects without a Rust translation, such as expressions over actor, string matching with matches and effects that create records.

Run the generated project

cd build/shop
cargo test