noah_sdk/
error.rs

1//! Error types for the Noah SDK
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Main error type for the Noah SDK
7#[derive(Debug, thiserror::Error)]
8pub enum NoahError {
9    /// HTTP client errors
10    #[error("HTTP error: {0}")]
11    HttpError(#[from] reqwest::Error),
12
13    /// API error responses from the server
14    #[error("API error: {0}")]
15    ApiError(Box<ApiErrorResponse>),
16
17    /// Authentication errors
18    #[error("Authentication error: {0}")]
19    AuthError(String),
20
21    /// Request validation errors
22    #[error("Validation error: {0}")]
23    ValidationError(String),
24
25    /// JSON deserialization errors
26    #[error("Deserialization error: {0}")]
27    DeserializationError(#[from] serde_json::Error),
28
29    /// JWT signing errors
30    #[error("JWT signing error: {0}")]
31    JwtError(String),
32
33    /// Other errors
34    #[error("Error: {0}")]
35    Other(#[from] anyhow::Error),
36}
37
38/// API error response structure matching the OpenAPI schema
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ApiErrorResponse {
41    /// Error type
42    #[serde(rename = "Type")]
43    pub error_type: ErrorType,
44
45    /// Unique instance identifier
46    #[serde(rename = "Instance")]
47    pub instance: Option<String>,
48
49    /// Action that caused the error
50    #[serde(rename = "Action")]
51    pub action: Option<String>,
52
53    /// Error details
54    #[serde(rename = "Detail")]
55    pub detail: Option<String>,
56
57    /// Error extensions
58    #[serde(rename = "Extensions")]
59    pub extensions: Option<ErrorExtensions>,
60
61    /// Request extension details
62    #[serde(rename = "RequestExtension")]
63    pub request_extension: Option<RequestExtension>,
64
65    /// Deny extension details
66    #[serde(rename = "DenyExtension")]
67    pub deny_extension: Option<Vec<DenyExtensionItem>>,
68}
69
70/// Error type enum
71#[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/// Error extensions
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ErrorExtensions {
85    /// Request details
86    #[serde(rename = "Request")]
87    pub request: Option<serde_json::Value>,
88
89    /// Feature flags
90    #[serde(rename = "Features")]
91    pub features: Option<ErrorExtensionFeatures>,
92}
93
94/// Error extension features
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct ErrorExtensionFeatures {
97    #[serde(flatten)]
98    pub features: std::collections::HashMap<String, Vec<String>>,
99}
100
101/// Request extension
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct RequestExtension {
104    /// Body field errors
105    #[serde(rename = "Body")]
106    pub body: Vec<RequestExtensionItem>,
107}
108
109/// Request extension item
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct RequestExtensionItem {
112    /// Field path
113    #[serde(rename = "Field")]
114    pub field: String,
115
116    /// Error reason
117    #[serde(rename = "Reason")]
118    pub reason: String,
119
120    /// Error description
121    #[serde(rename = "Description")]
122    pub description: String,
123}
124
125/// Deny extension item
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct DenyExtensionItem {
128    /// Denial reason
129    #[serde(rename = "Reason")]
130    pub reason: String,
131
132    /// Description
133    #[serde(rename = "Description")]
134    pub description: String,
135
136    /// Principal
137    #[serde(rename = "Principal")]
138    pub principal: DenyExtensionPrincipal,
139}
140
141/// Deny extension principal
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct DenyExtensionPrincipal {
144    /// Principal ID
145    #[serde(rename = "ID")]
146    pub id: String,
147
148    /// Principal type
149    #[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
184/// Result type alias for Noah SDK operations
185pub type Result<T> = std::result::Result<T, NoahError>;