use alloc::{
borrow::{Cow, ToOwned},
format,
string::String,
};
use core::{error, fmt};
#[derive(Clone, Debug)]
pub struct FieldError {
kind: FieldErrorKind,
}
#[derive(Clone, Debug)]
enum FieldErrorKind {
Owned(String),
Static(&'static str),
TypeMismatch {
expected: &'static str,
actual: ActualValue,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ActualValue {
Boolean,
Character,
SignedInteger,
UnsignedInteger,
FloatingPoint,
String,
Bytes,
Array,
Object,
}
impl ActualValue {
const fn as_str(self) -> &'static str {
match self {
Self::Boolean => "boolean",
Self::Character => "character",
Self::SignedInteger => "signed integer",
Self::UnsignedInteger => "unsigned integer",
Self::FloatingPoint => "floating point number",
Self::String => "string",
Self::Bytes => "bytes",
Self::Array => "array",
Self::Object => "object",
}
}
}
impl FieldError {
#[must_use]
pub fn new(message: impl Into<String>) -> Self {
Self {
kind: FieldErrorKind::Owned(message.into()),
}
}
#[must_use]
pub const fn static_message(message: &'static str) -> Self {
Self {
kind: FieldErrorKind::Static(message),
}
}
pub(crate) const fn type_mismatch(expected: &'static str, actual: ActualValue) -> Self {
Self {
kind: FieldErrorKind::TypeMismatch { expected, actual },
}
}
#[must_use]
pub fn message(&self) -> Cow<'_, str> {
match &self.kind {
FieldErrorKind::Owned(message) => Cow::Borrowed(message),
FieldErrorKind::Static(message) => Cow::Borrowed(message),
FieldErrorKind::TypeMismatch { expected, actual } => {
let actual = actual.as_str();
Cow::Owned(format!("expected {expected}, got {actual}"))
}
}
}
#[must_use]
pub fn into_message(self) -> String {
match self.kind {
FieldErrorKind::Owned(message) => message,
FieldErrorKind::Static(message) => message.to_owned(),
FieldErrorKind::TypeMismatch { expected, actual } => {
let actual = actual.as_str();
format!("expected {expected}, got {actual}")
}
}
}
}
impl PartialEq for FieldError {
fn eq(&self, other: &Self) -> bool {
field_error_messages_eq(&self.kind, &other.kind)
}
}
impl Eq for FieldError {}
impl fmt::Display for FieldError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
FieldErrorKind::Owned(message) => formatter.write_str(message),
FieldErrorKind::Static(message) => formatter.write_str(message),
FieldErrorKind::TypeMismatch { expected, actual } => {
let actual = actual.as_str();
write!(formatter, "expected {expected}, got {actual}")
}
}
}
}
impl error::Error for FieldError {}
impl From<String> for FieldError {
fn from(message: String) -> Self {
Self::new(message)
}
}
impl From<&'static str> for FieldError {
fn from(message: &'static str) -> Self {
Self::static_message(message)
}
}
fn field_error_messages_eq(left: &FieldErrorKind, right: &FieldErrorKind) -> bool {
match (left, right) {
(FieldErrorKind::Owned(left), FieldErrorKind::Owned(right)) => left == right,
(FieldErrorKind::Owned(left), FieldErrorKind::Static(right))
| (FieldErrorKind::Static(right), FieldErrorKind::Owned(left)) => left == right,
(FieldErrorKind::Static(left), FieldErrorKind::Static(right)) => left == right,
(
FieldErrorKind::TypeMismatch {
expected: left_expected,
actual: left_actual,
},
FieldErrorKind::TypeMismatch {
expected: right_expected,
actual: right_actual,
},
) => left_expected == right_expected && left_actual == right_actual,
(FieldErrorKind::TypeMismatch { expected, actual }, FieldErrorKind::Owned(message))
| (FieldErrorKind::Owned(message), FieldErrorKind::TypeMismatch { expected, actual }) => {
type_mismatch_message_eq(expected, *actual, message)
}
(FieldErrorKind::TypeMismatch { expected, actual }, FieldErrorKind::Static(message))
| (FieldErrorKind::Static(message), FieldErrorKind::TypeMismatch { expected, actual }) => {
type_mismatch_message_eq(expected, *actual, message)
}
}
}
fn type_mismatch_message_eq(expected: &str, actual: ActualValue, message: &str) -> bool {
let Some(rest) = message.strip_prefix("expected ") else {
return false;
};
let Some(rest) = rest.strip_prefix(expected) else {
return false;
};
let Some(rest) = rest.strip_prefix(", got ") else {
return false;
};
rest == actual.as_str()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equality_compares_displayed_messages() {
assert_eq!(FieldError::new("bad"), FieldError::static_message("bad"));
assert_eq!(
FieldError::type_mismatch("unsigned integer", ActualValue::String),
FieldError::new("expected unsigned integer, got string"),
);
}
}