actix_cloud/
response.rs

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