storm_config/
errors.rs

1use std::error::Error;
2use std::{fmt, result};
3
4use serde::{de, ser};
5
6// #[derive(Error, Debug)]
7// pub enum StormConfigError {
8//   #[error("Unable to locate the configuration file - {0}")]
9//   SpecificConfigFileNotFound(String),
10//   #[error("Unable to locate the package.json file - {0}")]
11//   PackageJsonNotFound(String),
12// }
13
14#[derive(Debug)]
15pub enum Unexpected {
16  Bool(bool),
17  I64(i64),
18  I128(i128),
19  U64(u64),
20  U128(u128),
21  Float(f64),
22  Str(String),
23  Unit,
24  Seq,
25  Map,
26}
27
28impl fmt::Display for Unexpected {
29  fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
30    match *self {
31      Unexpected::Bool(b) => write!(f, "boolean `{}`", b),
32      Unexpected::I64(i) => write!(f, "64-bit integer `{}`", i),
33      Unexpected::I128(i) => write!(f, "128-bit integer `{}`", i),
34      Unexpected::U64(i) => write!(f, "64-bit unsigned integer `{}`", i),
35      Unexpected::U128(i) => write!(f, "128-bit unsigned integer `{}`", i),
36      Unexpected::Float(v) => write!(f, "floating point `{}`", v),
37      Unexpected::Str(ref s) => write!(f, "string {:?}", s),
38      Unexpected::Unit => write!(f, "unit value"),
39      Unexpected::Seq => write!(f, "sequence"),
40      Unexpected::Map => write!(f, "map"),
41    }
42  }
43}
44
45/// Represents all possible errors that can occur when working with
46/// configuration.
47pub enum ConfigError {
48  /// Configuration is frozen and no further mutations can be made.
49  Frozen,
50
51  /// Configuration property was not found
52  NotFound(String),
53
54  /// Configuration path could not be parsed.
55  PathParse(nom::error::ErrorKind),
56
57  /// Configuration could not be parsed from file.
58  FileParse {
59    /// The URI used to access the file (if not loaded from a string).
60    /// Example: `/path/to/config.json`
61    uri: Option<String>,
62
63    /// The captured error from attempting to parse the file in its desired format.
64    /// This is the actual error object from the library used for the parsing.
65    cause: Box<dyn Error + Send + Sync>,
66  },
67
68  /// Value could not be converted into the requested type.
69  Type {
70    /// The URI that references the source that the value came from.
71    /// Example: `/path/to/config.json` or `Environment` or `etcd://localhost`
72    // TODO: Why is this called Origin but FileParse has a uri field?
73    origin: Option<String>,
74
75    /// What we found when parsing the value
76    unexpected: Unexpected,
77
78    /// What was expected when parsing the value
79    expected: &'static str,
80
81    /// The key in the configuration hash of this value (if available where the
82    /// error is generated).
83    key: Option<String>,
84  },
85
86  /// Custom message
87  Message(String),
88
89  /// Unadorned error from a foreign origin.
90  Foreign(Box<dyn Error + Send + Sync>),
91
92  /// Invalid format used in an extend directive.
93  InvalidExtendFormat,
94
95  /// Error occurred while merging configurations.
96  MergeFailure,
97}
98
99impl ConfigError {
100  // FIXME: pub(crate)
101  #[doc(hidden)]
102  pub fn invalid_type(
103    origin: Option<String>,
104    unexpected: Unexpected,
105    expected: &'static str,
106  ) -> Self {
107    Self::Type { origin, unexpected, expected, key: None }
108  }
109
110  // Have a proper error fire if the root of a file is ever not a Table
111  // TODO: for now only json5 checked, need to finish others
112  #[doc(hidden)]
113  pub fn invalid_root(origin: Option<&String>, unexpected: Unexpected) -> Box<Self> {
114    Box::new(Self::Type { origin: origin.cloned(), unexpected, expected: "a map", key: None })
115  }
116
117  // FIXME: pub(crate)
118  #[doc(hidden)]
119  #[must_use]
120  pub fn extend_with_key(self, key: &str) -> Self {
121    match self {
122      Self::Type { origin, unexpected, expected, .. } => {
123        Self::Type { origin, unexpected, expected, key: Some(key.into()) }
124      }
125
126      _ => self,
127    }
128  }
129
130  #[must_use]
131  fn prepend(self, segment: &str, add_dot: bool) -> Self {
132    let concat = |key: Option<String>| {
133      let key = key.unwrap_or_default();
134      let dot = if add_dot && key.as_bytes().first().unwrap_or(&b'[') != &b'[' { "." } else { "" };
135      format!("{}{}{}", segment, dot, key)
136    };
137    match self {
138      Self::Type { origin, unexpected, expected, key } => {
139        Self::Type { origin, unexpected, expected, key: Some(concat(key)) }
140      }
141      Self::NotFound(key) => Self::NotFound(concat(Some(key))),
142      _ => self,
143    }
144  }
145
146  #[must_use]
147  pub(crate) fn prepend_key(self, key: &str) -> Self {
148    self.prepend(key, true)
149  }
150
151  #[must_use]
152  pub(crate) fn prepend_index(self, idx: usize) -> Self {
153    self.prepend(&format!("[{}]", idx), false)
154  }
155}
156
157/// Alias for a `Result` with the error type set to `ConfigError`.
158pub type Result<T> = result::Result<T, ConfigError>;
159
160// Forward Debug to Display for readable panic! messages
161impl fmt::Debug for ConfigError {
162  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163    write!(f, "{}", *self)
164  }
165}
166
167impl fmt::Display for ConfigError {
168  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169    match *self {
170      ConfigError::Frozen => write!(f, "configuration is frozen"),
171
172      ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()),
173
174      ConfigError::Message(ref s) => write!(f, "{}", s),
175
176      ConfigError::Foreign(ref cause) => write!(f, "{}", cause),
177
178      ConfigError::NotFound(ref key) => {
179        write!(f, "configuration property {:?} not found", key)
180      }
181
182      ConfigError::Type { ref origin, ref unexpected, expected, ref key } => {
183        write!(f, "invalid type: {}, expected {}", unexpected, expected)?;
184
185        if let Some(ref key) = *key {
186          write!(f, " for key `{}`", key)?;
187        }
188
189        if let Some(ref origin) = *origin {
190          write!(f, " in {}", origin)?;
191        }
192
193        Ok(())
194      }
195
196      ConfigError::FileParse { ref cause, ref uri } => {
197        write!(f, "{}", cause)?;
198
199        if let Some(ref uri) = *uri {
200          write!(f, " in {}", uri)?;
201        }
202
203        Ok(())
204      }
205
206      ConfigError::InvalidExtendFormat => {
207        write!(f, "invalid format used in an extend directive")
208      }
209
210      ConfigError::MergeFailure => {
211        write!(f, "error occurred while merging configurations")
212      }
213    }
214  }
215}
216
217impl Error for ConfigError {}
218
219impl de::Error for ConfigError {
220  fn custom<T: fmt::Display>(msg: T) -> Self {
221    Self::Message(msg.to_string())
222  }
223}
224
225impl ser::Error for ConfigError {
226  fn custom<T: fmt::Display>(msg: T) -> Self {
227    Self::Message(msg.to_string())
228  }
229}