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>(
279 key: &str,
280 value: &str,
281 mut wip: Partial<'facet, BORROW>,
282) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
283 match wip.shape().def {
284 Def::Scalar => {
285 if wip.shape().is_type::<String>() {
286 let s = value.to_string();
287 wip = wip.set(s)?;
288 } else if wip.shape().is_type::<u64>() {
289 match value.parse::<u64>() {
290 Ok(num) => wip = wip.set(num)?,
291 Err(_) => {
292 return Err(UrlEncodedError::InvalidNumber(
293 key.to_string(),
294 value.to_string(),
295 ));
296 }
297 };
298 } else {
299 warn!("facet-yaml: unsupported scalar type: {}", wip.shape());
300 return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
301 }
302 Ok(wip)
303 }
304 _ => {
305 error!("Expected scalar field");
306 Err(UrlEncodedError::UnsupportedShape(format!(
307 "Expected scalar for field '{key}'"
308 )))
309 }
310 }
311}
312
313fn deserialize_nested_field<'facet, const BORROW: bool>(
315 key: &str,
316 nested_values: &NestedValues,
317 mut wip: Partial<'facet, BORROW>,
318) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
319 let shape = wip.shape();
320 match shape.ty {
321 Type::User(UserType::Struct(_)) => {
322 trace!("Deserializing nested struct field: {key}");
323
324 for nested_key in nested_values.keys() {
326 if let Some(index) = wip.field_index(nested_key) {
327 let value = nested_values.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
329 wip = deserialize_scalar_field(nested_key, value, wip)?;
330 wip = wip.end()?;
331 }
332 }
333
334 for nested_key in nested_values.nested.keys() {
336 if let Some(index) = wip.field_index(nested_key) {
337 let deeper_nested = nested_values.nested.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
339 wip = deserialize_nested_field(nested_key, deeper_nested, wip)?;
340 wip = wip.end()?;
341 }
342 }
343
344 Ok(wip)
345 }
346 _ => {
347 error!("Expected struct field for nested value");
348 Err(UrlEncodedError::UnsupportedShape(format!(
349 "Expected struct for nested field '{key}'"
350 )))
351 }
352 }
353}
354
355#[derive(Debug)]
357pub enum UrlEncodedError {
358 InvalidNumber(String, String),
360 UnsupportedShape(String),
362 UnsupportedType(String),
364 ReflectError(ReflectError),
366}
367
368impl From<ReflectError> for UrlEncodedError {
369 fn from(err: ReflectError) -> Self {
370 UrlEncodedError::ReflectError(err)
371 }
372}
373
374impl From<ShapeMismatchError> for UrlEncodedError {
375 fn from(err: ShapeMismatchError) -> Self {
376 UrlEncodedError::UnsupportedShape(format!(
377 "shape mismatch: expected {}, got {}",
378 err.expected, err.actual
379 ))
380 }
381}
382
383impl From<AllocError> for UrlEncodedError {
384 fn from(err: AllocError) -> Self {
385 UrlEncodedError::UnsupportedShape(format!(
386 "allocation failed for {}: {}",
387 err.shape, err.operation
388 ))
389 }
390}
391
392impl core::fmt::Display for UrlEncodedError {
393 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
394 match self {
395 UrlEncodedError::InvalidNumber(field, value) => {
396 write!(f, "Invalid number for field '{field}': '{value}'")
397 }
398 UrlEncodedError::UnsupportedShape(shape) => {
399 write!(f, "Unsupported shape: {shape}")
400 }
401 UrlEncodedError::UnsupportedType(ty) => {
402 write!(f, "Unsupported type: {ty}")
403 }
404 UrlEncodedError::ReflectError(err) => {
405 write!(f, "Reflection error: {err}")
406 }
407 }
408 }
409}
410
411impl std::error::Error for UrlEncodedError {}