use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
const NAME_PREVIEW: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Missing {
name: String,
},
TypeMismatch {
name: String,
expected: &'static str,
found: &'static str,
},
AlreadyDefined {
name: String,
},
}
impl Error {
pub(crate) fn missing(name: &str) -> Self {
Self::Missing {
name: preview(name),
}
}
pub(crate) fn mismatch(name: &str, expected: &'static str, found: &'static str) -> Self {
Self::TypeMismatch {
name: preview(name),
expected,
found,
}
}
pub(crate) fn already_defined(name: &str) -> Self {
Self::AlreadyDefined {
name: preview(name),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Missing { name } => write!(f, "no global named {name:?} is set"),
Self::TypeMismatch {
name,
expected,
found,
} => write!(f, "global {name:?} holds a {found}, not a {expected}"),
Self::AlreadyDefined { name } => write!(f, "global {name:?} is already defined"),
}
}
}
impl std::error::Error for Error {}
fn preview(name: &str) -> String {
match name.char_indices().nth(NAME_PREVIEW) {
Some((cut, _)) => format!("{}...", &name[..cut]),
None => name.to_owned(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display_every_variant() {
assert_eq!(
Error::missing("port").to_string(),
"no global named \"port\" is set"
);
assert_eq!(
Error::mismatch("port", "u32", "u16").to_string(),
"global \"port\" holds a u16, not a u32"
);
assert_eq!(
Error::already_defined("app").to_string(),
"global \"app\" is already defined"
);
}
#[test]
fn test_long_names_are_truncated() {
let shown = Error::missing(&"n".repeat(500)).to_string();
assert!(shown.len() < 110, "{shown}");
assert!(shown.contains("..."));
}
}