Skip to content
Clone
main
164 lines 4.6 KB Rust
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<T> {
pub code: i32,
pub message: String,
pub data: Option<T>,
pub error: Option<String>,
pub error_code: Option<String>,
#[serde(default)]
pub request_id: String,
#[serde(skip)]
#[schema(ignore)]
retry_after_secs: Option<u64>,
}
impl<T: Serialize> Serialize for Envelope<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<T: Serialize> Envelope<T> {
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<String>) -> 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<String>) -> Self {
self.error = Some(error.into());
self
}
pub fn with_error_code(mut self, error_code: impl Into<String>) -> Self {
self.error_code = Some(error_code.into());
self
}
pub fn with_request_id(mut self, request_id: impl Into<String>) -> 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<NoData> {
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<T: Serialize>(envelope: &Envelope<T>) -> 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<T: Serialize> Envelope<T> {
pub fn into_http_response(self) -> HttpResponse<BoxBody> {
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<T: Serialize> Responder for Envelope<T> {
type Body = BoxBody;
fn respond_to(self, _request: &HttpRequest) -> HttpResponse<Self::Body> {
self.into_http_response()
}
}