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