shared_framework/response/
mod.rs1pub mod html;
18
19use serde::{Deserialize, Serialize};
20use serde_json::Value;
21use std::sync::Arc;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26pub enum ResponseType {
27 Json,
29 Text,
31 Html,
33 Xml,
35 Javascript,
37 File,
39}
40
41impl Default for ResponseType {
42 fn default() -> Self {
43 Self::Json
44 }
45}
46
47pub trait TypedServiceResult: Send + Sync {
51 fn message(&self) -> &str;
53 fn code(&self) -> u16;
55 fn response_type(&self) -> ResponseType;
57 fn serialize(&self) -> Result<String, serde_json::Error>;
59 fn data_json(&self) -> Option<Value> {
61 None
62 }
63}
64
65#[derive(Debug, Clone, Serialize)]
70pub struct ServiceResult<T: Serialize> {
71 pub status: String,
73 pub message: String,
75 pub data: Option<T>,
77 #[serde(skip)]
78 pub code: u16,
80 #[serde(skip)]
81 pub response_type: ResponseType,
83 #[serde(skip)]
84 naked: bool,
85}
86
87impl<T: Serialize> ServiceResult<T> {
88 pub fn new(
90 status: impl Into<String>,
91 message: impl Into<String>,
92 data: Option<T>,
93 code: u16,
94 ) -> Self {
95 Self {
96 status: status.into(),
97 message: message.into(),
98 data,
99 code,
100 response_type: ResponseType::Json,
101 naked: false,
102 }
103 }
104 pub fn ok(message: impl Into<String>, data: T) -> Self {
106 Self::new("success", message, Some(data), 200)
107 }
108 pub fn ok_empty(message: impl Into<String>) -> Self
110 where
111 T: Default,
112 {
113 Self::new("success", message, None, 200)
114 }
115 pub fn stripped(mut self) -> Self {
117 self.naked = true;
118 self
119 }
120 pub fn with_response_type(mut self, rt: ResponseType) -> Self {
122 self.response_type = rt;
123 self
124 }
125 pub fn with_code(mut self, code: u16) -> Self {
127 self.code = code;
128 self
129 }
130
131 pub fn boxed(self) -> Box<dyn TypedServiceResult>
133 where
134 T: Send + Sync + 'static,
135 {
136 Box::new(self)
137 }
138
139 pub fn serialize_inherent(&self) -> Result<String, serde_json::Error> {
142 if self.naked {
143 return serde_json::to_string(&self.data);
144 }
145 let wrapper = serde_json::json!({ "status": self.status, "message": self.message, "data": self.data });
146 serde_json::to_string(&wrapper)
147 }
148}
149
150impl<T: Serialize + Send + Sync> TypedServiceResult for ServiceResult<T> {
151 fn message(&self) -> &str {
152 &self.message
153 }
154 fn code(&self) -> u16 {
155 self.code
156 }
157 fn response_type(&self) -> ResponseType {
158 self.response_type
159 }
160 fn serialize(&self) -> Result<String, serde_json::Error> {
161 self.serialize_inherent()
162 }
163 fn data_json(&self) -> Option<Value> {
164 self.data
165 .as_ref()
166 .and_then(|d| serde_json::to_value(d).ok())
167 }
168}
169
170pub struct GenericTypedResult {
173 pub message: String,
175 pub data: Option<Value>,
177 pub code: u16,
179 pub response_type: ResponseType,
181}
182
183impl TypedServiceResult for GenericTypedResult {
184 fn message(&self) -> &str {
185 &self.message
186 }
187 fn code(&self) -> u16 {
188 self.code
189 }
190 fn response_type(&self) -> ResponseType {
191 self.response_type
192 }
193 fn serialize(&self) -> Result<String, serde_json::Error> {
194 match &self.data {
195 Some(v) => serde_json::to_string(v),
196 None => Ok("null".to_string()),
197 }
198 }
199}
200
201#[derive(Debug, Clone, Serialize)]
204pub struct ErrorResult {
205 pub message: String,
207 pub data: Option<Value>,
209 pub code: u16,
211}
212
213impl ErrorResult {
214 pub fn new(message: impl Into<String>, data: Option<Value>, code: u16) -> Self {
216 Self {
217 message: message.into(),
218 data,
219 code,
220 }
221 }
222
223 pub fn forbidden(msg: impl Into<String>) -> Self {
225 Self::new(msg, None, 403)
226 }
227
228 pub fn service_unavailable(msg: impl Into<String>) -> Self {
230 Self::new(msg, None, 503)
231 }
232
233 pub fn bad_request(msg: impl Into<String>) -> Self {
235 Self::new(msg, None, 400)
236 }
237 pub fn not_found(msg: impl Into<String>) -> Self {
239 Self::new(msg, None, 404)
240 }
241 pub fn internal(msg: impl Into<String>) -> Self {
243 Self::new(msg, None, 500)
244 }
245 pub fn from_error<E: std::fmt::Display + ?Sized>(err: &E, code: u16) -> Self {
247 Self::new(err.to_string(), None, code)
248 }
249
250 pub fn of(err: &anyhow::Error) -> Self {
253 let msg = err.to_string();
254 let lower = msg.to_lowercase();
255 if lower.contains("validation") || lower.contains("invalid") || lower.contains("not found")
256 {
257 Self::new(msg, None, 400)
258 } else {
259 Self::new(msg, None, 500)
260 }
261 }
262 pub fn to_service_result(&self) -> ServiceResult<Value> {
264 ServiceResult::new("error", self.message.clone(), self.data.clone(), self.code)
265 }
266 pub fn code(&self) -> u16 {
268 self.code
269 }
270}
271
272impl std::fmt::Display for ErrorResult {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 write!(f, "Error {}: {}", self.code, self.message)
275 }
276}
277impl std::error::Error for ErrorResult {}
278
279impl From<sea_orm::DbErr> for ErrorResult {
280 fn from(err: sea_orm::DbErr) -> Self {
281 match err {
282 sea_orm::DbErr::RecordNotFound(_) => Self::not_found("Could not find resource"),
283 sea_orm::DbErr::Custom(e) => Self::new(e, None, 503),
284 sea_orm::DbErr::RecordNotInserted => Self::service_unavailable("Record not inserted"),
285 sea_orm::DbErr::RbacError(_) => Self::forbidden("Access denied"),
286 sea_orm::DbErr::AccessDenied {
287 permission,
288 resource,
289 } => Self::forbidden(format!("Access denied: {} on {}", permission, resource)),
290 others => {
291 tracing::error!(target: "framework", "Database error: {:?}", others);
292 Self::internal("An unknown error has occurred. Please try again later")
293 },
294 }
295 }
296}
297
298impl TypedServiceResult for ErrorResult {
299 fn message(&self) -> &str {
300 &self.message
301 }
302 fn code(&self) -> u16 {
303 self.code
304 }
305 fn response_type(&self) -> ResponseType {
306 ResponseType::Json
307 }
308 fn serialize(&self) -> Result<String, serde_json::Error> {
309 self.to_service_result().serialize_inherent()
310 }
311}
312
313pub fn build_response(
317 result: &dyn TypedServiceResult,
318 ctx: Arc<crate::logging::CorrelationContext>,
319) -> http::Response<String> {
320 let body = result
321 .serialize()
322 .unwrap_or_else(|_| result.message().to_string());
323 let mut builder = http::Response::builder()
324 .status(result.code())
325 .header("X-Request-ID", ctx.request_id())
326 .header("X-Correlation-ID", ctx.correlation_id())
327 .header("X-Correlation-Flow", ctx.flow().to_string());
328 let content_type = match result.response_type() {
329 ResponseType::Json => "application/json",
330 ResponseType::Html => "text/html",
331 ResponseType::Xml => "application/xml",
332 ResponseType::Javascript => "application/javascript",
333 ResponseType::File => "application/octet-stream",
334 ResponseType::Text => "text/plain",
335 };
336 builder = builder.header("Content-Type", content_type);
337 if result.response_type() == ResponseType::File {
338 builder = builder.header(
339 "Content-Disposition",
340 format!(
341 "attachment; filename=\"{}\"",
342 body.rsplit('/').next().unwrap_or("file")
343 ),
344 );
345 }
346 builder.body(body).unwrap()
347}
348
349pub fn build_response_service<T: Serialize + Send + Sync>(
351 result: &ServiceResult<T>,
352 ctx: Arc<crate::logging::CorrelationContext>,
353) -> http::Response<String> {
354 build_response(result as &dyn TypedServiceResult, ctx)
355}
356
357pub fn error_response(
359 err: &ErrorResult,
360 ctx: Arc<crate::logging::CorrelationContext>,
361) -> http::Response<String> {
362 let sr = err.to_service_result();
363 build_response(&sr, ctx)
364}