1use std::fmt::{self, Display};
2
3use actix_web::{
4 http::{
5 header::{self, ContentDisposition, DispositionParam, DispositionType},
6 StatusCode,
7 },
8 HttpResponse, HttpResponseBuilder,
9};
10use futures::{future, stream::once};
11
12pub type RspResult<T> = Result<T, ResponseError>;
13
14#[derive(Debug)]
15pub struct ResponseError(anyhow::Error);
16
17impl Display for ResponseError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 f.write_str(&self.0.to_string())
20 }
21}
22
23impl actix_web::ResponseError for ResponseError {
24 fn status_code(&self) -> StatusCode {
25 StatusCode::INTERNAL_SERVER_ERROR
26 }
27
28 fn error_response(&self) -> HttpResponse {
29 HttpResponse::build(self.status_code()).finish()
30 }
31}
32
33impl<T> From<T> for ResponseError
34where
35 T: Into<anyhow::Error>,
36{
37 fn from(t: T) -> Self {
38 Self(t.into())
39 }
40}
41
42pub trait ResponseCodeTrait {
43 fn code(&self) -> i64;
44 fn message(&self) -> &'static str;
45}
46
47pub type ResponseBuilderFn = Box<dyn Fn(&mut HttpResponseBuilder)>;
48
49pub struct Response<T> {
50 pub http_code: StatusCode,
51 pub code: i64,
52 pub message: String,
53 pub data: Option<T>,
54 pub builder: Vec<ResponseBuilderFn>,
55 #[cfg(feature = "i18n")]
56 pub translate: bool,
57}
58
59impl<T> Response<T> {
60 pub fn new<C>(r: C) -> Self
61 where
62 C: ResponseCodeTrait,
63 {
64 Self {
65 http_code: StatusCode::OK,
66 code: r.code(),
67 message: r.message().to_owned(),
68 data: None,
69 builder: Vec::new(),
70 #[cfg(feature = "i18n")]
71 translate: true,
72 }
73 }
74
75 pub fn new_code(code: StatusCode) -> Self {
76 Self {
77 http_code: code,
78 code: 0,
79 message: String::new(),
80 data: None,
81 builder: Vec::new(),
82 #[cfg(feature = "i18n")]
83 translate: false,
84 }
85 }
86
87 pub fn created<C, S>(r: C, location: S) -> Self
88 where
89 C: ResponseCodeTrait,
90 S: Into<String>,
91 {
92 let mut ret = Self::new(r);
93 ret.http_code = StatusCode::CREATED;
94 let location: String = location.into();
95 ret.builder(move |r| {
96 r.insert_header((header::LOCATION, location.clone()));
97 })
98 }
99
100 pub fn no_content() -> Self {
101 Self::new_code(StatusCode::NO_CONTENT)
102 }
103
104 pub fn bad_request<S: Into<String>>(s: S) -> Self {
105 Self::new_code(StatusCode::BAD_REQUEST).message(s)
106 }
107
108 pub fn forbidden() -> Self {
109 Self::new_code(StatusCode::FORBIDDEN)
110 }
111
112 pub fn not_found() -> Self {
113 Self::new_code(StatusCode::NOT_FOUND)
114 }
115
116 pub fn redirect<S: Into<String>>(code: StatusCode, s: S) -> Self {
117 let s: String = s.into();
118 Self::new_code(code).builder(move |r| {
119 r.insert_header((header::LOCATION, s.clone()));
120 })
121 }
122
123 pub fn builder<F>(mut self, f: F) -> Self
124 where
125 F: Fn(&mut HttpResponseBuilder) + 'static,
126 {
127 self.builder.push(Box::new(f));
128 self
129 }
130
131 pub fn message<S: Into<String>>(mut self, s: S) -> Self {
132 self.message = s.into();
133 self
134 }
135
136 pub fn data(mut self, data: T) -> Self {
137 self.data = Some(data);
138 self
139 }
140
141 pub fn file(name: String, data: Vec<u8>) -> HttpResponse {
142 let body = once(future::ok::<_, actix_web::Error>(data.into()));
143 let header = ContentDisposition {
144 disposition: DispositionType::Attachment,
145 parameters: vec![DispositionParam::Filename(name)],
146 };
147 HttpResponse::Ok()
148 .insert_header(("Content-Disposition", header))
149 .content_type("application/octet-stream")
150 .streaming(body)
151 }
152
153 #[cfg(feature = "i18n")]
154 pub fn translate(mut self) -> Self {
155 self.translate = true;
156 self
157 }
158
159 #[cfg(feature = "i18n")]
160 pub fn i18n_message(&self, req: &actix_web::HttpRequest) -> String {
161 use actix_web::HttpMessage as _;
162
163 if self.translate {
164 req.app_data::<actix_web::web::Data<crate::state::GlobalState>>()
165 .map_or_else(
166 || self.message.clone(),
167 |state| {
168 if let Some(ext) = req
169 .extensions()
170 .get::<std::sync::Arc<crate::request::Extension>>()
171 {
172 crate::t!(state.locale, &self.message, &ext.lang)
173 } else {
174 self.message.clone()
175 }
176 },
177 )
178 } else {
179 self.message.clone()
180 }
181 }
182}
183
184#[cfg(feature = "response-json")]
185pub type JsonResponse = Response<serde_json::Value>;
186
187#[cfg(feature = "response-json")]
188impl JsonResponse {
189 pub fn json<T: serde::Serialize>(mut self, data: T) -> Self {
190 self.data = Some(serde_json::json!(data));
191 self
192 }
193}
194
195#[cfg(feature = "response-json")]
196impl actix_web::Responder for JsonResponse {
197 type Body = actix_web::body::EitherBody<String>;
198
199 fn respond_to(
200 self,
201 #[allow(unused_variables)] req: &actix_web::HttpRequest,
202 ) -> HttpResponse<Self::Body> {
203 if self.http_code.is_success() {
204 #[cfg(feature = "i18n")]
205 let message = self.i18n_message(req);
206 #[cfg(not(feature = "i18n"))]
207 let message = self.message;
208 let mut body = serde_json::json!({
209 "code": self.code,
210 "message": message,
211 });
212 if let Some(data) = self.data {
213 body.as_object_mut()
214 .unwrap()
215 .insert(String::from("data"), data);
216 }
217 let body = body.to_string();
218 let mut rsp = HttpResponse::build(self.http_code);
219 if !body.is_empty() {
220 rsp.content_type(actix_web::http::header::ContentType::json());
221 }
222 for builder in self.builder {
223 builder(&mut rsp);
224 }
225 if body.is_empty() {
226 rsp.finish().map_into_right_body()
227 } else {
228 rsp.message_body(body).unwrap().map_into_left_body()
229 }
230 } else {
231 let mut rsp = HttpResponse::build(self.http_code);
232 for builder in self.builder {
233 builder(&mut rsp);
234 }
235 if self.message.is_empty() {
236 rsp.finish().map_into_right_body()
237 } else {
238 rsp.message_body(self.message).unwrap().map_into_left_body()
239 }
240 }
241 }
242}