xenon_lib/wire/
response.rs1use 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>;