Skip to main content

dynomite/conf/
error.rs

1//! Typed errors emitted by configuration parsing and validation.
2
3use std::path::PathBuf;
4
5use thiserror::Error;
6
7/// Errors that can occur while loading or validating a [`Config`].
8///
9/// [`Config`]: crate::conf::Config
10///
11/// # Examples
12///
13/// ```
14/// use dynomite::conf::{Config, ConfError};
15/// let err = Config::parse_str("").unwrap_err();
16/// assert!(matches!(err, ConfError::Yaml { .. } | ConfError::EmptyDocument));
17/// ```
18#[derive(Debug, Error)]
19pub enum ConfError {
20    /// I/O error while reading a configuration file.
21    #[error("conf: failed to read configuration file '{path}': {source}")]
22    Io {
23        /// The path that triggered the failure.
24        path: PathBuf,
25        /// The underlying I/O error.
26        #[source]
27        source: std::io::Error,
28    },
29
30    /// The YAML document was empty or missing the top-level pool.
31    #[error("conf: configuration document is empty")]
32    EmptyDocument,
33
34    /// The top-level mapping had more than one pool.
35    #[error("conf: configuration must contain exactly one pool, found {0}")]
36    TooManyPools(usize),
37
38    /// The top-level pool name was the empty string.
39    #[error("conf: pool name must not be empty")]
40    EmptyPoolName,
41
42    /// A directive name in the YAML is not recognized.
43    #[error("conf: directive '{name}' is unknown")]
44    UnknownKey {
45        /// The unrecognized YAML key.
46        name: String,
47    },
48
49    /// A required directive is absent from the configuration.
50    #[error("conf: directive '{0}' is missing")]
51    MissingRequired(&'static str),
52
53    /// `listen` / `dyn_listen` / `stats_listen` address could not be parsed.
54    #[error("conf: '{field}' has an invalid address '{value}': {reason}")]
55    BadAddr {
56        /// The directive whose value failed to parse.
57        field: &'static str,
58        /// The string that failed to parse.
59        value: String,
60        /// Human-readable parse failure reason.
61        reason: String,
62    },
63
64    /// A token list could not be parsed as comma-separated big-ints.
65    #[error("conf: token list '{value}' is not valid: {reason}")]
66    BadToken {
67        /// The token-list string.
68        value: String,
69        /// Human-readable parse failure reason.
70        reason: String,
71    },
72
73    /// `read_consistency` / `write_consistency` value is not a known level.
74    #[error("conf: directive '{field}' must be one of 'DC_ONE', 'DC_QUORUM', 'DC_SAFE_QUORUM', 'DC_EACH_SAFE_QUORUM', got '{value}'")]
75    BadConsistency {
76        /// The directive: `read_consistency` or `write_consistency`.
77        field: &'static str,
78        /// The unrecognized value.
79        value: String,
80    },
81
82    /// `secure_server_option` value is not a known mode.
83    #[error("conf: directive 'secure_server_option' must be one of 'none', 'rack', 'datacenter', 'all', got '{0}'")]
84    BadSecure(String),
85
86    /// `data_store` value is not 0 (Redis), 1 (Memcache), or 2
87    /// (Noxu). The string forms `redis`, `memcache`, and `noxu`
88    /// are also accepted on the YAML side and are translated to
89    /// these integers before validation.
90    #[error("conf: directive 'data_store' must be 0 (redis), 1 (memcache), or 2 (noxu), got {0}")]
91    BadDataStore(i64),
92
93    /// `data_store: noxu` was selected but `noxu_path` was not
94    /// supplied or `dynomited` was built without `--features
95    /// riak`.
96    #[error("conf: {0}")]
97    BadNoxuConfig(&'static str),
98
99    /// `hash` value is not a recognized hash algorithm name.
100    #[error("conf: directive 'hash' is not a valid hash function, got '{0}'")]
101    BadHash(String),
102
103    /// `distribution` value is not a recognized distribution
104    /// algorithm name.
105    #[error("conf: directive 'distribution' is not a valid distribution, got '{0}'")]
106    BadDistribution(String),
107
108    /// A numeric directive is out of its allowed range.
109    #[error("conf: directive '{field}' value {value} is out of range: {reason}")]
110    OutOfRange {
111        /// The directive name.
112        field: &'static str,
113        /// The offending value.
114        value: i64,
115        /// What range was expected.
116        reason: &'static str,
117    },
118
119    /// A server / `dyn_seeds` entry is malformed.
120    #[error("conf: '{field}' entry '{value}' is invalid: {reason}")]
121    BadServer {
122        /// The directive (`servers` or `dyn_seeds`).
123        field: &'static str,
124        /// The malformed entry.
125        value: String,
126        /// Human-readable failure reason.
127        reason: String,
128    },
129
130    /// `hash_tag` must be exactly two characters, per the C parser.
131    #[error("conf: directive 'hash_tag' must be a string of exactly 2 characters, got '{0}'")]
132    BadHashTag(String),
133
134    /// The YAML failed to parse at the document level.
135    #[error("conf: yaml parse error: {message}")]
136    Yaml {
137        /// The YAML error message (with location, if available).
138        message: String,
139    },
140}
141
142impl ConfError {
143    pub(crate) fn from_yaml(err: &serde_yaml::Error) -> Self {
144        let message = err.to_string();
145        // serde_yaml emits unknown-field errors as
146        //   `<key>: unknown field \`<name>\`, expected one of ...`.
147        // Pull the field name out so callers can pattern-match
148        // on the typed `UnknownKey` variant directly.
149        if let Some(start) = message.find("unknown field `") {
150            let after = &message[start + "unknown field `".len()..];
151            if let Some(end) = after.find('`') {
152                return ConfError::UnknownKey {
153                    name: after[..end].to_string(),
154                };
155            }
156        }
157        ConfError::Yaml { message }
158    }
159}