Skip to main content

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: 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            rsp.content_type(actix_web::http::header::ContentType::json());
220            for builder in self.builder {
221                builder(&mut rsp);
222            }
223            if self.message.is_empty() {
224                rsp.finish().map_into_right_body()
225            } else {
226                rsp.message_body(body).unwrap().map_into_left_body()
227            }
228        } else {
229            let mut rsp = HttpResponse::build(self.http_code);
230            for builder in self.builder {
231                builder(&mut rsp);
232            }
233            if self.message.is_empty() {
234                rsp.finish().map_into_right_body()
235            } else {
236                rsp.message_body(self.message).unwrap().map_into_left_body()
237            }
238        }
239    }
240}