snurr/
process.rs

1mod diagram;
2mod engine;
3pub mod handler;
4mod reader;
5mod scaffold;
6
7use crate::{IntermediateEvent, With, error::Error, model::Bpmn};
8use diagram::Diagram;
9use engine::ExecuteData;
10use handler::{Data, Handler, TaskResult};
11use std::{
12    marker::PhantomData,
13    path::Path,
14    str::FromStr,
15    sync::{Arc, Mutex},
16};
17
18/// Process that contains information from the BPMN file
19pub struct Process<T, S = Build>
20where
21    Self: Sync + Send,
22{
23    diagram: Diagram,
24    handler: Handler<T>,
25    _marker: PhantomData<S>,
26}
27
28/// Process Build state
29pub struct Build;
30
31/// Process Run state
32pub struct Run;
33
34impl<T> Process<T> {
35    /// Create new process and initialize it from the BPMN file path.
36    /// ```
37    /// use snurr::{Build, Process};
38    ///
39    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
40    ///     let bpmn: Process<()> = Process::new("examples/example.bpmn")?;
41    ///     Ok(())
42    /// }
43    /// ```
44    pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
45        Ok(Self {
46            diagram: reader::read_bpmn(quick_xml::Reader::from_file(path)?)?,
47            handler: Default::default(),
48            _marker: Default::default(),
49        })
50    }
51
52    /// Register a task function with name or bpmn id
53    pub fn task<F>(mut self, name: impl Into<String>, func: F) -> Self
54    where
55        F: Fn(Data<T>) -> TaskResult + 'static + Sync + Send,
56    {
57        self.handler.add_task(name, func);
58        self
59    }
60
61    /// Register an exclusive gateway function with name or bpmn id
62    pub fn exclusive<F>(mut self, name: impl Into<String>, func: F) -> Self
63    where
64        F: Fn(Data<T>) -> Option<&'static str> + 'static + Sync + Send,
65    {
66        self.handler.add_exclusive(name, func);
67        self
68    }
69
70    /// Register an inclusive gateway function with name or bpmn id
71    pub fn inclusive<F>(mut self, name: impl Into<String>, func: F) -> Self
72    where
73        F: Fn(Data<T>) -> With + 'static + Sync + Send,
74    {
75        self.handler.add_inclusive(name, func);
76        self
77    }
78
79    /// Register an event based gateway function with name or bpmn id
80    pub fn event_based<F>(mut self, name: impl Into<String>, func: F) -> Self
81    where
82        F: Fn(Data<T>) -> IntermediateEvent + 'static + Sync + Send,
83    {
84        self.handler.add_event_based(name, func);
85        self
86    }
87
88    /// Install and check that all required functions have been registered. You cannot run a process before `build` is called.
89    /// If `build` returns an error, it contains the missing functions.
90    pub fn build(mut self) -> Result<Process<T, Run>, Error> {
91        let result = self.diagram.install_and_check(self.handler.build()?);
92        if result.is_empty() {
93            Ok(Process {
94                diagram: self.diagram,
95                handler: self.handler,
96                _marker: Default::default(),
97            })
98        } else {
99            Err(Error::MissingImplementations(
100                result.into_iter().collect::<Vec<_>>().join(", "),
101            ))
102        }
103    }
104}
105
106impl<T> FromStr for Process<T> {
107    type Err = Error;
108
109    /// Create new process and initialize it from a BPMN `&str`.
110    /// ```
111    /// use snurr::{Build, Process};
112    ///
113    /// static BPMN_DATA: &str = include_str!("../examples/example.bpmn");
114    ///
115    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
116    ///     let bpmn: Process<()> = BPMN_DATA.parse()?;
117    ///     Ok(())
118    /// }
119    /// ```
120    fn from_str(s: &str) -> Result<Self, Self::Err> {
121        Ok(Self {
122            diagram: reader::read_bpmn(quick_xml::Reader::from_str(s))?,
123            handler: Default::default(),
124            _marker: Default::default(),
125        })
126    }
127}
128
129impl<T> Process<T, Run> {
130    /// Run the process and return the `T` or an `Error`.
131    /// ```
132    /// use snurr::Process;
133    ///
134    /// #[derive(Debug, Default)]
135    /// struct Counter {
136    ///     count: u32,
137    /// }
138    ///
139    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
140    ///     pretty_env_logger::init();
141    ///
142    ///     // Create process from BPMN file
143    ///     let bpmn = Process::<Counter>::new("examples/example.bpmn")?
144    ///         .task("Count 1", |input| {
145    ///             input.lock().unwrap().count += 1;
146    ///             None
147    ///         })
148    ///         .exclusive("equal to 3", |input| {
149    ///             match input.lock().unwrap().count {
150    ///                 3 => "YES",
151    ///                 _ => "NO",
152    ///             }
153    ///             .into()
154    ///         })
155    ///         .build()?;
156    ///
157    ///     // Run the process with input data
158    ///     let counter = bpmn.run(Counter::default())?;
159    ///
160    ///     // Print the result.
161    ///     println!("Count: {}", counter.count);
162    ///     Ok(())
163    /// }
164    /// ```
165    pub fn run(&self, data: T) -> Result<T, Error>
166    where
167        T: Send,
168    {
169        let data = Arc::new(Mutex::new(data));
170
171        // Run every process specified in the diagram
172        for bpmn in self
173            .diagram
174            .get_processes()
175            .ok_or(Error::MissingDefinitionsId)?
176            .iter()
177        {
178            if let Bpmn::Process { id, .. } = bpmn {
179                let process_data = self
180                    .diagram
181                    .get_process(*id.local())
182                    .ok_or_else(|| Error::MissingProcessData(id.bpmn().into()))?;
183                self.execute(ExecuteData::new(process_data, id, Arc::clone(&data)))?;
184            }
185        }
186
187        Arc::into_inner(data)
188            .ok_or(Error::NoProcessResult)?
189            .into_inner()
190            .map_err(|_| Error::NoProcessResult)
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn create_and_run() -> Result<(), Box<dyn std::error::Error>> {
200        let bpmn = Process::new("examples/example.bpmn")?
201            .task("Count 1", |_| None)
202            .exclusive("equal to 3", |_| None)
203            .build()?;
204        bpmn.run({})?;
205        Ok(())
206    }
207}