use std::{fmt, io};
use serde::Serialize;
#[derive(Debug)]
enum Cause {
Io(io::ErrorKind, Option<i32>),
Store(String),
}
impl fmt::Display for Cause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(kind, number) => {
write!(f, "io:{kind:?}")?;
if let Some(number) = number {
write!(f, ";errno={number}")?;
}
Ok(())
}
Self::Store(code) => write!(f, "store:{code}"),
}
}
}
#[derive(Debug, Serialize)]
pub(super) struct Error {
pub(super) code: &'static str,
pub(super) message: &'static str,
#[serde(skip)]
pub(super) status: u16,
#[serde(skip)]
cause: Option<Cause>,
}
impl Error {
pub(super) const fn new(status: u16, code: &'static str, message: &'static str) -> Self {
Self {
code,
message,
status,
cause: None,
}
}
pub(super) const fn config(message: &'static str) -> Self {
Self::new(500, "mcp_http_config_invalid", message)
}
pub(super) fn with_io(mut self, error: &io::Error) -> Self {
self.cause = Some(Cause::Io(error.kind(), error.raw_os_error()));
self
}
pub(super) fn with_store_refusal(mut self, code: &str) -> Self {
let named = !code.is_empty()
&& code.len() <= 96
&& code
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_');
self.cause = Some(Cause::Store(if named {
code.to_owned()
} else {
"unnamed".to_owned()
}));
self
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.code, self.message)?;
self.cause
.as_ref()
.map_or(Ok(()), |cause| write!(f, "; cause={cause}"))
}
}
impl std::error::Error for Error {}