Skip to main content

hocon/
error.rs

1use std::fmt;
2
3/// Error returned when HOCON input contains a syntax error.
4///
5/// Includes the line and column where the error was detected.
6#[non_exhaustive]
7#[derive(Debug, Clone)]
8pub struct ParseError {
9    /// Human-readable description of the error.
10    pub message: String,
11    /// 1-based line number.
12    pub line: usize,
13    /// 1-based column number.
14    pub col: usize,
15}
16
17impl fmt::Display for ParseError {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        write!(
20            f,
21            "ParseError at {}:{}: {}",
22            self.line, self.col, self.message
23        )
24    }
25}
26
27impl std::error::Error for ParseError {}
28
29/// Error returned when substitution resolution fails (e.g., missing
30/// required substitution, cyclic reference).
31#[non_exhaustive]
32#[derive(Debug, Clone)]
33pub struct ResolveError {
34    /// Human-readable description of the error.
35    pub message: String,
36    /// The substitution path that failed (e.g., `"db.host"`).
37    pub path: String,
38    /// 1-based line number where the substitution appeared.
39    pub line: usize,
40    /// 1-based column number where the substitution appeared.
41    pub col: usize,
42}
43
44impl fmt::Display for ResolveError {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        write!(
47            f,
48            "ResolveError at {}:{}: {} (path: {})",
49            self.line, self.col, self.message, self.path
50        )
51    }
52}
53
54impl ResolveError {
55    /// Construct a type-mismatch error for value-concat (S10.4/S10.13/S10.19).
56    ///
57    /// `line` and `col` are the position of the concat expression in the source
58    /// (from `ConcatPlaceholder`). `path` is the field path being resolved.
59    pub(crate) fn concat_type_mismatch(
60        left_type: &str,
61        right_type: &str,
62        line: usize,
63        col: usize,
64    ) -> Self {
65        ResolveError {
66            message: format!(
67                "value concatenation requires same-kind operands per HOCON S10; \
68                 got {} + {}",
69                left_type, right_type
70            ),
71            path: String::new(),
72            line,
73            col,
74        }
75    }
76}
77
78impl std::error::Error for ResolveError {}
79
80/// Error returned by [`Config`](crate::Config) getters when a key is missing
81/// or the value has the wrong type.
82#[non_exhaustive]
83#[derive(Debug, Clone)]
84pub struct ConfigError {
85    /// Human-readable description of the error.
86    pub message: String,
87    /// The dot-separated path that was looked up.
88    pub path: String,
89}
90
91impl fmt::Display for ConfigError {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "ConfigError: {} (path: {})", self.message, self.path)
94    }
95}
96
97impl std::error::Error for ConfigError {}
98
99impl ConfigError {
100    /// Returns `true` if this error was produced because the value at the path
101    /// contains an unresolved substitution placeholder.
102    ///
103    /// Use this instead of message-string matching to detect the "not resolved"
104    /// condition when the caller holds a `ConfigError` (i.e., from a typed getter
105    /// such as `get_string`, `get_i64`, etc.).
106    ///
107    /// # Example
108    /// ```rust,ignore
109    /// let err = config.get_string("my.key").unwrap_err();
110    /// if err.is_not_resolved() {
111    ///     // Call config.resolve(ResolveOptions::defaults()) first
112    /// }
113    /// ```
114    pub fn is_not_resolved(&self) -> bool {
115        self.message.starts_with("value is not resolved")
116    }
117}
118
119/// Error returned when a getter is called on a [`Config`](crate::Config) path
120/// whose value (or any transitive parent) contains an unresolved substitution
121/// placeholder.
122///
123/// Detect via `downcast_ref::<NotResolvedError>()` or by matching
124/// `HoconError::NotResolved(e)`.  Per E12 decision 12.
125#[derive(Debug, Clone)]
126pub struct NotResolvedError {
127    /// The dot-separated path that was accessed.
128    pub path: String,
129}
130
131impl fmt::Display for NotResolvedError {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(
134            f,
135            "value at path {:?} is not resolved (call resolve() before accessing values)",
136            self.path
137        )
138    }
139}
140
141impl std::error::Error for NotResolvedError {}
142
143/// Unified error type returned by top-level parse functions.
144///
145/// Wraps the three possible failure modes: syntax errors ([`ParseError`]),
146/// substitution resolution failures ([`ResolveError`]), and file I/O
147/// errors ([`std::io::Error`]).
148#[non_exhaustive]
149#[derive(Debug)]
150pub enum HoconError {
151    /// Syntax error during lexing or parsing.
152    Parse(ParseError),
153    /// Substitution resolution failure (missing key, cycle, etc.).
154    Resolve(ResolveError),
155    /// File I/O error when reading the top-level config file.
156    Io(std::io::Error),
157    /// A getter was called on a path whose value contains an unresolved
158    /// substitution placeholder. Per E12 decision 12.
159    NotResolved(NotResolvedError),
160}
161
162impl fmt::Display for HoconError {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match self {
165            HoconError::Parse(e) => write!(f, "{}", e),
166            HoconError::Resolve(e) => write!(f, "{}", e),
167            HoconError::Io(e) => write!(f, "I/O error: {}", e),
168            HoconError::NotResolved(e) => write!(f, "{}", e),
169        }
170    }
171}
172
173impl std::error::Error for HoconError {
174    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
175        match self {
176            HoconError::Parse(e) => Some(e),
177            HoconError::Resolve(e) => Some(e),
178            HoconError::Io(e) => Some(e),
179            HoconError::NotResolved(e) => Some(e),
180        }
181    }
182}
183
184impl From<ParseError> for HoconError {
185    fn from(e: ParseError) -> Self {
186        HoconError::Parse(e)
187    }
188}
189
190impl From<ResolveError> for HoconError {
191    fn from(e: ResolveError) -> Self {
192        HoconError::Resolve(e)
193    }
194}
195
196impl From<std::io::Error> for HoconError {
197    fn from(e: std::io::Error) -> Self {
198        HoconError::Io(e)
199    }
200}