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() -> ApiResponse<T> {
73        ApiResponse::<()>::unauthorized().cast()
74    }
75
76    /// Create an error response for any type
77    pub fn error_typed(code: &str, message: impl Into<String>) -> ApiResponse<T> {
78        ApiResponse::<()>::error(code, message).cast()
79    }
80
81    /// Create a validation error response for any type
82    pub fn validation_error_typed(message: impl Into<String>) -> ApiResponse<T> {
83        ApiResponse::<()>::validation_error(message).cast()
84    }
85
86    /// Create a not found response for any type
87    pub fn not_found_typed(message: impl Into<String>) -> ApiResponse<T> {
88        ApiResponse::<()>::not_found(message).cast()
89    }
90
91    /// Create a forbidden response with message for any type
92    pub fn forbidden_with_message_typed(message: impl Into<String>) -> ApiResponse<T> {
93        ApiResponse::<()>::forbidden_with_message(message).cast()
94    }
95
96    /// Create an error response with message for any type
97    pub fn error_with_message_typed(code: &str, message: impl Into<String>) -> ApiResponse<T> {
98        ApiResponse::<()>::error_with_message(code, message).cast()
99    }
100
101    /// Create a not found response with message for any type
102    pub fn not_found_with_message_typed(message: impl Into<String>) -> ApiResponse<T> {
103        ApiResponse::<()>::not_found_with_message(message).cast()
104    }
105
106    /// Create an internal error response for any type
107    pub fn internal_error_typed() -> ApiResponse<T> {
108        ApiResponse::<()>::internal_error().cast()
109    }
110
111    /// Create successful response with message
112    pub fn success_with_message(data: T, message: impl Into<String>) -> Self {
113        Self {
114            success: true,
115            data: Some(data),
116            error: None,
117            message: Some(message.into()),
118        }
119    }
120
121    /// Create simple success response
122    pub fn ok() -> ApiResponse<()> {
123        ApiResponse {
124            success: true,
125            data: None,
126            error: None,
127            message: None,
128        }
129    }
130
131    /// Create success response with message only
132    pub fn ok_with_message(message: impl Into<String>) -> ApiResponse<()> {
133        ApiResponse {
134            success: true,
135            data: None,
136            error: None,
137            message: Some(message.into()),
138        }
139    }
140}
141
142impl ApiResponse<()> {
143    /// Create error response
144    pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
145        Self {
146            success: false,
147            data: None,
148            error: Some(ApiError {
149                code: code.into(),
150                message: message.into(),
151                details: None,
152            }),
153            message: None,
154        }
155    }
156
157    /// Create error response with details
158    pub fn error_with_details(
159        code: impl Into<String>,
160        message: impl Into<String>,
161        details: serde_json::Value,
162    ) -> Self {
163        Self {
164            success: false,
165            data: None,
166            error: Some(ApiError {
167                code: code.into(),
168                message: message.into(),
169                details: Some(details),
170            }),
171            message: None,
172        }
173    }
174
175    /// Create validation error
176    pub fn validation_error(message: impl Into<String>) -> Self {
177        Self::error("VALIDATION_ERROR", message)
178    }
179
180    /// Create unauthorized error
181    pub fn unauthorized() -> Self {
182        Self::error("UNAUTHORIZED", "Authentication required")
183    }
184
185    /// Create forbidden error
186    pub fn forbidden() -> Self {
187        Self::error("FORBIDDEN", "Insufficient permissions")
188    }
189
190    /// Create forbidden error with custom message
191    pub fn forbidden_with_message(message: impl Into<String>) -> Self {
192        Self::error("FORBIDDEN", message)
193    }
194
195    /// Create not found error
196    pub fn not_found(resource: impl Into<String>) -> Self {
197        Self::error("NOT_FOUND", format!("{} not found", resource.into()))
198    }
199
200    /// Create not found error with custom message
201    pub fn not_found_with_message(message: impl Into<String>) -> Self {
202        Self::error("NOT_FOUND", message)
203    }
204
205    /// Create error response with custom message
206    pub fn error_with_message(code: impl Into<String>, message: impl Into<String>) -> Self {
207        Self::error(code, message)
208    }
209
210    /// Create internal server error
211    pub fn internal_error() -> Self {
212        Self::error("SERVER_ERROR", "Internal server error")
213    }
214}
215
216impl<T> IntoResponse for ApiResponse<T>
217where
218    T: Serialize,
219{
220    fn into_response(self) -> Response {
221        let status = if self.success {
222            StatusCode::OK
223        } else {
224            match self.error.as_ref().map(|e| e.code.as_str()) {
225                Some("UNAUTHORIZED") => StatusCode::UNAUTHORIZED,
226                Some("FORBIDDEN") => StatusCode::FORBIDDEN,
227                Some("NOT_FOUND") => StatusCode::NOT_FOUND,
228                Some("VALIDATION_ERROR") => StatusCode::BAD_REQUEST,
229                Some("RATE_LIMITED") => StatusCode::TOO_MANY_REQUESTS,
230                _ => StatusCode::INTERNAL_SERVER_ERROR,
231            }
232        };
233
234        (status, Json(self)).into_response()
235    }
236}
237
238/// Convert AuthError to API response
239impl From<crate::errors::AuthError> for ApiResponse<()> {
240    fn from(error: crate::errors::AuthError) -> Self {
241        match &error {
242            crate::errors::AuthError::Token(_) => Self::error("INVALID_TOKEN", error.to_string()),
243            crate::errors::AuthError::Validation { .. } => {
244                Self::validation_error(error.to_string())
245            }
246            crate::errors::AuthError::AuthMethod { .. } => {
247                Self::error("INVALID_CREDENTIALS", error.to_string())
248            }
249            crate::errors::AuthError::UserNotFound => Self::not_found(error.to_string()),
250            crate::errors::AuthError::Permission(_) => Self::forbidden(),
251            crate::errors::AuthError::RateLimit { .. } => {
252                Self::error("RATE_LIMITED", error.to_string())
253            }
254            _ => Self::internal_error(),
255        }
256    }
257}
258
259