bpxe_bpmn_schema/
lib.rs

1//! # BPMN Schema
2//!
3#[cfg(test)]
4use bpxe_internal_macros as bpxe_im;
5use thiserror::Error;
6
7/// Alias for URI
8pub type URI = String;
9/// Alias for ID
10pub type Id = String;
11/// Alias for Integer
12pub type Integer = num_bigint::BigInt;
13/// Alias for Int
14pub type Int = i32;
15
16use downcast_rs::{impl_downcast, Downcast};
17
18pub trait DocumentElementContainer: Downcast {
19    /// Find an element by ID
20    #[allow(unused_variables)]
21    fn find_by_id(&self, _id: &str) -> Option<&dyn DocumentElement> {
22        None
23    }
24
25    /// Find an element by ID and return a mutable reference
26    #[allow(unused_variables)]
27    fn find_by_id_mut(&mut self, id: &str) -> Option<&mut dyn DocumentElement> {
28        None
29    }
30}
31impl_downcast!(DocumentElementContainer);
32
33impl<T> DocumentElementContainer for Vec<T>
34where
35    T: DocumentElementContainer,
36{
37    fn find_by_id(&self, id: &str) -> Option<&dyn DocumentElement> {
38        for e in self.iter() {
39            if let Some(de) = e.find_by_id(id) {
40                return Some(de);
41            }
42        }
43        None
44    }
45    fn find_by_id_mut(&mut self, id: &str) -> Option<&mut dyn DocumentElement> {
46        for e in self.iter_mut() {
47            if let Some(de) = e.find_by_id_mut(id) {
48                return Some(de);
49            }
50        }
51        None
52    }
53}
54
55impl<T> DocumentElementContainer for Option<T>
56where
57    T: DocumentElementContainer,
58{
59    fn find_by_id(&self, id: &str) -> Option<&dyn DocumentElement> {
60        match self {
61            None => None,
62            Some(e) => e.find_by_id(id),
63        }
64    }
65    fn find_by_id_mut(&mut self, id: &str) -> Option<&mut dyn DocumentElement> {
66        match self {
67            None => None,
68            Some(e) => e.find_by_id_mut(id),
69        }
70    }
71}
72
73pub trait Cast<T: ?Sized> {
74    fn cast(&self) -> Option<&T> {
75        None
76    }
77    fn cast_mut(&mut self) -> Option<&mut T> {
78        None
79    }
80}
81
82pub trait DocumentElementWithContent: DocumentElement {
83    /// Gets document element's content
84    fn content(&self) -> &Option<String>;
85}
86
87pub trait DocumentElementWithContentMut: DocumentElement {
88    /// Gets a mutable reference to document element's content
89    fn content_mut(&mut self) -> &mut Option<String>;
90    /// Changes content
91    fn set_content(&mut self, content: Option<String>);
92}
93
94mod autogenerated;
95pub use autogenerated::*;
96
97mod expr;
98pub use expr::*;
99
100mod script;
101pub use script::*;
102
103mod token;
104use token::*;
105
106#[derive(Error, Debug)]
107pub enum EstablishSequenceFlowError {
108    #[error("source.id must be Some")]
109    NoSourceId,
110    #[error("target.id must be Some")]
111    NoTargetId,
112    #[error("can't find source of the right type")]
113    SourceNotFound,
114    #[error("can't find target of the right type")]
115    TargetNotFound,
116}
117
118impl Process {
119    /// Establishes sequence flow between flow identified nodes
120    ///
121    /// Resulting sequence flow will have `id` as an ID and it will be
122    /// added to the matching process.
123    // TODO: add support for sub-processes
124    pub fn establish_sequence_flow<E: Into<Expr>>(
125        &mut self,
126        source: &str,
127        target: &str,
128        id: &str,
129        condition_expression: Option<E>,
130    ) -> Result<&mut Self, EstablishSequenceFlowError> {
131        // The main reason why this method is written in a somewhat convoluted fashion (not saving
132        // source and target nodes when we check for their presence) has to do
133        // with the need to avoid multiple mutable borrows.
134
135        // check source element presence
136        self.find_by_id_mut(source)
137            .and_then(|e| Cast::<dyn FlowNodeTypeMut>::cast(e))
138            .ok_or(EstablishSequenceFlowError::SourceNotFound)?;
139
140        // check target element presence
141        self.find_by_id_mut(target)
142            .and_then(|e| Cast::<dyn FlowNodeTypeMut>::cast(e))
143            .ok_or(EstablishSequenceFlowError::TargetNotFound)?;
144
145        let sequence_flow = SequenceFlow {
146            id: Some(id.to_string()),
147            source_ref: source.to_string(),
148            target_ref: target.to_string(),
149            condition_expression: condition_expression.map(|e| e.into().into()),
150            ..SequenceFlow::default()
151        };
152
153        // add sequence flow
154        self.flow_elements_mut()
155            .push(FlowElement::SequenceFlow(sequence_flow));
156
157        // add outgoing
158        let source_node = self
159            .find_by_id_mut(source)
160            .and_then(|e| Cast::<dyn FlowNodeTypeMut>::cast_mut(e))
161            .unwrap();
162
163        source_node.outgoings_mut().push(id.into());
164
165        // add incoming
166        let target_node = self
167            .find_by_id_mut(target)
168            .and_then(|e| Cast::<dyn FlowNodeTypeMut>::cast_mut(e))
169            .unwrap();
170        target_node.incomings_mut().push(id.into());
171
172        Ok(self)
173    }
174}
175
176#[cfg(all(test, target_arch = "wasm32", target_os = "unknown"))]
177wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
178
179#[cfg(test)]
180#[bpxe_im::test]
181fn establishing_sequence_flow_in_process() {
182    let mut process = Process {
183        id: Some("proc1".to_string()),
184        flow_elements: vec![
185            FlowElement::StartEvent(StartEvent {
186                id: Some("start".to_string()),
187                ..Default::default()
188            }),
189            FlowElement::EndEvent(EndEvent {
190                id: Some("end".to_string()),
191                ..Default::default()
192            }),
193        ],
194        ..Default::default()
195    };
196
197    process
198        .establish_sequence_flow(
199            "start",
200            "end",
201            "test",
202            Some(FormalExpression {
203                content: Some("condition".into()),
204                ..Default::default()
205            }),
206        )
207        .unwrap();
208
209    let seq_flow = process
210        .find_by_id("test")
211        .unwrap()
212        .downcast_ref::<SequenceFlow>()
213        .unwrap();
214    assert_eq!(seq_flow.id(), &Some("test".to_string()));
215    assert_eq!(seq_flow.source_ref(), "start");
216    assert_eq!(seq_flow.target_ref(), "end");
217
218    let expr = seq_flow.condition_expression().as_ref().unwrap();
219    assert!(
220        matches!(expr, SequenceFlowConditionExpression(Expr::FormalExpression(FormalExpression { content, ..}))
221            if content.as_ref().unwrap() == "condition")
222    );
223
224    let start = Cast::<dyn FlowNodeType>::cast(process.find_by_id("start").unwrap()).unwrap();
225    assert_eq!(start.outgoings(), &vec!["test".to_string()]);
226
227    let end = Cast::<dyn FlowNodeType>::cast(process.find_by_id("end").unwrap()).unwrap();
228    assert_eq!(end.incomings(), &vec!["test".to_string()]);
229}
230
231#[cfg(test)]
232#[bpxe_im::test]
233fn failing_to_establish_sequence_flow_in_process() {
234    let mut process = Process {
235        id: Some("proc1".to_string()),
236        flow_elements: vec![
237            FlowElement::StartEvent(StartEvent {
238                id: Some("start".to_string()),
239                ..Default::default()
240            }),
241            FlowElement::EndEvent(EndEvent {
242                id: Some("end".to_string()),
243                ..Default::default()
244            }),
245        ],
246        ..Default::default()
247    };
248
249    assert!(matches!(
250        process
251            .establish_sequence_flow("no_start", "end", "test", None::<FormalExpression>)
252            .unwrap_err(),
253        EstablishSequenceFlowError::SourceNotFound
254    ));
255
256    assert!(matches!(
257        process
258            .establish_sequence_flow("start", "no_end", "test", None::<FormalExpression>)
259            .unwrap_err(),
260        EstablishSequenceFlowError::TargetNotFound
261    ));
262}
263
264/// Establishes and returns a sequence flow between nodes
265pub fn establish_sequence_flow<F, T, S, E>(
266    source: &mut F,
267    target: &mut T,
268    id: S,
269    condition_expression: Option<E>,
270) -> Result<SequenceFlow, EstablishSequenceFlowError>
271where
272    F: FlowNodeTypeMut,
273    T: FlowNodeTypeMut,
274    S: Into<String>,
275    E: Into<Expr>,
276{
277    let mut sequence_flow = SequenceFlow::default();
278    let id_s: Option<String> = Some(id.into());
279    sequence_flow.id = id_s.clone();
280
281    if source.id().is_none() {
282        return Err(EstablishSequenceFlowError::NoSourceId);
283    }
284
285    if target.id().is_none() {
286        return Err(EstablishSequenceFlowError::NoTargetId);
287    }
288
289    // it is safe now to unwrap ids
290    sequence_flow.set_source_ref(source.id().as_ref().unwrap().into());
291    sequence_flow.set_target_ref(target.id().as_ref().unwrap().into());
292    sequence_flow.set_condition_expression(condition_expression.map(|e| e.into().into()));
293
294    let id = id_s.unwrap();
295    source.outgoings_mut().push(id.clone());
296    target.incomings_mut().push(id);
297
298    Ok(sequence_flow)
299}
300
301#[cfg(test)]
302#[bpxe_im::test]
303fn establishing_sequence_flow() {
304    let mut start = StartEvent::default();
305    start.id = Some("start".into());
306    let mut end = EndEvent::default();
307    end.id = Some("end".into());
308    let seq_flow = establish_sequence_flow(
309        &mut start,
310        &mut end,
311        "test",
312        Some(FormalExpression {
313            content: Some("condition".into()),
314            ..Default::default()
315        }),
316    )
317    .unwrap();
318    assert_eq!(seq_flow.id(), &Some("test".to_string()));
319    assert_eq!(seq_flow.source_ref(), "start");
320    assert_eq!(seq_flow.target_ref(), "end");
321    assert_eq!(start.outgoings(), &vec!["test".to_string()]);
322    assert_eq!(end.incomings(), &vec!["test".to_string()]);
323    let expr = seq_flow.condition_expression().as_ref().unwrap();
324    assert!(
325        matches!(expr, SequenceFlowConditionExpression(Expr::FormalExpression(FormalExpression { content, ..}))
326            if content.as_ref().unwrap() == "condition")
327    );
328}
329
330#[cfg(test)]
331#[bpxe_im::test]
332fn failing_to_establish_sequence_flow() {
333    let mut start = StartEvent::default();
334    let mut end = EndEvent::default();
335    assert!(
336        establish_sequence_flow(&mut start, &mut end, "test", None::<FormalExpression>).is_err()
337    );
338    start.id = Some("start".into());
339    assert!(
340        establish_sequence_flow(&mut start, &mut end, "test", None::<FormalExpression>).is_err()
341    );
342    end.id = Some("end".into());
343    assert!(
344        establish_sequence_flow(&mut start, &mut end, "test", None::<FormalExpression>).is_ok()
345    );
346}
347
348#[cfg(test)]
349#[bpxe_im::test]
350fn find_by_id() {
351    let mut proc: Process = Default::default();
352    proc.id = Some("proc".into());
353    let mut start_event: StartEvent = Default::default();
354    start_event.id = Some("start".into());
355
356    proc.flow_elements
357        .push(FlowElement::StartEvent(start_event.clone()));
358
359    let mut definitions: Definitions = Default::default();
360    definitions
361        .root_elements
362        .push(RootElement::Process(proc.clone()));
363    let proc_ = definitions
364        .find_by_id("proc")
365        .expect("`proc` should have been found");
366    assert_eq!(proc_.element(), Element::Process);
367    assert_eq!(proc_.downcast_ref::<Process>().unwrap(), &proc);
368
369    let start_event_ = definitions
370        .find_by_id("start")
371        .expect("`start` should have been found");
372    assert_eq!(start_event_.element(), Element::StartEvent);
373    assert_eq!(
374        start_event_.downcast_ref::<StartEvent>().unwrap(),
375        &start_event
376    );
377
378    assert!(definitions.find_by_id("not_to_be_found").is_none());
379}
380
381#[cfg(test)]
382#[bpxe_im::test]
383fn find_by_id_mut() {
384    let mut proc: Process = Default::default();
385    proc.id = Some("proc".into());
386    let mut start_event: StartEvent = Default::default();
387    start_event.id = Some("start".into());
388
389    proc.flow_elements
390        .push(FlowElement::StartEvent(start_event.clone()));
391
392    let mut definitions: Definitions = Default::default();
393    definitions
394        .root_elements
395        .push(RootElement::Process(proc.clone()));
396    let proc_ = definitions
397        .find_by_id_mut("proc")
398        .expect("`proc` should have been found");
399    assert_eq!(proc_.element(), Element::Process);
400    assert_eq!(proc_.downcast_ref::<Process>().unwrap(), &proc);
401
402    let start_event_ = definitions
403        .find_by_id_mut("start")
404        .expect("`start` should have been found");
405    assert_eq!(start_event_.element(), Element::StartEvent);
406    assert_eq!(
407        start_event_.downcast_ref::<StartEvent>().unwrap(),
408        &start_event
409    );
410
411    assert!(definitions.find_by_id_mut("not_to_be_found").is_none());
412}
413
414#[cfg(test)]
415#[bpxe_im::test]
416fn document_element_clone() {
417    let mut proc: Process = Default::default();
418    proc.id = Some("proc".into());
419    let mut definitions: Definitions = Default::default();
420    definitions.root_elements.push(RootElement::Process(proc));
421    if let Some(proc_) = definitions.find_by_id("proc") {
422        dyn_clone::clone_box(proc_);
423    } else {
424        unreachable!();
425    }
426}