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}
75
76impl fmt::Display for IncludeError {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        match self {
79            Self::Io(error) => write!(f, "filesystem error: {error}"),
80            Self::Parse { format, source } => write!(f, "{format} parse error: {source}"),
81            Self::UnknownFormat { path } => write!(
82                f,
83                "unsupported config format (expected .yml, .yaml, or .json): {}",
84                path.display()
85            ),
86            Self::ReadOnly { path } => {
87                write!(f, "config file is read-only: {}", path.display())
88            }
89            Self::DuplicateId { id } => write!(f, "duplicate entry id `{id}`"),
90            Self::EntryNotFound { id } => write!(f, "entry `{id}` not found"),
91            Self::InvalidId { id } => {
92                write!(
93                    f,
94                    "invalid entry id `{id}`: must be non-empty and contain no `:`"
95                )
96            }
97            Self::InvalidName => write!(f, "entry name must be non-empty"),
98            Self::MissingEnv { expression } => {
99                write!(f, "environment variable unset: `{expression}`")
100            }
101            Self::UnknownExpression { expression } => {
102                write!(f, "unsupported template expression `{expression}`")
103            }
104            Self::Unterminated { input } => {
105                write!(f, "unterminated template `${{{{`}} in `{input}`")
106            }
107            Self::NotInTree => write!(f, "entry does not belong to this tree"),
108            Self::Cycle => write!(f, "cannot move an entry into its own subtree"),
109            Self::Watch { source } => write!(f, "file watcher error: {source}"),
110        }
111    }
112}
113
114impl StdError for IncludeError {
115    fn source(&self) -> Option<&(dyn StdError + 'static)> {
116        match self {
117            Self::Io(error) => Some(error),
118            Self::Parse { source, .. } => Some(source.as_ref()),
119            Self::Watch { source } => Some(source.as_ref()),
120            _ => None,
121        }
122    }
123}
124
125impl From<io::Error> for IncludeError {
126    fn from(error: io::Error) -> Self {
127        Self::Io(error)
128    }
129}