1use std::fmt::{self, Display};
9
10use actix_web::{
11 http::{
12 header::{self, ContentDisposition, DispositionParam, DispositionType},
13 StatusCode,
14 },
15 HttpResponse, HttpResponseBuilder,
16};
17use futures::{future, stream::once};
18
19pub type RspResult<T> = Result<T, ResponseError>;
21
22#[derive(Debug)]
27pub struct ResponseError(anyhow::Error);
28
29impl Display for ResponseError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.write_str(&self.0.to_string())
32 }
33}
34
35impl actix_web::ResponseError for ResponseError {
36 fn status_code(&self) -> StatusCode {
37 StatusCode::INTERNAL_SERVER_ERROR
38 }
39
40 fn error_response(&self) -> HttpResponse {
41 HttpResponse::build(self.status_code()).finish()
42 }
43}
44
45impl<T> From<T> for ResponseError
46where
47 T: Into<anyhow::Error>,
48{
49 fn from(t: T) -> Self {
50 Self(t.into())
51 }
52}
53
54pub trait ResponseCodeTrait {
59 fn code(&self) -> i64;
61 fn message(&self) -> &'static str;
63}
64
65pub type ResponseBuilderFn = Box<dyn Fn(&mut HttpResponseBuilder)>;
66
67pub struct Response<T> {
75 pub http_code: StatusCode,
76 pub code: i64,
77 pub message: String,
78 pub data: Option<T>,
79 pub builder: Vec<ResponseBuilderFn>,
80 #[cfg(feature = "i18n")]
81 pub translate: bool,
82}
83
84impl<T> Response<T> {
85 pub fn new<C>(r: C) -> Self
88 where
89 C: ResponseCodeTrait,
90 {
91 Self {
92 http_code: StatusCode::OK,
93 code: r.code(),
94 message: r.message().to_owned(),
95 data: None,
96 builder: Vec::new(),
97 #[cfg(feature = "i18n")]
98 translate: true,
99 }
100 }
101
102 pub fn new_code(code: StatusCode) -> Self {
105 Self {
106 http_code: code,
107 code: 0,
108 message: String::new(),
109 data: None,
110 builder: Vec::new(),
111 #[cfg(feature = "i18n")]
112 translate: false,
113 }
114 }
115
116 pub fn created<C, S>(r: C, location: S) -> Self
118 where
119 C: ResponseCodeTrait,
120 S: Into<String>,
121 {
122 let mut ret = Self::new(r);
123 ret.http_code = StatusCode::CREATED;
124 let location: String = location.into();
125 ret.builder(move |r| {
126 r.insert_header((header::LOCATION, location.clone()));
127 })
128 }
129
130 pub fn no_content() -> Self {
132 Self::new_code(StatusCode::NO_CONTENT)
133 }
134
135 pub fn bad_request<S: Into<String>>(s: S) -> Self {
137 Self::new_code(StatusCode::BAD_REQUEST).message(s)
138 }
139
140 pub fn forbidden() -> Self {
142 Self::new_code(StatusCode::FORBIDDEN)
143 }
144
145 pub fn not_found() -> Self {
147 Self::new_code(StatusCode::NOT_FOUND)
148 }
149
150 pub fn redirect<S: Into<String>>(code: StatusCode, s: S) -> Self {
153 let s: String = s.into();
154 Self::new_code(code).builder(move |r| {
155 r.insert_header((header::LOCATION, s.clone()));
156 })
157 }
158
159 pub fn builder<F>(mut self, f: F) -> Self
162 where
163 F: Fn(&mut HttpResponseBuilder) + 'static,
164 {
165 self.builder.push(Box::new(f));
166 self
167 }
168
169 pub fn message<S: Into<String>>(mut self, s: S) -> Self {
172 self.message = s.into();
173 self
174 }
175
176 pub fn data(mut self, data: T) -> Self {
178 self.data = Some(data);
179 self
180 }
181
182 pub fn file(name: String, data: Vec<u8>) -> HttpResponse {
185 let body = once(future::ok::<_, actix_web::Error>(data.into()));
186 let header = ContentDisposition {
187 disposition: DispositionType::Attachment,
188 parameters: vec![DispositionParam::Filename(name)],
189 };
190 HttpResponse::Ok()
191 .insert_header(("Content-Disposition", header))
192 .content_type("application/octet-stream")
193 .streaming(body)
194 }
195
196 #[cfg(feature = "i18n")]
197 pub fn translate(mut self) -> Self {
201 self.translate = true;
202 self
203 }
204
205 #[cfg(feature = "i18n")]
206 pub fn i18n_message(&self, req: &actix_web::HttpRequest) -> String {
212 use actix_web::HttpMessage as _;
213
214 if self.translate {
215 req.app_data::<actix_web::web::Data<crate::state::GlobalState>>()
216 .map_or_else(
217 || self.message.clone(),
218 |state| {
219 if let Some(ext) = req
220 .extensions()
221 .get::<std::sync::Arc<crate::request::Extension>>()
222 {
223 crate::t!(state.locale, &self.message, &ext.lang)
224 } else {
225 self.message.clone()
226 }
227 },
228 )
229 } else {
230 self.message.clone()
231 }
232 }
233}
234
235#[cfg(feature = "response-json")]
236pub type JsonResponse = Response<serde_json::Value>;
237
238#[cfg(feature = "response-json")]
239impl JsonResponse {
240 pub fn json<T: serde::Serialize>(mut self, data: T) -> Self {
242 self.data = Some(serde_json::json!(data));
243 self
244 }
245}
246
247#[cfg(feature = "response-json")]
248impl actix_web::Responder for JsonResponse {
249 type Body = actix_web::body::EitherBody<String>;
250
251 fn respond_to(
252 self,
253 #[allow(unused_variables)] req: &actix_web::HttpRequest,
254 ) -> HttpResponse<Self::Body> {
255 if self.http_code.is_success() {
256 #[cfg(feature = "i18n")]
257 let message = self.i18n_message(req);
258 #[cfg(not(feature = "i18n"))]
259 let message = self.message;
260 let mut body = serde_json::json!({
261 "code": self.code,
262 "message": message,
263 });
264 if let Some(data) = self.data {
265 body.as_object_mut()
266 .unwrap()
267 .insert(String::from("data"), data);
268 }
269 let body = body.to_string();
270 let mut rsp = HttpResponse::build(self.http_code);
271 if self.http_code != StatusCode::NO_CONTENT {
272 rsp.content_type(actix_web::http::header::ContentType::json());
273 }
274 for builder in self.builder {
275 builder(&mut rsp);
276 }
277 if self.http_code == StatusCode::NO_CONTENT {
278 rsp.finish().map_into_right_body()
279 } else {
280 rsp.message_body(body).unwrap().map_into_left_body()
281 }
282 } else {
283 let mut rsp = HttpResponse::build(self.http_code);
284 for builder in self.builder {
285 builder(&mut rsp);
286 }
287 if self.message.is_empty() {
288 rsp.finish().map_into_right_body()
289 } else {
290 rsp.message_body(self.message).unwrap().map_into_left_body()
291 }
292 }
293 }
294}