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 { 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 { // 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 { #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, #[serde(default)] pub code: ErrorCode, #[serde(default)] pub message: Cow<'static, str>, } impl AppResponse { pub fn new>>(code: ErrorCode, message: M) -> Self { Self { data: None, code, message: message.into(), } } static_app_response!(Ok, AppError::Ok); pub fn split(self) -> (Option, 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 { 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>) -> 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 Display for AppResponse where T: Display, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{:?}:{}", self.code, self.message)) } } impl std::error::Error for AppResponse where T: Debug + Display {} /// Provides a conversion from `T1` to `AppResponse`. /// /// This implementation allows for automatic conversion of any type `T1` that can be /// transformed into an `AppError` into an `AppResponse`. This is useful for /// seamlessly converting various error types into a standardized application response. /// impl From for AppResponse where T1: Into, { fn from(value: T1) -> Self { let err: AppResponseError = value.into(); AppResponse::new(err.code, err.message) } } impl AppResponse where T: serde::de::DeserializeOwned + 'static, { pub async fn from_response(resp: reqwest::Response) -> Result { 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>) -> Self { Self { code, message: message.into(), } } pub fn is_record_not_found(&self) -> bool { matches!(self.code, ErrorCode::RecordNotFound) } } impl From for AppResponseError where AppError: std::convert::From, { 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) } }