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}