#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BpmnGateway {
Exclusive,
Parallel,
Inclusive,
EventBased,
Complex,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BpmnEvent {
Start,
Intermediate,
End,
Boundary,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BpmnTask {
name: String,
}
impl BpmnTask {
pub fn new(name: impl Into<String>) -> Self {
BpmnTask { name: name.into() }
}
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BpmnNode {
id: String,
kind: BpmnNodeKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BpmnNodeKind {
Task(BpmnTask),
Gateway(BpmnGateway),
Event(BpmnEvent),
}
impl BpmnNode {
pub fn task(id: impl Into<String>, task: BpmnTask) -> Self {
BpmnNode {
id: id.into(),
kind: BpmnNodeKind::Task(task),
}
}
pub fn gateway(id: impl Into<String>, gateway: BpmnGateway) -> Self {
BpmnNode {
id: id.into(),
kind: BpmnNodeKind::Gateway(gateway),
}
}
pub fn event(id: impl Into<String>, event: BpmnEvent) -> Self {
BpmnNode {
id: id.into(),
kind: BpmnNodeKind::Event(event),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn kind(&self) -> &BpmnNodeKind {
&self.kind
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BpmnEdge {
source: String,
target: String,
}
impl BpmnEdge {
pub fn new(source: impl Into<String>, target: impl Into<String>) -> Self {
BpmnEdge {
source: source.into(),
target: target.into(),
}
}
pub fn source(&self) -> &str {
&self.source
}
pub fn target(&self) -> &str {
&self.target
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BpmnProcess {
nodes: Vec<BpmnNode>,
edges: Vec<BpmnEdge>,
}
impl BpmnProcess {
pub fn new(
nodes: impl IntoIterator<Item = BpmnNode>,
edges: impl IntoIterator<Item = BpmnEdge>,
) -> Self {
BpmnProcess {
nodes: nodes.into_iter().collect(),
edges: edges.into_iter().collect(),
}
}
pub fn nodes(&self) -> &[BpmnNode] {
&self.nodes
}
pub fn edges(&self) -> &[BpmnEdge] {
&self.edges
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<(), BpmnRefusal> {
use std::collections::HashSet;
if self.nodes.is_empty() {
return Err(BpmnRefusal::EmptyProcess);
}
let mut ids: HashSet<&str> = HashSet::new();
let mut has_start = false;
let mut has_end = false;
for n in &self.nodes {
if !ids.insert(n.id()) {
return Err(BpmnRefusal::DuplicateNodeId);
}
match n.kind() {
BpmnNodeKind::Event(BpmnEvent::Start) => has_start = true,
BpmnNodeKind::Event(BpmnEvent::End) => has_end = true,
_ => {}
}
}
if !has_start {
return Err(BpmnRefusal::MissingStartEvent);
}
if !has_end {
return Err(BpmnRefusal::MissingEndEvent);
}
for e in &self.edges {
if !ids.contains(e.source()) || !ids.contains(e.target()) {
return Err(BpmnRefusal::DanglingEdge);
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BpmnLane {
id: String,
name: String,
node_ids: Vec<String>,
}
impl BpmnLane {
pub fn new<I, S>(id: impl Into<String>, name: impl Into<String>, node_ids: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
BpmnLane {
id: id.into(),
name: name.into(),
node_ids: node_ids.into_iter().map(Into::into).collect(),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn node_ids(&self) -> &[String] {
&self.node_ids
}
#[must_use = "check the shape-check result"]
pub fn validate(&self, known_ids: &std::collections::HashSet<&str>) -> Result<(), BpmnRefusal> {
for nid in &self.node_ids {
if !known_ids.contains(nid.as_str()) {
return Err(BpmnRefusal::LaneNodeNotDeclared);
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BpmnPool {
id: String,
name: String,
process: BpmnProcess,
lanes: Vec<BpmnLane>,
}
impl BpmnPool {
pub fn new<I>(
id: impl Into<String>,
name: impl Into<String>,
process: BpmnProcess,
lanes: I,
) -> Self
where
I: IntoIterator<Item = BpmnLane>,
{
BpmnPool {
id: id.into(),
name: name.into(),
process,
lanes: lanes.into_iter().collect(),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn process(&self) -> &BpmnProcess {
&self.process
}
pub fn lanes(&self) -> &[BpmnLane] {
&self.lanes
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<(), BpmnRefusal> {
self.process.validate()?;
let known: std::collections::HashSet<&str> =
self.process.nodes().iter().map(BpmnNode::id).collect();
for lane in &self.lanes {
lane.validate(&known)?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BpmnRefusal {
EmptyProcess,
DuplicateNodeId,
MissingStartEvent,
MissingEndEvent,
DanglingEdge,
MalformedGateway,
DisconnectedNode,
LaneNodeNotDeclared,
}
impl core::fmt::Display for BpmnRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let law = match self {
BpmnRefusal::EmptyProcess => "EmptyProcess",
BpmnRefusal::DuplicateNodeId => "DuplicateNodeId",
BpmnRefusal::MissingStartEvent => "MissingStartEvent",
BpmnRefusal::MissingEndEvent => "MissingEndEvent",
BpmnRefusal::DanglingEdge => "DanglingEdge",
BpmnRefusal::MalformedGateway => "MalformedGateway",
BpmnRefusal::DisconnectedNode => "DisconnectedNode",
BpmnRefusal::LaneNodeNotDeclared => "LaneNodeNotDeclared",
};
write!(f, "BPMN refused by law: {law}")
}
}