api_response/
lib.rs

1//! # API Response Library
2//!
3//! This library provides a consistent structure for API responses, including
4//! success and error handling, with support for various serialization formats
5//! like JSON and Protobuf.
6//!
7//! ## Modules
8//!
9//! * `meta`: Contains the meta structures.
10//! * `success`: Contains the success response structures.
11//! * `error`: Contains the error handling structures.
12
13// -------- rust coding guidelines: https://rust-coding-guidelines.github.io/rust-coding-guidelines-zh/ --------
14// -------- rustc lint doc: https://doc.rust-lang.org/rustc/lints/listing/index.html --------
15// -------- rust-clippy doc: https://rust-lang.github.io/rust-clippy/master/index.html --------
16
17// [REQUIRED] G.VAR.02 Do not use non-ASCII characters in identifiers
18#![deny(non_ascii_idents)]
19// [REQUIRED]
20#![allow(clippy::disallowed_names)]
21// [REQUIRED]
22#![allow(clippy::blanket_clippy_restriction_lints)]
23// [REQUIRED] G.CMT.02 Add Panic documentation in the docs of public APIs that may panic under certain circumstances
24#![warn(clippy::missing_panics_doc)]
25// [RECOMMENDED] G.VAR.03 Variable shadowing should be used carefully
26#![warn(clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated)]
27// [RECOMMENDED] G.CNS.05 Use const fn for functions or methods wherever applicable
28#![warn(clippy::missing_const_for_fn)]
29// [REQUIRED] G.TYP.01 Prefer safe conversion functions over `as` for type casting
30#![warn(
31    clippy::cast_lossless,
32    clippy::cast_possible_truncation,
33    clippy::cast_possible_wrap,
34    clippy::ptr_as_ptr
35)]
36// [RECOMMENDED] G.VAR.01 Avoid using too many meaningless variable names when destructuring tuples with more than four
37// variables
38#![warn(clippy::many_single_char_names)]
39// [RECOMMENDED] G.TYP.02 Explicitly specify the type for numeric literals
40#![warn(clippy::default_numeric_fallback)]
41// [RECOMMENDED] G.TYP.03 Use `try_from` methods instead of relying on numeric boundaries for safe conversion
42#![warn(clippy::checked_conversions)]
43// [RECOMMENDED] G.TYP.BOL.02 Use `if` expressions instead of `match` for boolean conditions
44#![warn(clippy::match_bool)]
45// [RECOMMENDED] G.TYP.BOL.05 Use logical operators (&&/||) instead of bitwise operators (&/|) for boolean operations
46// when not necessary
47#![warn(clippy::needless_bitwise_bool)]
48// [REQUIRED] G.TYP.INT.02 Avoid `as` casting between signed and unsigned integers; use safe conversion functions
49#![deny(clippy::cast_sign_loss)]
50// [REQUIRED] G.TYP.INT.03 Avoid using `%` for modulo operations on negative numbers
51#![warn(clippy::modulo_arithmetic)]
52// [REQUIRED] G.TYP.FLT.02 Avoid precision loss when casting from any numeric type to floating-point; use safe
53// conversion functions
54#![warn(clippy::cast_precision_loss)]
55// [REQUIRED] G.TYP.FLT.03 Be cautious of precision loss in floating-point arithmetic and comparisons
56#![warn(clippy::float_arithmetic, clippy::float_cmp, clippy::float_cmp_const)]
57// [REQUIRED] G.TYP.FLT.04 Use Rust's built-in methods for floating-point calculations
58#![warn(clippy::imprecise_flops, clippy::suboptimal_flops)]
59// [OPTIONAL] G.TYP.ARR.01 Use static variables instead of constants for large global arrays
60#![warn(clippy::large_stack_arrays)]
61// [RECOMMENDED] G.TYP.SCT.01 Add `#[non_exhaustive]` attribute to publicly exported structs
62#![warn(clippy::exhaustive_structs)]
63// [RECOMMENDED] G.TYP.ENM.05 Add `#[non_exhaustive]` attribute to publicly exported enums
64#![warn(clippy::exhaustive_enums)]
65// [RECOMMENDED] G.TYP.SCT.02 Consider refactoring when a struct contains more than three boolean fields
66#![warn(clippy::struct_excessive_bools)]
67// [RECOMMENDED] G.FUD.03 Consider using a custom struct or enum instead of many boolean parameters in function
68// signatures
69#![warn(clippy::fn_params_excessive_bools)]
70// [RECOMMENDED] G.TYP.ENM.04 Avoid using glob imports for enum variants in `use` statements
71#![warn(clippy::enum_glob_use)]
72// [RECOMMENDED] G.CTF.02 Ensure `else` branches are present whenever `else if` is used
73#![warn(clippy::else_if_without_else)]
74// [RECOMMENDED] G.STR.02 Use `push_str` method for appending strings
75#![warn(clippy::string_add_assign, clippy::string_add)]
76// [RECOMMENDED] G.STR.03 Convert string literals containing only ASCII characters to byte sequences using `b"str"`
77// syntax instead of `as_bytes()`
78#![warn(clippy::string_lit_as_bytes)]
79// [RECOMMENDED] G.STR.05 Take care to avoid disrupting UTF-8 encoding when slicing strings at specific positions
80#![warn(clippy::string_slice)]
81// [RECOMMENDED] G.FUD.02 Prefer passing large values by reference if function parameters implement `Copy`
82#![warn(clippy::large_types_passed_by_value)]
83// [RECOMMENDED] G.FUD.04 Pass small `Copy` type values by value instead of by reference
84#![warn(clippy::trivially_copy_pass_by_ref)]
85// [REQUIRED] G.GEN.02 Be cautious to avoid using generic default implementations of some methods from Rust's standard
86// library; prefer specific type implementations
87#![warn(clippy::inefficient_to_string)]
88// [RECOMMENDED] G.TRA.BLN.01 Prefer using the concrete type's `default()` method over calling `Default::default()`
89#![warn(clippy::default_trait_access)]
90// [REQUIRED] G.TRA.BLN.02 Do not implement the `Copy` trait for iterators
91#![warn(clippy::copy_iterator)]
92// [RECOMMENDED] G.TRA.BLN.07 Use `copied` method instead of `cloned` for iterable `Copy` types
93#![warn(clippy::cloned_instead_of_copied)]
94// [RECOMMENDED] G.ERR.01 Avoid using `unwrap` indiscriminately when handling `Option<T>` and `Result<T, E>`
95#![warn(clippy::unwrap_used)]
96// [RECOMMENDED] G.MOD.03 Avoid using wildcard imports in module declarations
97#![warn(clippy::wildcard_imports)]
98// [REQUIRED] G.MOD.04 Avoid using different module layout styles within the same project
99#![warn(clippy::self_named_module_files)]
100// [RECOMMENDED] G.CAR.02 Ensure that necessary metadata is included in the `Cargo.toml` of the crate
101#![warn(clippy::cargo_common_metadata)]
102// [RECOMMENDED] G.CAR.03 Avoid negative or redundant prefixes and suffixes in feature names
103#![warn(clippy::negative_feature_names, clippy::redundant_feature_names)]
104// [REQUIRED] G.CAR.04 Avoid using wildcard dependencies in `Cargo.toml`
105#![warn(clippy::wildcard_dependencies)]
106// [RECOMMENDED] G.MAC.01 Only use the `dbg!()` macro for debugging code
107#![warn(clippy::dbg_macro)]
108// [REQUIRED] Ensure that locks are released before `await` is called in asynchronous code
109#![warn(clippy::await_holding_lock)]
110// [REQUIRED] Handle `RefCell` references across `await` points
111#![warn(clippy::await_holding_refcell_ref)]
112// [RECOMMENDED] G.ASY.04 Avoid defining unnecessary async functions
113#![warn(clippy::unused_async)]
114// [REQUIRED] G.UNS.SAS.02 Use `assert!` instead of `debug_assert!` to verify boundary conditions in unsafe functions
115#![warn(clippy::debug_assert_with_mut_call)]
116#![cfg_attr(feature = "try", feature(try_trait_v2))]
117
118#[cfg(feature = "try")]
119mod try_trait;
120
121#[cfg(feature = "salvo")]
122mod salvo_trait;
123
124mod error;
125pub mod error_code;
126#[cfg(feature = "lite")]
127pub(crate) mod lite;
128mod meta;
129mod result;
130mod success;
131mod utils;
132
133use std::{error::Error, fmt::Debug};
134
135pub use prelude::*;
136pub mod prelude {
137    pub use serde::{Deserialize, Serialize, de::DeserializeOwned};
138
139    pub use crate::{
140        ApiResponse, api_err,
141        error::{ApiError, ErrorResponse},
142        error_code,
143        error_code::ety_grpc,
144        meta::{Cost, DefaultMeta, Pagination, RateLimit, UserMeta},
145        result::ApiResult,
146        success::{ApiSuccessResponse, SuccessResponse},
147        utils::{ErrWrapper, IntoError, MaybeString},
148    };
149}
150
151/// Enum to represent the overall API response
152#[cfg_attr(not(feature = "lite"), derive(Serialize, Deserialize))]
153#[cfg_attr(not(feature = "lite"), serde(tag = "status", rename_all = "lowercase"))]
154#[derive(Debug)]
155#[allow(clippy::exhaustive_enums)]
156pub enum ApiResponse<Data, Meta> {
157    Success(SuccessResponse<Data, Meta>),
158    Error(ErrorResponse<Meta>),
159}
160
161impl<Data, Meta> From<SuccessResponse<Data, Meta>> for ApiResponse<Data, Meta> {
162    fn from(value: SuccessResponse<Data, Meta>) -> Self {
163        Self::Success(value)
164    }
165}
166
167impl<Data, Meta> From<ErrorResponse<Meta>> for ApiResponse<Data, Meta> {
168    fn from(value: ErrorResponse<Meta>) -> Self {
169        Self::Error(value)
170    }
171}
172
173impl<Data, Meta> From<ApiError> for ApiResponse<Data, Meta> {
174    fn from(error: ApiError) -> Self {
175        ApiResponse::Error(ErrorResponse::from_error(error))
176    }
177}
178
179impl<Data, Meta> ApiResponse<Data, Meta> {
180    #[inline(always)]
181    pub const fn new_success(data: Data, meta: Meta) -> Self {
182        Self::Success(SuccessResponse::new(data, meta))
183    }
184    #[inline(always)]
185    pub const fn from_success(data: Data) -> Self {
186        Self::Success(SuccessResponse::from_data(data))
187    }
188    #[inline(always)]
189    pub const fn from_success_data(data: Data) -> Self {
190        Self::Success(SuccessResponse::from_data(data))
191    }
192    #[inline(always)]
193    pub const fn new_error(error: ApiError, meta: Meta) -> Self {
194        Self::Error(ErrorResponse::new(error, meta))
195    }
196    #[inline(always)]
197    pub const fn from_error(error: ApiError) -> Self {
198        Self::Error(ErrorResponse::from_error(error))
199    }
200    #[inline(always)]
201    pub fn from_error_msg(code: impl Into<u32>, message: impl Into<String>) -> Self {
202        Self::Error(ErrorResponse::from_error_msg(code, message))
203    }
204    #[inline(always)]
205    pub fn from_error_source(
206        code: impl Into<u32>,
207        source: impl Error + Send + Sync + 'static,
208        set_source_detail: bool,
209        message: Option<String>,
210    ) -> Self {
211        Self::Error(ErrorResponse::from_error_source(
212            code,
213            source,
214            set_source_detail,
215            message,
216        ))
217    }
218    #[inline(always)]
219    pub fn with_meta(mut self, meta: Meta) -> Self {
220        self.set_meta(meta);
221        self
222    }
223    #[inline(always)]
224    pub fn set_meta(&mut self, meta: Meta) -> &mut Self {
225        match self {
226            ApiResponse::Success(success_response) => {
227                success_response.set_meta(meta);
228            }
229            ApiResponse::Error(error_response) => {
230                error_response.set_meta(meta);
231            }
232        }
233        self
234    }
235    pub const fn is_success(&self) -> bool {
236        matches!(self, Self::Success(_))
237    }
238    pub const fn is_error(&self) -> bool {
239        matches!(self, Self::Error(_))
240    }
241    pub const fn get_meta(&self) -> Option<&Meta> {
242        match self {
243            ApiResponse::Success(success_response) => success_response.meta.as_ref(),
244            ApiResponse::Error(error_response) => error_response.meta.as_ref(),
245        }
246    }
247    /// # Panics
248    /// If it is `ApiResponse::Error`, trigger a panic using the `expect_msg`
249    /// parameter.
250    pub fn expect(self, expect_msg: &str) -> SuccessResponse<Data, Meta> {
251        match self {
252            ApiResponse::Success(success_response) => success_response,
253            ApiResponse::Error(_) => {
254                panic!("{expect_msg}")
255            }
256        }
257    }
258    /// # Panics
259    /// If it is `ApiResponse::Error`, trigger a panic.
260    pub fn unwrap(self) -> SuccessResponse<Data, Meta> {
261        match self {
262            ApiResponse::Success(success_response) => success_response,
263            ApiResponse::Error(_) => {
264                panic!("called `ApiResponse::unwrap()` on an `ApiResponse::Error` value")
265            }
266        }
267    }
268    /// # Panics
269    /// If it is `ApiResponse::Success`, trigger a panic.
270    pub fn unwrap_err(self) -> ErrorResponse<Meta> {
271        match self {
272            ApiResponse::Success(_) => {
273                panic!("called `ApiResponse::unwrap_err()` on an `ApiResponse::Success` value")
274            }
275            ApiResponse::Error(error_response) => error_response,
276        }
277    }
278    pub fn into_result(self) -> ApiResult<Data, Meta> {
279        self.into()
280    }
281    pub fn into_result_data(self) -> Result<Data, ErrorResponse<Meta>> {
282        match self {
283            ApiResponse::Success(success_response) => Ok(success_response.data),
284            ApiResponse::Error(error_response) => Err(error_response),
285        }
286    }
287    pub fn into_result_without_meta(self) -> Result<Data, ApiError> {
288        match self {
289            ApiResponse::Success(success_response) => Ok(success_response.data),
290            ApiResponse::Error(error_response) => Err(error_response.error),
291        }
292    }
293}