Skip to main content

ironflow_api/
response.rs

1//! Standard response types and helpers for the REST API.
2//!
3//! All successful responses are wrapped in the [`ApiResponse`] envelope
4//! with optional pagination metadata.
5
6use axum::Json;
7use serde::Serialize;
8
9/// Pagination metadata.
10///
11/// Included in the response when the endpoint returns paginated results.
12///
13/// # Examples
14///
15/// ```
16/// use ironflow_api::response::ApiMeta;
17///
18/// let meta = ApiMeta {
19///     page: Some(1),
20///     per_page: Some(20),
21///     total: Some(100),
22/// };
23/// assert_eq!(meta.page, Some(1));
24/// ```
25#[derive(Debug, Serialize, Clone)]
26pub struct ApiMeta {
27    /// Current page number (1-based).
28    pub page: Option<u32>,
29    /// Items per page.
30    pub per_page: Option<u32>,
31    /// Total number of items matching the filter.
32    pub total: Option<u64>,
33}
34
35impl ApiMeta {
36    /// Create an empty metadata object (no pagination).
37    pub fn empty() -> Self {
38        ApiMeta {
39            page: None,
40            per_page: None,
41            total: None,
42        }
43    }
44
45    /// Create pagination metadata.
46    pub fn paginated(page: u32, per_page: u32, total: u64) -> Self {
47        ApiMeta {
48            page: Some(page),
49            per_page: Some(per_page),
50            total: Some(total),
51        }
52    }
53}
54
55/// Standard response envelope for all successful API responses.
56///
57/// Serialized as: `{ "data": ..., "meta": { "page": ..., "total": ... } }`
58///
59/// # Examples
60///
61/// ```
62/// use ironflow_api::response::ApiResponse;
63///
64/// let response = ApiResponse {
65///     data: "hello".to_string(),
66///     meta: None,
67/// };
68/// assert_eq!(response.data, "hello");
69/// ```
70#[derive(Debug, Serialize)]
71pub struct ApiResponse<T: Serialize> {
72    /// The response payload.
73    pub data: T,
74    /// Optional pagination metadata.
75    pub meta: Option<ApiMeta>,
76}
77
78/// Helper to wrap data in a successful response without pagination.
79///
80/// # Examples
81///
82/// ```no_run
83/// use ironflow_api::response::ok;
84///
85/// # async fn handler() {
86/// let data = vec!["a", "b"];
87/// let response = ok(data);
88/// // Returns: { "data": ["a", "b"] }
89/// # }
90/// ```
91pub fn ok<T: Serialize>(data: T) -> Json<ApiResponse<T>> {
92    Json(ApiResponse { data, meta: None })
93}
94
95/// Helper to wrap paginated data in a successful response.
96///
97/// # Examples
98///
99/// ```no_run
100/// use ironflow_api::response::ok_paged;
101///
102/// # async fn handler() {
103/// let data = vec!["a", "b"];
104/// let response = ok_paged(data, 1, 20, 100);
105/// // Returns: { "data": ["a", "b"], "meta": { "page": 1, "per_page": 20, "total": 100 } }
106/// # }
107/// ```
108pub fn ok_paged<T: Serialize>(
109    data: T,
110    page: u32,
111    per_page: u32,
112    total: u64,
113) -> Json<ApiResponse<T>> {
114    Json(ApiResponse {
115        data,
116        meta: Some(ApiMeta::paginated(page, per_page, total)),
117    })
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use serde_json::json;
124
125    #[test]
126    fn api_meta_empty() {
127        let meta = ApiMeta::empty();
128        assert_eq!(meta.page, None);
129        assert_eq!(meta.per_page, None);
130        assert_eq!(meta.total, None);
131    }
132
133    #[test]
134    fn api_meta_paginated() {
135        let meta = ApiMeta::paginated(2, 50, 200);
136        assert_eq!(meta.page, Some(2));
137        assert_eq!(meta.per_page, Some(50));
138        assert_eq!(meta.total, Some(200));
139    }
140
141    #[test]
142    fn api_response_serialize() {
143        let response = ApiResponse {
144            data: vec![1, 2, 3],
145            meta: None,
146        };
147        let json_val = serde_json::to_value(&response).expect("serialize");
148        assert_eq!(json_val["data"], json!([1, 2, 3]));
149        assert_eq!(json_val["meta"], serde_json::json!(null));
150    }
151
152    #[test]
153    fn api_response_with_meta_serialize() {
154        let response = ApiResponse {
155            data: vec!["a"],
156            meta: Some(ApiMeta::paginated(1, 10, 50)),
157        };
158        let json_val = serde_json::to_value(&response).expect("serialize");
159        assert_eq!(json_val["data"], json!(["a"]));
160        assert_eq!(json_val["meta"]["page"], 1);
161        assert_eq!(json_val["meta"]["per_page"], 10);
162        assert_eq!(json_val["meta"]["total"], 50);
163    }
164}