Skip to main content

asbuilt_core/
error.rs

1//! Errors that name the offending input, so a message reads without a
2//! rerun.
3
4use std::path::PathBuf;
5
6/// Anything the core, a front-end, or the config can get wrong.
7#[derive(Debug, thiserror::Error)]
8pub enum Error {
9    #[error("reading {path}: {source}")]
10    Io {
11        path: PathBuf,
12        #[source]
13        source: std::io::Error,
14    },
15
16    /// `asbuilt.toml` did not parse or had a key the schema rejects.
17    #[error("{path}: {source}")]
18    Config {
19        path: PathBuf,
20        #[source]
21        source: toml::de::Error,
22    },
23
24    /// An `[[externals.relations]]` `from` that names no generated
25    /// element and no sibling external. A typo here is an error, not a
26    /// diagram that quietly omits an edge.
27    #[error(
28        "externals.relations: \"{from}\" (under external \"{external}\") names no element in the surveyed model"
29    )]
30    UnknownRelationSource { external: String, from: String },
31
32    /// An `[[externals]]` `id` that is not a LikeC4 identifier as written.
33    #[error(
34        "externals: id \"{id}\" is not an identifier (letters, digits and _, not starting with a digit, not a keyword)"
35    )]
36    InvalidExternalId { id: String },
37
38    /// Two model ids that become the same LikeC4 identifier once `-`
39    /// maps to `_` and a leading digit is prefixed.
40    #[error("element ids \"{first}\" and \"{second}\" both become the LikeC4 id \"{id}\"")]
41    DuplicateId {
42        id: String,
43        first: String,
44        second: String,
45    },
46
47    /// A `[theme]` key naming an element kind the surveyed model does not
48    /// have; a typo would otherwise style nothing, silently.
49    #[error("theme: \"{kind}\" is not an element kind of the surveyed model (have {have})")]
50    UnknownThemeKind { kind: String, have: String },
51
52    /// No front-end recognized the root.
53    #[error("no supported stack detected at {root}")]
54    NoFrontend { root: PathBuf },
55
56    /// A front-end failed; the source says which file or module.
57    #[error("{frontend} front-end: {source}")]
58    Frontend {
59        frontend: String,
60        #[source]
61        source: Box<dyn std::error::Error + Send + Sync + 'static>,
62    },
63}
64
65pub type Result<T> = std::result::Result<T, Error>;
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn an_unknown_relation_source_names_the_external_and_the_from() {
73        let msg = Error::UnknownRelationSource {
74            external: "node_driver".into(),
75            from: "playwright_rs.nope".into(),
76        }
77        .to_string();
78        assert!(msg.contains("node_driver"), "{msg}");
79        assert!(msg.contains("playwright_rs.nope"), "{msg}");
80    }
81
82    #[test]
83    fn a_duplicate_id_names_both_originals_and_the_collision() {
84        let msg = Error::DuplicateId {
85            id: "foo_bar".into(),
86            first: "foo-bar".into(),
87            second: "foo_bar".into(),
88        }
89        .to_string();
90        assert!(
91            msg.contains("\"foo-bar\"") && msg.contains("\"foo_bar\""),
92            "{msg}"
93        );
94    }
95}