201 lines
5.0 KiB
Rust
201 lines
5.0 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::borrow::Cow;
|
|
|
|
use app_error::AppError;
|
|
pub use app_error::ErrorCode;
|
|
use std::fmt::{Debug, Display};
|
|
|
|
#[cfg(feature = "cloud")]
|
|
pub use crate::response_actix::*;
|
|
|
|
/// A macro to generate static AppResponse functions with predefined error codes.
|
|
macro_rules! static_app_response {
|
|
($name:ident, $error:expr) => {
|
|
#[allow(non_snake_case, missing_docs)]
|
|
pub fn $name() -> AppResponse<T> {
|
|
AppResponse::new($error.code(), $error.to_string())
|
|
}
|
|
}; // ($name:ident, $code:expr, $msg:expr) => {
|
|
// #[allow(non_snake_case, missing_docs)]
|
|
// pub fn $name(msg: $msg) -> AppResponse<T> {
|
|
// AppResponse::new($code, $code(msg.to_string()).to_string())
|
|
// }
|
|
// };
|
|
}
|
|
|
|
/// Represents a standardized application response.
|
|
///
|
|
/// This structure is used to send consistent responses from the server,
|
|
/// containing optional data, an error code, and a message.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct AppResponse<T> {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub data: Option<T>,
|
|
|
|
#[serde(default)]
|
|
pub code: ErrorCode,
|
|
|
|
#[serde(default)]
|
|
pub message: Cow<'static, str>,
|
|
}
|
|
|
|
impl<T> AppResponse<T> {
|
|
pub fn new<M: Into<Cow<'static, str>>>(code: ErrorCode, message: M) -> Self {
|
|
Self {
|
|
data: None,
|
|
code,
|
|
message: message.into(),
|
|
}
|
|
}
|
|
|
|
static_app_response!(Ok, AppError::Ok);
|
|
|
|
pub fn split(self) -> (Option<T>, AppResponseError) {
|
|
if self.is_ok() {
|
|
(self.data, AppResponseError::new(self.code, self.message))
|
|
} else {
|
|
(None, AppResponseError::new(self.code, self.message))
|
|
}
|
|
}
|
|
|
|
pub fn into_data(self) -> Result<T, AppResponseError> {
|
|
if self.is_ok() {
|
|
match self.data {
|
|
None => Err(AppResponseError::from(AppError::MissingPayload(
|
|
"".to_string(),
|
|
))),
|
|
Some(data) => Ok(data),
|
|
}
|
|
} else {
|
|
Err(AppResponseError::new(self.code, self.message))
|
|
}
|
|
}
|
|
|
|
pub fn into_error(self) -> Result<(), AppResponseError> {
|
|
if matches!(self.code, ErrorCode::Ok) {
|
|
Ok(())
|
|
} else {
|
|
Err(AppResponseError::new(self.code, self.message))
|
|
}
|
|
}
|
|
|
|
pub fn with_data(mut self, data: T) -> Self {
|
|
self.data = Some(data);
|
|
self
|
|
}
|
|
|
|
pub fn with_message(mut self, message: impl Into<Cow<'static, str>>) -> Self {
|
|
self.message = message.into();
|
|
self
|
|
}
|
|
|
|
pub fn with_code(mut self, code: ErrorCode) -> Self {
|
|
self.code = code;
|
|
self
|
|
}
|
|
|
|
pub fn is_ok(&self) -> bool {
|
|
matches!(self.code, ErrorCode::Ok)
|
|
}
|
|
}
|
|
|
|
impl<T> Display for AppResponse<T>
|
|
where
|
|
T: Display,
|
|
{
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_fmt(format_args!("{:?}:{}", self.code, self.message))
|
|
}
|
|
}
|
|
|
|
impl<T> std::error::Error for AppResponse<T> where T: Debug + Display {}
|
|
|
|
/// Provides a conversion from `T1` to `AppResponse<T>`.
|
|
///
|
|
/// This implementation allows for automatic conversion of any type `T1` that can be
|
|
/// transformed into an `AppError` into an `AppResponse<T>`. This is useful for
|
|
/// seamlessly converting various error types into a standardized application response.
|
|
///
|
|
impl<T, T1> From<T1> for AppResponse<T>
|
|
where
|
|
T1: Into<AppResponseError>,
|
|
{
|
|
fn from(value: T1) -> Self {
|
|
let err: AppResponseError = value.into();
|
|
AppResponse::new(err.code, err.message)
|
|
}
|
|
}
|
|
|
|
impl<T> AppResponse<T>
|
|
where
|
|
T: serde::de::DeserializeOwned + 'static,
|
|
{
|
|
pub async fn from_response(resp: reqwest::Response) -> Result<Self, anyhow::Error> {
|
|
let status_code = resp.status();
|
|
if !status_code.is_success() {
|
|
let body = resp.text().await?;
|
|
anyhow::bail!("got error code: {}, body: {}", status_code, body)
|
|
}
|
|
|
|
let bytes = resp.bytes().await?;
|
|
let resp = serde_json::from_slice(&bytes)?;
|
|
Ok(resp)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
|
pub struct AppResponseError {
|
|
pub code: ErrorCode,
|
|
pub message: Cow<'static, str>,
|
|
}
|
|
|
|
impl AppResponseError {
|
|
pub fn new(code: ErrorCode, message: impl Into<Cow<'static, str>>) -> Self {
|
|
Self {
|
|
code,
|
|
message: message.into(),
|
|
}
|
|
}
|
|
|
|
pub fn with_message(&self, message: impl Into<Cow<'static, str>>) -> Self {
|
|
Self {
|
|
code: self.code,
|
|
message: message.into(),
|
|
}
|
|
}
|
|
|
|
pub fn is_record_not_found(&self) -> bool {
|
|
matches!(self.code, ErrorCode::RecordNotFound)
|
|
}
|
|
}
|
|
|
|
impl<T> From<T> for AppResponseError
|
|
where
|
|
AppError: std::convert::From<T>,
|
|
{
|
|
fn from(value: T) -> Self {
|
|
let err = AppError::from(value);
|
|
Self {
|
|
code: err.code(),
|
|
message: Cow::Owned(err.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for AppResponseError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_fmt(format_args!("code:{:?} msg: {}", self.code, self.message))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "cloud")]
|
|
impl actix_web::error::ResponseError for AppResponseError {
|
|
fn status_code(&self) -> actix_web::http::StatusCode {
|
|
actix_web::http::StatusCode::OK
|
|
}
|
|
|
|
fn error_response(&self) -> actix_web::HttpResponse {
|
|
actix_web::HttpResponse::Ok().json(self)
|
|
}
|
|
}
|