facet_urlencoded/
lib.rs

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