Skip to main content

isoloom_spec/syntax/
warning.rs

1//! Something a valid spec says that is likely not what its author means: shown beside the
2//! problems, never failing validation.
3//!
4//! ```text
5//! warning: machines.web.app.steps[1]  (isoloom.yml:9:11)
6//!   `nginx` installs a daemon that starts on Debian VMs only: ...
7//!   fix: `service: nginx`, or `{ service: { name: nginx, enabled: false } }`
8//! ```
9
10use std::fmt;
11
12use serde::Serialize;
13
14use super::problem::{FieldPath, Span, lines};
15
16/// A warning about a field of the spec.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
18pub struct Warning {
19    /// The field it is about.
20    pub path: FieldPath,
21    /// Where that field is in the file, when it is written there.
22    #[serde(flatten, skip_serializing_if = "Option::is_none")]
23    pub span: Option<Span>,
24    /// What is likely wrong.
25    pub reason: String,
26    /// What to write instead.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub fix: Option<String>,
29}
30
31impl Warning {
32    /// A warning at `path`, not yet placed.
33    #[must_use]
34    pub fn new(path: FieldPath, reason: impl Into<String>) -> Warning {
35        Warning {
36            path,
37            span: None,
38            reason: reason.into(),
39            fix: None,
40        }
41    }
42
43    /// The warning, with its fix.
44    #[must_use]
45    pub fn fix(mut self, fix: impl Into<String>) -> Warning {
46        self.fix = Some(fix.into());
47        self
48    }
49
50    /// The three lines an author reads, naming `file` (`isoloom.yml`) on the first.
51    #[must_use]
52    pub fn render(&self, file: &str) -> String {
53        lines(&format!("warning: {}", self.path), self.span, file, &self.reason, self.fix.as_deref())
54    }
55}
56
57impl fmt::Display for Warning {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        f.write_str(&self.render("isoloom.yml"))
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn a_warning_reads_as_three_lines() {
69        let mut w = Warning::new(FieldPath::new("machines.web.app.steps[1]"), "`nginx` starts on Debian VMs only").fix("`service: nginx`");
70        w.span = Some(Span { line: 9, column: 11 });
71        assert_eq!(
72            w.render("isoloom.yml"),
73            "warning: machines.web.app.steps[1]  (isoloom.yml:9:11)\n  `nginx` starts on Debian VMs only\n  fix: `service: nginx`"
74        );
75        let json = serde_json::to_value(&w).unwrap();
76        assert_eq!((json["path"].as_str(), json["line"].as_u64()), (Some("machines.web.app.steps[1]"), Some(9)));
77    }
78}