statum-macros 0.7.0

Proc macros for representing legal workflow and protocol states explicitly in Rust
Documentation
#![allow(unused_imports)]
extern crate self as statum;
pub use statum_macros::__statum_emit_validator_methods_impl;
pub use statum_core::__private;
pub use statum_core::TransitionInventory;
pub use statum_core::{
    CanTransitionMap, CanTransitionTo, CanTransitionWith, DataState, Error, MachineDescriptor,
    MachineGraph, MachineIntrospection, MachineStateIdentity, RebuildAttempt, RebuildReport,
    StateDescriptor, StateMarker, TransitionDescriptor, UnitState,
};

use statum_macros::{machine, state, transition, validators};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ReviewPayload<T> {
    reviewer: T,
}

#[state]
enum WorkflowState {
    Draft,
    Review(ReviewPayload<&'static str>),
}

#[machine]
struct Workflow<WorkflowState, Context> {
    ctx: Context,
    audit: &'static str,
}

#[transition]
impl<Context> Workflow<Draft, Context> {
    fn submit(self, reviewer: &'static str) -> Workflow<Review, Context> {
        self.transition_with(ReviewPayload { reviewer })
    }
}

struct Row {
    status: &'static str,
    reviewer: Option<&'static str>,
}

#[validators(Workflow)]
impl Row {
    fn is_draft(&self) -> core::result::Result<(), statum_core::Error> {
        let _ = &ctx;
        let _ = &audit;
        if self.status == "draft" {
            Ok(())
        } else {
            Err(statum::Error::InvalidState)
        }
    }

    fn is_review(
        &self,
    ) -> core::result::Result<ReviewPayload<&'static str>, statum_core::Error> {
        let _ = &ctx;
        let _ = &audit;
        if self.status == "review" {
            Ok(ReviewPayload {
                reviewer: self.reviewer.expect("reviewer"),
            })
        } else {
            Err(statum::Error::InvalidState)
        }
    }
}

fn main() {
    use workflow::IntoMachinesExt as _;

    let review = Workflow::<Draft, &'static str>::builder()
        .ctx("ctx")
        .audit("audit")
        .build()
        .submit("alice");
    assert_eq!(review.ctx, "ctx");
    assert_eq!(review.audit, "audit");
    assert_eq!(review.state_data.reviewer, "alice");

    let graph = <Workflow<Review, &'static str> as MachineIntrospection>::GRAPH;
    let submit = graph
        .transition_from_method(workflow::StateId::Draft, "submit")
        .unwrap();
    assert_eq!(graph.legal_targets(submit.id).unwrap(), &[workflow::StateId::Review]);

    let rebuilt = Row {
        status: "review",
        reviewer: Some("bob"),
    }
    .into_machine()
    .ctx("rebuilt")
    .audit("persisted")
    .build()
    .unwrap();
    match rebuilt {
        workflow::SomeState::Review(machine) => {
            assert_eq!(machine.ctx, "rebuilt");
            assert_eq!(machine.audit, "persisted");
            assert_eq!(machine.state_data.reviewer, "bob");
        }
        _ => panic!("expected review state"),
    }

    let batch = vec![Row {
        status: "draft",
        reviewer: None,
    }]
    .into_machines_by(|_| workflow::Fields::<&'static str> {
        ctx: "batch",
        audit: "batch-audit",
    })
    .build();
    match batch.into_iter().next().unwrap().unwrap() {
        workflow::SomeState::Draft(machine) => {
            assert_eq!(machine.ctx, "batch");
            assert_eq!(machine.audit, "batch-audit");
        }
        _ => panic!("expected draft state"),
    }
}