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 (Valkey), 1 (Memcache), or 2
87 /// (Dyniak). The string forms `valkey` (and the back-compat
88 /// alias `redis`), `memcache`, and `dyniak` are also accepted
89 /// on the YAML side and are translated to these integers
90 /// before validation.
91 #[error(
92 "conf: directive 'data_store' must be 0 (valkey), 1 (memcache), or 2 (dyniak), got {0}"
93 )]
94 BadDataStore(i64),
95
96 /// `data_store: dyniak` was selected but `noxu_path` was not
97 /// supplied or `dynomited` was built without `--features
98 /// riak`.
99 #[error("conf: {0}")]
100 BadDyniakConfig(&'static str),
101
102 /// `hash` value is not a recognized hash algorithm name.
103 #[error("conf: directive 'hash' is not a valid hash function, got '{0}'")]
104 BadHash(String),
105
106 /// `distribution` value is not a recognized distribution
107 /// algorithm name.
108 #[error("conf: directive 'distribution' is not a valid distribution, got '{0}'")]
109 BadDistribution(String),
110
111 /// A numeric directive is out of its allowed range.
112 #[error("conf: directive '{field}' value {value} is out of range: {reason}")]
113 OutOfRange {
114 /// The directive name.
115 field: &'static str,
116 /// The offending value.
117 value: i64,
118 /// What range was expected.
119 reason: &'static str,
120 },
121
122 /// A server / `dyn_seeds` entry is malformed.
123 #[error("conf: '{field}' entry '{value}' is invalid: {reason}")]
124 BadServer {
125 /// The directive (`servers` or `dyn_seeds`).
126 field: &'static str,
127 /// The malformed entry.
128 value: String,
129 /// Human-readable failure reason.
130 reason: String,
131 },
132
133 /// `hash_tag` must be exactly two characters.
134 #[error("conf: directive 'hash_tag' must be a string of exactly 2 characters, got '{0}'")]
135 BadHashTag(String),
136
137 /// The YAML failed to parse at the document level.
138 #[error("conf: yaml parse error: {message}")]
139 Yaml {
140 /// The YAML error message (with location, if available).
141 message: String,
142 },
143}
144
145impl ConfError {
146 pub(crate) fn from_yaml(err: &serde_yaml::Error) -> Self {
147 let message = err.to_string();
148 // serde_yaml emits unknown-field errors as
149 // `<key>: unknown field \`<name>\`, expected one of ...`.
150 // Pull the field name out so callers can pattern-match
151 // on the typed `UnknownKey` variant directly.
152 if let Some(start) = message.find("unknown field `") {
153 let after = &message[start + "unknown field `".len()..];
154 if let Some(end) = after.find('`') {
155 return ConfError::UnknownKey {
156 name: after[..end].to_string(),
157 };
158 }
159 }
160 ConfError::Yaml { message }
161 }
162}