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}