gotham_restful/response/
mod.rs

1use 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/// A response, used to create the final gotham response from.
52///
53/// This type is not meant to be used as the return type of endpoint handlers. While it can be
54/// freely used without the `openapi` feature, it is more complicated to use when you enable it,
55/// since this type does not store any schema information. You can attach schema information
56/// like so:
57///
58/// ```rust
59/// # #[cfg(feature = "openapi")] mod example {
60/// # use gotham::hyper::StatusCode;
61/// # use gotham_restful::*;
62/// # use openapi_type::*;
63/// fn schema(code: StatusCode) -> OpenapiSchema {
64/// 	assert_eq!(code, StatusCode::ACCEPTED);
65/// 	<()>::schema()
66/// }
67///
68/// fn status_codes() -> Vec<StatusCode> {
69/// 	vec![StatusCode::ACCEPTED]
70/// }
71///
72/// #[create(schema = "schema", status_codes = "status_codes")]
73/// fn create(body: Raw<Vec<u8>>) {}
74/// # }
75/// ```
76#[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	/// Create a new [Response] from raw data.
86	#[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	/// Create a [Response] with mime type json from already serialized data.
97	#[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	/// Create a _204 No Content_ [Response].
108	#[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	/// Create an empty _403 Forbidden_ [Response].
119	#[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	/// Return the status code of this [Response].
130	pub fn status(&self) -> StatusCode {
131		self.status
132	}
133
134	/// Return the mime type of this [Response].
135	pub fn mime(&self) -> Option<&Mime> {
136		self.mime.as_ref()
137	}
138
139	/// Add an HTTP header to the [Response].
140	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
169/// This trait needs to be implemented by every type returned from an endpoint to
170/// to provide the response.
171pub trait IntoResponse {
172	type Err: Into<HandlerError> + Send + Sync + 'static;
173
174	/// Turn this into a response that can be returned to the browser. This api will likely
175	/// change in the future.
176	fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>>;
177
178	/// Return a list of supported mime types.
179	fn accepted_types() -> Option<Vec<Mime>> {
180		None
181	}
182}
183
184/// Additional details for [IntoResponse] to be used with an OpenAPI-aware router.
185#[cfg(feature = "openapi")]
186pub trait ResponseSchema {
187	/// All status codes returned by this response. Returns `[StatusCode::OK]` by default.
188	fn status_codes() -> Vec<StatusCode> {
189		vec![StatusCode::OK]
190	}
191
192	/// Return the schema of the response for the given status code. The code may
193	/// only be one that was previously returned by [Self::status_codes]. The
194	/// implementation should panic if that is not the case.
195	fn schema(code: StatusCode) -> OpenapiSchema;
196}
197
198#[cfg(feature = "openapi")]
199mod private {
200	pub trait Sealed {}
201}
202
203/// A trait provided to convert a resource's result to json, and provide an OpenAPI schema to the
204/// router. This trait is implemented for all types that implement [IntoResponse] and
205/// [ResponseSchema].
206#[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/// The default json returned on an 500 Internal Server Error.
216#[derive(Debug, Serialize)]
217#[cfg_attr(feature = "openapi", derive(OpenapiType))]
218pub(crate) struct ResourceError {
219	/// This is always `true` and can be used to detect an error response without looking at the
220	/// HTTP status code.
221	error: bool,
222	/// The error message.
223	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}