rocket_enumform/
lib.rs

1// Copyright (c) 2016 Sergio Benitez
2// Copyright (c) 2021 Cognite AS
3//! Rocket extension to permit enums in application/x-www-form-urlencoded forms
4//! This crate is a workaround for [https://github.com/SergioBenitez/Rocket/issues/1937](rocket#1937).
5//!
6//! It is derived from the included [serde_json](`rocket::serde::json`]) implementation in rocket.
7//!
8//! ```rust
9//! # use rocket_enumform::UrlEncoded;
10//! # use serde::Deserialize;
11//! # use rocket::post;
12//! #[derive(Debug, Deserialize)]
13//! #[serde(tag = "type")]
14//! enum Body {
15//!     #[serde(rename = "variant_one")]
16//!     VariantOne(VariantOne),
17//!     #[serde(rename = "variant_two")]
18//!     VariantTwo(VariantTwo),
19//! }
20//!
21//! #[derive(Debug, Deserialize)]
22//! struct VariantOne {
23//!     content_one: String
24//! }
25//!
26//! #[derive(Debug, Deserialize)]
27//! struct VariantTwo {
28//!     content_two: String
29//! }
30//!
31//! #[post("/form", format = "form", data = "<data>")]
32//! fn body(data: UrlEncoded<Body>) -> String { format!("{:?}", data) }
33//! ```
34//! ## status
35//!
36//! Works but not tested, nor have local testing affordances been added yet.
37//!
38// # Testing
39//
40// TODO; idea is use the underlying serde_urlencoded serializer and implement the glue
41// needed as extension traits.
42//
43// ///The [`LocalRequest`] and [`LocalResponse`] types provide [`json()`] and
44// ///[`into_json()`] methods to create a request with serialized JSON and
45// ///deserialize a response as JSON, respectively.
46//
47// ///[`LocalRequest`]: crate::local::blocking::LocalRequest [`LocalResponse`]:
48// ///crate::local::blocking::LocalResponse [`json()`]:
49// ///crate::local::blocking::LocalRequest::json() [`into_json()`]:
50// ///crate::local::blocking::LocalResponse::into_json()
51
52use std::ops::{Deref, DerefMut};
53use std::{error, fmt, io};
54
55use rocket::data::{Data, FromData, Limits, Outcome};
56use rocket::error_;
57use rocket::form::prelude as form;
58use rocket::http::uri::fmt::{Formatter as UriFormatter, FromUriParam, Query, UriDisplay};
59use rocket::http::{ContentType, Status};
60use rocket::request::{local_cache, Request};
61use rocket::response::{self, content, Responder};
62use serde::{Deserialize, Serialize};
63
64/// The UrlEncoded guard: easily consume x-www-form-urlencoded requests.
65///
66/// ## Receiving
67///
68/// `UrlEncoded` is both a data guard and a form guard.
69///
70/// ### Data Guard
71///
72/// To deserialize request body data from x-www-form-urlencoded, add a `data`
73/// route argument with a target type of `UrlEncoded<T>`, where `T` is some type
74/// you'd like to parse. `T` must implement [`serde::Deserialize`]. See
75/// [`serde_urlencoded`](serde_urlencoded) docs on the flatten-workaround for important hints about
76/// more complex datatypes.
77///
78/// ```rust
79/// # #[macro_use] extern crate rocket;
80/// #
81/// # type User = usize;
82/// use rocket_enumform::UrlEncoded;
83///
84/// #[post("/user", format = "form", data = "<user>")]
85/// fn new_user(user: UrlEncoded<User>) {
86///     /* ... */
87/// }
88/// ```
89///
90/// You don't _need_ to use `format = "form"`, but it _may_ be what you
91/// want. Using `format = urlencoded` means that any request that doesn't
92/// specify "application/x-www-form-urlencoded" as its `Content-Type` header
93/// value will not be routed to the handler.
94///
95/// ### Incoming Data Limits
96///
97/// The default size limit for incoming UrlEncoded data is the built in form
98/// limit. Setting a limit protects your application from denial of service
99/// (DoS) attacks and from resource exhaustion through high memory consumption.
100/// The limit can be increased by setting the `limits.form` configuration
101/// parameter. For instance, to increase the UrlEncoded limit to 5MiB for all
102/// environments, you may add the following to your `Rocket.toml`:
103///
104/// ```toml
105/// [global.limits]
106/// form = 5242880
107/// ```
108#[repr(transparent)]
109#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
110pub struct UrlEncoded<T>(pub T);
111
112/// Error returned by the [`UrlEncoded`] guard when deserialization fails.
113#[derive(Debug)]
114pub enum Error<'a> {
115    /// An I/O error occurred while reading the incoming request data.
116    Io(io::Error),
117
118    /// The client's data was received successfully but failed to parse as valid
119    /// UrlEncoded or as the requested type. The `&str` value in `.0` is the raw data
120    /// received from the user, while the `Error` in `.1` is the deserialization
121    /// error from `serde`.
122    Parse(&'a str, ::serde_urlencoded::de::Error),
123}
124
125impl<'a> fmt::Display for Error<'a> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::Io(err) => write!(f, "i/o error: {}", err),
129            Self::Parse(_, err) => write!(f, "parse error: {}", err),
130        }
131    }
132}
133
134impl<'a> error::Error for Error<'a> {
135    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
136        match self {
137            Self::Io(err) => Some(err),
138            Self::Parse(_, err) => Some(err),
139        }
140    }
141}
142
143impl<T> UrlEncoded<T> {
144    /// Consumes the UrlEncoded wrapper and returns the wrapped item.
145    ///
146    /// # Example
147    /// ```rust
148    /// use rocket_enumform::UrlEncoded;
149    /// let string = "Hello".to_string();
150    /// let outer = UrlEncoded(string);
151    /// assert_eq!(outer.into_inner(), "Hello".to_string());
152    /// ```
153    #[inline(always)]
154    pub fn into_inner(self) -> T {
155        self.0
156    }
157}
158
159impl<'r, T: Deserialize<'r>> UrlEncoded<T> {
160    fn from_str(s: &'r str) -> Result<Self, Error<'r>> {
161        ::serde_urlencoded::from_str(s)
162            .map(UrlEncoded)
163            .map_err(|e| Error::Parse(s, e))
164    }
165
166    async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Result<Self, Error<'r>> {
167        let limit = req.limits().get("form").unwrap_or(Limits::FORM);
168        let string = match data.open(limit).into_string().await {
169            Ok(s) if s.is_complete() => s.into_inner(),
170            Ok(_) => {
171                let eof = io::ErrorKind::UnexpectedEof;
172                return Err(Error::Io(io::Error::new(eof, "data limit exceeded")));
173            }
174            Err(e) => return Err(Error::Io(e)),
175        };
176
177        Self::from_str(local_cache!(req, string))
178    }
179}
180
181#[rocket::async_trait]
182impl<'r, T: Deserialize<'r>> FromData<'r> for UrlEncoded<T> {
183    type Error = Error<'r>;
184
185    async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
186        match Self::from_data(req, data).await {
187            Ok(value) => Outcome::Success(value),
188            Err(Error::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
189                Outcome::Failure((Status::PayloadTooLarge, Error::Io(e)))
190            }
191            Err(Error::Parse(s, e)) => {
192                error_!("{:?}", e);
193                Outcome::Failure((Status::UnprocessableEntity, Error::Parse(s, e)))
194            }
195            Err(e) => Outcome::Failure((Status::BadRequest, e)),
196        }
197    }
198}
199
200/// Serializes the wrapped value into UrlEncoding. Returns a response with Content-Type
201/// application/x-www-form-urlencode and a fixed-size body with the serialized value. If serialization
202/// fails, an `Err` of `Status::InternalServerError` is returned.
203impl<'r, T: Serialize> Responder<'r, 'static> for UrlEncoded<T> {
204    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
205        let string = ::serde_urlencoded::to_string(&self.0).map_err(|e| {
206            error_!("UrlEncoding failed to serialize: {:?}", e);
207            Status::InternalServerError
208        })?;
209
210        content::Custom(ContentType::Form, string).respond_to(req)
211    }
212}
213
214impl<T: Serialize> UriDisplay<Query> for UrlEncoded<T> {
215    fn fmt(&self, f: &mut UriFormatter<'_, Query>) -> fmt::Result {
216        let string = ::serde_urlencoded::to_string(&self.0).map_err(|_| fmt::Error)?;
217        f.write_value(&string)
218    }
219}
220
221macro_rules! impl_from_uri_param_from_inner_type {
222    ($($lt:lifetime)?, $T:ty) => (
223        impl<$($lt,)? T: Serialize> FromUriParam<Query, $T> for UrlEncoded<T> {
224            type Target = UrlEncoded<$T>;
225
226            #[inline(always)]
227            fn from_uri_param(param: $T) -> Self::Target {
228                UrlEncoded(param)
229            }
230        }
231    )
232}
233
234impl_from_uri_param_from_inner_type!(, T);
235impl_from_uri_param_from_inner_type!('a, &'a T);
236impl_from_uri_param_from_inner_type!('a, &'a mut T);
237
238rocket::http::impl_from_uri_param_identity!([Query] (T: Serialize) UrlEncoded<T>);
239
240impl<T> From<T> for UrlEncoded<T> {
241    fn from(value: T) -> Self {
242        UrlEncoded(value)
243    }
244}
245
246impl<T> Deref for UrlEncoded<T> {
247    type Target = T;
248
249    #[inline(always)]
250    fn deref(&self) -> &T {
251        &self.0
252    }
253}
254
255impl<T> DerefMut for UrlEncoded<T> {
256    #[inline(always)]
257    fn deref_mut(&mut self) -> &mut T {
258        &mut self.0
259    }
260}
261
262impl From<Error<'_>> for form::Error<'_> {
263    fn from(e: Error<'_>) -> Self {
264        match e {
265            Error::Io(e) => e.into(),
266            Error::Parse(_, e) => form::Error::custom(e),
267        }
268    }
269}
270
271#[rocket::async_trait]
272impl<'v, T: Deserialize<'v> + Send> form::FromFormField<'v> for UrlEncoded<T> {
273    fn from_value(field: form::ValueField<'v>) -> Result<Self, form::Errors<'v>> {
274        Ok(Self::from_str(field.value)?)
275    }
276
277    async fn from_data(f: form::DataField<'v, '_>) -> Result<Self, form::Errors<'v>> {
278        Ok(Self::from_data(f.request, f.data).await?)
279    }
280}
281
282/// Deserialize an instance of type `T` from bytes of UrlEncoded text.
283///
284/// **_Always_ use [`UrlEncoded`] to deserialize UrlEncoded request data.**
285///
286/// # Example
287///
288/// ```
289/// use rocket::serde::Deserialize;
290///
291/// #[derive(Debug, PartialEq, Deserialize)]
292/// struct Data<'r> {
293///     framework: &'r str,
294///     stars: usize,
295/// }
296///
297/// let bytes = br#"framework=Rocket&stars=5"#;
298///
299/// let data: Data = rocket_enumform::from_slice(bytes).unwrap();
300/// assert_eq!(data, Data { framework: "Rocket", stars: 5, });
301/// ```
302///
303/// # Errors
304///
305/// This conversion can fail if the structure of the input does not match the
306/// structure expected by `T`, for example if `T` is a struct type but the input
307/// contains something other than a UrlEncoded map. It can also fail if the structure
308/// is correct but `T`'s implementation of `Deserialize` decides that something
309/// is wrong with the data, for example required struct fields are missing from
310/// the UrlEncoded map or some number is too big to fit in the expected primitive
311/// type.
312#[inline(always)]
313pub fn from_slice<'a, T>(slice: &'a [u8]) -> Result<T, ::serde_urlencoded::de::Error>
314where
315    T: Deserialize<'a>,
316{
317    ::serde_urlencoded::from_bytes(slice)
318}
319
320/// Deserialize an instance of type `T` from a string of UrlEncoded text.
321///
322/// **_Always_ use [`UrlEncoded`] to deserialize UrlEncoded request data.**
323///
324/// # Example
325///
326/// ```
327/// use rocket::serde::Deserialize;
328///
329/// #[derive(Debug, PartialEq, Deserialize)]
330/// struct Data<'r> {
331///     framework: &'r str,
332///     stars: usize,
333/// }
334///
335/// let string = r#"framework=Rocket&stars=5"#;
336///
337/// let data: Data = rocket_enumform::from_str(string).unwrap();
338/// assert_eq!(data, Data { framework: "Rocket", stars: 5 });
339/// ```
340///
341/// # Errors
342///
343/// This conversion can fail if the structure of the input does not match the
344/// structure expected by `T`, for example if `T` is a struct type but the input
345/// contains something other than a UrlEncoded map. It can also fail if the structure
346/// is correct but `T`'s implementation of `Deserialize` decides that something
347/// is wrong with the data, for example required struct fields are missing from
348/// the UrlEncoded map or some number is too big to fit in the expected primitive
349/// type.
350#[inline(always)]
351pub fn from_str<'a, T>(string: &'a str) -> Result<T, ::serde_urlencoded::de::Error>
352where
353    T: Deserialize<'a>,
354{
355    ::serde_urlencoded::from_str(string)
356}