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
93impl ConfigError {
94  // FIXME: pub(crate)
95  #[doc(hidden)]
96  pub fn invalid_type(
97    origin: Option<String>,
98    unexpected: Unexpected,
99    expected: &'static str,
100  ) -> Self {
101    Self::Type { origin, unexpected, expected, key: None }
102  }
103
104  // Have a proper error fire if the root of a file is ever not a Table
105  // TODO: for now only json5 checked, need to finish others
106  #[doc(hidden)]
107  pub fn invalid_root(origin: Option<&String>, unexpected: Unexpected) -> Box<Self> {
108    Box::new(Self::Type { origin: origin.cloned(), unexpected, expected: "a map", key: None })
109  }
110
111  // FIXME: pub(crate)
112  #[doc(hidden)]
113  #[must_use]
114  pub fn extend_with_key(self, key: &str) -> Self {
115    match self {
116      Self::Type { origin, unexpected, expected, .. } => {
117        Self::Type { origin, unexpected, expected, key: Some(key.into()) }
118      }
119
120      _ => self,
121    }
122  }
123
124  #[must_use]
125  fn prepend(self, segment: &str, add_dot: bool) -> Self {
126    let concat = |key: Option<String>| {
127      let key = key.unwrap_or_default();
128      let dot = if add_dot && key.as_bytes().first().unwrap_or(&b'[') != &b'[' { "." } else { "" };
129      format!("{}{}{}", segment, dot, key)
130    };
131    match self {
132      Self::Type { origin, unexpected, expected, key } => {
133        Self::Type { origin, unexpected, expected, key: Some(concat(key)) }
134      }
135      Self::NotFound(key) => Self::NotFound(concat(Some(key))),
136      _ => self,
137    }
138  }
139
140  #[must_use]
141  pub(crate) fn prepend_key(self, key: &str) -> Self {
142    self.prepend(key, true)
143  }
144
145  #[must_use]
146  pub(crate) fn prepend_index(self, idx: usize) -> Self {
147    self.prepend(&format!("[{}]", idx), false)
148  }
149}
150
151/// Alias for a `Result` with the error type set to `ConfigError`.
152pub type Result<T> = result::Result<T, ConfigError>;
153
154// Forward Debug to Display for readable panic! messages
155impl fmt::Debug for ConfigError {
156  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157    write!(f, "{}", *self)
158  }
159}
160
161impl fmt::Display for ConfigError {
162  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163    match *self {
164      ConfigError::Frozen => write!(f, "configuration is frozen"),
165
166      ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()),
167
168      ConfigError::Message(ref s) => write!(f, "{}", s),
169
170      ConfigError::Foreign(ref cause) => write!(f, "{}", cause),
171
172      ConfigError::NotFound(ref key) => {
173        write!(f, "configuration property {:?} not found", key)
174      }
175
176      ConfigError::Type { ref origin, ref unexpected, expected, ref key } => {
177        write!(f, "invalid type: {}, expected {}", unexpected, expected)?;
178
179        if let Some(ref key) = *key {
180          write!(f, " for key `{}`", key)?;
181        }
182
183        if let Some(ref origin) = *origin {
184          write!(f, " in {}", origin)?;
185        }
186
187        Ok(())
188      }
189
190      ConfigError::FileParse { ref cause, ref uri } => {
191        write!(f, "{}", cause)?;
192
193        if let Some(ref uri) = *uri {
194          write!(f, " in {}", uri)?;
195        }
196
197        Ok(())
198      }
199    }
200  }
201}
202
203impl Error for ConfigError {}
204
205impl de::Error for ConfigError {
206  fn custom<T: fmt::Display>(msg: T) -> Self {
207    Self::Message(msg.to_string())
208  }
209}
210
211impl ser::Error for ConfigError {
212  fn custom<T: fmt::Display>(msg: T) -> Self {
213    Self::Message(msg.to_string())
214  }
215}