sodg/
alerts.rs

1// Copyright (c) 2022-2023 Yegor Bugayenko
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included
11// in all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{Alert, Sodg};
22use anyhow::anyhow;
23use anyhow::Result;
24
25impl Sodg {
26    /// Attach a new alert to this graph.
27    ///
28    /// For example, you don't want
29    /// more than one edge to depart from any vertex:
30    ///
31    /// ```
32    /// use sodg::Sodg;
33    /// let mut g = Sodg::empty();
34    /// g.alerts_on().unwrap();
35    /// g.alert_on(|g, vx| {
36    ///   for v in vx {
37    ///     if g.kids(v).unwrap().len() > 1 {
38    ///       return vec![format!("Too many kids at ν{v}")];
39    ///     }
40    ///   }
41    ///   return vec![];
42    /// });
43    /// g.add(0).unwrap();
44    /// g.add(1).unwrap();
45    /// g.add(2).unwrap();
46    /// g.bind(0, 1, "first").unwrap();
47    /// assert!(g.bind(0, 2, "second").is_err());
48    /// ```
49    pub fn alert_on(&mut self, a: Alert) {
50        self.alerts.push(a);
51    }
52
53    /// Disable all alerts.
54    pub fn alerts_off(&mut self) {
55        self.alerts_active = false;
56    }
57
58    /// Enable all alerts.
59    ///
60    /// This function also runs all vertices through
61    /// all checks and returns the list of errors found. If everything
62    /// was fine, an empty vector is returned.
63    ///
64    /// # Errors
65    ///
66    /// An error may be returned if validation fails, after the alerts are turned ON.
67    pub fn alerts_on(&mut self) -> Result<()> {
68        self.alerts_active = true;
69        self.validate(self.vertices.keys().copied().collect())
70    }
71
72    /// Check all alerts for the given list of vertices.
73    ///
74    /// # Errors
75    ///
76    /// If any of them have any issues, `Err` is returned.
77    #[allow(clippy::needless_pass_by_value)]
78    pub fn validate(&self, vx: Vec<u32>) -> Result<()> {
79        if self.alerts_active {
80            for a in &self.alerts {
81                let msgs = a(self, vx.clone());
82                if !msgs.is_empty() {
83                    return Err(anyhow!("{}", msgs.join("; ")));
84                }
85            }
86        }
87        Ok(())
88    }
89}
90
91#[test]
92fn panic_on_simple_alert() -> Result<()> {
93    let mut g = Sodg::empty();
94    g.alerts_on()?;
95    g.alert_on(|_, _| vec![format!("{}", "oops")]);
96    assert!(g.add(0).is_err());
97    Ok(())
98}
99
100#[test]
101fn dont_panic_when_alerts_disabled() -> Result<()> {
102    let mut g = Sodg::empty();
103    g.alert_on(|_, _| vec!["should never happen".to_string()]);
104    g.alerts_off();
105    assert!(g.add(0).is_ok());
106    Ok(())
107}
108
109#[test]
110fn panic_on_complex_alert() -> Result<()> {
111    let mut g = Sodg::empty();
112    g.alert_on(|_, vx| {
113        let v = 42;
114        if vx.contains(&v) {
115            vec![format!("Vertex ν{v} is not allowed")]
116        } else {
117            vec![]
118        }
119    });
120    g.alerts_on()?;
121    assert!(g.add(42).is_err());
122    Ok(())
123}