#[cfg(feature = "figment")]
use crate::error::{Error, ErrorKind};
#[cfg(feature = "figment")]
fn message(error: &figment::Error) -> String {
use figment::error::Kind;
match &error.kind {
Kind::InvalidType(actual, expected) => {
format!(
"invalid type: found {}, expected {expected}",
kind_of(actual)
)
}
Kind::InvalidValue(actual, expected) => {
format!(
"invalid value: found {}, expected {expected}",
kind_of(actual)
)
}
_ => crate::loader::redacted(&without_quoted_source(&error.to_string())),
}
}
fn without_quoted_source(message: &str) -> String {
message
.lines()
.filter(|line| !is_gutter(line))
.collect::<Vec<_>>()
.join("\n")
.trim_end()
.to_owned()
}
fn is_gutter(line: &str) -> bool {
line.trim_start()
.trim_start_matches(|character: char| character.is_ascii_digit())
.trim_start()
.starts_with('|')
}
#[cfg(feature = "figment")]
fn kind_of(actual: &figment::error::Actual) -> &'static str {
use figment::error::Actual;
match actual {
Actual::Bool(_) => "a boolean",
Actual::Unsigned(_) => "an unsigned integer",
Actual::Signed(_) => "a signed integer",
Actual::Float(_) => "a float",
Actual::Char(_) => "a character",
Actual::Str(_) => "a string",
Actual::Bytes(_) => "a byte string",
Actual::Unit => "a unit",
Actual::Option => "an option",
Actual::NewtypeStruct => "a newtype struct",
Actual::Seq => "a list",
Actual::Map => "a table",
Actual::Enum => "an enum",
Actual::UnitVariant => "a unit variant",
Actual::NewtypeVariant => "a newtype variant",
Actual::TupleVariant => "a tuple variant",
Actual::StructVariant => "a struct variant",
Actual::Other(_) => "something else",
}
}
#[cfg(feature = "figment")]
fn kind_of_error(error: &figment::Error) -> ErrorKind {
use figment::error::Kind;
match &error.kind {
Kind::MissingField(_) => ErrorKind::Missing,
Kind::InvalidType(..)
| Kind::InvalidValue(..)
| Kind::InvalidLength(..)
| Kind::ISizeOutOfRange(_)
| Kind::USizeOutOfRange(_) => ErrorKind::Type,
Kind::Message(_) if error.path.is_empty() => ErrorKind::Parse,
Kind::Message(_) => ErrorKind::Type,
_ => ErrorKind::Backend,
}
}
#[cfg(any(feature = "figment", test))]
pub(crate) fn error_path(error: &figment::Error) -> Vec<String> {
use figment::error::Kind;
let mut path = error.path.clone();
if path.is_empty() {
if let Kind::MissingField(field) = &error.kind {
path.push(field.to_string());
}
}
path
}
#[cfg(feature = "figment")]
pub(crate) fn translate(error: &figment::Error) -> Error {
let mut translated = Error::new(kind_of_error(error), message(error));
for segment in error_path(error).into_iter().rev() {
translated = translated.prepend_key(segment);
}
translated
}
#[cfg(test)]
mod tests {
use super::without_quoted_source;
#[test]
fn a_quoted_source_line_is_dropped_and_the_reason_kept() {
let rendered = "TOML parse error at line 2, column 25\n \
|\n2 | password = \"hunter2\n | ^\n\
invalid basic string\n";
assert_eq!(
without_quoted_source(rendered),
"TOML parse error at line 2, column 25\ninvalid basic string"
);
}
#[test]
fn a_one_line_message_passes_through_untouched() {
let rendered = "EOF while parsing an object at line 1 column 28";
assert_eq!(without_quoted_source(rendered), rendered);
}
}