### Run Statum Examples and Tests Source: https://github.com/eboody/statum/blob/main/RELEASE_NOTES.md Execute the test suite for the statum-examples package using Cargo. This command verifies the functionality of the provided examples. ```bash cargo test -p statum-examples ``` -------------------------------- ### Statum 60-Second Example: Light Switch Source: https://github.com/eboody/statum/blob/main/README.md A concise example demonstrating Statum's core features: defining states (Off, On), a machine (LightSwitch), and transitions (switch_on, switch_off). It illustrates how states become distinct types, enforcing compile-time checks for valid transitions. ```rust use statum::{machine, state, transition}; #[state] enum LightState { Off, On, } #[machine] struct LightSwitch { name: String, } #[transition] impl LightSwitch { fn switch_on(self) -> LightSwitch { self.transition() } } #[transition] impl LightSwitch { fn switch_off(self) -> LightSwitch { self.transition() } } fn main() { let light = LightSwitch::::builder() .name("desk lamp".to_owned()) .build(); let light = light.switch_on(); let _light = light.switch_off(); } ``` -------------------------------- ### Install statum-core dependency Source: https://github.com/eboody/statum/blob/main/statum-core/README.md Add the statum-core crate to your Cargo.toml file to include it in your Rust project. ```toml [dependencies] statum-core = "0.5" ``` -------------------------------- ### Define a state machine with Statum Source: https://github.com/eboody/statum/blob/main/statum/README.md This example demonstrates defining states, a machine context, and legal transitions between states using the #[state], #[machine], and #[transition] macros. ```rust use statum::{machine, state, transition}; #[state] enum LightState { Off, On, } #[machine] struct Light { name: String, } #[transition] impl Light { fn switch_on(self) -> Light { self.transition() } } #[transition] impl Light { fn switch_off(self) -> Light { self.transition() } } ``` -------------------------------- ### Implement Publish Transition for Document State Machine (Rust) Source: https://github.com/eboody/statum/blob/main/docs/tutorial-review-workflow.md Introduces the first legal transition, 'publish', which moves the document from the 'Draft' state to the 'Published' state. This example uses the statum library to define a type-safe transition method on the Draft state. ```rust use statum::{machine, state, transition}; #[state] enum DocumentState { Draft, Published, } #[machine] struct Document { id: i64, title: String, body: String, } #[transition] impl Document { fn publish(self) -> Document { self.transition() } } fn main() { let draft = Document::::builder() .id(1) .title("RFC: Typed review workflow".to_owned()) .body("Start small, then add features.".to_owned()) .build(); let _published = draft.publish(); } ``` -------------------------------- ### Install Statum dependency Source: https://github.com/eboody/statum/blob/main/statum/README.md Add the statum crate to your Cargo.toml file to begin using state machine macros and traits. ```toml [dependencies] statum = "0.6.3" ``` -------------------------------- ### Install statum-macros dependency Source: https://github.com/eboody/statum/blob/main/statum-macros/README.md Add the statum-macros crate to your Cargo.toml file to enable procedural macro support for state machine definitions. ```toml [dependencies] statum-macros = "0.5" ``` -------------------------------- ### Install macro_registry dependency Source: https://github.com/eboody/statum/blob/main/macro_registry/README.md Add the macro_registry crate to your Cargo.toml file to begin using its proc-macro infrastructure. ```toml [dependencies] macro_registry = "0.5.4" ``` -------------------------------- ### Implement Statum State Machine Skeleton Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md A complete example of defining states, transitions, and persistence validators for a document lifecycle using Statum macros. ```rust use std::sync::Arc; use statum::{machine, state, transition, validators}; #[state] pub enum DocumentState { Draft, InReview(ReviewData), Published(PublishMeta), } pub struct ReviewData { reviewer: String, } pub struct PublishMeta { published_at_unix: i64, } trait Storage {} trait Publisher {} #[machine] pub struct DocumentMachine { id: String, storage: Arc, publisher: Arc, } #[transition] impl DocumentMachine { fn submit_for_review(self, reviewer: String) -> DocumentMachine { self.transition_with(ReviewData { reviewer }) } } #[transition] impl DocumentMachine { fn publish(self, unix_ts: i64) -> DocumentMachine { self.transition_with(PublishMeta { published_at_unix: unix_ts }) } } enum DbStatus { Draft, InReview, Published, } struct DbDocument { status: DbStatus, } #[validators(DocumentMachine)] impl DbDocument { fn is_draft(&self) -> statum::Result<()> { matches!(self.status, DbStatus::Draft) .then_some(()) .ok_or(statum::Error::InvalidState) } fn is_in_review(&self) -> statum::Result { matches!(self.status, DbStatus::InReview) .then_some(ReviewData { reviewer: "sam".into() }) .ok_or(statum::Error::InvalidState) } fn is_published(&self) -> statum::Result { matches!(self.status, DbStatus::Published) .then_some(PublishMeta { published_at_unix: 0 }) .ok_or(statum::Error::InvalidState) } } ``` -------------------------------- ### Install module_path_extractor dependency Source: https://github.com/eboody/statum/blob/main/module_path_extractor/README.md Add the crate to your Cargo.toml file to include it in your Rust project dependencies. ```toml [dependencies] module_path_extractor = "0.5.4" ``` -------------------------------- ### Common Transition Signatures in Statum (Rust) Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Provides examples of common function signatures for transition methods within Statum machines in Rust. It covers direct returns for always-legal transitions and the use of `Result` or `Option` when transitions are gated by runtime checks. ```rust fn approve(self) -> DocumentMachine; fn try_publish(self) -> Result, statum::Error>; fn maybe_publish(self) -> Option>; ``` -------------------------------- ### Project Event Logs with ProjectionReducer Source: https://context7.com/eboody/statum/llms.txt Demonstrates how to implement the ProjectionReducer trait to transform append-only event logs into typed projections. It includes examples for both single-stream reduction and grouped multi-stream reduction. ```rust use statum::projection::{ProjectionReducer, reduce_one, reduce_grouped}; #[derive(Clone)] struct OrderEvent { order_id: u64, event_type: String, amount: Option, } struct OrderProjection { order_id: u64, total: u64, status: String, } struct OrderProjector; impl ProjectionReducer for OrderProjector { type Projection = OrderProjection; type Error = &'static str; fn seed(&self, event: &OrderEvent) -> Result { if event.event_type != "created" { return Err("stream must start with created event"); } Ok(OrderProjection { order_id: event.order_id, total: 0, status: "created".to_string(), }) } fn apply(&self, proj: &mut Self::Projection, event: &OrderEvent) -> Result<(), Self::Error> { match event.event_type.as_str() { "item_added" => { proj.total += event.amount.unwrap_or(0); } "paid" => { proj.status = "paid".to_string(); } _ => return Err("unknown event type"), } Ok(()) } } fn main() -> Result<(), Box> { let events = vec![ OrderEvent { order_id: 1, event_type: "created".into(), amount: None }, OrderEvent { order_id: 1, event_type: "item_added".into(), amount: Some(100) }, OrderEvent { order_id: 1, event_type: "paid".into(), amount: None }, ]; let projection = reduce_one(events, &OrderProjector)?; assert_eq!(projection.total, 100); assert_eq!(projection.status, "paid"); let events = vec![ OrderEvent { order_id: 2, event_type: "created".into(), amount: None }, OrderEvent { order_id: 1, event_type: "created".into(), amount: None }, OrderEvent { order_id: 2, event_type: "item_added".into(), amount: Some(50) }, OrderEvent { order_id: 1, event_type: "item_added".into(), amount: Some(200) }, ]; let projections = reduce_grouped(events, |e| e.order_id, &OrderProjector)?; assert_eq!(projections[0].total, 50); assert_eq!(projections[1].total, 200); Ok(()) } ``` -------------------------------- ### Statum: Adding Derives to Machine Struct Source: https://github.com/eboody/statum/blob/main/README.md This example shows how to apply common derives like Debug and Clone to a Statum machine struct. It's important to place these derives below the #[machine] attribute to avoid compilation errors related to missing fields. ```rust #[machine] #[derive(Debug, Clone)] struct LightSwitch { name: String, } ``` -------------------------------- ### Define Basic Document State Machine (Rust) Source: https://github.com/eboody/statum/blob/main/docs/tutorial-review-workflow.md Sets up the initial state machine for a document with 'Draft' and 'Published' states using the statum library. It demonstrates the basic structure of defining states and a machine without any transitions or context. ```rust use statum::{machine, state}; #[state] enum DocumentState { Draft, Published, } #[machine] struct Document {} fn main() { let _draft = Document::::builder().build(); } ``` -------------------------------- ### Implement State-Dependent Data in Statum Source: https://github.com/eboody/statum/blob/main/docs/tutorial-review-workflow.md Demonstrates how to attach specific data structures to machine states, ensuring data like 'ReviewAssignment' only exists when a document is in the 'InReview' state. This enforces strict API boundaries where transitions and methods are only available in valid states. ```rust use statum::{machine, state, transition}; #[state] enum DocumentState { Draft, InReview(ReviewAssignment), Published, } #[derive(Clone, Debug, PartialEq, Eq)] struct ReviewAssignment { reviewer: String, } #[machine] struct Document { id: i64, title: String, body: String, } #[transition] impl Document { fn submit(self, reviewer: String) -> Document { self.transition_with(ReviewAssignment { reviewer }) } } impl Document { fn reviewer(&self) -> &str { &self.state_data.reviewer } } #[transition] impl Document { fn approve(self) -> Document { self.transition() } } ``` -------------------------------- ### Implement Type-Safe State Transitions Source: https://github.com/eboody/statum/blob/main/docs/tutorial-review-workflow.md Shows the pattern of matching on a rehydrated state once at the service boundary, then executing state-specific transitions on the resulting concrete machine. ```rust fn submit_document(row: DocumentRow, reviewer: String) -> statum::Result> { match load_document_state(row)? { document::SomeState::Draft(machine) => Ok(machine.submit(reviewer)), _ => Err(statum::Error::InvalidState), } } fn approve_document(row: DocumentRow) -> statum::Result> { match load_document_state(row)? { document::SomeState::InReview(machine) => Ok(machine.approve()), _ => Err(statum::Error::InvalidState), } } ``` -------------------------------- ### Resolve Wrapper Enum to Concrete Document State Source: https://github.com/eboody/statum/blob/main/docs/tutorial-review-workflow.md Demonstrates how to convert a generic document wrapper into a specific state-typed machine. This ensures that only methods valid for the current state are accessible to the compiler. ```rust fn load_draft_document(row: DocumentRow) -> statum::Result> { match load_document_state(row)? { document::SomeState::Draft(machine) => Ok(machine), _ => Err(statum::Error::InvalidState), } } fn load_in_review_document(row: DocumentRow) -> statum::Result> { match load_document_state(row)? { document::SomeState::InReview(machine) => Ok(machine), _ => Err(statum::Error::InvalidState), } } ``` -------------------------------- ### Configure Statum Dependency Source: https://github.com/eboody/statum/blob/main/RELEASE_NOTES.md Add the Statum library to your Rust project by updating the Cargo.toml file. This ensures the project uses version 0.3.0 or higher. ```toml [dependencies] statum = "0.3.0" ``` -------------------------------- ### Install Statum Dependency Source: https://github.com/eboody/statum/blob/main/README.md This snippet shows how to add Statum as a dependency to your Rust project using Cargo.toml. Ensure you are using a compatible Rust version (1.93+). ```toml [dependencies] statum = "0.6.3" ``` -------------------------------- ### Batch Rebuilding Typed Machines (Rust) Source: https://github.com/eboody/statum/blob/main/docs/case-study-event-log-rebuild.md Illustrates how to efficiently rebuild multiple typed machines in a single pass from grouped event projections. This is crucial for append-only systems that typically process workflows in batches. ```rust let machines = grouped_projection.into_machines(); ``` -------------------------------- ### Separate Helper Methods from Statum Transitions (Rust) Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Illustrates the correct way to organize code within a Statum machine implementation in Rust. It shows placing helper methods like constructors (`from_command`) in regular `impl` blocks, separate from the `#[transition]` impl blocks which should only contain legal transition methods. ```rust // Wrong: helper method in a #[transition] impl block. #[transition] impl PostMessageMachine { fn from_command(cmd: PostMessageCommand) -> Self { /* ... */ } } // Right: helper in regular impl; transitions stay in #[transition] blocks. impl PostMessageMachine { fn from_command(cmd: PostMessageCommand) -> Self { /* ... */ } } #[transition] impl PostMessageMachine { fn validate_message(self) -> Result, Error> { /* ... */ } } ``` -------------------------------- ### Build Statum Machine with Context Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Shows the builder pattern for initializing a machine from a database row. This process injects necessary dependencies like storage and publishers to complete the state transition. ```rust let typed = row .into_machine() .id("doc-123".to_string()) .storage(storage) .publisher(publisher) .build()?; ``` -------------------------------- ### Implement Legal State Transitions with `#[transition]` (Rust) Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Shows how to implement legal state transitions for a Statum machine in Rust using the `#[transition]` macro. It demonstrates defining transition methods that are only callable from specific source states and using `transition_with` for states that carry data. ```rust use statum::transition; #[transition] impl DocumentMachine { fn submit_for_review(self, reviewer: String) -> DocumentMachine { self.transition_with(ReviewData { reviewer }) } } #[transition] impl DocumentMachine { fn publish(self, unix_ts: i64) -> DocumentMachine { self.transition_with(PublishMeta { published_at_unix: unix_ts }) } } ``` -------------------------------- ### Implement Rollbacks and Snapshots Source: https://github.com/eboody/statum/blob/main/docs/patterns.md Shows how to preserve state history by carrying previous data into the next state during a transition. ```rust #[transition] impl Machine { fn publish(self) -> Machine { let previous = self.state_data.clone(); self.transition_with(PublishData { previous }) } } ``` -------------------------------- ### Construct Machines via Builders Source: https://github.com/eboody/statum/blob/main/docs/migration.md Statum encourages a builder-first workflow for machine construction, providing per-state builders for both unit and data-bearing states. ```rust let draft = Machine::::builder() .field_a(...) .build(); let review = Machine::::builder() .field_a(...) .state_data(ReviewData { ... }) .build(); ``` -------------------------------- ### Manage State-Specific Data Source: https://github.com/eboody/statum/blob/main/docs/patterns.md Illustrates how to attach data only to valid states using the transition_with method. This ensures data is only available when the machine is in the appropriate state. ```rust #[state] enum ReviewState { Draft, InReview(ReviewData), Published, } #[transition] impl Document { fn submit_for_review(self, reviewer: String) -> Document { self.transition_with(ReviewData { reviewer }) } } ``` -------------------------------- ### Query items in a module Source: https://github.com/eboody/statum/blob/main/macro_registry/README.md Use the query module to retrieve a list of candidate items (structs or enums) from a specific file path and module path. ```rust use macro_registry::query::{ItemKind, candidates_in_module}; let machines = candidates_in_module( file_path, "crate::workflow", ItemKind::Struct, Some("machine"), ); ``` -------------------------------- ### Implement Guarded Transitions Source: https://github.com/eboody/statum/blob/main/docs/patterns.md Shows how to enforce preconditions before a state transition occurs. The transition only proceeds if the guard method returns true. ```rust impl Machine { fn try_activate(self) -> statum::Result> { if self.can_activate() { Ok(self.activate()) } else { Err(statum::Error::InvalidState) } } } ``` -------------------------------- ### Add Durable Machine Context to Document State Machine (Rust) Source: https://github.com/eboody/statum/blob/main/docs/tutorial-review-workflow.md Enhances the Document state machine by adding durable fields (id, title, body) that are present in all states. This demonstrates how to include persistent data within the machine's context using the statum library. ```rust use statum::{machine, state}; #[state] enum DocumentState { Draft, Published, } #[machine] struct Document { id: i64, title: String, body: String, } fn main() { let _draft = Document::::builder() .id(1) .title("RFC: Typed review workflow".to_owned()) .body("Start small, then add features.".to_owned()) .build(); } ``` -------------------------------- ### Process Event Logs and Projections Source: https://github.com/eboody/statum/blob/main/docs/patterns.md Demonstrates the pattern of reducing event logs into projection rows before rehydrating them into typed state machines. ```rust use statum::projection::{ProjectionReducer, reduce_grouped}; let rows = reduce_grouped(events, |event| event.order_id, &OrderProjector)?; let machines = rows.into_machines().build(); ``` -------------------------------- ### Rebuilding a Typed Machine from Events (Rust) Source: https://github.com/eboody/statum/blob/main/docs/case-study-event-log-rebuild.md This snippet demonstrates the core Statum pattern for rebuilding a typed machine from a projected row derived from an event stream. It shows the explicit boundary between unvalidated facts and legal domain states, ensuring representational correctness. ```rust let row = projection::reduce_one(events, &OrderProjector)?; let state = row.into_machine().build()?; ``` -------------------------------- ### Resolve module path from proc-macro call site Source: https://github.com/eboody/statum/blob/main/module_path_extractor/README.md Demonstrates the typical workflow of retrieving source information from a proc-macro call site and resolving it to a module path. ```rust let (file_path, line_number) = module_path_extractor::get_source_info().expect("macro call-site source info"); let module_path = module_path_extractor::find_module_path(&file_path, line_number).expect("module path"); ``` -------------------------------- ### Compose Builder and Statum Machine Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Demonstrates the separation of concerns where a builder handles data assembly and the Statum machine enforces lifecycle transitions. ```rust let command = PostMessageCommand::builder() .sender(sender) .receiver(receiver) .body(body) .build(); let flow = PostMessageMachine::::from_command(command); ``` -------------------------------- ### Validate Persistence Rehydration with Statum Source: https://github.com/eboody/statum/blob/main/docs/tutorial-review-workflow.md Shows how to define validators for raw database rows to ensure they conform to legal machine states upon loading. The 'validators' macro allows mapping raw data into typed 'SomeState' variants, returning an error if the data is invalid. ```rust use statum::{machine, state, transition, validators}; #[derive(Clone, Debug)] struct DocumentRow { id: i64, title: String, body: String, status: Status, reviewer: Option, } #[validators(Document)] impl DocumentRow { fn is_in_review(&self) -> statum::Result { if *id <= 0 || title.is_empty() || body.is_empty() || self.status != Status::InReview { return Err(statum::Error::InvalidState); } self.reviewer .clone() .filter(|reviewer| !reviewer.trim().is_empty()) .map(|reviewer| ReviewAssignment { reviewer }) .ok_or(statum::Error::InvalidState) } } fn load_document_state(row: DocumentRow) -> statum::Result { row.clone() .into_machine() .id(row.id) .title(row.title) .body(row.body) .build() } ``` -------------------------------- ### Import Machine Extensions Source: https://github.com/eboody/statum/blob/main/docs/migration.md For cross-module batch rebuilds, ensure the machine-scoped trait is imported to access extension methods. ```rust use task_machine::IntoMachinesExt as _; ``` -------------------------------- ### Branching and Guarding in Statum Transitions Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Demonstrates how to handle runtime branching outside of transition definitions using enums and guard methods. This approach keeps transition logic focused and maintains static type safety. ```rust enum ReviewDecision { Approve, Reject, } impl DocumentMachine { fn decide(self, decision: ReviewDecision) -> Result, statum::Error> { match decision { ReviewDecision::Approve => Ok(self.publish(now_unix())), ReviewDecision::Reject => Err(statum::Error::InvalidState), } } } impl DocumentMachine { fn can_publish(&self) -> bool { !self.state_data.reviewer.is_empty() } } enum Next { Published(DocumentMachine), ReturnedToDraft(DocumentMachine), } ``` -------------------------------- ### Apply Transition Attribute Source: https://github.com/eboody/statum/blob/main/docs/migration.md In Statum 0.5+, transition methods must be explicitly marked with the #[transition] attribute within the implementation block. ```rust #[transition] impl Machine { fn submit(self) -> Machine { self.transition() } } ``` -------------------------------- ### Three-Layer Flow Shape for Endpoint Logic (Rust) Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Illustrates a recommended three-layer flow shape for handling endpoint or application logic with Statum machines in Rust. It separates responsibilities into boundary adaptation, protocol transitions, and orchestration at the call site. ```rust let flow = PostMessageMachine::::from_command(cmd); let flow = flow.validate_message()?; let flow = flow.apply_moderation(&moderator)?; let built = flow.build(now); ``` -------------------------------- ### Implement a cached registry domain Source: https://github.com/eboody/statum/blob/main/macro_registry/README.md Define a custom registry domain by implementing RegistryDomain and NamedRegistryDomain to manage typed metadata lookups with caching support. ```rust use macro_registry::analysis::{FileAnalysis, StructEntry}; use macro_registry::registry::{ LookupMode, NamedRegistryDomain, RegistryDomain, RegistryKey, RegistryValue, SourceContext, StaticRegistry, try_ensure_loaded_by_name_from_source, }; #[derive(Clone, Eq, Hash, PartialEq)] struct MachinePath(String); impl AsRef for MachinePath { fn as_ref(&self) -> &str { &self.0 } } impl RegistryKey for MachinePath { fn from_module_path(module_path: String) -> Self { Self(module_path) } } #[derive(Clone)] struct MachineMeta { name: String, file_path: Option, } impl RegistryValue for MachineMeta { fn file_path(&self) -> Option<&str> { self.file_path.as_deref() } fn set_file_path(&mut self, file_path: String) { self.file_path = Some(file_path); } } struct MachineDomain; impl RegistryDomain for MachineDomain { type Key = MachinePath; type Value = MachineMeta; type Entry = StructEntry; fn entries(analysis: &FileAnalysis) -> &[Self::Entry] { &analysis.structs } fn entry_line(entry: &Self::Entry) -> usize { entry.line_number } fn build_value(entry: &Self::Entry, _module: &Self::Key) -> Option { Some(MachineMeta { name: entry.item.ident.to_string(), file_path: None, }) } } impl NamedRegistryDomain for MachineDomain { fn entry_name(entry: &Self::Entry) -> String { entry.item.ident.to_string() } fn value_name(value: &Self::Value) -> String { value.name.clone() } } static MACHINES: StaticRegistry = StaticRegistry::new(); let source = SourceContext::new(file_path, line_number); let loaded = try_ensure_loaded_by_name_from_source::( &MACHINES, LookupMode::Exact(MachinePath("crate::workflow".into())), "TaskMachine", &source, )?; ``` -------------------------------- ### Perform Data-to-Data Edges Source: https://github.com/eboody/statum/blob/main/docs/patterns.md Uses transition_map to derive the next state's payload by consuming the current state's data, avoiding unnecessary cloning. ```rust #[transition] impl Order { fn ship(self, tracking: String) -> Order { self.transition_map(|packed| ShippedData { order_id: packed.order_id, tracking, }) } } ``` -------------------------------- ### Implement Conditional Branch Routing Source: https://github.com/eboody/statum/blob/main/docs/typestate-builder-design-playbook.md Shows how to handle branching logic within a state machine transition by returning an enum representing possible outcome states. ```rust enum PublishDecision { Published(DocumentMachine), StayInReview(DocumentMachine), } impl DocumentMachine { fn decide_publish(self, can_publish: bool, unix_ts: i64) -> PublishDecision { if can_publish { PublishDecision::Published(self.publish(unix_ts)) } else { PublishDecision::StayInReview(self) } } } ```