1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, thiserror::Error)]
8pub enum NoahError {
9 #[error("HTTP error: {0}")]
11 HttpError(#[from] reqwest::Error),
12
13 #[error("API error: {0}")]
15 ApiError(Box<ApiErrorResponse>),
16
17 #[error("Authentication error: {0}")]
19 AuthError(String),
20
21 #[error("Validation error: {0}")]
23 ValidationError(String),
24
25 #[error("Deserialization error: {0}")]
27 DeserializationError(#[from] serde_json::Error),
28
29 #[error("JWT signing error: {0}")]
31 JwtError(String),
32
33 #[error("Error: {0}")]
35 Other(#[from] anyhow::Error),
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ApiErrorResponse {
41 #[serde(rename = "Type")]
43 pub error_type: ErrorType,
44
45 #[serde(rename = "Instance")]
47 pub instance: Option<String>,
48
49 #[serde(rename = "Action")]
51 pub action: Option<String>,
52
53 #[serde(rename = "Detail")]
55 pub detail: Option<String>,
56
57 #[serde(rename = "Extensions")]
59 pub extensions: Option<ErrorExtensions>,
60
61 #[serde(rename = "RequestExtension")]
63 pub request_extension: Option<RequestExtension>,
64
65 #[serde(rename = "DenyExtension")]
67 pub deny_extension: Option<Vec<DenyExtensionItem>>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(rename_all = "PascalCase")]
73pub enum ErrorType {
74 InvalidMessage,
75 Unexpected,
76 ResourceNotFound,
77 Unauthorized,
78 Forbidden,
79 InsufficientBalance,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ErrorExtensions {
85 #[serde(rename = "Request")]
87 pub request: Option<serde_json::Value>,
88
89 #[serde(rename = "Features")]
91 pub features: Option<ErrorExtensionFeatures>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct ErrorExtensionFeatures {
97 #[serde(flatten)]
98 pub features: std::collections::HashMap<String, Vec<String>>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct RequestExtension {
104 #[serde(rename = "Body")]
106 pub body: Vec<RequestExtensionItem>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct RequestExtensionItem {
112 #[serde(rename = "Field")]
114 pub field: String,
115
116 #[serde(rename = "Reason")]
118 pub reason: String,
119
120 #[serde(rename = "Description")]
122 pub description: String,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct DenyExtensionItem {
128 #[serde(rename = "Reason")]
130 pub reason: String,
131
132 #[serde(rename = "Description")]
134 pub description: String,
135
136 #[serde(rename = "Principal")]
138 pub principal: DenyExtensionPrincipal,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct DenyExtensionPrincipal {
144 #[serde(rename = "ID")]
146 pub id: String,
147
148 #[serde(rename = "Type")]
150 pub principal_type: String,
151}
152
153impl fmt::Display for ApiErrorResponse {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 write!(f, "{}", self.error_type)?;
156 if let Some(ref detail) = self.detail {
157 write!(f, ": {detail}")?;
158 }
159 Ok(())
160 }
161}
162
163impl fmt::Display for ErrorType {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 ErrorType::InvalidMessage => write!(f, "InvalidMessage"),
167 ErrorType::Unexpected => write!(f, "Unexpected"),
168 ErrorType::ResourceNotFound => write!(f, "ResourceNotFound"),
169 ErrorType::Unauthorized => write!(f, "Unauthorized"),
170 ErrorType::Forbidden => write!(f, "Forbidden"),
171 ErrorType::InsufficientBalance => write!(f, "InsufficientBalance"),
172 }
173 }
174}
175
176impl std::error::Error for ApiErrorResponse {}
177
178impl From<ApiErrorResponse> for NoahError {
179 fn from(err: ApiErrorResponse) -> Self {
180 NoahError::ApiError(Box::new(err))
181 }
182}
183
184pub type Result<T> = std::result::Result<T, NoahError>;