Skip to main content

facet_urlencoded/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use facet_core::{Def, Facet, Type, UserType};
5use facet_reflect::{AllocError, Partial, ReflectError, ShapeMismatchError, TypePlan};
6use log::*;
7
8#[cfg(test)]
9mod tests;
10
11mod form;
12pub use form::Form;
13
14mod query;
15pub use query::Query;
16
17#[cfg(feature = "axum")]
18mod axum;
19#[cfg(feature = "axum")]
20pub use self::axum::{FormRejection, QueryRejection};
21
22/// Deserializes a URL encoded form data string into a value of type `T` that implements `Facet`.
23///
24/// This function supports parsing both flat structures and nested structures using the common
25/// bracket notation. For example, a form field like `user[name]` will be deserialized into
26/// a struct with a field named `user` that contains a field named `name`.
27///
28/// # Nested Structure Format
29///
30/// For nested structures, the library supports the standard bracket notation used in most web frameworks:
31/// - Simple nested objects: `object[field]=value`
32/// - Deeply nested objects: `object[field1][field2]=value`
33///
34/// # Basic Example
35///
36/// ```
37/// use facet::Facet;
38/// use facet_urlencoded::from_str;
39///
40/// #[derive(Debug, Facet, PartialEq)]
41/// struct SearchParams {
42///     query: String,
43///     page: u64,
44/// }
45///
46/// let query_string = "query=rust+programming&page=2";
47///
48/// let params: SearchParams = from_str(query_string).expect("Failed to parse URL encoded data");
49/// assert_eq!(params, SearchParams { query: "rust programming".to_string(), page: 2 });
50/// ```
51///
52/// # Nested Structure Example
53///
54/// ```
55/// use facet::Facet;
56/// use facet_urlencoded::from_str;
57///
58/// #[derive(Debug, Facet, PartialEq)]
59/// struct Address {
60///     street: String,
61///     city: String,
62/// }
63///
64/// #[derive(Debug, Facet, PartialEq)]
65/// struct User {
66///     name: String,
67///     address: Address,
68/// }
69///
70/// let query_string = "name=John+Doe&address[street]=123+Main+St&address[city]=Anytown";
71///
72/// let user: User = from_str(query_string).expect("Failed to parse URL encoded data");
73/// assert_eq!(user, User {
74///     name: "John Doe".to_string(),
75///     address: Address {
76///         street: "123 Main St".to_string(),
77///         city: "Anytown".to_string(),
78///     },
79/// });
80/// ```
81pub fn from_str<'input: 'facet, 'facet, T: Facet<'facet>>(
82    urlencoded: &'input str,
83) -> Result<T, UrlEncodedError> {
84    let plan = TypePlan::<T>::build()?;
85    let partial = plan.partial()?;
86    let partial = from_str_value(partial, urlencoded)?;
87    let result: T = partial.build()?.materialize()?;
88    Ok(result)
89}
90
91/// Deserializes a URL encoded form data string into an owned value of type `T`.
92///
93/// This is similar to [`from_str`] but works with types that implement `Facet<'static>`,
94/// which means they don't borrow from the input. This is useful when the input is
95/// temporary (e.g., from an HTTP request body) but you need an owned result.
96///
97/// # Example
98///
99/// ```
100/// use facet::Facet;
101/// use facet_urlencoded::from_str_owned;
102///
103/// #[derive(Debug, Facet, PartialEq)]
104/// struct SearchParams {
105///     query: String,
106///     page: u64,
107/// }
108///
109/// let query_string = "query=rust&page=1";
110/// let params: SearchParams = from_str_owned(query_string).expect("Failed to parse");
111/// assert_eq!(params, SearchParams { query: "rust".to_string(), page: 1 });
112/// ```
113pub fn from_str_owned<T: Facet<'static>>(urlencoded: &str) -> Result<T, UrlEncodedError> {
114    let plan = TypePlan::<T>::build()?;
115    let partial = plan.partial_owned()?;
116    let partial = from_str_value(partial, urlencoded)?;
117    let result: T = partial.build()?.materialize()?;
118    Ok(result)
119}
120
121/// Deserializes a URL encoded form data string into an heap-allocated value.
122///
123/// This is the lower-level function that works with `Partial` directly.
124fn from_str_value<'facet, const BORROW: bool>(
125    mut wip: Partial<'facet, BORROW>,
126    urlencoded: &str,
127) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
128    trace!("Starting URL encoded form data deserialization");
129
130    // Parse the URL encoded string into key-value pairs
131    let pairs = form_urlencoded::parse(urlencoded.as_bytes());
132
133    // Process the input into a nested structure
134    let mut nested_values = NestedValues::new();
135    for (key, value) in pairs {
136        nested_values.insert(&key, value.to_string());
137    }
138
139    // Create pre-initialized structure so that we have all the required fields
140    // for better error reporting when fields are missing
141    initialize_nested_structures(&mut nested_values);
142
143    // Process the deserialization
144    wip = deserialize_value(wip, &nested_values)?;
145    Ok(wip)
146}
147
148/// Ensures that all nested structures have entries in the NestedValues
149/// This helps ensure we get better error reporting when fields are missing
150fn initialize_nested_structures(nested: &mut NestedValues) {
151    // Go through each nested value and recursively initialize it
152    for nested_value in nested.nested.values_mut() {
153        initialize_nested_structures(nested_value);
154    }
155}
156
157/// Internal helper struct to represent nested values from URL-encoded data
158struct NestedValues {
159    // Root level key-value pairs
160    flat: std::collections::HashMap<String, String>,
161    // Nested structures: key -> nested map
162    nested: std::collections::HashMap<String, NestedValues>,
163}
164
165impl NestedValues {
166    fn new() -> Self {
167        Self {
168            flat: std::collections::HashMap::new(),
169            nested: std::collections::HashMap::new(),
170        }
171    }
172
173    fn insert(&mut self, key: &str, value: String) {
174        // For bracket notation like user[name] or user[address][city]
175        if let Some(open_bracket) = key.find('[')
176            && let Some(close_bracket) = key.find(']')
177            && open_bracket < close_bracket
178        {
179            let parent_key = &key[0..open_bracket];
180            let nested_key = &key[(open_bracket + 1)..close_bracket];
181            let remainder = &key[(close_bracket + 1)..];
182
183            let nested = self
184                .nested
185                .entry(parent_key.to_string())
186                .or_insert_with(NestedValues::new);
187
188            if remainder.is_empty() {
189                // Simple case: user[name]=value
190                nested.flat.insert(nested_key.to_string(), value);
191            } else {
192                // Handle deeply nested case like user[address][city]=value
193                let new_key = format!("{nested_key}{remainder}");
194                nested.insert(&new_key, value);
195            }
196            return;
197        }
198
199        // If we get here, it's a flat key-value pair
200        self.flat.insert(key.to_string(), value);
201    }
202
203    fn get(&self, key: &str) -> Option<&String> {
204        self.flat.get(key)
205    }
206
207    #[expect(dead_code)]
208    fn get_nested(&self, key: &str) -> Option<&NestedValues> {
209        self.nested.get(key)
210    }
211
212    fn keys(&self) -> impl Iterator<Item = &String> {
213        self.flat.keys()
214    }
215
216    #[expect(dead_code)]
217    fn nested_keys(&self) -> impl Iterator<Item = &String> {
218        self.nested.keys()
219    }
220}
221
222/// Deserialize a value recursively using the nested values
223fn deserialize_value<'facet, const BORROW: bool>(
224    mut wip: Partial<'facet, BORROW>,
225    values: &NestedValues,
226) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
227    let shape = wip.shape();
228    match shape.ty {
229        Type::User(UserType::Struct(_)) => {
230            trace!("Deserializing struct");
231
232            // Process flat fields
233            for key in values.keys() {
234                if let Some(index) = wip.field_index(key) {
235                    let value = values.get(key).unwrap(); // Safe because we're iterating over keys
236                    wip = wip.begin_nth_field(index)?;
237                    wip = deserialize_scalar_field(key, value, wip)?;
238                    wip = wip.end()?;
239                } else {
240                    trace!("Unknown field: {key}");
241                }
242            }
243
244            // Process nested fields
245            for key in values.nested.keys() {
246                if let Some(index) = wip.field_index(key) {
247                    let nested_values = values.nested.get(key).unwrap(); // Safe because we're iterating over keys
248                    wip = wip.begin_nth_field(index)?;
249                    wip = deserialize_nested_field(key, nested_values, wip)?;
250                    wip = wip.end()?;
251                } else {
252                    trace!("Unknown nested field: {key}");
253                }
254            }
255
256            trace!("Finished deserializing struct");
257            Ok(wip)
258        }
259        _ => {
260            error!("Unsupported root type");
261            Err(UrlEncodedError::UnsupportedShape(
262                "Unsupported root type".to_string(),
263            ))
264        }
265    }
266}
267
268/// Helper function to deserialize a scalar field
269fn deserialize_scalar_field<'facet, const BORROW: bool>(
270    key: &str,
271    value: &str,
272    mut wip: Partial<'facet, BORROW>,
273) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
274    match wip.shape().def {
275        Def::Scalar => {
276            if wip.shape().is_type::<String>() {
277                let s = value.to_string();
278                wip = wip.set(s)?;
279            } else if wip.shape().is_type::<u64>() {
280                match value.parse::<u64>() {
281                    Ok(num) => wip = wip.set(num)?,
282                    Err(_) => {
283                        return Err(UrlEncodedError::InvalidNumber(
284                            key.to_string(),
285                            value.to_string(),
286                        ));
287                    }
288                };
289            } else {
290                warn!("facet-yaml: unsupported scalar type: {}", wip.shape());
291                return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
292            }
293            Ok(wip)
294        }
295        _ => {
296            error!("Expected scalar field");
297            Err(UrlEncodedError::UnsupportedShape(format!(
298                "Expected scalar for field '{key}'"
299            )))
300        }
301    }
302}
303
304/// Helper function to deserialize a nested field
305fn deserialize_nested_field<'facet, const BORROW: bool>(
306    key: &str,
307    nested_values: &NestedValues,
308    mut wip: Partial<'facet, BORROW>,
309) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
310    let shape = wip.shape();
311    match shape.ty {
312        Type::User(UserType::Struct(_)) => {
313            trace!("Deserializing nested struct field: {key}");
314
315            // Process flat fields in the nested structure
316            for nested_key in nested_values.keys() {
317                if let Some(index) = wip.field_index(nested_key) {
318                    let value = nested_values.get(nested_key).unwrap(); // Safe because we're iterating over keys
319                    wip = wip.begin_nth_field(index)?;
320                    wip = deserialize_scalar_field(nested_key, value, wip)?;
321                    wip = wip.end()?;
322                }
323            }
324
325            // Process deeper nested fields
326            for nested_key in nested_values.nested.keys() {
327                if let Some(index) = wip.field_index(nested_key) {
328                    let deeper_nested = nested_values.nested.get(nested_key).unwrap(); // Safe because we're iterating over keys
329                    wip = wip.begin_nth_field(index)?;
330                    wip = deserialize_nested_field(nested_key, deeper_nested, wip)?;
331                    wip = wip.end()?;
332                }
333            }
334
335            Ok(wip)
336        }
337        _ => {
338            error!("Expected struct field for nested value");
339            Err(UrlEncodedError::UnsupportedShape(format!(
340                "Expected struct for nested field '{key}'"
341            )))
342        }
343    }
344}
345
346/// Errors that can occur during URL encoded form data deserialization.
347#[derive(Debug)]
348pub enum UrlEncodedError {
349    /// The field value couldn't be parsed as a number.
350    InvalidNumber(String, String),
351    /// The shape is not supported for deserialization.
352    UnsupportedShape(String),
353    /// The type is not supported for deserialization.
354    UnsupportedType(String),
355    /// Reflection error
356    ReflectError(ReflectError),
357}
358
359impl From<ReflectError> for UrlEncodedError {
360    fn from(err: ReflectError) -> Self {
361        UrlEncodedError::ReflectError(err)
362    }
363}
364
365impl From<ShapeMismatchError> for UrlEncodedError {
366    fn from(err: ShapeMismatchError) -> Self {
367        UrlEncodedError::UnsupportedShape(format!(
368            "shape mismatch: expected {}, got {}",
369            err.expected, err.actual
370        ))
371    }
372}
373
374impl From<AllocError> for UrlEncodedError {
375    fn from(err: AllocError) -> Self {
376        UrlEncodedError::UnsupportedShape(format!(
377            "allocation failed for {}: {}",
378            err.shape, err.operation
379        ))
380    }
381}
382
383impl core::fmt::Display for UrlEncodedError {
384    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
385        match self {
386            UrlEncodedError::InvalidNumber(field, value) => {
387                write!(f, "Invalid number for field '{field}': '{value}'")
388            }
389            UrlEncodedError::UnsupportedShape(shape) => {
390                write!(f, "Unsupported shape: {shape}")
391            }
392            UrlEncodedError::UnsupportedType(ty) => {
393                write!(f, "Unsupported type: {ty}")
394            }
395            UrlEncodedError::ReflectError(err) => {
396                write!(f, "Reflection error: {err}")
397            }
398        }
399    }
400}
401
402impl std::error::Error for UrlEncodedError {}