use crate::{extract::Extractor, Request, StatusCode};
use http_kit::http_error;
use serde::de::DeserializeOwned;
use serde_urlencoded::from_str;
#[derive(Debug, Clone)]
pub struct Query<T>(pub T);
impl_deref!(Query);
http_error!(
pub QueryError, StatusCode::BAD_REQUEST, "Failed to parse query string");
impl<T: Send + Sync + DeserializeOwned + 'static> Extractor for Query<T> {
type Error = QueryError;
async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
let data = request.uri().query().unwrap_or_default();
Ok(Self(from_str(data).map_err(|error| {
tracing::debug!(%error, "failed to parse query string");
QueryError::new()
})?))
}
#[cfg(feature = "openapi")]
fn openapi() -> Option<crate::openapi::ExtractorSchema> {
Some(crate::openapi::ExtractorSchema {
location: crate::openapi::ParameterLocation::Query,
content_type: None,
schema: crate::openapi::maybe_schema_of::<T>(),
})
}
#[cfg(feature = "openapi")]
fn register_openapi_schemas(
defs: &mut std::collections::BTreeMap<String, crate::openapi::SchemaRef>,
) {
crate::openapi::maybe_register_schema_for::<T>(defs);
}
}
#[cfg(test)]
mod tests {
use super::Query;
use crate::{Body, Method, StatusCode};
use http_kit::HttpError;
use serde::Deserialize;
use skyzen_core::Extractor;
#[derive(Debug, Deserialize, PartialEq)]
struct Search {
q: String,
page: u8,
}
#[tokio::test]
async fn parses_struct_from_query_string() {
let mut request = request("http://localhost/search?q=rust&page=2");
let Query(search) = Query::<Search>::extract(&mut request).await.unwrap();
assert_eq!(
search,
Search {
q: "rust".into(),
page: 2
}
);
}
#[tokio::test]
async fn surfaces_bad_request_for_invalid_payload() {
let mut request = request("http://localhost/search?q=rust&page=two");
let error = Query::<Search>::extract(&mut request).await.unwrap_err();
assert_eq!(error.status(), StatusCode::BAD_REQUEST);
}
fn request(uri: &str) -> http_kit::Request {
let mut request = http_kit::Request::new(Body::empty());
*request.uri_mut() = uri.parse().expect("invalid uri");
*request.method_mut() = Method::GET;
request
}
}