anycms_core/
result.rs

1use crate::pagination::ResultPagination;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5/// API response wrapper with unified structure
6///
7/// # Fields
8/// - `success`: Indicates if the request was successful
9/// - `data`: Contains a single value (mutually exclusive with `list`)
10/// - `list`: Contains a list of values (mutually exclusive with `data`)
11/// - `message`: Optional message or error description
12/// - `code`: Optional error code
13/// - `pagination`: Optional pagination metadata for list responses
14/// - `extra`: Additional metadata as key-value pairs
15#[derive(Debug, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct ApiResult<T> {
18    pub success: bool,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub data: Option<T>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub list: Option<Vec<T>>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub message: Option<String>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub code: Option<i32>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub pagination: Option<ResultPagination>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub extra: Option<Value>,
31}
32
33impl<T> ApiResult<T> {
34    /// Create a successful response with a single value
35    pub fn value(v: T) -> Self {
36        ApiResult {
37            success: true,
38            data: Some(v),
39            list: None,
40            message: None,
41            code: None,
42            pagination: None,
43            extra: None,
44        }
45    }
46
47    /// Create a successful response with a list of values
48    pub fn list(v: Vec<T>) -> Self {
49        ApiResult {
50            success: true,
51            data: None,
52            list: Some(v),
53            message: None,
54            code: None,
55            pagination: None,
56            extra: None,
57        }
58    }
59
60    /// Create a failed response with a message (alias for `failure`)
61    pub fn fail(message: &str) -> Self {
62        Self::failure(message)
63    }
64
65    /// Create a failed response with a message
66    pub fn failure(message: &str) -> Self {
67        ApiResult {
68            success: false,
69            data: None,
70            list: None,
71            message: Some(message.to_string()),
72            code: None,
73            pagination: None,
74            extra: None,
75        }
76    }
77
78    /// Create a successful response without data
79    pub fn success() -> Self {
80        ApiResult {
81            success: true,
82            data: None,
83            list: None,
84            message: None,
85            code: None,
86            pagination: None,
87            extra: None,
88        }
89    }
90
91    /// Add extra metadata to the response
92    ///
93    /// # Example
94    /// ```rust
95    /// let result = ApiResult::success()
96    ///     .with_extra("timestamp", json!(1234567890))
97    ///     .with_extra("version", json!("1.0.0"));
98    /// ```
99    pub fn with_extra(mut self, key: &str, value: Value) -> Self {
100        match self.extra {
101            Some(ref mut v) => {
102                v[key] = value;
103            }
104            None => {
105                let mut v = serde_json::Map::new();
106                v.insert(key.to_string(), value);
107                self.extra = Some(v.into());
108            }
109        }
110        self
111    }
112
113    /// Set error code for the response
114    pub fn with_code(mut self, code: i32) -> Self {
115        self.code = Some(code);
116        self
117    }
118
119    /// Set pagination metadata for list responses
120    pub fn with_pagination(mut self, pagination: ResultPagination) -> Self {
121        self.pagination = Some(pagination);
122        self
123    }
124
125    /// Set message for the response
126    pub fn with_message(mut self, message: &str) -> Self {
127        self.message = Some(message.to_string());
128        self
129    }
130}
131
132/// Conversion into Result for convenient error handling
133impl<T, E> Into<Result<ApiResult<T>, E>> for ApiResult<T> {
134    fn into(self) -> Result<ApiResult<T>, E> {
135        Ok(self)
136    }
137}
138
139/// Type alias for standard Result with Box<dyn Error>
140pub type DefaultResult<T> = Result<T, Box<dyn std::error::Error>>;
141
142/// Type alias for Result with anyhow::Error
143pub type AnyhowResult<T> = Result<T, anyhow::Error>;
144
145/// API result without any data payload
146///
147/// Use this for endpoints that only return success/failure status
148/// or endpoints that only use `extra` metadata without structured data.
149///
150/// # Example
151/// ```rust
152/// // Simple success response
153/// ApiResult::<()>::success()
154///
155/// // Error response
156/// ApiResult::<()>::failure("Operation failed")
157/// ```
158pub type EmptyResult = ApiResult<()>;