mod diagram;
mod engine;
pub mod handler;
mod reader;
mod scaffold;
use crate::{IntermediateEvent, With, error::Error, model::Bpmn};
use diagram::Diagram;
use engine::ExecuteData;
use handler::{Data, Handler, TaskResult};
use std::{
marker::PhantomData,
path::Path,
str::FromStr,
sync::{Arc, Mutex},
};
pub struct Process<S, T> {
diagram: Diagram,
handler: Handler<T>,
_marker: PhantomData<S>,
}
pub struct Build;
pub struct Run;
impl<T> Process<Build, T> {
pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
Ok(Self {
diagram: reader::read_bpmn(quick_xml::Reader::from_file(path)?)?,
handler: Default::default(),
_marker: Default::default(),
})
}
pub fn task<F>(mut self, name: impl Into<String>, func: F) -> Self
where
F: Fn(Data<T>) -> TaskResult + 'static + Sync,
{
self.handler.add_task(name, func);
self
}
pub fn exclusive<F>(mut self, name: impl Into<String>, func: F) -> Self
where
F: Fn(Data<T>) -> Option<&'static str> + 'static + Sync,
{
self.handler.add_exclusive(name, func);
self
}
pub fn inclusive<F>(mut self, name: impl Into<String>, func: F) -> Self
where
F: Fn(Data<T>) -> With + 'static + Sync,
{
self.handler.add_inclusive(name, func);
self
}
pub fn event_based<F>(mut self, name: impl Into<String>, func: F) -> Self
where
F: Fn(Data<T>) -> IntermediateEvent + 'static + Sync,
{
self.handler.add_event_based(name, func);
self
}
pub fn build(mut self) -> Result<Process<Run, T>, Error> {
let result = self.diagram.install_and_check(self.handler.build()?);
if result.is_empty() {
Ok(Process {
diagram: self.diagram,
handler: self.handler,
_marker: Default::default(),
})
} else {
Err(Error::MissingImplementations(
result.into_iter().collect::<Vec<_>>().join(", "),
))
}
}
}
impl<T> FromStr for Process<Build, T> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
diagram: reader::read_bpmn(quick_xml::Reader::from_str(s))?,
handler: Default::default(),
_marker: Default::default(),
})
}
}
impl<T> Process<Run, T> {
pub fn run(&self, data: T) -> Result<T, Error>
where
T: Send,
{
let data = Arc::new(Mutex::new(data));
for bpmn in self
.diagram
.get_processes()
.ok_or(Error::MissingDefinitionsId)?
.iter()
{
if let Bpmn::Process { id, .. } = bpmn {
let process_data = self
.diagram
.get_process(*id.local())
.ok_or_else(|| Error::MissingProcessData(id.bpmn().into()))?;
self.execute(ExecuteData::new(process_data, id, Arc::clone(&data)))?;
}
}
Arc::into_inner(data)
.ok_or(Error::NoProcessResult)?
.into_inner()
.map_err(|_| Error::NoProcessResult)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_and_run() -> Result<(), Box<dyn std::error::Error>> {
let bpmn = Process::new("examples/example.bpmn")?
.task("Count 1", |_| None)
.exclusive("equal to 3", |_| None)
.build()?;
bpmn.run({})?;
Ok(())
}
}