finitomata 0.1.1

Finite state machines with rich lifecycle callbacks, persistence, and supervision
Documentation
use async_trait::async_trait;
use finitomata::{
    Finitomata, FinitomataSupervisor, TransitionResult, finitomata,
    persistency::memory::InMemoryPersistency,
};
use std::time::Duration;

#[finitomata(
    fsm = r#"
        [*] --> draft
        draft --> |submit| review
        review --> |approve| published
        review --> |reject| draft
        published --> |archive| archived
        archived --> |restore| draft
        draft --> |delete| deleted
        deleted --> |confirm| [*]
    "#,
    syntax = "mermaid",
    auto_terminate = true
)]
#[derive(Debug, Clone, Default)]
struct Document;

#[derive(Debug, Clone)]
struct DocPayload {
    title: String,
    version: u32,
    reviewer: Option<String>,
}

#[async_trait]
impl Finitomata for Document {
    type State = DocumentState;
    type Event = DocumentEvent;
    type Payload = DocPayload;

    async fn on_transition(
        &mut self,
        _from: &DocumentState,
        event: &DocumentEvent,
        _event_payload: &DocPayload,
        state_payload: &mut DocPayload,
    ) -> TransitionResult<DocumentState, DocPayload> {
        match event {
            DocumentEvent::Submit => {
                state_payload.version += 1;
                println!(
                    "  Submitted '{}' v{} for review",
                    state_payload.title, state_payload.version
                );
                TransitionResult::Ok(DocumentState::Review)
            }
            DocumentEvent::Approve => {
                state_payload.reviewer = Some("editor@example.com".into());
                println!(
                    "  Approved '{}' by {:?}",
                    state_payload.title, state_payload.reviewer
                );
                TransitionResult::Ok(DocumentState::Published)
            }
            DocumentEvent::Reject => {
                println!("  Rejected '{}' — back to draft", state_payload.title);
                TransitionResult::Ok(DocumentState::Draft)
            }
            DocumentEvent::Archive => {
                println!("  Archived '{}'", state_payload.title);
                TransitionResult::Ok(DocumentState::Archived)
            }
            DocumentEvent::Restore => {
                println!("  Restored '{}' from archive", state_payload.title);
                state_payload.reviewer = None;
                TransitionResult::Ok(DocumentState::Draft)
            }
            DocumentEvent::Delete => {
                println!("  Deleted '{}'", state_payload.title);
                TransitionResult::Ok(DocumentState::Deleted)
            }
            DocumentEvent::Confirm => {
                println!("  Confirmed deletion of '{}'", state_payload.title);
                TransitionResult::Ok(DocumentState::Deleted)
            }
        }
    }

    async fn on_enter(&mut self, state: &DocumentState, payload: &mut DocPayload) {
        println!("  [{}] entered: {state}", payload.title);
    }
}

#[tokio::main]
async fn main() {
    println!("=== Finitomata Persistent Document Workflow ===\n");

    let graph = Document::build_graph();
    let persistency = InMemoryPersistency::<Document>::new();

    let supervisor = FinitomataSupervisor::<Document>::new("docs", graph)
        .with_persistency(persistency)
        .with_auto_terminate(true);

    let payload = DocPayload {
        title: "Architecture RFC".into(),
        version: 0,
        reviewer: None,
    };

    println!("--- Starting document workflow ---");
    supervisor
        .start_fsm("doc_1", Document, payload)
        .await
        .unwrap();
    tokio::time::sleep(Duration::from_millis(50)).await;

    println!("\n--- Submitting for review ---");
    let ep = DocPayload {
        title: String::new(),
        version: 0,
        reviewer: None,
    };
    supervisor
        .transition("doc_1", DocumentEvent::Submit, ep.clone())
        .await
        .unwrap();
    tokio::time::sleep(Duration::from_millis(50)).await;

    println!("\n--- Rejecting (back to draft) ---");
    supervisor
        .transition("doc_1", DocumentEvent::Reject, ep.clone())
        .await
        .unwrap();
    tokio::time::sleep(Duration::from_millis(50)).await;

    println!("\n--- Re-submitting ---");
    supervisor
        .transition("doc_1", DocumentEvent::Submit, ep.clone())
        .await
        .unwrap();
    tokio::time::sleep(Duration::from_millis(50)).await;

    println!("\n--- Approving ---");
    supervisor
        .transition("doc_1", DocumentEvent::Approve, ep.clone())
        .await
        .unwrap();
    tokio::time::sleep(Duration::from_millis(50)).await;

    println!("\n--- Archiving ---");
    supervisor
        .transition("doc_1", DocumentEvent::Archive, ep.clone())
        .await
        .unwrap();
    tokio::time::sleep(Duration::from_millis(50)).await;

    if let Some(state) = supervisor.state("doc_1") {
        println!("\n--- Final state ---");
        println!("  State: {:?}", state.current);
        println!("  Payload: {:?}", state.payload);
        println!("  History: {} entries", state.history.len());
    }

    println!("\n=== Done ===");
}