pasaka/
lib.rs

1//! # Example
2//! ```
3//! # #[macro_use] extern crate pasaka;
4//! use pasaka::*;
5//!
6//! #[passage]
7//! fn Example(mut h: PassageHandle, state: i32) -> PassageResult {
8//!     h.text("Text can be outputted using .text.")
9//!         .text("It can also be chained.")
10//!         .text(format!("Count: {state}"));
11//!
12//!     h.choice()
13//!         .option("The expression is usually a choice", |h, state| {
14//!             h.passage(Example, state + 1)
15//!         })
16//!         .build(state)
17//! }
18//!
19//! # fn main() {}
20//! ```
21
22extern crate self as pasaka;
23
24use std::{
25    collections::HashMap,
26    sync::{LazyLock, Mutex},
27};
28
29mod choice;
30pub use choice::ChoiceHandle;
31pub use choice::ChoiceResult;
32pub use choice::PassageHandle;
33pub use choice::PassageResult;
34
35mod engine;
36pub use engine::Engine;
37
38mod runner;
39pub use runner::*;
40
41pub trait PassageImpl: 'static
42where
43    Self::State: serde::Serialize + for<'a> serde::Deserialize<'a>,
44{
45    type State;
46
47    fn run(&self, h: PassageHandle, state: Self::State) -> PassageResult;
48
49    fn with_state(&self, state: Self::State) -> Passage {
50        Passage {
51            state: serde_json::to_value(state).unwrap(),
52            registry_key: self.registry_key(),
53        }
54    }
55
56    fn name(&self) -> &'static str;
57
58    fn module_path(&self) -> &'static str;
59
60    fn registry_key(&self) -> String {
61        format!("{}::{}", self.module_path(), self.name())
62    }
63}
64
65/// Combination of a passage name together with its state.
66///
67/// [Passage] can be used to provide a "callback".
68#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
69pub struct Passage {
70    state: serde_json::Value,
71    registry_key: String,
72}
73
74impl Passage {
75    pub fn run(self, h: PassageHandle) -> PassageResult {
76        let guard = PASSAGE_REGISTRY
77            .lock()
78            .expect("accessing passage registry should not panic");
79        let f = guard.get(&self.registry_key).expect(&format!(
80            "passage {} should be registered using #[passage]",
81            self.registry_key
82        ));
83
84        f(h, self.state)
85    }
86
87    pub fn state<S: for<'a> serde::Deserialize<'a>>(&self) -> Option<S> {
88        serde_json::from_value(self.state.clone()).ok()
89    }
90}
91
92type BoxedPassage = Box<dyn Fn(PassageHandle, serde_json::Value) -> PassageResult + Send + Sync>;
93
94static PASSAGE_REGISTRY: LazyLock<Mutex<HashMap<String, BoxedPassage>>> =
95    LazyLock::new(|| Mutex::new(HashMap::new()));
96
97/// Register passage to the global registry.
98/// You should not call this function,
99/// as the [`#[passage]`](passage) macro calls it for you.
100pub fn register_passage<P: PassageImpl + Send + Sync>(passage: P) {
101    PASSAGE_REGISTRY
102        .lock()
103        .expect("registering passage registry should not panic")
104        .insert(
105            passage.registry_key(),
106            Box::new(move |h, value| {
107                let state: P::State = serde_json::from_value(value)
108                    .expect("deserialized value should match passage state");
109                passage.run(h, state)
110            }),
111        );
112}
113
114pub use pasaka_macro::passage;
115
116/// Re-exports for use inside of the [`#[passage]`](passage) macro.
117pub mod macro_support {
118    /// Re-export for use inside of the [`#[passage]`](crate::passage) macro.
119    pub mod ctor {
120        pub use ctor::*;
121    }
122
123    /// Re-export for use inside of the [`#[passage]`](crate::passage) macro.
124    pub mod serde {
125        pub use serde::*;
126    }
127
128    /// Re-export for use inside of the [`#[passage]`](crate::passage) macro.
129    #[cfg(feature = "web")]
130    pub mod yew {
131        pub use yew::*;
132    }
133}