Skip to main content

platform_core/
error.rs

1use serde::{Deserialize, Serialize};
2use std::error::Error;
3use std::fmt::{Debug, Display};
4use thiserror::Error;
5
6pub type AppResult<T> = Result<T, AppError>;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ErrorCode {
11    Validation,
12    Unauthorized,
13    Forbidden,
14    NotFound,
15    Conflict,
16    RateLimited,
17    ExternalDependency,
18    Internal,
19}
20
21impl ErrorCode {
22    pub fn as_str(self) -> &'static str {
23        match self {
24            Self::Validation => "validation_failed",
25            Self::Unauthorized => "unauthorized",
26            Self::Forbidden => "forbidden",
27            Self::NotFound => "not_found",
28            Self::Conflict => "conflict",
29            Self::RateLimited => "rate_limited",
30            Self::ExternalDependency => "external_dependency_failure",
31            Self::Internal => "internal_error",
32        }
33    }
34}
35
36#[derive(Error)]
37pub struct AppError {
38    pub code: ErrorCode,
39    pub public_message: String,
40    pub retryable: bool,
41    pub details: Vec<ErrorDetail>,
42    #[source]
43    source: Option<Box<dyn Error + Send + Sync>>,
44}
45
46impl Debug for AppError {
47    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        formatter
49            .debug_struct("AppError")
50            .field("code", &self.code)
51            .field("public_message", &self.public_message)
52            .field("retryable", &self.retryable)
53            .field("details", &self.details)
54            .finish_non_exhaustive()
55    }
56}
57
58impl Display for AppError {
59    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(formatter, "{}: {}", self.code.as_str(), self.public_message)
61    }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
65pub struct ErrorDetail {
66    pub field: Option<String>,
67    pub reason: String,
68}
69
70impl AppError {
71    pub fn new(code: ErrorCode, public_message: impl Into<String>) -> Self {
72        Self {
73            code,
74            public_message: public_message.into(),
75            retryable: false,
76            details: Vec::new(),
77            source: None,
78        }
79    }
80
81    pub fn validation(public_message: impl Into<String>, details: Vec<ErrorDetail>) -> Self {
82        Self {
83            code: ErrorCode::Validation,
84            public_message: public_message.into(),
85            retryable: false,
86            details,
87            source: None,
88        }
89    }
90
91    pub fn with_source(mut self, source: impl Error + Send + Sync + 'static) -> Self {
92        self.source = Some(Box::new(source));
93        self
94    }
95
96    pub fn retryable(mut self) -> Self {
97        self.retryable = true;
98        self
99    }
100}