use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf};
use bstr::BString;
use crate::{Class, ResourceExhaustionKind};
pub type Metadata = BTreeMap<Cow<'static, str>, MetadataValue>;
pub struct Message {
pub message: Cow<'static, str>,
pub class: Option<Class>,
pub values: Metadata,
}
impl Message {
pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
Self {
message: message.into(),
class: None,
values: Metadata::new(),
}
}
pub fn with_class(mut self, class: Class) -> Self {
self.class = Some(class);
self
}
pub fn with(mut self, key: impl Into<Cow<'static, str>>, value: impl Into<MetadataValue>) -> Self {
self.values.insert(key.into(), value.into());
self
}
}
impl fmt::Debug for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("Message");
debug.field("message", &self.message);
if let Some(class) = self.class {
debug.field("class", &format_args!("{class:?}"));
}
if !self.values.is_empty() {
debug.field("values", &format_args!("{:?}", self.values));
}
debug.finish()
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)?;
for (key, value) in &self.values {
write!(f, ", {key:?}={value}")?;
}
Ok(())
}
}
impl std::error::Error for Message {}
impl From<Cow<'static, str>> for Message {
fn from(message: Cow<'static, str>) -> Self {
Self::new(message)
}
}
impl From<String> for Message {
fn from(message: String) -> Self {
Self::new(message)
}
}
impl From<&'static str> for Message {
fn from(message: &'static str) -> Self {
Self::new(message)
}
}
pub fn validation(message: impl Into<Cow<'static, str>>) -> Message {
Message::new(message).with_class(Class::Validation)
}
pub fn corruption(message: impl Into<Cow<'static, str>>) -> Message {
Message::new(message).with_class(Class::Corruption)
}
pub fn not_found(message: impl Into<Cow<'static, str>>) -> Message {
Message::new(message).with_class(Class::NotFound)
}
pub fn retryable(message: impl Into<Cow<'static, str>>) -> Message {
Message::new(message).with_class(Class::Retryable)
}
pub fn resource_exhaustion(kind: ResourceExhaustionKind, message: impl Into<Cow<'static, str>>) -> Message {
Message::new(message).with_class(Class::ResourceExhaustion(kind))
}
pub fn allocation_limit(message: impl Into<Cow<'static, str>>) -> Message {
resource_exhaustion(ResourceExhaustionKind::AllocationLimit, message)
}
pub fn allocation_failure(message: impl Into<Cow<'static, str>>) -> Message {
resource_exhaustion(ResourceExhaustionKind::AllocationFailure, message)
}
pub fn io(kind: std::io::ErrorKind, message: impl Into<Cow<'static, str>>) -> Message {
Message::new(message).with_class(Class::Io(kind))
}
#[derive(Clone, PartialEq)]
#[non_exhaustive]
pub enum MetadataValue {
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
String(String),
Bytes(BString),
Path(PathBuf),
}
impl fmt::Debug for MetadataValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MetadataValue::Bool(value) => write!(f, "Bool({value:?})"),
MetadataValue::I64(value) => write!(f, "I64({value:?})"),
MetadataValue::U64(value) => write!(f, "U64({value:?})"),
MetadataValue::F64(value) => write!(f, "F64({value:?})"),
MetadataValue::String(value) => write!(f, "String({value:?})"),
MetadataValue::Bytes(value) => write!(f, "Bytes({value:?})"),
MetadataValue::Path(value) => write!(f, "Path({value:?})"),
}
}
}
impl fmt::Display for MetadataValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MetadataValue::Bool(value) => fmt::Display::fmt(value, f),
MetadataValue::I64(value) => fmt::Display::fmt(value, f),
MetadataValue::U64(value) => fmt::Display::fmt(value, f),
MetadataValue::F64(value) => fmt::Display::fmt(value, f),
MetadataValue::String(value) => fmt::Debug::fmt(value, f),
MetadataValue::Bytes(value) => fmt::Debug::fmt(value, f),
MetadataValue::Path(value) => fmt::Debug::fmt(value, f),
}
}
}
macro_rules! from {
($variant:ident: $($ty:ty),+ $(,)?) => {
$(impl From<$ty> for MetadataValue {
fn from(value: $ty) -> Self {
Self::$variant(value.into())
}
})+
};
}
from!(Bool: bool);
from!(I64: i8, i16, i32, i64);
from!(U64: u8, u16, u32, u64);
from!(F64: f32, f64);
from!(String: String, &str);
from!(Bytes: BString, &bstr::BStr, Vec<u8>, &[u8]);
from!(Path: PathBuf, &std::path::Path);
impl From<usize> for MetadataValue {
fn from(value: usize) -> Self {
Self::U64(value as u64)
}
}
impl From<isize> for MetadataValue {
fn from(value: isize) -> Self {
Self::I64(value as i64)
}
}