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}