use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ErrorCode {
NotFound = 1,
AlreadyExists = 2,
InvalidArgument = 3,
PermissionDenied = 4,
FailedPrecondition = 5,
ResourceExhausted = 6,
Unavailable = 7,
InternalError = 8,
NotSupported = 9,
Unknown = 10,
}
impl From<u32> for ErrorCode {
fn from(value: u32) -> Self {
match value {
1 => Self::NotFound,
2 => Self::AlreadyExists,
3 => Self::InvalidArgument,
4 => Self::PermissionDenied,
5 => Self::FailedPrecondition,
6 => Self::ResourceExhausted,
7 => Self::Unavailable,
8 => Self::InternalError,
9 => Self::NotSupported,
_ => Self::Unknown,
}
}
}
impl From<ErrorCode> for u32 {
fn from(value: ErrorCode) -> Self {
value as u32
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::NotFound => "NotFound",
Self::AlreadyExists => "AlreadyExists",
Self::InvalidArgument => "InvalidArgument",
Self::PermissionDenied => "PermissionDenied",
Self::FailedPrecondition => "FailedPrecondition",
Self::ResourceExhausted => "ResourceExhausted",
Self::Unavailable => "Unavailable",
Self::InternalError => "InternalError",
Self::NotSupported => "NotSupported",
Self::Unknown => "Unknown",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
pub code: ErrorCode,
pub message: String,
}
impl Error {
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
}
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::new(ErrorCode::NotFound, message)
}
pub fn already_exists(message: impl Into<String>) -> Self {
Self::new(ErrorCode::AlreadyExists, message)
}
pub fn invalid_argument(message: impl Into<String>) -> Self {
Self::new(ErrorCode::InvalidArgument, message)
}
pub fn permission_denied(message: impl Into<String>) -> Self {
Self::new(ErrorCode::PermissionDenied, message)
}
pub fn failed_precondition(message: impl Into<String>) -> Self {
Self::new(ErrorCode::FailedPrecondition, message)
}
pub fn internal(message: impl Into<String>) -> Self {
Self::new(ErrorCode::InternalError, message)
}
pub fn resource_exhausted(message: impl Into<String>) -> Self {
Self::new(ErrorCode::ResourceExhausted, message)
}
pub fn not_supported(message: impl Into<String>) -> Self {
Self::new(ErrorCode::NotSupported, message)
}
pub fn is_not_found(&self) -> bool {
self.code == ErrorCode::NotFound
}
pub fn is_already_exists(&self) -> bool {
self.code == ErrorCode::AlreadyExists
}
pub fn is_invalid_argument(&self) -> bool {
self.code == ErrorCode::InvalidArgument
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "a3s-vec error {}: {}", self.code, self.message)
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
let code = match value.kind() {
std::io::ErrorKind::NotFound => ErrorCode::NotFound,
std::io::ErrorKind::PermissionDenied => ErrorCode::PermissionDenied,
std::io::ErrorKind::AlreadyExists => ErrorCode::AlreadyExists,
_ => ErrorCode::InternalError,
};
Self::new(code, value.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::new(ErrorCode::InternalError, format!("JSON error: {value}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_codes_are_stable() {
for code in [
ErrorCode::NotFound,
ErrorCode::AlreadyExists,
ErrorCode::InvalidArgument,
ErrorCode::PermissionDenied,
ErrorCode::FailedPrecondition,
ErrorCode::ResourceExhausted,
ErrorCode::Unavailable,
ErrorCode::InternalError,
ErrorCode::NotSupported,
ErrorCode::Unknown,
] {
assert_eq!(ErrorCode::from(u32::from(code)), code);
assert!(!format!("{code}").is_empty());
}
assert_eq!(u32::from(ErrorCode::InvalidArgument), 3);
assert_eq!(ErrorCode::from(99), ErrorCode::Unknown);
}
#[test]
fn helpers_preserve_context() {
let error = Error::not_found("document x");
assert!(error.is_not_found());
assert!(error.to_string().contains("document x"));
assert_eq!(Error::already_exists("dup").code, ErrorCode::AlreadyExists);
assert_eq!(
Error::invalid_argument("bad").code,
ErrorCode::InvalidArgument
);
assert_eq!(
Error::permission_denied("no").code,
ErrorCode::PermissionDenied
);
assert_eq!(
Error::failed_precondition("pre").code,
ErrorCode::FailedPrecondition
);
assert_eq!(
Error::resource_exhausted("oom").code,
ErrorCode::ResourceExhausted
);
assert_eq!(Error::internal("boom").code, ErrorCode::InternalError);
assert_eq!(Error::not_supported("yet").code, ErrorCode::NotSupported);
assert_eq!(
Error::new(ErrorCode::Unavailable, "down").code,
ErrorCode::Unavailable
);
}
#[test]
fn json_errors_map_to_internal() {
let err: Error = serde_json::from_str::<serde_json::Value>("{")
.unwrap_err()
.into();
assert_eq!(err.code, ErrorCode::InternalError);
assert!(err.message.contains("JSON"));
}
#[test]
fn io_and_predicate_helpers_cover_surface() {
let not_found: Error = std::io::Error::new(std::io::ErrorKind::NotFound, "gone").into();
assert!(not_found.is_not_found());
let exists: Error = std::io::Error::new(std::io::ErrorKind::AlreadyExists, "here").into();
assert!(exists.is_already_exists());
let denied: Error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "no").into();
assert_eq!(denied.code, ErrorCode::PermissionDenied);
let other: Error = std::io::Error::other("boom").into();
assert_eq!(other.code, ErrorCode::InternalError);
assert!(Error::invalid_argument("x").is_invalid_argument());
assert!(!Error::not_found("y").is_invalid_argument());
}
}