auth_framework/api/
responses.rs

1//! API Response Types
2//!
3//! Common response types for the REST API
4
5use axum::{
6    Json,
7    http::StatusCode,
8    response::{IntoResponse, Response},
9};
10use serde::Serialize;
11
12/// Standard API response wrapper
13#[derive(Debug, Serialize)]
14pub struct ApiResponse<T> {
15    pub success: bool,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub data: Option<T>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub error: Option<ApiError>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub message: Option<String>,
22}
23
24/// API error details
25#[derive(Debug, Serialize)]
26pub struct ApiError {
27    pub code: String,
28    pub message: String,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub details: Option<serde_json::Value>,
31}
32
33/// Pagination information
34#[derive(Debug, Serialize)]
35pub struct Pagination {
36    pub page: u32,
37    pub limit: u32,
38    pub total: u64,
39    pub pages: u32,
40}
41
42/// API result type
43pub type ApiResult<T> = Result<ApiResponse<T>, ApiResponse<()>>;
44
45impl<T> ApiResponse<T> {
46    /// Create successful response with data
47    pub fn success(data: T) -> Self {
48        Self {
49            success: true,
50            data: Some(data),
51            error: None,
52            message: None,
53        }
54    }
55
56    /// Convert this ApiResponse to another type (for error responses)
57    pub fn cast<U>(self) -> ApiResponse<U> {
58        ApiResponse {
59            success: self.success,
60            data: None,
61            error: self.error,
62            message: self.message,
63        }
64    }
65
66    /// Create a forbidden response for any type
67    pub fn forbidden_typed() -> ApiResponse<T> {
68        ApiResponse::<()>::forbidden().cast()
69    }
70
71    /// Create an unauthorized response for any type
72    pub fn unauthorized_typed(code: &str, message: impl Into<String>) -> ApiResponse<T> {
73        ApiResponse::<()>::error(code, message).cast()
74    }
75
76    /// Create a conflict response for any type
77    pub fn conflict_typed(code: &str, message: impl Into<String>) -> ApiResponse<T> {
78        ApiResponse::<()>::conflict(code, message).cast()
79    }
80
81    /// Create an error response for any type
82    pub fn error_typed(code: &str, message: impl Into<String>) -> ApiResponse<T> {
83        ApiResponse::<()>::error(code, message).cast()
84    }
85
86    /// Create a validation error response for any type
87    pub fn validation_error_typed(message: impl Into<String>) -> ApiResponse<T> {
88        ApiResponse::<()>::validation_error(message).cast()
89    }
90
91    /// Create a not found response for any type
92    pub fn not_found_typed(message: impl Into<String>) -> ApiResponse<T> {
93        ApiResponse::<()>::not_found(message).cast()
94    }
95
96    /// Create a forbidden response with message for any type
97    pub fn forbidden_with_message_typed(message: impl Into<String>) -> ApiResponse<T> {
98        ApiResponse::<()>::forbidden_with_message(message).cast()
99    }
100
101    /// Create an error response with message for any type
102    pub fn error_with_message_typed(code: &str, message: impl Into<String>) -> ApiResponse<T> {
103        ApiResponse::<()>::error_with_message(code, message).cast()
104    }
105
106    /// Create a not found response with message for any type
107    pub fn not_found_with_message_typed(message: impl Into<String>) -> ApiResponse<T> {
108        ApiResponse::<()>::not_found_with_message(message).cast()
109    }
110
111    /// Create an internal error response for any type
112    pub fn internal_error_typed() -> ApiResponse<T> {
113        ApiResponse::<()>::internal_error().cast()
114    }
115
116    /// Create successful response with message
117    pub fn success_with_message(data: T, message: impl Into<String>) -> Self {
118        Self {
119            success: true,
120            data: Some(data),
121            error: None,
122            message: Some(message.into()),
123        }
124    }
125
126    /// Create simple success response
127    pub fn ok() -> ApiResponse<()> {
128        ApiResponse {
129            success: true,
130            data: None,
131            error: None,
132            message: None,
133        }
134    }
135
136    /// Create success response with message only
137    pub fn ok_with_message(message: impl Into<String>) -> ApiResponse<()> {
138        ApiResponse {
139            success: true,
140            data: None,
141            error: None,
142            message: Some(message.into()),
143        }
144    }
145}
146
147impl ApiResponse<()> {
148    /// Create error response
149    pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
150        Self {
151            success: false,
152            data: None,
153            error: Some(ApiError {
154                code: code.into(),
155                message: message.into(),
156                details: None,
157            }),
158            message: None,
159        }
160    }
161
162    /// Create error response with details
163    pub fn error_with_details(
164        code: impl Into<String>,
165        message: impl Into<String>,
166        details: serde_json::Value,
167    ) -> Self {
168        Self {
169            success: false,
170            data: None,
171            error: Some(ApiError {
172                code: code.into(),
173                message: message.into(),
174                details: Some(details),
175            }),
176            message: None,
177        }
178    }
179
180    /// Create validation error
181    pub fn validation_error(message: impl Into<String>) -> Self {
182        Self::error("VALIDATION_ERROR", message)
183    }
184
185    /// Create unauthorized error
186    pub fn unauthorized() -> Self {
187        Self::error("UNAUTHORIZED", "Authentication required")
188    }
189
190    /// Create forbidden error
191    pub fn forbidden() -> Self {
192        Self::error("FORBIDDEN", "Insufficient permissions")
193    }
194
195    /// Create forbidden error with custom message
196    pub fn forbidden_with_message(message: impl Into<String>) -> Self {
197        Self::error("FORBIDDEN", message)
198    }
199
200    /// Create not found error
201    pub fn not_found(resource: impl Into<String>) -> Self {
202        Self::error("NOT_FOUND", format!("{} not found", resource.into()))
203    }
204
205    /// Create not found error with custom message
206    pub fn not_found_with_message(message: impl Into<String>) -> Self {
207        Self::error("NOT_FOUND", message)
208    }
209
210    /// Create conflict error (409)
211    pub fn conflict(code: impl Into<String>, message: impl Into<String>) -> Self {
212        Self::error(code, message)
213    }
214
215    /// Create error response with custom message
216    pub fn error_with_message(code: impl Into<String>, message: impl Into<String>) -> Self {
217        Self::error(code, message)
218    }
219
220    /// Create internal server error
221    pub fn internal_error() -> Self {
222        Self::error("SERVER_ERROR", "Internal server error")
223    }
224}
225
226impl<T> IntoResponse for ApiResponse<T>
227where
228    T: Serialize,
229{
230    fn into_response(self) -> Response {
231        let status = if self.success {
232            StatusCode::OK
233        } else {
234            match self.error.as_ref().map(|e| e.code.as_str()) {
235                Some("UNAUTHORIZED") | Some("INVALID_CREDENTIALS") | Some("AUTH_ERROR") | Some("INVALID_TOKEN") => StatusCode::UNAUTHORIZED,
236                Some("FORBIDDEN") => StatusCode::FORBIDDEN,
237                Some("NOT_FOUND") => StatusCode::NOT_FOUND,
238                Some("VALIDATION_ERROR") => StatusCode::BAD_REQUEST,
239                Some("USERNAME_EXISTS") | Some("EMAIL_EXISTS") => StatusCode::CONFLICT,
240                Some("RATE_LIMITED") => StatusCode::TOO_MANY_REQUESTS,
241                _ => StatusCode::INTERNAL_SERVER_ERROR,
242            }
243        };
244
245        (status, Json(self)).into_response()
246    }
247}
248
249/// Convert AuthError to API response
250impl From<crate::errors::AuthError> for ApiResponse<()> {
251    fn from(error: crate::errors::AuthError) -> Self {
252        match &error {
253            crate::errors::AuthError::Token(_) => Self::error("INVALID_TOKEN", error.to_string()),
254            crate::errors::AuthError::Validation { .. } => {
255                Self::validation_error(error.to_string())
256            }
257            crate::errors::AuthError::AuthMethod { .. } => {
258                Self::error("INVALID_CREDENTIALS", error.to_string())
259            }
260            crate::errors::AuthError::UserNotFound => Self::not_found(error.to_string()),
261            crate::errors::AuthError::Permission(_) => Self::forbidden(),
262            crate::errors::AuthError::RateLimit { .. } => {
263                Self::error("RATE_LIMITED", error.to_string())
264            }
265            _ => Self::internal_error(),
266        }
267    }
268}
269