1use std::fmt;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
10pub enum RotaStellarError {
11 #[error("Authentication error: {0}")]
13 Authentication(#[from] AuthenticationError),
14
15 #[error("API error: {0}")]
17 Api(#[from] ApiError),
18
19 #[error("Validation error: {0}")]
21 Validation(#[from] ValidationError),
22
23 #[error("Network error: {0}")]
25 Network(#[from] NetworkError),
26}
27
28#[derive(Error, Debug)]
30pub enum AuthenticationError {
31 #[error("API key is required. Get your key at https://rotastellar.com/dashboard")]
33 MissingApiKey,
34
35 #[error("Invalid API key format: {masked_key}. Keys should start with 'rs_live_' or 'rs_test_'")]
37 InvalidApiKey { masked_key: String },
38}
39
40impl AuthenticationError {
41 pub fn invalid_api_key(api_key: &str) -> Self {
43 let masked = if api_key.len() > 10 {
44 format!("{}...", &api_key[..10])
45 } else {
46 api_key.to_string()
47 };
48 Self::InvalidApiKey { masked_key: masked }
49 }
50}
51
52#[derive(Error, Debug)]
54pub struct ApiError {
55 pub message: String,
57 pub status_code: u16,
59 pub request_id: Option<String>,
61 pub details: Option<serde_json::Value>,
63}
64
65impl fmt::Display for ApiError {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 write!(f, "[{}] {}", self.status_code, self.message)?;
68 if let Some(ref id) = self.request_id {
69 write!(f, " (request_id: {})", id)?;
70 }
71 Ok(())
72 }
73}
74
75impl ApiError {
76 pub fn new(message: impl Into<String>, status_code: u16) -> Self {
78 Self {
79 message: message.into(),
80 status_code,
81 request_id: None,
82 details: None,
83 }
84 }
85
86 pub fn rate_limited(retry_after: Option<u32>) -> Self {
88 Self {
89 message: "Rate limit exceeded".to_string(),
90 status_code: 429,
91 request_id: None,
92 details: retry_after.map(|r| serde_json::json!({ "retry_after": r })),
93 }
94 }
95
96 pub fn not_found(resource_type: &str, resource_id: &str) -> Self {
98 Self {
99 message: format!("{} not found: {}", resource_type, resource_id),
100 status_code: 404,
101 request_id: None,
102 details: Some(serde_json::json!({
103 "resource_type": resource_type,
104 "resource_id": resource_id
105 })),
106 }
107 }
108
109 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
111 self.request_id = Some(request_id.into());
112 self
113 }
114}
115
116#[derive(Error, Debug)]
118#[error("Validation error on '{field}': {message}")]
119pub struct ValidationError {
120 pub field: String,
122 pub message: String,
124}
125
126impl ValidationError {
127 pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
129 Self {
130 field: field.into(),
131 message: message.into(),
132 }
133 }
134}
135
136#[derive(Error, Debug)]
138pub enum NetworkError {
139 #[error("Request timed out after {0} seconds")]
141 Timeout(f64),
142
143 #[error("Connection failed: {0}")]
145 Connection(String),
146
147 #[error("Network error: {0}")]
149 Other(String),
150}
151
152pub type Result<T> = std::result::Result<T, RotaStellarError>;