nestforge_core/
response.rs1use axum::{
2 response::{IntoResponse, Response},
3 Json,
4};
5use http::StatusCode;
6use serde::Serialize;
7use serde_json::json;
8
9use crate::HttpException;
10
11pub trait ResponseSerializer<T>: Send + Sync + 'static {
12 type Output: Serialize;
13
14 fn serialize(value: T) -> Self::Output;
15}
16
17#[derive(Debug, Clone, Serialize)]
18pub struct ResponseEnvelope<T> {
19 pub success: bool,
20 pub data: T,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub meta: Option<serde_json::Value>,
23}
24
25pub struct Serialized<T, S>
26where
27 S: ResponseSerializer<T>,
28{
29 value: T,
30 _marker: std::marker::PhantomData<S>,
31}
32
33impl<T, S> Serialized<T, S>
34where
35 S: ResponseSerializer<T>,
36{
37 pub fn new(value: T) -> Self {
38 Self {
39 value,
40 _marker: std::marker::PhantomData,
41 }
42 }
43}
44
45impl<T> ResponseEnvelope<T> {
46 pub fn ok(data: T) -> Self {
47 Self {
48 success: true,
49 data,
50 meta: None,
51 }
52 }
53
54 pub fn with_meta(mut self, meta: impl Into<serde_json::Value>) -> Self {
55 self.meta = Some(meta.into());
56 self
57 }
58
59 pub fn paginated(data: T, page: u64, per_page: u64, total: u64) -> Self {
60 Self::ok(data).with_meta(json!({
61 "page": page,
62 "per_page": per_page,
63 "total": total,
64 }))
65 }
66}
67
68impl<T> IntoResponse for ResponseEnvelope<T>
69where
70 T: Serialize,
71{
72 fn into_response(self) -> Response {
73 (StatusCode::OK, Json(self)).into_response()
74 }
75}
76
77impl<T, S> IntoResponse for Serialized<T, S>
78where
79 S: ResponseSerializer<T>,
80{
81 fn into_response(self) -> Response {
82 (StatusCode::OK, Json(S::serialize(self.value))).into_response()
83 }
84}
85
86pub type ApiEnvelopeResult<T> = Result<ResponseEnvelope<T>, HttpException>;
87pub type ApiSerializedResult<T, S> = Result<Serialized<T, S>, HttpException>;