feat(lib/export): add rest module with utoipa routes for auth views
ZhenYi committed
Modified lib/export/src/email.rs lib/export/src/email.rs +3 −3 View lib/export/src/email.rs
@@ -91,11 +91,11 @@
9191 }
9292
9393 pub async fn send(&self, email: &Email) -> Result<SendResponse, ExportError> {
94 Ok(self.client()?.send(email).await?)
94 self.client()?.send(email).await
9595 }
9696
9797 pub async fn send_queued(&self, email: &Email) -> Result<SendResponse, ExportError> {
98 Ok(self.client()?.send_queued(email).await?)
98 self.client()?.send_queued(email).await
9999 }
100100
101101 pub async fn send_batch(
@@ -103,7 +103,7 @@
103103 emails: &[Email],
104104 queue: bool,
105105 ) -> Result<BatchResponse, ExportError> {
106 Ok(self.client()?.send_batch(emails, queue).await?)
106 self.client()?.send_batch(emails, queue).await
107107 }
108108
109109 pub async fn ping(&self) -> Result<String, ExportError> {
Modified lib/export/src/lib.rs lib/export/src/lib.rs +1 −0 View lib/export/src/lib.rs
@@ -3,6 +3,7 @@
33pub mod redis;
44pub mod storage;
55pub mod email;
6pub mod rest;
67
78use std::sync::Arc;
89use config::AppConfig;
Added lib/export/src/rest.rs lib/export/src/rest.rs +198 −0 View lib/export/src/rest.rs
@@ -0,0 +1,198 @@
1use axum::Json;
2use axum::extract::{FromRequest, Request};
3use axum::http::StatusCode;
4use axum::response::{IntoResponse, Response};
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::ops::{Deref, DerefMut};
9use utoipa::ToSchema;
10
11#[derive(Debug, Serialize, Deserialize, ToSchema)]
12pub struct NoData;
13
14#[derive(Serialize, Deserialize, Debug, ToSchema)]
15pub struct AppResponseData<T> {
16 pub code: i32,
17 pub message: String,
18 pub data: Option<T>,
19 pub error: Option<String>,
20 pub duration: String,
21 pub request_id: String,
22 pub at: String,
23 pub log_id: String,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
27pub struct AppRequestData<T> {
28 pub request_id: String,
29 pub at: String,
30 pub data: Option<T>,
31}
32
33impl<T> AppResponseData<T>
34where
35 T: Serialize,
36{
37 pub fn ok(data: T) -> Self {
38 AppResponseData {
39 code: 200,
40 message: "success".to_string(),
41 data: Some(data),
42 error: None,
43 duration: String::new(),
44 request_id: String::new(),
45 at: String::new(),
46 log_id: String::new(),
47 }
48 }
49
50 pub fn err(code: i32, message: impl Into<String>) -> Self {
51 AppResponseData {
52 code,
53 message: message.into(),
54 data: None,
55 error: None,
56 duration: String::new(),
57 request_id: String::new(),
58 at: String::new(),
59 log_id: String::new(),
60 }
61 }
62
63 pub fn err_with_detail(
64 code: i32,
65 message: impl Into<String>,
66 detail: impl Into<String>,
67 ) -> Self {
68 AppResponseData {
69 code,
70 message: message.into(),
71 data: None,
72 error: Some(detail.into()),
73 duration: String::new(),
74 request_id: String::new(),
75 at: String::new(),
76 log_id: String::new(),
77 }
78 }
79
80 pub fn with_code(mut self, code: i32) -> Self {
81 self.code = code;
82 self
83 }
84
85 pub fn with_message(mut self, message: impl Into<String>) -> Self {
86 self.message = message.into();
87 self
88 }
89
90 pub fn with_error(mut self, error: impl Into<String>) -> Self {
91 self.error = Some(error.into());
92 self
93 }
94
95 pub fn with_duration(mut self, duration: impl Into<String>) -> Self {
96 self.duration = duration.into();
97 self
98 }
99
100 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
101 self.request_id = request_id.into();
102 self
103 }
104
105 pub fn with_at(mut self, at: impl Into<String>) -> Self {
106 self.at = at.into();
107 self
108 }
109
110 pub fn with_log_id(mut self, log_id: impl Into<String>) -> Self {
111 self.log_id = log_id.into();
112 self
113 }
114}
115
116impl AppResponseData<NoData> {
117 pub fn empty() -> Self {
118 AppResponseData {
119 code: 200,
120 message: "success".to_string(),
121 data: None,
122 error: None,
123 duration: String::new(),
124 request_id: String::new(),
125 at: String::new(),
126 log_id: String::new(),
127 }
128 }
129
130 pub fn err_plain(code: i32, message: impl Into<String>) -> Self {
131 let msg = message.into();
132 AppResponseData {
133 code,
134 message: msg.clone(),
135 data: None,
136 error: Some(msg),
137 duration: String::new(),
138 request_id: String::new(),
139 at: String::new(),
140 log_id: String::new(),
141 }
142 }
143}
144
145impl fmt::Display for AppResponseData<()> {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "API error: code={}, message={}", self.code, self.message)
148 }
149}
150
151impl<T: Serialize> IntoResponse for AppResponseData<T> {
152 fn into_response(self) -> Response {
153 let status =
154 StatusCode::from_u16(self.code as u16).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
155 (status, Json(self)).into_response()
156 }
157}
158
159#[derive(Debug)]
160pub struct AppRequest<T>(pub T);
161
162impl<T> AppRequest<T> {
163 pub fn into_inner(self) -> T {
164 self.0
165 }
166}
167
168impl<T> Deref for AppRequest<T> {
169 type Target = T;
170
171 fn deref(&self) -> &Self::Target {
172 &self.0
173 }
174}
175
176impl<T> DerefMut for AppRequest<T> {
177 fn deref_mut(&mut self) -> &mut Self::Target {
178 &mut self.0
179 }
180}
181
182impl<T, S> FromRequest<S> for AppRequest<T>
183where
184 T: DeserializeOwned + 'static,
185 S: Send + Sync,
186{
187 type Rejection = AppResponseData<()>;
188 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
189 let Json(payload) = Json::<AppRequestData<T>>::from_request(req, state)
190 .await
191 .map_err(|e| AppResponseData::<()>::err(400, format!("invalid request body: {}", e)))?;
192
193 match payload.data {
194 Some(data) => Ok(AppRequest(data)),
195 None => Err(AppResponseData::<()>::err(400, "missing request data")),
196 }
197 }
198}
Modified lib/export/Cargo.toml lib/export/Cargo.toml +3 −0 View lib/export/Cargo.toml
@@ -10,3 +10,6 @@
1010redis = { workspace = true }
1111tokio = { workspace = true }
1212mailbaby = { git = "https://github.com/mailbabys/mailbaby-rs", rev = "e408acf48a19e4fd554b020a865d144706f7c2de", features = ["rest", "grpc"] }
13axum = "0.8.9"
14serde = { version = "1.0.229", features = ["derive"] }
15utoipa = "5.5.0"