Skip to main content

nestforge_core/
response.rs

1use axum::{
2    response::{IntoResponse, Response},
3    Json,
4};
5use http::StatusCode;
6use serde::Serialize;
7use serde_json::json;
8
9use crate::HttpException;
10
11/**
12 * ResponseSerializer Trait
13 *
14 * A trait for defining custom response serialization logic.
15 * Use this when you want to transform a domain object into a specific
16 * API response format, such as hiding fields, renaming keys, or
17 * flattening structures.
18 *
19 * # Type Parameters
20 * - `T`: The input type to serialize
21 *
22 * # Associated Types
23 * - `Output`: The serialized output type (must implement Serialize)
24 *
25 * # Example
26 * ```rust
27 * struct UserResponseSerializer;
28 * impl ResponseSerializer<User> for UserResponseSerializer {
29 *     type Output = UserResponse;
30 *     fn serialize(value: User) -> Self::Output {
31 *         UserResponse {
32 *             id: value.id,
33 *             name: value.name,
34 *             // password is hidden
35 *         }
36 *     }
37 * }
38 * ```
39 */
40pub trait ResponseSerializer<T>: Send + Sync + 'static {
41    type Output: Serialize;
42
43    fn serialize(value: T) -> Self::Output;
44}
45
46/**
47 * ResponseEnvelope
48 *
49 * A standard API response wrapper that provides consistent JSON structure
50 * across all API responses. Wraps data in a predictable format that includes
51 * success status, data payload, and optional metadata.
52 *
53 * # JSON Structure
54 * ```json
55 * {
56 *   "success": true,
57 *   "data": { ... },
58 *   "meta": { ... }
59 * }
60 * ```
61 *
62 * # Usage
63 * ```rust
64 * fn get_users() -> ApiResult<Vec<User>> {
65 *     Ok(ResponseEnvelope::ok(users))
66 * }
67 * ```
68 */
69#[derive(Debug, Clone, Serialize)]
70pub struct ResponseEnvelope<T> {
71    pub success: bool,
72    pub data: T,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub meta: Option<serde_json::Value>,
75}
76
77/**
78 * Serialized Wrapper
79 *
80 * A wrapper type that applies a ResponseSerializer to a value.
81 * When returned from a controller, it automatically serializes
82 * the inner value using the specified serializer.
83 *
84 * # Type Parameters
85 * - `T`: The type being serialized
86 * - `S`: The serializer type implementing ResponseSerializer<T>
87 */
88pub struct Serialized<T, S>
89where
90    S: ResponseSerializer<T>,
91{
92    value: T,
93    _marker: std::marker::PhantomData<S>,
94}
95
96impl<T, S> Serialized<T, S>
97where
98    S: ResponseSerializer<T>,
99{
100    pub fn new(value: T) -> Self {
101        Self {
102            value,
103            _marker: std::marker::PhantomData,
104        }
105    }
106}
107
108impl<T> ResponseEnvelope<T> {
109    /// Creates a success response with the given data.
110    pub fn ok(data: T) -> Self {
111        Self {
112            success: true,
113            data,
114            meta: None,
115        }
116    }
117
118    /// Adds metadata to the response.
119    pub fn with_meta(mut self, meta: impl Into<serde_json::Value>) -> Self {
120        self.meta = Some(meta.into());
121        self
122    }
123
124    /// Helper for creating paginated responses.
125    pub fn paginated(data: T, page: u64, per_page: u64, total: u64) -> Self {
126        Self::ok(data).with_meta(json!({
127            "page": page,
128            "per_page": per_page,
129            "total": total,
130        }))
131    }
132}
133
134impl<T> IntoResponse for ResponseEnvelope<T>
135where
136    T: Serialize,
137{
138    fn into_response(self) -> Response {
139        (StatusCode::OK, Json(self)).into_response()
140    }
141}
142
143impl<T, S> IntoResponse for Serialized<T, S>
144where
145    S: ResponseSerializer<T>,
146{
147    fn into_response(self) -> Response {
148        (StatusCode::OK, Json(S::serialize(self.value))).into_response()
149    }
150}
151
152/// A helper type for returning `Result<ResponseEnvelope<T>, HttpException>`.
153pub type ApiEnvelopeResult<T> = Result<ResponseEnvelope<T>, HttpException>;
154
155/// A helper type for returning `Result<Serialized<T, S>, HttpException>`.
156pub type ApiSerializedResult<T, S> = Result<Serialized<T, S>, HttpException>;