xenon_lib/wire/
response.rs

1use rocket::http::Status;
2use rocket::response::{content, Responder};
3use rocket::{response, Request};
4use serde::{Deserialize, Serialize};
5use std::borrow::Cow;
6
7#[derive(Debug, Clone, Serialize)]
8pub struct ApiSuccessResponse<T: Serialize> {
9    success: bool,
10    data: T,
11}
12
13impl<T: Serialize> ApiSuccessResponse<T> {
14    pub fn new(data: T) -> Self {
15        Self {
16            success: true,
17            data,
18        }
19    }
20}
21
22impl<'r, T: Serialize> Responder<'r, 'static> for ApiSuccessResponse<T> {
23    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
24        let string = serde_json::to_string(&self).map_err(|e| {
25            error!("JSON failed to serialize: {:?}", e);
26            Status::InternalServerError
27        })?;
28
29        let mut resp = content::Json(string).respond_to(req)?;
30        resp.set_status(Status::Ok);
31        Ok(resp)
32    }
33}
34
35impl<T: Serialize> From<T> for ApiSuccessResponse<T> {
36    fn from(d: T) -> Self {
37        Self::new(d)
38    }
39}
40
41#[derive(Debug, Clone, Serialize)]
42pub struct ApiErrorResponse {
43    success: bool,
44    #[serde(flatten)]
45    error: ApiError,
46}
47
48impl ApiErrorResponse {
49    pub fn new(error: ApiError) -> Self {
50        Self {
51            success: false,
52            error,
53        }
54    }
55}
56
57impl<'r> Responder<'r, 'static> for ApiErrorResponse {
58    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
59        let string = serde_json::to_string(&self).map_err(|e| {
60            error!("JSON failed to serialize: {:?}", e);
61            Status::InternalServerError
62        })?;
63
64        let mut resp = content::Json(string).respond_to(req)?;
65        resp.set_status(self.error.into());
66        Ok(resp)
67    }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(tag = "code", rename_all = "snake_case")]
72pub enum ApiError {
73    NotFound {
74        entity: Cow<'static, str>,
75    },
76    MissingUserInfo,
77    InvalidSnowflake,
78    DatabaseFailure,
79    RateLimit {
80        burst_limit: u16,
81        reset_after: u64,
82        retry_after: f64,
83    },
84    Unexpected {
85        message: Cow<'static, str>,
86        details: Option<Cow<'static, str>>,
87    },
88    LimitReached {
89        entity: Cow<'static, str>,
90    },
91    InsufficientTier,
92    InvalidDataField {
93        field: Cow<'static, str>,
94        message: Option<Cow<'static, str>>,
95    },
96}
97
98impl From<ApiError> for ApiErrorResponse {
99    fn from(e: ApiError) -> Self {
100        Self::new(e)
101    }
102}
103
104impl From<ApiError> for Status {
105    fn from(e: ApiError) -> Self {
106        use ApiError::*;
107
108        match e {
109            NotFound { .. } => Status::NotFound,
110            MissingUserInfo => Status::Unauthorized,
111            InvalidSnowflake => Status::BadRequest,
112            DatabaseFailure => Status::InternalServerError,
113            RateLimit { .. } => Status::TooManyRequests,
114            Unexpected { .. } => Status::InternalServerError,
115            LimitReached { .. } => Status::BadRequest,
116            InsufficientTier => Status::BadRequest,
117            InvalidDataField { .. } => Status::BadRequest,
118        }
119    }
120}
121
122impl From<mongodb::error::Error> for ApiErrorResponse {
123    fn from(e: mongodb::error::Error) -> Self {
124        error!("Database error: {:?}", *e.kind);
125        ApiError::DatabaseFailure.into()
126    }
127}
128
129pub type ApiResponse<T> = Result<ApiSuccessResponse<T>, ApiErrorResponse>;