use std::convert::Infallible;
use std::str::FromStr;
use std::{fmt, io};
use destream::{de, en};
pub type TCResult<T> = Result<T, TCError>;
#[derive(Clone)]
struct ErrorData {
message: String,
stack: Vec<String>,
}
struct DataVisitor;
impl de::Visitor for DataVisitor {
type Value = ErrorData;
fn expecting() -> &'static str {
"an error message and optional stacktrace"
}
async fn visit_map<A: de::MapAccess>(self, mut access: A) -> Result<Self::Value, A::Error> {
let message = if let Some(key) = access.next_key::<String>(()).await? {
if key == "message" {
access.next_value(()).await
} else {
Err(de::Error::invalid_value(key, "message"))
}
} else {
Err(de::Error::invalid_length(0, Self::expecting()))
}?;
let stack = if let Some(key) = access.next_key::<String>(()).await? {
if key == "stack" {
access.next_value(()).await
} else {
Err(de::Error::invalid_value(key, "stack"))
}
} else {
Ok(Default::default())
}?;
Ok(ErrorData { message, stack })
}
fn visit_string<E: de::Error>(self, message: String) -> Result<Self::Value, E> {
Ok(ErrorData {
message,
stack: vec![],
})
}
}
impl de::FromStream for ErrorData {
type Context = ();
async fn from_stream<D: de::Decoder>(_: (), decoder: &mut D) -> Result<Self, D::Error> {
decoder.decode_any(DataVisitor).await
}
}
impl<'en> en::IntoStream<'en> for ErrorData {
fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
if self.stack.is_empty() {
return en::IntoStream::into_stream(self.message, encoder);
}
use en::EncodeMap;
let mut map = encoder.encode_map(Some(2))?;
map.encode_entry("message", self.message)?;
map.encode_entry("stack", self.stack)?;
map.end()
}
}
impl<'en> en::ToStream<'en> for ErrorData {
fn to_stream<E: en::Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
if self.stack.is_empty() {
return en::ToStream::to_stream(&self.message, encoder);
}
use en::EncodeMap;
let mut map = encoder.encode_map(Some(2))?;
map.encode_entry("message", &self.message)?;
map.end()
}
}
impl<T> From<T> for ErrorData
where
T: fmt::Display,
{
fn from(message: T) -> Self {
Self {
message: message.to_string(),
stack: vec![],
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum ErrorKind {
BadGateway,
BadRequest,
Conflict,
Forbidden,
Internal,
MethodNotAllowed,
NotFound,
NotImplemented,
Timeout,
Unauthorized,
Unavailable,
}
impl ErrorKind {
pub fn is_conflict(&self) -> bool {
*self == Self::Conflict
}
pub fn is_retriable(&self) -> bool {
[Self::Timeout, Self::Unavailable]
.into_iter()
.any(|code| *self == code)
}
}
impl FromStr for ErrorKind {
type Err = TCError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"bad_gateway" => Ok(Self::BadGateway),
"bad_request" => Ok(Self::BadRequest),
"conflict" => Ok(Self::Conflict),
"forbidden" => Ok(Self::Forbidden),
"internal_error" => Ok(Self::Internal),
"method_not_allowed" => Ok(Self::MethodNotAllowed),
"not_found" => Ok(Self::NotFound),
"not_implemented" => Ok(Self::NotImplemented),
"request_timeout" => Ok(Self::Timeout),
"unauthorized" => Ok(Self::Unauthorized),
"temporarily_unavailable" => Ok(Self::Unavailable),
other => Err(bad_request!("unrecognized error code: {other}")),
}
}
}
impl de::FromStream for ErrorKind {
type Context = ();
async fn from_stream<D: de::Decoder>(cxt: (), decoder: &mut D) -> Result<Self, D::Error> {
let code = String::from_stream(cxt, decoder).await?;
code.parse()
.map_err(|_| de::Error::invalid_value(code, "an error code"))
}
}
impl<'en> en::IntoStream<'en> for ErrorKind {
fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
self.to_string().into_stream(encoder)
}
}
impl fmt::Debug for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_string().replace("_", " "))
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Self::BadGateway => "bad_gateway",
Self::BadRequest => "bad_request",
Self::Conflict => "conflict",
Self::Forbidden => "forbidden",
Self::Internal => "internal_error",
Self::MethodNotAllowed => "method_not_allowed",
Self::NotFound => "not_found",
Self::NotImplemented => "not_implemented",
Self::Timeout => "request_timeout",
Self::Unauthorized => "unauthorized",
Self::Unavailable => "temporarily_unavailable",
})
}
}
#[derive(Clone)]
pub struct TCError {
kind: ErrorKind,
data: ErrorData,
}
impl TCError {
pub fn new<I: fmt::Display>(code: ErrorKind, message: I) -> Self {
#[cfg(debug_assertions)]
match code {
ErrorKind::Internal | ErrorKind::MethodNotAllowed | ErrorKind::NotImplemented => {
panic!("{code}: {message}")
}
other => log::warn!("{other}: {message}"),
}
Self {
kind: code,
data: message.into(),
}
}
pub fn with_stack<I, S, SI>(code: ErrorKind, message: I, stack: S) -> Self
where
I: fmt::Display,
SI: fmt::Display,
S: IntoIterator<Item = SI>,
{
let stack = stack.into_iter().map(|msg| msg.to_string()).collect();
#[cfg(debug_assertions)]
match code {
ErrorKind::Internal | ErrorKind::MethodNotAllowed | ErrorKind::NotImplemented => {
panic!("{code}: {message} (cause: {stack:?})")
}
other => log::warn!("{other}: {message} (cause: {stack:?})"),
}
Self {
kind: code,
data: ErrorData {
message: message.to_string(),
stack,
},
}
}
pub fn bad_request<I: fmt::Display>(info: I) -> Self {
Self::new(ErrorKind::BadGateway, info)
}
pub fn bad_gateway<I: fmt::Display>(locator: I) -> Self {
Self::new(ErrorKind::BadGateway, locator)
}
pub fn conflict<I: fmt::Display>(info: I) -> Self {
#[cfg(debug_assertions)]
panic!("conflict: {info}");
#[cfg(not(debug_assertions))]
Self::new(ErrorKind::Conflict, info)
}
pub fn internal<I: fmt::Display>(info: I) -> Self {
#[cfg(debug_assertions)]
panic!("internal error: {info}");
#[cfg(not(debug_assertions))]
Self::new(ErrorKind::Internal, info)
}
pub fn method_not_allowed<M: fmt::Debug, P: fmt::Display>(method: M, path: P) -> Self {
let message = format!("endpoint {} does not support {:?}", path, method);
#[cfg(debug_assertions)]
panic!("{message}");
#[cfg(not(debug_assertions))]
Self::new(ErrorKind::MethodNotAllowed, message)
}
pub fn not_found<I: fmt::Display>(locator: I) -> Self {
Self::new(ErrorKind::NotFound, locator)
}
pub fn unauthorized<I: fmt::Display>(info: I) -> Self {
Self::new(ErrorKind::Unauthorized, info)
}
pub fn unexpected<V: fmt::Debug>(value: V, expected: &str) -> Self {
Self::bad_request(format!("invalid value {value:?}: expected {expected}"))
}
pub fn unsupported<I: fmt::Display>(info: I) -> Self {
Self::bad_request(info)
}
pub fn code(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
&self.data.message
}
pub fn consume<I: fmt::Debug>(mut self, cause: I) -> Self {
self.data.stack.push(format!("{:?}", cause));
self
}
}
impl std::error::Error for TCError {}
impl From<pathlink::ParseError> for TCError {
fn from(err: pathlink::ParseError) -> Self {
Self::bad_request(err)
}
}
#[cfg(feature = "ha-ndarray")]
impl From<ha_ndarray::Error> for TCError {
fn from(err: ha_ndarray::Error) -> Self {
Self::new(ErrorKind::Internal, err)
}
}
#[cfg(feature = "rjwt")]
impl From<rjwt::Error> for TCError {
fn from(err: rjwt::Error) -> Self {
#[cfg(debug_assertions)]
panic!("rjwt error: {err}");
#[cfg(not(debug_assertions))]
match err.into_inner() {
(rjwt::ErrorKind::Auth | rjwt::ErrorKind::Time, msg) => Self::unauthorized(msg),
(rjwt::ErrorKind::Base64 | rjwt::ErrorKind::Format | rjwt::ErrorKind::Json, msg) => {
Self::bad_request(msg)
}
(rjwt::ErrorKind::Fetch, msg) => Self::bad_gateway(msg),
}
}
}
#[cfg(feature = "txn_lock")]
impl From<txn_lock::Error> for TCError {
fn from(err: txn_lock::Error) -> Self {
Self::conflict(err)
}
}
#[cfg(feature = "txfs")]
impl From<txfs::Error> for TCError {
fn from(cause: txfs::Error) -> Self {
match cause {
txfs::Error::Conflict(cause) => Self::conflict(cause),
txfs::Error::IO(cause) => Self::from(cause),
txfs::Error::NotFound(cause) => Self::not_found(cause),
txfs::Error::Parse(cause) => Self::from(cause),
}
}
}
impl From<io::Error> for TCError {
fn from(cause: io::Error) -> Self {
match cause.kind() {
io::ErrorKind::AlreadyExists => {
#[cfg(debug_assertions)]
panic!("tried to create an entry that already exists: {}", cause);
#[cfg(not(debug_assertions))]
bad_request!("tried to create an entry that already exists").consume(cause)
}
io::ErrorKind::InvalidInput => bad_request!("{}", cause),
io::ErrorKind::NotFound => TCError::not_found(cause),
io::ErrorKind::PermissionDenied => {
bad_gateway!("host filesystem permission denied").consume(cause)
}
io::ErrorKind::WouldBlock => {
conflict!("synchronous filesystem access failed").consume(cause)
}
kind => internal!("host filesystem error: {:?}", kind).consume(cause),
}
}
}
impl From<Infallible> for TCError {
fn from(_: Infallible) -> Self {
internal!("an unanticipated error occurred--please file a bug report")
}
}
struct ErrorVisitor;
impl de::Visitor for ErrorVisitor {
type Value = TCError;
fn expecting() -> &'static str {
"an error code, message, and optional stacktrace"
}
async fn visit_map<A: de::MapAccess>(self, mut access: A) -> Result<Self::Value, A::Error> {
if let Some(kind) = access.next_key(()).await? {
let data = access.next_value(()).await?;
Ok(TCError { kind, data })
} else {
Err(de::Error::invalid_length(0, Self::expecting()))
}
}
}
impl de::FromStream for TCError {
type Context = ();
async fn from_stream<D: de::Decoder>(_: (), decoder: &mut D) -> Result<Self, D::Error> {
decoder.decode_map(ErrorVisitor).await
}
}
impl<'en> en::IntoStream<'en> for TCError {
fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
use en::EncodeMap;
let mut map = encoder.encode_map(Some(1))?;
map.encode_entry(self.kind, self.data)?;
map.end()
}
}
impl<'en> en::ToStream<'en> for TCError {
fn to_stream<E: en::Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
use en::EncodeMap;
let mut map = encoder.encode_map(Some(1))?;
map.encode_entry(self.kind, &self.data)?;
map.end()
}
}
impl fmt::Debug for TCError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for TCError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.kind, self.data.message)
}
}
#[macro_export]
macro_rules! bad_gateway {
($($t:tt)*) => {{
$crate::TCError::new($crate::ErrorKind::BadGateway, format!($($t)*))
}}
}
#[macro_export]
macro_rules! bad_request {
($($t:tt)*) => {{
$crate::TCError::bad_request(format!($($t)*))
}}
}
#[macro_export]
macro_rules! conflict {
($($t:tt)*) => {{
$crate::TCError::conflict(format!($($t)*))
}}
}
#[macro_export]
macro_rules! forbidden {
($($t:tt)*) => {{
$crate::TCError::new($crate::ErrorKind::Unauthorized, format!($($t)*))
}}
}
#[macro_export]
macro_rules! not_found {
($($t:tt)*) => {{
$crate::TCError::not_found(format!($($t)*))
}}
}
#[macro_export]
macro_rules! not_implemented {
($($t:tt)*) => {{
$crate::TCError::new($crate::ErrorKind::NotImplemented, format!($($t)*))
}}
}
#[macro_export]
macro_rules! timeout {
($($t:tt)*) => {{
$crate::TCError::new($crate::ErrorKind::Timeout, format!($($t)*))
}}
}
#[macro_export]
macro_rules! internal {
($($t:tt)*) => {{
$crate::TCError::internal(format!($($t)*))
}}
}
#[macro_export]
macro_rules! unauthorized {
($($t:tt)*) => {{
$crate::TCError::unauthorized(format!($($t)*))
}}
}
#[macro_export]
macro_rules! unavailable {
($($t:tt)*) => {{
$crate::TCError::new($crate::ErrorKind::Unavailable, format!($($t)*))
}}
}