gotham_restful/response/
mod.rs1use futures_util::future::{self, BoxFuture, FutureExt};
2use gotham::{
3 handler::HandlerError,
4 hyper::{
5 header::{HeaderMap, HeaderName, HeaderValue},
6 Body, StatusCode
7 },
8 mime::{Mime, APPLICATION_JSON, STAR_STAR}
9};
10#[cfg(feature = "openapi")]
11use openapi_type::{OpenapiSchema, OpenapiType};
12use serde::Serialize;
13#[cfg(feature = "errorlog")]
14use std::fmt::Display;
15use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin};
16
17mod auth_result;
18#[allow(unreachable_pub)]
19pub use auth_result::{AuthError, AuthErrorOrOther, AuthResult, AuthSuccess};
20
21mod no_content;
22#[allow(unreachable_pub)]
23pub use no_content::NoContent;
24
25mod raw;
26#[allow(unreachable_pub)]
27pub use raw::Raw;
28
29mod redirect;
30#[allow(unreachable_pub)]
31pub use redirect::Redirect;
32
33mod result;
34#[allow(unreachable_pub)]
35pub use result::IntoResponseError;
36
37mod success;
38#[allow(unreachable_pub)]
39pub use success::Success;
40
41pub(crate) trait OrAllTypes {
42 fn or_all_types(self) -> Vec<Mime>;
43}
44
45impl OrAllTypes for Option<Vec<Mime>> {
46 fn or_all_types(self) -> Vec<Mime> {
47 self.unwrap_or_else(|| vec![STAR_STAR])
48 }
49}
50
51#[derive(Debug)]
77pub struct Response {
78 pub(crate) status: StatusCode,
79 pub(crate) body: Body,
80 pub(crate) mime: Option<Mime>,
81 pub(crate) headers: HeaderMap
82}
83
84impl Response {
85 #[must_use = "Creating a response is pointless if you don't use it"]
87 pub fn new<B: Into<Body>>(status: StatusCode, body: B, mime: Option<Mime>) -> Self {
88 Self {
89 status,
90 body: body.into(),
91 mime,
92 headers: Default::default()
93 }
94 }
95
96 #[must_use = "Creating a response is pointless if you don't use it"]
98 pub fn json<B: Into<Body>>(status: StatusCode, body: B) -> Self {
99 Self {
100 status,
101 body: body.into(),
102 mime: Some(APPLICATION_JSON),
103 headers: Default::default()
104 }
105 }
106
107 #[must_use = "Creating a response is pointless if you don't use it"]
109 pub fn no_content() -> Self {
110 Self {
111 status: StatusCode::NO_CONTENT,
112 body: Body::empty(),
113 mime: None,
114 headers: Default::default()
115 }
116 }
117
118 #[must_use = "Creating a response is pointless if you don't use it"]
120 pub fn forbidden() -> Self {
121 Self {
122 status: StatusCode::FORBIDDEN,
123 body: Body::empty(),
124 mime: None,
125 headers: Default::default()
126 }
127 }
128
129 pub fn status(&self) -> StatusCode {
131 self.status
132 }
133
134 pub fn mime(&self) -> Option<&Mime> {
136 self.mime.as_ref()
137 }
138
139 pub fn header(&mut self, name: HeaderName, value: HeaderValue) {
141 self.headers.insert(name, value);
142 }
143
144 pub(crate) fn with_headers(mut self, headers: HeaderMap) -> Self {
145 self.headers = headers;
146 self
147 }
148
149 #[cfg(test)]
150 pub(crate) fn full_body(
151 mut self
152 ) -> Result<Vec<u8>, <Body as gotham::hyper::body::HttpBody>::Error> {
153 use futures_executor::block_on;
154 use gotham::hyper::body::to_bytes;
155
156 let bytes: &[u8] = &block_on(to_bytes(&mut self.body))?;
157 Ok(bytes.to_vec())
158 }
159}
160
161impl IntoResponse for Response {
162 type Err = Infallible;
163
164 fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>> {
165 future::ok(self).boxed()
166 }
167}
168
169pub trait IntoResponse {
172 type Err: Into<HandlerError> + Send + Sync + 'static;
173
174 fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>>;
177
178 fn accepted_types() -> Option<Vec<Mime>> {
180 None
181 }
182}
183
184#[cfg(feature = "openapi")]
186pub trait ResponseSchema {
187 fn status_codes() -> Vec<StatusCode> {
189 vec![StatusCode::OK]
190 }
191
192 fn schema(code: StatusCode) -> OpenapiSchema;
196}
197
198#[cfg(feature = "openapi")]
199mod private {
200 pub trait Sealed {}
201}
202
203#[cfg(feature = "openapi")]
207pub trait IntoResponseWithSchema: IntoResponse + ResponseSchema + private::Sealed {}
208
209#[cfg(feature = "openapi")]
210impl<R: IntoResponse + ResponseSchema> private::Sealed for R {}
211
212#[cfg(feature = "openapi")]
213impl<R: IntoResponse + ResponseSchema> IntoResponseWithSchema for R {}
214
215#[derive(Debug, Serialize)]
217#[cfg_attr(feature = "openapi", derive(OpenapiType))]
218pub(crate) struct ResourceError {
219 error: bool,
222 message: String
224}
225
226impl<T: ToString> From<T> for ResourceError {
227 fn from(message: T) -> Self {
228 Self {
229 error: true,
230 message: message.to_string()
231 }
232 }
233}
234
235#[cfg(feature = "errorlog")]
236fn errorlog<E: Display>(e: E) {
237 error!("The handler encountered an error: {e}");
238}
239
240#[cfg(not(feature = "errorlog"))]
241fn errorlog<E>(_e: E) {}
242
243fn handle_error<E>(e: E) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
244where
245 E: Debug + IntoResponseError
246{
247 let msg = format!("{e:?}");
248 let res = e.into_response_error();
249 match &res {
250 Ok(res) if res.status.is_server_error() => errorlog(msg),
251 Err(err) => {
252 errorlog(msg);
253 errorlog(format!("{err:?}"));
254 },
255 _ => {}
256 };
257 future::ready(res).boxed()
258}
259
260impl<Res> IntoResponse for Pin<Box<dyn Future<Output = Res> + Send>>
261where
262 Res: IntoResponse + 'static
263{
264 type Err = Res::Err;
265
266 fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
267 self.then(IntoResponse::into_response).boxed()
268 }
269
270 fn accepted_types() -> Option<Vec<Mime>> {
271 Res::accepted_types()
272 }
273}
274
275#[cfg(feature = "openapi")]
276impl<Res> ResponseSchema for Pin<Box<dyn Future<Output = Res> + Send>>
277where
278 Res: ResponseSchema
279{
280 fn status_codes() -> Vec<StatusCode> {
281 Res::status_codes()
282 }
283
284 fn schema(code: StatusCode) -> OpenapiSchema {
285 Res::schema(code)
286 }
287}
288
289#[cfg(test)]
290mod test {
291 use super::*;
292 use futures_executor::block_on;
293 use thiserror::Error;
294
295 #[derive(Debug, Default, Deserialize, Serialize)]
296 #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
297 struct Msg {
298 msg: String
299 }
300
301 #[derive(Debug, Default, Error)]
302 #[error("An Error")]
303 struct MsgError;
304
305 #[test]
306 fn result_from_future() {
307 let nc = NoContent::default();
308 let res = block_on(nc.into_response()).unwrap();
309
310 let fut_nc = async move { NoContent::default() }.boxed();
311 let fut_res = block_on(fut_nc.into_response()).unwrap();
312
313 assert_eq!(res.status, fut_res.status);
314 assert_eq!(res.mime, fut_res.mime);
315 assert_eq!(res.full_body().unwrap(), fut_res.full_body().unwrap());
316 }
317}