use actix_web::body::BoxBody; use actix_web::http::StatusCode; use actix_web::{HttpRequest, HttpResponse, Responder}; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize, Serializer}; use utoipa::ToSchema; use crate::context; pub const OK_CODE: i32 = 200; pub const OK_MESSAGE: &str = "success"; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize, ToSchema)] pub struct NoData; #[derive(Debug, Clone, PartialEq, Eq, Deserialize, ToSchema)] pub struct Envelope { pub code: i32, pub message: String, pub data: Option, pub error: Option, pub error_code: Option, #[serde(default)] pub request_id: String, #[serde(skip)] #[schema(ignore)] retry_after_secs: Option, } impl Serialize for Envelope { fn serialize(&self, serializer: S) -> Result { let request_id = resolve(&self.request_id, context::request_id().as_deref()); let mut state = serializer.serialize_struct("Envelope", 6)?; state.serialize_field("code", &self.code)?; state.serialize_field("message", &self.message)?; state.serialize_field("data", &self.data)?; state.serialize_field("error", &self.error)?; state.serialize_field("error_code", &self.error_code)?; state.serialize_field("request_id", &request_id)?; state.end() } } fn resolve(explicit: &str, injected: Option<&str>) -> String { if explicit.is_empty() { injected.unwrap_or_default().to_owned() } else { explicit.to_owned() } } impl Envelope { pub fn ok(data: T) -> Self { Self { code: OK_CODE, message: OK_MESSAGE.to_owned(), data: Some(data), error: None, error_code: None, request_id: String::new(), retry_after_secs: None, } } pub fn err(code: i32, message: impl Into) -> Self { Self { code, message: message.into(), data: None, error: None, error_code: None, request_id: String::new(), retry_after_secs: None, } } pub fn with_error(mut self, error: impl Into) -> Self { self.error = Some(error.into()); self } pub fn with_error_code(mut self, error_code: impl Into) -> Self { self.error_code = Some(error_code.into()); self } pub fn with_request_id(mut self, request_id: impl Into) -> Self { self.request_id = request_id.into(); self } pub fn with_retry_after(mut self, seconds: u64) -> Self { self.retry_after_secs = Some(seconds); self } pub fn status(&self) -> StatusCode { status_of(self.code) } pub fn into_body(&self) -> BoxBody { body_of(self) } } impl Envelope { pub fn empty() -> Self { Self { code: OK_CODE, message: OK_MESSAGE.to_owned(), data: None, error: None, error_code: None, request_id: String::new(), retry_after_secs: None, } } } pub fn status_of(code: i32) -> StatusCode { u16::try_from(code) .ok() .and_then(|code| StatusCode::from_u16(code).ok()) .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) } fn body_of(envelope: &Envelope) -> BoxBody { let bytes = serde_json::to_vec(envelope).unwrap_or_else(|error| { tracing::error!(%error, "failed to serialize the response envelope"); Vec::from(SERIALIZE_FALLBACK) }); BoxBody::new(bytes) } const SERIALIZE_FALLBACK: &[u8] = br#"{"code":500,"message":"internal server error","data":null,"error":null,"error_code":null,"request_id":""}"#; impl Envelope { pub fn into_http_response(self) -> HttpResponse { let status = status_of(self.code); let mut response = HttpResponse::build(status); if let Some(seconds) = self.retry_after_secs { response.insert_header((actix_web::http::header::RETRY_AFTER, seconds.to_string())); } response .content_type("application/json") .body(body_of(&self)) } } impl std::fmt::Display for Envelope<()> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "API error: code={}, message={}", self.code, self.message) } } impl Responder for Envelope { type Body = BoxBody; fn respond_to(self, _request: &HttpRequest) -> HttpResponse { self.into_http_response() } }