Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Form extractor #140

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions libs/pavex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ serde_html_form = "0.2"
serde_json = "1"
serde_path_to_error = "0.1"

# Form body extractor
form_urlencoded = "1.2"

# Blueprint builder
indexmap = { version = "2", features = ["serde"] }
fs-err = "2.7.0"
Expand Down
12 changes: 7 additions & 5 deletions libs/pavex/src/request/body/buffered_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use super::{
/// Buffer the entire body of an incoming request in memory.
///
/// `BufferedBody` is the ideal building block for _other_ extractors that need to
/// have the entire body available in memory to do their job (e.g. [`JsonBody`](super::JsonBody)).
/// have the entire body available in memory to do their job (e.g. [`JsonBody`](super::JsonBody)).
///
/// It can also be useful if you need to access the raw bytes of the body ahead of deserialization
/// (e.g. to compute its hash as a step of a signature verification process).
Expand Down Expand Up @@ -71,7 +71,7 @@ use super::{
/// # Body size limit
///
/// To prevent denial-of-service attacks, Pavex enforces an upper limit on the body size when
/// trying to buffer it in memory. The default limit is 2 MBs.
/// trying to buffer it in memory. The default limit is 2 MBs.
///
/// [`BufferedBody::extract`] will return the [`SizeLimitExceeded`](ExtractBufferedBodyError::SizeLimitExceeded) error variant if the limit is exceeded.
///
Expand All @@ -98,7 +98,7 @@ use super::{
/// }
/// ```
///
/// You can also disable the limit entirely:
/// You can also disable the limit entirely:
///
/// ```rust
/// use pavex::f;
Expand All @@ -119,7 +119,7 @@ use super::{
/// ```
///
/// There might be situations where you want granular control instead of having
/// a single global limit for all incoming requests.
/// a single global limit for all incoming requests.
/// You can leverage nesting for this purpose:
///
/// ```rust
Expand Down Expand Up @@ -242,9 +242,11 @@ impl From<BufferedBody> for Bytes {

#[cfg(test)]
mod tests {
use bytes::Bytes;
use http::HeaderMap;
use http_body_util::Full;

use crate::request::RequestHead;
use crate::request::{body::BufferedBody, RequestHead};

// No headers.
fn dummy_request_head() -> RequestHead {
Expand Down
61 changes: 59 additions & 2 deletions libs/pavex/src/request/body/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,35 @@ impl ExtractJsonBodyError {
}
}

#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
/// The error returned by [`FormBody::extract`] when the extraction fails.
///
/// [`FormBody::extract`]: crate::request::body::form::FormBody::extract
pub enum ExtractFormBodyError {
#[error(transparent)]
/// See [`MissingFormContentType`] for details.
MissingContentType(#[from] MissingFormContentType),
#[error(transparent)]
/// See [`FormContentTypeMismatch`] for details.
ContentTypeMismatch(#[from] FormContentTypeMismatch),
#[error(transparent)]
/// See [`FormDeserializationError`] for details.
DeserializationError(#[from] FormDeserializationError),
}

impl ExtractFormBodyError {
/// Convert an [`ExtractFormBodyError`] into an HTTP response.
pub fn into_response(&self) -> Response<Full<Bytes>> {
match self {
ExtractFormBodyError::MissingContentType(_)
| ExtractFormBodyError::ContentTypeMismatch(_) => Response::unsupported_media_type(),
ExtractFormBodyError::DeserializationError(_) => Response::bad_request(),
}
.set_typed_body(format!("{}", self))
}
}

#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
/// The error returned by [`BufferedBody::extract`] when the extraction fails.
Expand Down Expand Up @@ -66,9 +95,9 @@ pub struct SizeLimitExceeded {
/// The maximum size limit enforced by this server.
pub max_n_bytes: usize,
/// The value of the `Content-Length` header for the request that breached the body
/// size limit.
/// size limit.
///
/// It's set to `None` if the `Content-Length` header was missing or invalid.
/// It's set to `None` if the `Content-Length` header was missing or invalid.
/// If it's set to `Some(n)` and `n` is smaller than `max_n_bytes`, then the request
/// lied about the size of its body in the `Content-Length` header.
pub content_length: Option<usize>,
Expand Down Expand Up @@ -111,3 +140,31 @@ pub struct JsonContentTypeMismatch {
/// The actual value of the `Content-Type` header for this request.
pub actual: String,
}

#[derive(Debug, thiserror::Error)]
#[error(
"The `Content-Type` header is missing. This endpoint expects requests with a `Content-Type` header set to `application/x-www-form-urlencoded`"
)]
#[non_exhaustive]
/// The `Content-Type` header is missing, while we expected it to be set to `application/x-www-form-urlencoded`.
pub struct MissingFormContentType;

#[derive(Debug, thiserror::Error)]
#[error("Failed to deserialize the body as a form.\n{source}")]
#[non_exhaustive]
/// Something went wrong when deserializing the request body into the specified type.
pub struct FormDeserializationError {
#[source]
pub(super) source: serde_path_to_error::Error<serde_html_form::de::Error>,
}

#[derive(Debug, thiserror::Error)]
#[error(
"The `Content-Type` header was set to `{actual}`. This endpoint expects requests with a `Content-Type` header set to `application/x-www-form-urlencoded`"
)]
#[non_exhaustive]
/// The `Content-Type` header not set to `application/x-www-form-urlencoded`.
pub struct FormContentTypeMismatch {
/// The actual value of the `Content-Type` header for this request.
pub actual: String,
}
Loading
Loading