use async_trait::async_trait;
use reinhardt_http::Request;
use serde::de::DeserializeOwned;
use std::fmt::{self, Debug};
use std::ops::Deref;
use super::{ParamContext, ParamError, ParamResult, ParamType, extract::FromRequest};
#[cfg(feature = "multi-value-arrays")]
use std::collections::HashMap;
pub struct Query<T>(pub T);
impl<T> Query<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> Deref for Query<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Debug> Debug for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: Clone> Clone for Query<T> {
fn clone(&self) -> Self {
Query(self.0.clone())
}
}
#[cfg(feature = "multi-value-arrays")]
fn parse_query_multi_value(query_string: &str) -> HashMap<String, Vec<String>> {
let mut result: HashMap<String, Vec<String>> = HashMap::new();
for (key, value) in form_urlencoded::parse(query_string.as_bytes()) {
result
.entry(key.into_owned())
.or_default()
.push(value.into_owned());
}
result
}
#[cfg(feature = "multi-value-arrays")]
fn string_to_json_value(s: &str) -> serde_json::Value {
if let Ok(i) = s.parse::<i64>() {
return serde_json::Value::Number(i.into());
}
if let Ok(f) = s.parse::<f64>()
&& let Some(num) = serde_json::Number::from_f64(f)
{
return serde_json::Value::Number(num);
}
if let Ok(b) = s.parse::<bool>() {
return serde_json::Value::Bool(b);
}
serde_json::Value::String(s.to_string())
}
#[cfg(feature = "multi-value-arrays")]
fn multi_value_to_json_value(multi_map: &HashMap<String, Vec<String>>) -> serde_json::Value {
let mut result = serde_json::Map::new();
for (key, values) in multi_map {
let value = if values.len() == 1 {
string_to_json_value(&values[0])
} else {
serde_json::Value::Array(values.iter().map(|v| string_to_json_value(v)).collect())
};
result.insert(key.clone(), value);
}
serde_json::Value::Object(result)
}
#[async_trait]
impl<T> FromRequest for Query<T>
where
T: DeserializeOwned + Send,
{
async fn from_request(req: &Request, _ctx: &ParamContext) -> ParamResult<Self> {
let query_string = req.uri.query().unwrap_or("");
#[cfg(feature = "multi-value-arrays")]
let result = {
let multi_map = parse_query_multi_value(query_string);
let json_value = multi_value_to_json_value(&multi_map);
serde_json::from_value(json_value).map(Query).map_err(|e| {
let raw_value = if query_string.is_empty() {
None
} else {
Some(query_string.to_string())
};
let mut ctx = super::ParamErrorContext::new(ParamType::Query, e.to_string())
.with_expected_type::<T>()
.with_source(Box::new(e));
if let Some(raw) = raw_value {
ctx = ctx.with_raw_value(raw);
}
ParamError::InvalidParameter(Box::new(ctx))
})
};
#[cfg(not(feature = "multi-value-arrays"))]
let result = serde_urlencoded::from_str(query_string)
.map(Query)
.map_err(|e| {
let raw_value = if query_string.is_empty() {
None
} else {
Some(query_string.to_string())
};
ParamError::url_encoding::<T>(ParamType::Query, e, raw_value)
});
result
}
}
#[cfg(feature = "validation")]
impl<T> super::validation::WithValidation for Query<T> {}
#[async_trait]
impl<T> crate::Injectable for Query<T>
where
T: DeserializeOwned + Send + Sync + 'static,
{
async fn inject(ctx: &crate::InjectionContext) -> crate::DiResult<Self> {
let request = ctx
.get_http_request()
.ok_or(crate::DiError::MissingParamContext { extractor: "Query" })?;
let param_ctx = ctx
.get_param_context()
.ok_or(crate::DiError::MissingParamContext { extractor: "Query" })?;
<Query<T> as FromRequest>::from_request(request, param_ctx)
.await
.map_err(crate::DiError::from_param_error)
}
}
impl<T> super::has_inner::HasInner for Query<T> {
type Inner = T;
fn inner_ref(&self) -> &T {
&self.0
}
fn into_inner(self) -> T {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[allow(dead_code)]
#[derive(Debug, Deserialize, PartialEq)]
struct TestQuery {
page: Option<i32>,
limit: Option<i32>,
search: Option<String>,
}
}