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: u16,
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: 200,
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: u16) -> 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 bad_request<S: Into<String>>(s: S) -> Self {
88 Self::new_code(400).message(s)
89 }
90
91 pub fn forbidden() -> Self {
92 Self::new_code(403)
93 }
94
95 pub fn not_found() -> Self {
96 Self::new_code(404)
97 }
98
99 pub fn redirect<S: Into<String>>(code: u16, s: S) -> Self {
100 let s: String = s.into();
101 Self::new_code(code).builder(move |r| {
102 r.insert_header((header::LOCATION, s.clone()));
103 })
104 }
105
106 pub fn builder<F>(mut self, f: F) -> Self
107 where
108 F: Fn(&mut HttpResponseBuilder) + 'static,
109 {
110 self.builder.push(Box::new(f));
111 self
112 }
113
114 pub fn message<S: Into<String>>(mut self, s: S) -> Self {
115 self.message = s.into();
116 self
117 }
118
119 pub fn data(mut self, data: T) -> Self {
120 self.data = Some(data);
121 self
122 }
123
124 pub fn file(name: String, data: Vec<u8>) -> HttpResponse {
125 let body = once(future::ok::<_, actix_web::Error>(data.into()));
126 let header = ContentDisposition {
127 disposition: DispositionType::Attachment,
128 parameters: vec![DispositionParam::Filename(name)],
129 };
130 HttpResponse::Ok()
131 .insert_header(("Content-Disposition", header))
132 .content_type("application/octet-stream")
133 .streaming(body)
134 }
135
136 #[cfg(feature = "i18n")]
137 pub fn translate(mut self) -> Self {
138 self.translate = true;
139 self
140 }
141
142 #[cfg(feature = "i18n")]
143 pub fn i18n_message(&self, req: &actix_web::HttpRequest) -> String {
144 use actix_web::HttpMessage as _;
145
146 if self.translate {
147 req.app_data::<actix_web::web::Data<crate::state::GlobalState>>()
148 .map_or_else(
149 || self.message.clone(),
150 |state| {
151 if let Some(ext) = req
152 .extensions()
153 .get::<std::sync::Arc<crate::request::Extension>>()
154 {
155 crate::t!(state.locale, &self.message, &ext.lang)
156 } else {
157 self.message.clone()
158 }
159 },
160 )
161 } else {
162 self.message.clone()
163 }
164 }
165}
166
167#[cfg(feature = "response-json")]
168pub type JsonResponse = Response<serde_json::Value>;
169
170#[cfg(feature = "response-json")]
171impl JsonResponse {
172 pub fn json<T: serde::Serialize>(mut self, data: T) -> Self {
173 self.data = Some(serde_json::json!(data));
174 self
175 }
176}
177
178#[cfg(feature = "response-json")]
179impl actix_web::Responder for JsonResponse {
180 type Body = actix_web::body::EitherBody<String>;
181
182 fn respond_to(
183 self,
184 #[allow(unused_variables)] req: &actix_web::HttpRequest,
185 ) -> HttpResponse<Self::Body> {
186 if self.http_code == 200 {
187 #[cfg(feature = "i18n")]
188 let message = self.i18n_message(req);
189 #[cfg(not(feature = "i18n"))]
190 let message = self.message;
191 let mut body = serde_json::json!({
192 "code": self.code,
193 "message": message,
194 });
195 if let Some(data) = self.data {
196 body.as_object_mut()
197 .unwrap()
198 .insert(String::from("data"), data);
199 }
200 let body = body.to_string();
201 let mut rsp =
202 HttpResponse::build(actix_web::http::StatusCode::from_u16(self.http_code).unwrap());
203 rsp.content_type(actix_web::http::header::ContentType::json());
204 for builder in self.builder {
205 builder(&mut rsp);
206 }
207 rsp.message_body(body).unwrap().map_into_left_body()
208 } else {
209 let mut rsp =
210 HttpResponse::build(actix_web::http::StatusCode::from_u16(self.http_code).unwrap());
211 for builder in self.builder {
212 builder(&mut rsp);
213 }
214 rsp.message_body(self.message).unwrap().map_into_left_body()
215 }
216 }
217}