Skip to main content

chrony_confile/
error.rs

1//! Error types for parsing, validation, and value construction.
2//!
3//! This module defines the three error types used throughout the crate:
4//! - [`ParseError`] -- I/O errors, parse errors, and include-level exceeded errors
5//! - [`DirectiveError`] -- Errors specific to a single directive (invalid option, value, etc.)
6//! - [`ValueError`] -- Errors from constructing out-of-range or invalid values
7//!
8//! Both [`ParseError`] and [`DirectiveError`] implement [`std::error::Error`] (via `thiserror`),
9//! making them compatible with `?` and any error-reporting crate.
10
11use std::{io, path::PathBuf};
12use crate::span::Span;
13
14/// Represents errors that can occur during parsing of chrony configuration files.
15///
16/// This includes I/O errors when reading included files, directive-level parse errors,
17/// and configuration-level errors such as exceeding the maximum include depth or
18/// encountering lines exceeding the maximum length.
19///
20/// [`ParseError`] implements [`std::error::Error`] via `thiserror` and supports
21/// conversion from [`std::io::Error`].
22///
23/// # Examples
24///
25/// ```rust
26/// use chrony_confile::ChronyConfig;
27///
28/// // Malformed input produces a ParseError
29/// let result = ChronyConfig::parse("::invalid directive::");
30/// assert!(result.is_err());
31/// ```
32#[derive(Debug, thiserror::Error)]
33pub enum ParseError {
34    #[error("I/O error: {0}")]
35    Io(#[from] io::Error),
36
37    /// A directive-level parse error with an optional file name.
38    #[error("{inner}")]
39    Parse {
40        file: Option<PathBuf>,
41        #[source]
42        inner: DirectiveError,
43    },
44
45    /// Maximum include nesting depth was exceeded during config expansion.
46    #[error("Maximum include level reached at {file}:{line}")]
47    IncludeLevelExceeded { file: PathBuf, line: usize },
48
49    /// A line exceeded the maximum allowed length (2048 bytes).
50    #[error("Line too long ({len} bytes) at {}:{line}", file.as_deref().map_or("?", |p| p.to_str().unwrap_or("?")))]
51    LineTooLong {
52        file: Option<PathBuf>,
53        line: usize,
54        len: usize,
55    },
56}
57
58impl PartialEq for ParseError {
59    fn eq(&self, other: &Self) -> bool {
60        match (self, other) {
61            (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
62            (
63                Self::Parse {
64                    file: f1,
65                    inner: i1,
66                },
67                Self::Parse {
68                    file: f2,
69                    inner: i2,
70                },
71            ) => f1 == f2 && i1 == i2,
72            (
73                Self::IncludeLevelExceeded {
74                    file: f1,
75                    line: l1,
76                },
77                Self::IncludeLevelExceeded {
78                    file: f2,
79                    line: l2,
80                },
81            ) => f1 == f2 && l1 == l2,
82            (
83                Self::LineTooLong {
84                    file: f1,
85                    line: l1,
86                    len: n1,
87                },
88                Self::LineTooLong {
89                    file: f2,
90                    line: l2,
91                    len: n2,
92                },
93            ) => f1 == f2 && l1 == l2 && n1 == n2,
94            _ => false,
95        }
96    }
97}
98
99/// Errors specific to a single directive during parsing.
100///
101/// Covers invalid directive names, invalid options, invalid or out-of-range values,
102/// and incorrect argument counts.
103///
104/// These errors are typically wrapped in [`ParseError::Parse`] when returned from
105/// the top-level parsing API.
106#[derive(Debug, thiserror::Error, PartialEq)]
107pub enum DirectiveError {
108    /// The directive name is not a recognised chrony directive.
109    #[error("Invalid directive `{name}` at {span}")]
110    InvalidDirective { name: String, span: Span },
111
112    /// The option is not valid for the given directive.
113    /// The option is not valid for the given directive.
114    #[error("Invalid option `{option}` in `{directive}` at {span}")]
115    InvalidOption {
116        directive: String,
117        option: String,
118        span: Span,
119    },
120
121    /// The argument value could not be parsed as the expected type.
122    #[error("Invalid value in `{directive}`: expected {expected}, got `{got}` at {span}")]
123    InvalidValue {
124        directive: String,
125        expected: &'static str,
126        got: String,
127        span: Span,
128    },
129
130    /// A numeric value fell outside the valid range.
131    #[error("Value `{value}` out of range (min {min}, max {max}) in `{directive}` at {span}")]
132    ValueOutOfRange {
133        directive: String,
134        value: String,
135        min: String,
136        max: String,
137        span: Span,
138    },
139
140    /// Not enough arguments were provided for the directive.
141    #[error("Missing arguments in `{directive}`: expected {expected}, got {got} at {span}")]
142    MissingArgument {
143        directive: String,
144        expected: usize,
145        got: usize,
146        span: Span,
147    },
148
149    /// More arguments were provided than the directive expects.
150    #[error("Too many arguments in `{directive}`: expected {expected}, got {got} at {span}")]
151    TooManyArguments {
152        directive: String,
153        expected: usize,
154        got: usize,
155        span: Span,
156    },
157}
158
159/// Errors from constructing bounded integer values.
160///
161/// Returned by types like [`PollInterval`](crate::values::PollInterval),
162/// [`UdpPort`](crate::values::UdpPort), and [`Stratum`](crate::values::Stratum) when
163/// a value is out of range or has an invalid format.
164///
165/// # Examples
166///
167/// ```rust
168/// use chrony_confile::values::PollInterval;
169///
170/// // Values outside the valid range produce a ValueError
171/// let err = PollInterval::new(100).unwrap_err();
172/// assert!(err.to_string().contains("out of range"));
173/// ```
174#[derive(Debug, thiserror::Error, PartialEq)]
175pub enum ValueError {
176    /// The value is outside the valid range of the bounded integer type.
177    #[error("Value {value} out of range ({min}..{max})")]
178    OutOfRange { value: i64, min: i64, max: i64 },
179
180    /// The string could not be parsed as the expected format.
181    #[error("Invalid format `{value}`: expected {expected}")]
182    InvalidFormat { value: String, expected: &'static str },
183
184    /// The keyword did not match any of the valid options.
185    #[error("Invalid keyword `{value}`, expected one of: {}", valid.join(", "))]
186    InvalidKeyword {
187        value: String,
188        valid: &'static [&'static str],
189    },
190}