1#![warn(missing_docs)]
2#![doc = include_str!("../readme-footer.md")]
12
13use facet_core::{Def, Facet, Type, UserType};
14use facet_reflect::{AllocError, Partial, ReflectError, ShapeMismatchError, TypePlan};
15use log::*;
16
17#[cfg(test)]
18mod tests;
19
20mod form;
21pub use form::Form;
22
23mod query;
24pub use query::Query;
25
26#[cfg(feature = "axum")]
27mod axum;
28#[cfg(feature = "axum")]
29pub use self::axum::{FormRejection, QueryRejection};
30
31pub fn from_str<'input: 'facet, 'facet, T: Facet<'facet>>(
91 urlencoded: &'input str,
92) -> Result<T, UrlEncodedError> {
93 let plan = TypePlan::<T>::build()?;
94 let partial = plan.partial()?;
95 let partial = from_str_value(partial, urlencoded)?;
96 let result: T = partial.build()?.materialize()?;
97 Ok(result)
98}
99
100pub fn from_str_owned<T: Facet<'static>>(urlencoded: &str) -> Result<T, UrlEncodedError> {
123 let plan = TypePlan::<T>::build()?;
124 let partial = plan.partial_owned()?;
125 let partial = from_str_value(partial, urlencoded)?;
126 let result: T = partial.build()?.materialize()?;
127 Ok(result)
128}
129
130fn from_str_value<'facet, const BORROW: bool>(
134 mut wip: Partial<'facet, BORROW>,
135 urlencoded: &str,
136) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
137 trace!("Starting URL encoded form data deserialization");
138
139 let pairs = form_urlencoded::parse(urlencoded.as_bytes());
141
142 let mut nested_values = NestedValues::new();
144 for (key, value) in pairs {
145 nested_values.insert(&key, value.to_string());
146 }
147
148 initialize_nested_structures(&mut nested_values);
151
152 wip = deserialize_value(wip, &nested_values)?;
154 Ok(wip)
155}
156
157fn initialize_nested_structures(nested: &mut NestedValues) {
160 for nested_value in nested.nested.values_mut() {
162 initialize_nested_structures(nested_value);
163 }
164}
165
166struct NestedValues {
168 flat: std::collections::HashMap<String, String>,
170 nested: std::collections::HashMap<String, NestedValues>,
172}
173
174impl NestedValues {
175 fn new() -> Self {
176 Self {
177 flat: std::collections::HashMap::new(),
178 nested: std::collections::HashMap::new(),
179 }
180 }
181
182 fn insert(&mut self, key: &str, value: String) {
183 if let Some(open_bracket) = key.find('[')
185 && let Some(close_bracket) = key.find(']')
186 && open_bracket < close_bracket
187 {
188 let parent_key = &key[0..open_bracket];
189 let nested_key = &key[(open_bracket + 1)..close_bracket];
190 let remainder = &key[(close_bracket + 1)..];
191
192 let nested = self
193 .nested
194 .entry(parent_key.to_string())
195 .or_insert_with(NestedValues::new);
196
197 if remainder.is_empty() {
198 nested.flat.insert(nested_key.to_string(), value);
200 } else {
201 let new_key = format!("{nested_key}{remainder}");
203 nested.insert(&new_key, value);
204 }
205 return;
206 }
207
208 self.flat.insert(key.to_string(), value);
210 }
211
212 fn get(&self, key: &str) -> Option<&String> {
213 self.flat.get(key)
214 }
215
216 #[expect(dead_code)]
217 fn get_nested(&self, key: &str) -> Option<&NestedValues> {
218 self.nested.get(key)
219 }
220
221 fn keys(&self) -> impl Iterator<Item = &String> {
222 self.flat.keys()
223 }
224
225 #[expect(dead_code)]
226 fn nested_keys(&self) -> impl Iterator<Item = &String> {
227 self.nested.keys()
228 }
229}
230
231fn deserialize_value<'facet, const BORROW: bool>(
233 mut wip: Partial<'facet, BORROW>,
234 values: &NestedValues,
235) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
236 let shape = wip.shape();
237 match shape.ty {
238 Type::User(UserType::Struct(_)) => {
239 trace!("Deserializing struct");
240
241 for key in values.keys() {
243 if let Some(index) = wip.field_index(key) {
244 let value = values.get(key).unwrap(); wip = wip.begin_nth_field(index)?;
246 wip = deserialize_scalar_field(key, value, wip)?;
247 wip = wip.end()?;
248 } else {
249 trace!("Unknown field: {key}");
250 }
251 }
252
253 for key in values.nested.keys() {
255 if let Some(index) = wip.field_index(key) {
256 let nested_values = values.nested.get(key).unwrap(); wip = wip.begin_nth_field(index)?;
258 wip = deserialize_nested_field(key, nested_values, wip)?;
259 wip = wip.end()?;
260 } else {
261 trace!("Unknown nested field: {key}");
262 }
263 }
264
265 trace!("Finished deserializing struct");
266 Ok(wip)
267 }
268 _ => {
269 error!("Unsupported root type");
270 Err(UrlEncodedError::UnsupportedShape(
271 "Unsupported root type".to_string(),
272 ))
273 }
274 }
275}
276
277fn deserialize_scalar_field<'facet, const BORROW: bool>(
286 key: &str,
287 value: &str,
288 mut wip: Partial<'facet, BORROW>,
289) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
290 let is_option = matches!(wip.shape().def, Def::Option(_));
293 if is_option {
294 wip = wip.begin_some()?;
295 }
296 match wip.shape().def {
297 Def::Scalar => {
298 if wip.shape().is_type::<String>() {
299 let s = value.to_string();
300 wip = wip.set(s)?;
301 } else if wip.shape().is_type::<u64>() {
302 match value.parse::<u64>() {
303 Ok(num) => wip = wip.set(num)?,
304 Err(_) => {
305 return Err(UrlEncodedError::InvalidNumber(
306 key.to_string(),
307 value.to_string(),
308 ));
309 }
310 };
311 } else if wip.shape().is_type::<i64>() {
312 match value.parse::<i64>() {
313 Ok(num) => wip = wip.set(num)?,
314 Err(_) => {
315 return Err(UrlEncodedError::InvalidNumber(
316 key.to_string(),
317 value.to_string(),
318 ));
319 }
320 };
321 } else if wip.shape().is_type::<bool>() {
322 let parsed = match value {
323 "true" | "1" | "on" | "yes" => true,
324 "false" | "0" | "off" | "no" | "" => false,
325 _ => {
326 return Err(UrlEncodedError::UnsupportedType(format!(
327 "{}: unparseable bool literal {value:?}",
328 wip.shape()
329 )));
330 }
331 };
332 wip = wip.set(parsed)?;
333 } else {
334 warn!("facet-urlencoded: unsupported scalar type: {}", wip.shape());
335 return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
336 }
337 if is_option {
340 wip = wip.end()?;
341 }
342 Ok(wip)
343 }
344 _ => {
345 error!("Expected scalar field");
346 Err(UrlEncodedError::UnsupportedShape(format!(
347 "Expected scalar for field '{key}'"
348 )))
349 }
350 }
351}
352
353fn deserialize_nested_field<'facet, const BORROW: bool>(
355 key: &str,
356 nested_values: &NestedValues,
357 mut wip: Partial<'facet, BORROW>,
358) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
359 let shape = wip.shape();
360 match shape.ty {
361 Type::User(UserType::Struct(_)) => {
362 trace!("Deserializing nested struct field: {key}");
363
364 for nested_key in nested_values.keys() {
366 if let Some(index) = wip.field_index(nested_key) {
367 let value = nested_values.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
369 wip = deserialize_scalar_field(nested_key, value, wip)?;
370 wip = wip.end()?;
371 }
372 }
373
374 for nested_key in nested_values.nested.keys() {
376 if let Some(index) = wip.field_index(nested_key) {
377 let deeper_nested = nested_values.nested.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
379 wip = deserialize_nested_field(nested_key, deeper_nested, wip)?;
380 wip = wip.end()?;
381 }
382 }
383
384 Ok(wip)
385 }
386 _ => {
387 error!("Expected struct field for nested value");
388 Err(UrlEncodedError::UnsupportedShape(format!(
389 "Expected struct for nested field '{key}'"
390 )))
391 }
392 }
393}
394
395#[derive(Debug)]
397pub enum UrlEncodedError {
398 InvalidNumber(String, String),
400 UnsupportedShape(String),
402 UnsupportedType(String),
404 ReflectError(ReflectError),
406}
407
408impl From<ReflectError> for UrlEncodedError {
409 fn from(err: ReflectError) -> Self {
410 UrlEncodedError::ReflectError(err)
411 }
412}
413
414impl From<ShapeMismatchError> for UrlEncodedError {
415 fn from(err: ShapeMismatchError) -> Self {
416 UrlEncodedError::UnsupportedShape(format!(
417 "shape mismatch: expected {}, got {}",
418 err.expected, err.actual
419 ))
420 }
421}
422
423impl From<AllocError> for UrlEncodedError {
424 fn from(err: AllocError) -> Self {
425 UrlEncodedError::UnsupportedShape(format!(
426 "allocation failed for {}: {}",
427 err.shape, err.operation
428 ))
429 }
430}
431
432impl core::fmt::Display for UrlEncodedError {
433 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
434 match self {
435 UrlEncodedError::InvalidNumber(field, value) => {
436 write!(f, "Invalid number for field '{field}': '{value}'")
437 }
438 UrlEncodedError::UnsupportedShape(shape) => {
439 write!(f, "Unsupported shape: {shape}")
440 }
441 UrlEncodedError::UnsupportedType(ty) => {
442 write!(f, "Unsupported type: {ty}")
443 }
444 UrlEncodedError::ReflectError(err) => {
445 write!(f, "Reflection error: {err}")
446 }
447 }
448 }
449}
450
451impl std::error::Error for UrlEncodedError {}