Expand description
Core crate for the cinderblock framework — a declarative, resource-oriented application framework for Rust.
This crate provides the resource! macro, the Resource trait, CRUD
operation traits (Create, Update, Destroy, ReadAction), the
runtime Context, and a built-in InMemoryDataLayer
for prototyping.
§The resource! macro
The resource! macro is the primary entry point for defining domain
models. It accepts a declarative DSL and generates:
- A struct with the declared attributes (derives
Serialize,Deserialize,Clone,Debug). - A
Resourcetrait impl with primary key metadata and the configured data layer. - For each action, a marker struct and the corresponding CRUD trait impl. Create and update actions also generate an input struct.
- Extension dispatch — each declared extension receives the full DSL tokens so it can generate its own code (e.g. route handlers, SQL queries).
§DSL reference
ⓘ
use cinderblock_core::resource;
resource! {
// A dotted name identifying the resource. The last segment becomes the
// struct name; all segments are available at runtime via `Resource::NAME`.
name = Helpdesk.Support.Ticket;
// Optional: override the data layer. Defaults to `InMemoryDataLayer`.
// data_layer = cinderblock_sqlx::sqlite::SqliteDataLayer;
attributes {
// Each attribute is `name Type` followed by either `;` or an options block.
ticket_id Uuid {
primary_key true; // Marks this as the primary key (default: false).
writable false; // Excludes from create/update input structs (default: true).
generated true; // Indicates the PK is auto-generated (default: false).
default || Uuid::new_v4(); // Closure producing a default value.
}
// Simple form — writable, not a primary key, no default.
subject String;
status TicketStatus;
}
actions {
// ── Read actions ──
//
// A read action returns `Vec<Resource>`. It can optionally declare
// arguments (typed query parameters) and filters.
// Minimal read — no filters, no arguments. Arguments type is `()`.
read all;
// Read with a compile-time literal filter.
read open_tickets {
filter { status == TicketStatus::Open };
};
// Read with a runtime argument bound to a filter.
// Generates a `ByStatusArguments` struct with a `status` field.
read by_status {
argument { status: TicketStatus };
filter { status == arg(status) };
};
// Optional arguments use `Option<T>`. When `None`, the filter is
// skipped entirely at runtime.
read search {
argument { status: Option<TicketStatus> };
filter { status == arg(status) };
};
// ── Create actions ──
//
// A create action generates an input struct from the resource's
// writable attributes and a `Create<A>` impl that builds a new
// resource instance.
// Accepts all writable attributes. Generates `OpenInput { subject, status }`.
create open;
// Restrict which fields the input struct includes.
// Generates `AssignInput { subject }`.
create assign {
accept [subject];
};
// ── Update actions ──
//
// An update action fetches the resource by primary key, applies
// changes, and persists the result. It generates an input struct
// and an `Update<A>` impl.
// Accepts all writable attributes.
update edit;
// Accept no fields from the caller, but apply a programmatic
// mutation via `change_ref`. Multiple `change_ref` blocks are
// applied in order.
update close {
accept [];
change_ref |ticket| {
ticket.status = TicketStatus::Closed;
};
};
// ── Destroy actions ──
//
// A destroy action deletes the resource by primary key.
destroy remove;
}
// Optional: declare extensions. Each extension module receives the
// full resource DSL and its own configuration block, then generates
// additional code (e.g. route handlers, SQL queries).
extensions {
cinderblock_json_api {
route = { method = GET; path = "/"; action = all; };
route = { method = POST; path = "/"; action = open; };
};
cinderblock_sqlx {
table = "tickets";
};
}
}§Generated items
For a resource named Helpdesk.Support.Ticket with actions open
(create), close (update), open_tickets (read), and remove (destroy),
the macro generates:
| Generated item | Kind | Description |
|---|---|---|
Ticket | struct | The resource struct with all declared attributes |
Open | struct (marker) | Create action marker |
OpenInput | struct | Input fields for the open create action |
Close | struct (marker) | Update action marker |
CloseInput | struct | Input fields for the close update action |
OpenTickets | struct (marker) | Read action marker |
Remove | struct (marker) | Destroy action marker |
Action names are converted to PascalCase for the marker and input struct
names (e.g. open_tickets becomes OpenTickets, and its input struct
would be OpenTicketsInput).
§Using the generated types
ⓘ
use cinderblock_core::Context;
let ctx = Context::new();
// Create
let ticket = cinderblock_core::create::<Ticket, Open>(
OpenInput { subject: "Printer is broken".into(), status: TicketStatus::Open },
&ctx,
).await?;
// Read (with arguments)
let open = cinderblock_core::read::<Ticket, ByStatus>(
&ctx,
&ByStatusArguments { status: TicketStatus::Open },
).await?;
// Read (no arguments — pass `&()`)
let all_open = cinderblock_core::read::<Ticket, OpenTickets>(&ctx, &()).await?;
// Update
let closed = cinderblock_core::update::<Ticket, Close>(
&ticket.ticket_id,
CloseInput {},
&ctx,
).await?;
// Destroy
let removed = cinderblock_core::destroy::<Ticket, Remove>(
&ticket.ticket_id,
&ctx,
).await?;Re-exports§
pub use serde;
Modules§
Macros§
Structs§
Traits§
- Create
- Trait placed on a
Resourcespecifying how to create the resource using actionA. - Destroy
- Marker trait for destroy actions.
- Perform
Read - Trait indicating that a
DataLayercan performReadActionA. - Read
Action - Marker trait showing indicating that a struct is a read action.
- Resource
- Marker trait for a resource.
- Update
- Trait placed on a
Resourcespecifying how to update a resource using actionA.
Functions§
- create
- Create resource
Rusing actionA. - destroy
- Destroy resource
Rusing actionA. - read
- Read resource
Rusing actionA. - update
- Update resource
Rusing actionA. First fetches an instance ofRusing the primary key.