Skip to main content

markdown_contract/declarative/
errors.rs

1//! The loader's error: a declarative document that is malformed or uses a feature
2//! outside `mcVersion: 2`. The variants a host may act on are separate:
3//! [`DeclarativeError::UnsupportedVersion`], [`DeclarativeError::RetiredVersion`]
4//! (`mcVersion: 1`, which `markdown-contract migrate` rewrites) and
5//! [`DeclarativeError::CodeContractRef`].
6
7/// A declarative document that could not be compiled into a contract / config.
8#[derive(Debug, thiserror::Error)]
9pub enum DeclarativeError {
10    /// the YAML text itself does not parse
11    #[error("invalid YAML: {0}")]
12    InvalidYaml(String),
13
14    /// the document parses but is not a mapping with the required envelope
15    #[error("{0}")]
16    InvalidDocument(String),
17
18    /// an `mcVersion` outside the supported set — never best-effort parsed
19    #[error("unsupported mcVersion: {0} (this build supports 2)")]
20    UnsupportedVersion(String),
21
22    /// `mcVersion: 1` — retired; `markdown-contract migrate` rewrites a v1 document
23    #[error(
24        "mcVersion 1 is no longer supported; rewrite the file as mcVersion 2 with `markdown-contract migrate --write <file>` (the CLI of @osdlc/markdown-contract)"
25    )]
26    RetiredVersion,
27
28    /// a `kind` that is neither `contract` nor `config`
29    #[error("kind must be \"contract\" or \"config\" (got {0})")]
30    InvalidKind(String),
31
32    /// a schema node outside the closed v2 vocabulary
33    #[error("{0}")]
34    InvalidSchema(String),
35
36    /// a body-grammar node outside the v2 DSL (or a build-time grammar violation)
37    #[error("{0}")]
38    InvalidBody(String),
39
40    /// a `requires:` / `forbids:` match spec outside the closed vocabulary (or a
41    /// duplicate / contradiction)
42    #[error("{0}")]
43    InvalidTextSpec(String),
44
45    /// a config document outside the v2 DSL
46    #[error("{0}")]
47    InvalidConfig(String),
48
49    /// a config contract ref that is not a `.yaml` / `.yml` file, such as a `.ts` module
50    #[error(
51        "{path}: a contract ref must be a .yaml file (got '{target}'); referencing a code-authored .js/.ts contract is not supported"
52    )]
53    CodeContractRef {
54        /// where the ref sits in the config, such as `rules[0].contract`
55        path: String,
56        /// the file the ref resolves to, after the `contracts` name lookup
57        target: String,
58    },
59
60    /// a referenced contract file could not be read
61    #[error("failed to read contract ref '{target}': {reason}")]
62    ContractRefRead {
63        /// the file the ref resolves to
64        target: String,
65        /// why the read failed
66        reason: String,
67    },
68
69    /// a config or contract file passed to a `*_file` loader could not be read
70    #[error("failed to read '{path}': {reason}")]
71    FileRead {
72        /// the file
73        path: String,
74        /// why the read failed
75        reason: String,
76    },
77}
78
79#[cfg(test)]
80mod tests {
81    use super::DeclarativeError;
82
83    // The variants a host may act on render their messages verbatim.
84    #[test]
85    fn hook_variants_render_their_messages() {
86        let e = DeclarativeError::UnsupportedVersion("3".into());
87        assert_eq!(
88            e.to_string(),
89            "unsupported mcVersion: 3 (this build supports 2)"
90        );
91        let e = DeclarativeError::RetiredVersion;
92        assert_eq!(
93            e.to_string(),
94            "mcVersion 1 is no longer supported; rewrite the file as mcVersion 2 with `markdown-contract migrate --write <file>` (the CLI of @osdlc/markdown-contract)"
95        );
96        let e = DeclarativeError::CodeContractRef {
97            path: "rules[0].contract".into(),
98            target: "./task.contract.ts".into(),
99        };
100        assert!(e.to_string().contains(".js/.ts contract is not supported"));
101    }
102}