tako_rs_extractors/
query_multi.rs1use std::borrow::Cow;
12
13use http::StatusCode;
14use http::request::Parts;
15use serde::de::DeserializeOwned;
16use tako_rs_core::extractors::FromRequest;
17use tako_rs_core::extractors::FromRequestParts;
18use tako_rs_core::responder::Responder;
19use tako_rs_core::types::Request;
20
21pub struct QueryMulti<T>(pub T);
41
42#[derive(Debug, Clone, Default)]
46pub struct QueryMultiOptions {
47 csv_keys: Vec<&'static str>,
48}
49
50impl QueryMultiOptions {
51 pub fn csv_key(mut self, key: &'static str) -> Self {
53 self.csv_keys.push(key);
54 self
55 }
56
57 fn rewrite<'a>(&self, query: &'a str) -> Cow<'a, str> {
66 if self.csv_keys.is_empty() {
67 return Cow::Borrowed(query);
68 }
69
70 let mut out = String::with_capacity(query.len());
71 let mut first = true;
72 for pair in query.split('&').filter(|p| !p.is_empty()) {
73 let (key, value) = match pair.find('=') {
74 Some(idx) => (&pair[..idx], &pair[idx + 1..]),
75 None => (pair, ""),
76 };
77 let decoded_key = urlencoding::decode(key).unwrap_or(Cow::Borrowed(key));
81 let decoded_value = urlencoding::decode(value).unwrap_or(Cow::Borrowed(value));
82 if self.csv_keys.contains(&decoded_key.as_ref()) && decoded_value.contains(',') {
83 for part in decoded_value.split(',') {
84 if !first {
85 out.push('&');
86 }
87 first = false;
88 out.push_str(key);
89 out.push('=');
90 out.push_str(&urlencoding::encode(part));
94 }
95 } else {
96 if !first {
97 out.push('&');
98 }
99 first = false;
100 out.push_str(pair);
101 }
102 }
103 Cow::Owned(out)
104 }
105}
106
107#[derive(Debug)]
109pub enum QueryMultiError {
110 DeserializationError(String),
112}
113
114impl Responder for QueryMultiError {
115 fn into_response(self) -> tako_rs_core::types::Response {
116 match self {
117 Self::DeserializationError(e) => (
118 StatusCode::BAD_REQUEST,
119 format!("failed to deserialize query: {e}"),
120 )
121 .into_response(),
122 }
123 }
124}
125
126fn lookup_options(extensions: &http::Extensions) -> QueryMultiOptions {
127 extensions
128 .get::<QueryMultiOptions>()
129 .cloned()
130 .unwrap_or_default()
131}
132
133fn parse<T: DeserializeOwned>(query: &str, opts: &QueryMultiOptions) -> Result<T, QueryMultiError> {
134 let rewritten = opts.rewrite(query);
135 serde_html_form::from_str::<T>(rewritten.as_ref())
136 .map_err(|e| QueryMultiError::DeserializationError(e.to_string()))
137}
138
139impl<'a, T> FromRequest<'a> for QueryMulti<T>
140where
141 T: DeserializeOwned + Send + 'a,
142{
143 type Error = QueryMultiError;
144
145 fn from_request(
146 req: &'a mut Request,
147 ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
148 let opts = lookup_options(req.extensions());
149 let q = req.uri().query().unwrap_or("").to_string();
150 futures_util::future::ready(parse::<T>(&q, &opts).map(QueryMulti))
151 }
152}
153
154impl<'a, T> FromRequestParts<'a> for QueryMulti<T>
155where
156 T: DeserializeOwned + Send + 'a,
157{
158 type Error = QueryMultiError;
159
160 fn from_request_parts(
161 parts: &'a mut Parts,
162 ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
163 let opts = lookup_options(&parts.extensions);
164 let q = parts.uri.query().unwrap_or("").to_string();
165 futures_util::future::ready(parse::<T>(&q, &opts).map(QueryMulti))
166 }
167}