mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-29 15:49:16 +01:00
Add example showing how to handle empty vs missing query params (#443)
This commit is contained in:
parent
a048d6443b
commit
98907b8887
3 changed files with 133 additions and 0 deletions
12
examples/query-params-with-empty-strings/Cargo.toml
Normal file
12
examples/query-params-with-empty-strings/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "example-query-params-with-empty-strings"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = { path = "../.." }
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tower = { version = "0.4", features = ["util"] }
|
||||||
|
hyper = "0.14"
|
116
examples/query-params-with-empty-strings/src/main.rs
Normal file
116
examples/query-params-with-empty-strings/src/main.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
//! Run with
|
||||||
|
//!
|
||||||
|
//! ```not_rust
|
||||||
|
//! cargo run -p example-query-params-with-empty-strings
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use axum::{extract::Query, routing::get, Router};
|
||||||
|
use serde::{de::IntoDeserializer, Deserialize, Deserializer};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(app().into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Router {
|
||||||
|
Router::new().route("/", get(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handler(Query(params): Query<Params>) -> String {
|
||||||
|
format!("{:?}", params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See the tests below for which combinations of `foo` and `bar` result in
|
||||||
|
/// which deserializations.
|
||||||
|
///
|
||||||
|
/// This example only shows one possible way to do this. [`serde_with`] provides
|
||||||
|
/// another way. Use which ever method works best for you.
|
||||||
|
///
|
||||||
|
/// [`serde_with`]: https://docs.rs/serde_with/1.11.0/serde_with/rust/string_empty_as_none/index.html
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Params {
|
||||||
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||||
|
foo: Option<String>,
|
||||||
|
bar: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serde deserialization decorator to map empty Strings to None,
|
||||||
|
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
T: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
let opt = Option::<String>::deserialize(de)?;
|
||||||
|
match opt.as_deref() {
|
||||||
|
None | Some("") => Ok(None),
|
||||||
|
Some(s) => T::deserialize(s.into_deserializer()).map(Some),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use axum::{body::Body, http::Request};
|
||||||
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_something() {
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("foo=foo&bar=bar").await,
|
||||||
|
r#"Params { foo: Some("foo"), bar: Some("bar") }"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("foo=&bar=bar").await,
|
||||||
|
r#"Params { foo: None, bar: Some("bar") }"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("foo=&bar=").await,
|
||||||
|
r#"Params { foo: None, bar: Some("") }"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("foo=foo").await,
|
||||||
|
r#"Params { foo: Some("foo"), bar: None }"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("bar=bar").await,
|
||||||
|
r#"Params { foo: None, bar: Some("bar") }"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("foo=").await,
|
||||||
|
r#"Params { foo: None, bar: None }"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("bar=").await,
|
||||||
|
r#"Params { foo: None, bar: Some("") }"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
send_request_get_body("").await,
|
||||||
|
r#"Params { foo: None, bar: None }"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_request_get_body(query: &str) -> String {
|
||||||
|
let body = app()
|
||||||
|
.oneshot(
|
||||||
|
Request::builder()
|
||||||
|
.uri(format!("/?{}", query))
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_body();
|
||||||
|
let bytes = hyper::body::to_bytes(body).await.unwrap();
|
||||||
|
String::from_utf8(bytes.to_vec()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,6 +39,11 @@ use std::ops::Deref;
|
||||||
///
|
///
|
||||||
/// If the query string cannot be parsed it will reject the request with a `400
|
/// If the query string cannot be parsed it will reject the request with a `400
|
||||||
/// Bad Request` response.
|
/// Bad Request` response.
|
||||||
|
///
|
||||||
|
/// For handling values being empty vs missing see the (query-params-with-empty-strings)[example]
|
||||||
|
/// example.
|
||||||
|
///
|
||||||
|
/// [example]: https://github.com/tokio-rs/axum/blob/main/examples/query-params-with-empty-strings/src/main.rs
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct Query<T>(pub T);
|
pub struct Query<T>(pub T);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue