use crate::http::StatusCode;
use crate::{Context, Error, Next, Result, State, SyncContext, Variable};
use url::form_urlencoded::parse;
struct QueryScope;
pub trait Query {
fn must_query<'a>(&self, name: &'a str) -> Result<Variable<'a, String>>;
fn query<'a>(&self, name: &'a str) -> Option<Variable<'a, String>>;
}
pub async fn query_parser<S: State>(mut ctx: Context<S>, next: Next) -> Result {
let uri = ctx.uri();
let query_string = uri.query().unwrap_or("").to_string();
for (key, value) in parse(query_string.as_bytes()) {
ctx.store_scoped(QueryScope, &key, value.to_string());
}
next.await
}
impl<S: State> Query for SyncContext<S> {
fn must_query<'a>(&self, name: &'a str) -> Result<Variable<'a, String>> {
self.query(name).ok_or_else(|| {
Error::new(
StatusCode::BAD_REQUEST,
format!("query `{}` is required", name),
true,
)
})
}
fn query<'a>(&self, name: &'a str) -> Option<Variable<'a, String>> {
self.load_scoped::<QueryScope, String>(name)
}
}
#[cfg(test)]
mod tests {
use crate::http::StatusCode;
use crate::preload::*;
use crate::query::query_parser;
use crate::App;
use async_std::task::spawn;
#[tokio::test]
async fn query() -> Result<(), Box<dyn std::error::Error>> {
let (addr, server) = App::new(())
.gate(query_parser)
.end(|ctx| async move {
assert!(ctx.query("name").is_none());
Ok(())
})
.run()?;
spawn(server);
reqwest::get(&format!("http://{}/", addr)).await?;
let (addr, server) = App::new(())
.gate(query_parser)
.end(|ctx| async move {
assert_eq!("Hexilee", &*ctx.must_query("name")?);
Ok(())
})
.run()?;
spawn(server);
let resp = reqwest::get(&format!("http://{}?name=Hexilee", addr)).await?;
assert_eq!(StatusCode::OK, resp.status());
Ok(())
}
#[tokio::test]
async fn query_parse() -> Result<(), Box<dyn std::error::Error>> {
let (addr, server) = App::new(())
.gate(query_parser)
.end(|ctx| async move {
assert!(ctx.must_query("age")?.parse::<u64>().is_err());
Ok(())
})
.run()?;
spawn(server);
let resp = reqwest::get(&format!("http://{}?age=Hexilee", addr)).await?;
assert_eq!(StatusCode::OK, resp.status());
let (addr, server) = App::new(())
.gate(query_parser)
.end(|ctx| async move {
assert_eq!(120, ctx.must_query("age")?.parse::<u64>()?);
Ok(())
})
.run()?;
spawn(server);
let resp = reqwest::get(&format!("http://{}?age=120", addr)).await?;
assert_eq!(StatusCode::OK, resp.status());
Ok(())
}
#[tokio::test]
async fn query_action() -> Result<(), Box<dyn std::error::Error>> {
let (addr, server) = App::new(())
.gate(query_parser)
.end(|ctx| async move {
assert_eq!("Hexilee", &*ctx.must_query("name")?);
assert_eq!("rust", &*ctx.must_query("lang")?);
Ok(())
})
.run()?;
spawn(server);
let resp =
reqwest::get(&format!("http://{}?name=Hexilee&lang=rust", addr)).await?;
assert_eq!(StatusCode::OK, resp.status());
Ok(())
}
}