actix_cloud/
response.rs

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}