Skip to main content

cordis_include/
error.rs

1//! Error type for the include layer.
2
3use std::error::Error as StdError;
4use std::fmt;
5use std::io;
6use std::path::PathBuf;
7
8/// Result alias used throughout `cordis-include`.
9pub type Result<T> = std::result::Result<T, IncludeError>;
10
11/// Errors produced while reading entry trees and loader files.
12#[derive(Debug)]
13pub enum IncludeError {
14    /// An underlying filesystem operation failed.
15    Io(io::Error),
16    /// A config file could not be parsed or serialized.
17    Parse {
18        /// Which format was being processed (`"yaml"` or `"json"`).
19        format: &'static str,
20        /// The parser or serializer error.
21        source: Box<dyn StdError + Send + Sync>,
22    },
23    /// The file extension does not map to a supported format.
24    UnknownFormat {
25        /// The path that was opened.
26        path: PathBuf,
27    },
28    /// The target file is read-only, so configuration cannot be written back.
29    ReadOnly {
30        /// The path that was opened.
31        path: PathBuf,
32    },
33    /// Two entries in one tree declare the same id.
34    DuplicateId {
35        /// The conflicting id.
36        id: String,
37    },
38    /// No entry with the requested id exists in the tree.
39    EntryNotFound {
40        /// The requested (possibly composite) id.
41        id: String,
42    },
43    /// An entry id is empty or contains the `:` path separator.
44    InvalidId {
45        /// The offending id.
46        id: String,
47    },
48    /// An entry has no plugin name.
49    InvalidName,
50    /// A `${{ env.NAME }}` template referenced an unset variable.
51    MissingEnv {
52        /// The full expression that failed, e.g. `env.MISSING`.
53        expression: String,
54    },
55    /// A `${{ ... }}` template used an unsupported expression.
56    UnknownExpression {
57        /// The unsupported expression.
58        expression: String,
59    },
60    /// A `${{` template was never closed with `}}`.
61    Unterminated {
62        /// The input containing the dangling `${{`.
63        input: String,
64    },
65    /// The referenced entry does not belong to this tree.
66    NotInTree,
67    /// The requested move would place an entry inside its own subtree.
68    Cycle,
69    /// Setting up or running a file watcher failed (`watch` feature).
70    Watch {
71        /// The underlying watcher error.
72        source: Box<dyn StdError + Send + Sync>,
73    },
74    /// Free-form message from layers composing multiple files (import
75    /// cycles, unreadable sub-files).
76    Message {
77        /// The message.
78        message: String,
79    },
80}
81
82impl fmt::Display for IncludeError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Self::Io(error) => write!(f, "filesystem error: {error}"),
86            Self::Parse { format, source } => write!(f, "{format} parse error: {source}"),
87            Self::UnknownFormat { path } => write!(
88                f,
89                "unsupported config format (expected .yml, .yaml, or .json): {}",
90                path.display()
91            ),
92            Self::ReadOnly { path } => {
93                write!(f, "config file is read-only: {}", path.display())
94            }
95            Self::DuplicateId { id } => write!(f, "duplicate entry id `{id}`"),
96            Self::EntryNotFound { id } => write!(f, "entry `{id}` not found"),
97            Self::InvalidId { id } => {
98                write!(
99                    f,
100                    "invalid entry id `{id}`: must be non-empty and contain no `:`"
101                )
102            }
103            Self::InvalidName => write!(f, "entry name must be non-empty"),
104            Self::MissingEnv { expression } => {
105                write!(f, "environment variable unset: `{expression}`")
106            }
107            Self::UnknownExpression { expression } => {
108                write!(f, "unsupported template expression `{expression}`")
109            }
110            Self::Unterminated { input } => {
111                write!(f, "unterminated template `${{{{`}} in `{input}`")
112            }
113            Self::NotInTree => write!(f, "entry does not belong to this tree"),
114            Self::Cycle => write!(f, "cannot move an entry into its own subtree"),
115            Self::Watch { source } => write!(f, "file watcher error: {source}"),
116            Self::Message { message } => write!(f, "{message}"),
117        }
118    }
119}
120
121impl StdError for IncludeError {
122    fn source(&self) -> Option<&(dyn StdError + 'static)> {
123        match self {
124            Self::Io(error) => Some(error),
125            Self::Parse { source, .. } => Some(source.as_ref()),
126            Self::Watch { source } => Some(source.as_ref()),
127            _ => None,
128        }
129    }
130}
131
132impl From<io::Error> for IncludeError {
133    fn from(error: io::Error) -> Self {
134        Self::Io(error)
135    }
136}