Skip to main content

nils_markdown/
error.rs

1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum RenderError {
5    #[error("template parse failed for `{name}`: {source}")]
6    TemplateParse {
7        name: String,
8        #[source]
9        source: tera::Error,
10    },
11
12    #[error("template not registered: `{name}`")]
13    MissingTemplate { name: String },
14
15    #[error(
16        "template `{name}` references the non-deterministic `now()` function; \
17         nils-markdown rejects templates that depend on wall-clock time"
18    )]
19    NonDeterministicTemplate { name: String },
20
21    #[error("view serialization failed: {source}")]
22    Serialize {
23        #[source]
24        source: serde_json::Error,
25    },
26
27    #[error("tera render failed for `{name}`: {source}")]
28    Render {
29        name: String,
30        #[source]
31        source: tera::Error,
32    },
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    fn assert_send_sync_static<T: Send + Sync + 'static>() {}
40
41    #[test]
42    fn render_error_is_send_sync_static() {
43        assert_send_sync_static::<RenderError>();
44    }
45
46    #[test]
47    fn render_error_round_trips_through_anyhow() {
48        let err = RenderError::MissingTemplate {
49            name: "issue_body".into(),
50        };
51        let any: anyhow::Error = err.into();
52        let printed = format!("{any}");
53        assert!(printed.contains("issue_body"), "{printed}");
54    }
55
56    #[test]
57    fn template_parse_error_renders_name_and_source() {
58        let tera_err = tera::Error::msg("syntax error at line 3");
59        let err = RenderError::TemplateParse {
60            name: "dashboard".into(),
61            source: tera_err,
62        };
63        let printed = format!("{err}");
64        assert!(printed.contains("dashboard"), "{printed}");
65        assert!(printed.contains("syntax error"), "{printed}");
66    }
67
68    #[test]
69    fn missing_template_renders_name() {
70        let err = RenderError::MissingTemplate {
71            name: "absent".into(),
72        };
73        assert_eq!(format!("{err}"), "template not registered: `absent`");
74    }
75
76    #[test]
77    fn non_deterministic_template_renders_name() {
78        let err = RenderError::NonDeterministicTemplate {
79            name: "time_based".into(),
80        };
81        let printed = format!("{err}");
82        assert!(printed.contains("time_based"), "{printed}");
83        assert!(printed.contains("non-deterministic"), "{printed}");
84    }
85
86    #[test]
87    fn serialize_error_renders_source() {
88        let source =
89            serde_json::from_str::<serde_json::Value>("{ not json").expect_err("invalid json");
90        let err = RenderError::Serialize { source };
91        let printed = format!("{err}");
92        assert!(
93            printed.starts_with("view serialization failed"),
94            "{printed}"
95        );
96    }
97
98    #[test]
99    fn render_runtime_error_renders_name() {
100        let err = RenderError::Render {
101            name: "lifecycle".into(),
102            source: tera::Error::msg("missing variable"),
103        };
104        let printed = format!("{err}");
105        assert!(printed.contains("lifecycle"), "{printed}");
106        assert!(printed.contains("missing variable"), "{printed}");
107    }
108}