extern crate alloc;
use alloc::borrow::Cow;
use alloc::string::String;
use core::fmt;
#[cfg(feature = "serde")]
use alloc::string::ToString;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ErrorKind {
NotFound,
InvalidInput,
Unauthorized,
Conflict,
Unavailable,
Internal,
}
impl ErrorKind {
pub fn http_status(self) -> u16 {
match self {
Self::NotFound => 404,
Self::InvalidInput => 400,
Self::Unauthorized => 403,
Self::Conflict => 409,
Self::Unavailable => 503,
Self::Internal => 500,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::NotFound => "not_found",
Self::InvalidInput => "invalid_input",
Self::Unauthorized => "unauthorized",
Self::Conflict => "conflict",
Self::Unavailable => "unavailable",
Self::Internal => "internal",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum ErrorDomain {
Db,
Query,
Runtime,
Types,
}
impl ErrorDomain {
pub fn as_str(self) -> &'static str {
match self {
Self::Db => "db",
Self::Query => "query",
Self::Runtime => "runtime",
Self::Types => "types",
}
}
}
impl fmt::Display for ErrorDomain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ErrorCode {
domain: ErrorDomain,
code: u32,
}
impl ErrorCode {
pub fn new(domain: ErrorDomain, code: u32) -> Self {
Self { domain, code }
}
pub fn domain(self) -> ErrorDomain {
self.domain
}
pub fn code(self) -> u32 {
self.code
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.domain, self.code)
}
}
#[cfg(feature = "serde")]
impl Serialize for ErrorCode {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for ErrorCode {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = alloc::string::String::deserialize(d)?;
let (domain_str, code_str) = s
.split_once(':')
.ok_or_else(|| serde::de::Error::custom("expected 'domain:N'"))?;
let domain = match domain_str {
"db" => ErrorDomain::Db,
"query" => ErrorDomain::Query,
"runtime" => ErrorDomain::Runtime,
"types" => ErrorDomain::Types,
other => {
return Err(serde::de::Error::custom(alloc::format!(
"unknown domain: {other}"
)))
}
};
let code: u32 = code_str.parse().map_err(serde::de::Error::custom)?;
Ok(ErrorCode::new(domain, code))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum RetryHint {
NoRetry,
Retryable,
}
pub const DETAILS_TRUNCATED_KEY: &str = "details_truncated";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Details {
entries: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)>,
dropped: Option<usize>,
}
impl Details {
pub fn new<I>(pairs: I) -> Self
where
I: IntoIterator<Item = (&'static str, &'static str)>,
{
let all: alloc::vec::Vec<(&'static str, &'static str)> = pairs.into_iter().collect();
Self::from_owned(
all.into_iter()
.map(|(k, v)| (Cow::Borrowed(k), Cow::Borrowed(v))),
)
}
fn from_owned<I>(pairs: I) -> Self
where
I: IntoIterator<Item = (Cow<'static, str>, Cow<'static, str>)>,
{
let mut ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
alloc::vec::Vec::new();
let mut total_ordinary: usize = 0;
let mut collisions: usize = 0;
for (k, v) in pairs {
if k.as_ref() == DETAILS_TRUNCATED_KEY {
collisions += 1;
} else {
total_ordinary += 1;
if ordinary.len() < 8 {
ordinary.push((k, v));
}
}
}
Self::build(ordinary, total_ordinary, collisions)
}
fn build(
ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)>,
total_ordinary: usize,
collisions: usize,
) -> Self {
if total_ordinary <= 8 && collisions == 0 {
return Self {
entries: ordinary,
dropped: None,
};
}
let keep = total_ordinary.min(7);
let dropped = (total_ordinary - keep) + collisions;
let mut entries: alloc::vec::Vec<_> = ordinary.into_iter().take(keep).collect();
entries.push((
Cow::Borrowed(DETAILS_TRUNCATED_KEY),
Cow::Owned(alloc::format!("{dropped}")),
));
Self {
entries,
dropped: Some(dropped),
}
}
pub fn get(&self, key: &str) -> Option<&str> {
self.entries
.iter()
.find(|(k, _)| k.as_ref() == key)
.map(|(_, v)| v.as_ref())
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
self.entries.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
}
pub fn dropped_count(&self) -> Option<usize> {
self.dropped
}
}
#[cfg(feature = "serde")]
impl Serialize for Details {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
let mut map = s.serialize_map(Some(self.entries.len()))?;
for (k, v) in &self.entries {
map.serialize_entry(k.as_ref(), v.as_ref())?;
}
map.end()
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Details {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
use serde::de::{MapAccess, Visitor};
struct DetailsVisitor;
impl<'de> Visitor<'de> for DetailsVisitor {
type Value = Details;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a map of string key-value pairs")
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Details, A::Error> {
let mut ordinary: alloc::vec::Vec<(Cow<'static, str>, Cow<'static, str>)> =
alloc::vec::Vec::new();
let mut total_ordinary: usize = 0;
let mut reserved_count: usize = 0;
let mut reserved_is_trailing = false;
let mut reserved_follows_seven = false;
let mut last_reserved_value: Option<String> = None;
while let Some((k, v)) = map.next_entry::<String, String>()? {
if k == DETAILS_TRUNCATED_KEY {
reserved_count += 1;
reserved_is_trailing = true;
reserved_follows_seven = total_ordinary == 7;
last_reserved_value = Some(v);
} else {
reserved_is_trailing = false;
total_ordinary += 1;
if ordinary.len() < 8 {
ordinary.push((Cow::Owned(k), Cow::Owned(v)));
}
}
}
if reserved_count == 1 && reserved_is_trailing && reserved_follows_seven {
if let Some(dropped) =
last_reserved_value.as_deref().and_then(|s| s.parse().ok())
{
let mut entries = ordinary;
entries.push((
Cow::Borrowed(DETAILS_TRUNCATED_KEY),
Cow::Owned(alloc::format!("{dropped}")),
));
return Ok(Details {
entries,
dropped: Some(dropped),
});
}
}
Ok(Details::build(ordinary, total_ordinary, reserved_count))
}
}
d.deserialize_map(DetailsVisitor)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KhiveError {
kind: ErrorKind,
message: String,
code: Option<ErrorCode>,
details: Option<Details>,
}
impl KhiveError {
pub fn not_found(resource: impl fmt::Display, id: impl fmt::Display) -> Self {
Self {
kind: ErrorKind::NotFound,
message: alloc::format!("{resource} not found: {id}"),
code: None,
details: None,
}
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::InvalidInput,
message: alloc::format!("invalid input: {}", message.into()),
code: None,
details: None,
}
}
pub fn unauthorized(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Unauthorized,
message: alloc::format!("unauthorized: {}", message.into()),
code: None,
details: None,
}
}
pub fn conflict(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Conflict,
message: alloc::format!("conflict: {}", message.into()),
code: None,
details: None,
}
}
pub fn unavailable(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Unavailable,
message: alloc::format!("unavailable: {}", message.into()),
code: None,
details: None,
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Internal,
message: alloc::format!("internal: {}", message.into()),
code: None,
details: None,
}
}
pub fn with_code(mut self, code: ErrorCode) -> Self {
self.code = Some(code);
self
}
pub fn with_details(mut self, details: Details) -> Self {
self.details = Some(details);
self
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
&self.message
}
pub fn code(&self) -> Option<ErrorCode> {
self.code
}
pub fn details(&self) -> Option<&Details> {
self.details.as_ref()
}
pub fn retry_hint(&self) -> RetryHint {
match self.kind {
ErrorKind::Unavailable => RetryHint::Retryable,
_ => RetryHint::NoRetry,
}
}
}
impl fmt::Display for KhiveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
#[cfg(feature = "std")]
impl std::error::Error for KhiveError {}