Skip to main content

pflow_tokenmodel/
validate.rs

1//! Schema validation.
2
3use std::collections::HashSet;
4
5use crate::error::{Error, Result};
6use crate::schema::Schema;
7
8impl Schema {
9    /// Validates the schema for structural correctness.
10    pub fn validate(&self) -> Result<()> {
11        let mut state_ids = HashSet::new();
12        let mut action_ids = HashSet::new();
13
14        for st in &self.states {
15            if st.id.is_empty() {
16                return Err(Error::EmptyId);
17            }
18            if !state_ids.insert(st.id.clone()) {
19                return Err(Error::DuplicateId(st.id.clone()));
20            }
21        }
22
23        for a in &self.actions {
24            if a.id.is_empty() {
25                return Err(Error::EmptyId);
26            }
27            if !action_ids.insert(a.id.clone()) {
28                return Err(Error::DuplicateId(a.id.clone()));
29            }
30        }
31
32        for arc in &self.arcs {
33            let source_is_state = state_ids.contains(&arc.source);
34            let source_is_action = action_ids.contains(&arc.source);
35            let target_is_state = state_ids.contains(&arc.target);
36            let target_is_action = action_ids.contains(&arc.target);
37
38            if !source_is_state && !source_is_action {
39                return Err(Error::InvalidArcSource(arc.source.clone()));
40            }
41            if !target_is_state && !target_is_action {
42                return Err(Error::InvalidArcTarget(arc.target.clone()));
43            }
44            if (source_is_state && target_is_state) || (source_is_action && target_is_action) {
45                return Err(Error::InvalidArcConnection);
46            }
47        }
48
49        Ok(())
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use crate::schema::*;
56
57    #[test]
58    fn test_valid_schema() {
59        let mut s = Schema::new("test");
60        s.add_token_state("p1", 1);
61        s.add_action(Action {
62            id: "t1".into(),
63            guard: String::new(),
64            event_id: String::new(),
65            event_bindings: None,
66        });
67        s.add_arc(Arc {
68            source: "p1".into(),
69            target: "t1".into(),
70            keys: vec![],
71            value: String::new(),
72        });
73        assert!(s.validate().is_ok());
74    }
75
76    #[test]
77    fn test_empty_id() {
78        let mut s = Schema::new("test");
79        s.add_state(State {
80            id: String::new(),
81            kind: Kind::Token,
82            initial: None,
83            typ: String::new(),
84            exported: false,
85        });
86        assert!(s.validate().is_err());
87    }
88
89    #[test]
90    fn test_duplicate_id() {
91        let mut s = Schema::new("test");
92        s.add_token_state("p1", 1);
93        s.add_token_state("p1", 2);
94        assert!(s.validate().is_err());
95    }
96
97    #[test]
98    fn test_invalid_arc_source() {
99        let mut s = Schema::new("test");
100        s.add_token_state("p1", 1);
101        s.add_action(Action {
102            id: "t1".into(),
103            guard: String::new(),
104            event_id: String::new(),
105            event_bindings: None,
106        });
107        s.add_arc(Arc {
108            source: "missing".into(),
109            target: "t1".into(),
110            keys: vec![],
111            value: String::new(),
112        });
113        assert!(s.validate().is_err());
114    }
115
116    #[test]
117    fn test_state_to_state_arc() {
118        let mut s = Schema::new("test");
119        s.add_token_state("p1", 1);
120        s.add_token_state("p2", 0);
121        s.add_arc(Arc {
122            source: "p1".into(),
123            target: "p2".into(),
124            keys: vec![],
125            value: String::new(),
126        });
127        assert!(s.validate().is_err());
128    }
129}