statum-graph 0.7.0

Static graph export for Statum machine introspection
Documentation
#![allow(dead_code)]

use statum_graph::{
    CodebaseDoc, CodebaseRelationBasis, CodebaseRelationKind, CodebaseRelationSource,
};

mod payment {
    use statum::{machine, state, transition};

    #[state]
    pub enum State {
        Authorized,
        Captured,
    }

    #[machine]
    pub struct Machine<State> {}

    #[transition]
    impl Machine<Authorized> {
        fn capture(self) -> Machine<Captured> {
            self.transition()
        }
    }

    pub use machine::via;
}

mod fulfillment {
    use statum::{machine, state, transition};

    #[state]
    pub enum State {
        ReadyToShip,
        Shipping,
    }

    #[machine]
    pub struct Machine<State> {}

    #[transition]
    impl Machine<ReadyToShip> {
        fn start_shipping(
            self,
            #[via(crate::payment::via::Capture)] payment: crate::payment::Machine<
                crate::payment::Captured,
            >,
        ) -> Machine<Shipping> {
            let _ = payment;
            self.transition()
        }
    }
}

#[test]
fn linked_codebase_exports_attested_via_relations() {
    let doc = CodebaseDoc::linked().expect("linked codebase doc");

    let payment = doc
        .machines()
        .iter()
        .find(|machine| machine.rust_type_path.ends_with("payment::Machine"))
        .expect("payment machine");
    let fulfillment = doc
        .machines()
        .iter()
        .find(|machine| machine.rust_type_path.ends_with("fulfillment::Machine"))
        .expect("fulfillment machine");
    let captured = payment
        .states
        .iter()
        .find(|state| state.rust_name == "Captured")
        .expect("payment captured state");
    let authorized = payment
        .states
        .iter()
        .find(|state| state.rust_name == "Authorized")
        .expect("payment authorized state");
    let payment_capture = payment
        .transitions
        .iter()
        .find(|transition| transition.method_name == "capture")
        .expect("payment capture transition");
    let fulfillment_start = fulfillment
        .transitions
        .iter()
        .find(|transition| transition.method_name == "start_shipping")
        .expect("fulfillment start_shipping transition");

    assert_eq!(doc.relations().len(), 2);

    let direct_relation = doc
        .relations()
        .iter()
        .find(|relation| {
            relation.kind == CodebaseRelationKind::TransitionParam
                && relation.basis == CodebaseRelationBasis::DirectTypeSyntax
                && matches!(
                    relation.source,
                    CodebaseRelationSource::TransitionParam {
                        machine,
                        transition,
                        param_index: 0,
                        param_name: Some("payment"),
                    } if machine == fulfillment.index && transition == fulfillment_start.index
                )
        })
        .expect("direct payment relation");
    assert_eq!(direct_relation.target_machine, payment.index);
    assert_eq!(direct_relation.target_state, captured.index);
    assert_eq!(direct_relation.attested_via, None);

    let via_relation = doc
        .relations()
        .iter()
        .find(|relation| {
            relation.kind == CodebaseRelationKind::TransitionParam
                && relation.basis == CodebaseRelationBasis::ViaDeclaration
                && matches!(
                    relation.source,
                    CodebaseRelationSource::TransitionParam {
                        machine,
                        transition,
                        param_index: 0,
                        param_name: Some("payment"),
                    } if machine == fulfillment.index && transition == fulfillment_start.index
                )
        })
        .expect("attested payment relation");
    assert_eq!(via_relation.target_machine, payment.index);
    assert_eq!(via_relation.target_state, captured.index);

    let attested = via_relation
        .attested_via
        .as_ref()
        .expect("attested route");
    assert_eq!(attested.producers.len(), 1);
    assert!(attested.via_module_path.ends_with("payment::via"));
    assert_eq!(attested.route_name, "Capture");
    assert_eq!(attested.producers[0].machine, payment.index);
    assert_eq!(attested.producers[0].state, authorized.index);
    assert_eq!(attested.producers[0].transition, payment_capture.index);

    let groups = doc.machine_relation_groups();
    let group = groups
        .iter()
        .find(|group| group.from_machine == fulfillment.index && group.to_machine == payment.index)
        .expect("fulfillment -> payment relation group");
    assert_eq!(group.relation_indices.len(), 2);
    assert_eq!(
        group
            .counts
            .iter()
            .map(|count| count.display_label())
            .collect::<Vec<_>>(),
        vec!["param".to_string(), "param [via]".to_string()]
    );

    let via_relation_index = group
        .relation_indices
        .iter()
        .copied()
        .find(|index| doc.relations()[*index].basis == CodebaseRelationBasis::ViaDeclaration)
        .expect("via relation index");
    let detail = doc
        .relation_detail(via_relation_index)
        .expect("attested relation detail");
    assert_eq!(detail.source_machine.index, fulfillment.index);
    assert_eq!(
        detail
            .source_transition
            .map(|transition| transition.method_name),
        Some("start_shipping")
    );
    assert_eq!(detail.target_machine.index, payment.index);
    assert_eq!(detail.target_state.rust_name, "Captured");
    assert_eq!(
        detail
            .attested_via_transition
            .map(|transition| transition.method_name),
        Some("capture")
    );
    assert_eq!(
        detail.attested_via_state.map(|state| state.rust_name),
        Some("Authorized")
    );
    assert_eq!(
        detail
            .attested_via_machine
            .map(|machine| machine.rust_type_path),
        Some(payment.rust_type_path)
    );
    assert_eq!(detail.attested_via_producers.len(), 1);
    assert_eq!(
        detail.attested_via_producers[0].transition.method_name,
        "capture"
    );
}