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<T: Facet>(urlencoded: &str) -> Result<T, UrlEncodedError> {
72    let val = from_str_value(Wip::alloc::<T>(), urlencoded)?;
73    Ok(val.materialize::<T>()?)
74}
75
76/// Deserializes a URL encoded form data string into an heap-allocated value.
77///
78/// This is the lower-level function that works with `Wip` directly.
79fn from_str_value<'mem>(
80    wip: Wip<'mem>,
81    urlencoded: &str,
82) -> Result<HeapValue<'mem>, UrlEncodedError> {
83    trace!("Starting URL encoded form data deserialization");
84
85    // Parse the URL encoded string into key-value pairs
86    let pairs = form_urlencoded::parse(urlencoded.as_bytes());
87
88    // Process the input into a nested structure
89    let mut nested_values = NestedValues::new();
90    for (key, value) in pairs {
91        nested_values.insert(&key, value.to_string());
92    }
93
94    // Create pre-initialized structure so that we have all the required fields
95    // for better error reporting when fields are missing
96    initialize_nested_structures(&mut nested_values);
97
98    // Process the deserialization
99    deserialize_value(wip, &nested_values)
100}
101
102/// Ensures that all nested structures have entries in the NestedValues
103/// This helps ensure we get better error reporting when fields are missing
104fn initialize_nested_structures(nested: &mut NestedValues) {
105    // Go through each nested value and recursively initialize it
106    for nested_value in nested.nested.values_mut() {
107        initialize_nested_structures(nested_value);
108    }
109}
110
111/// Internal helper struct to represent nested values from URL-encoded data
112struct NestedValues {
113    // Root level key-value pairs
114    flat: std::collections::HashMap<String, String>,
115    // Nested structures: key -> nested map
116    nested: std::collections::HashMap<String, NestedValues>,
117}
118
119impl NestedValues {
120    fn new() -> Self {
121        Self {
122            flat: std::collections::HashMap::new(),
123            nested: std::collections::HashMap::new(),
124        }
125    }
126
127    fn insert(&mut self, key: &str, value: String) {
128        // For bracket notation like user[name] or user[address][city]
129        if let Some(open_bracket) = key.find('[') {
130            if let Some(close_bracket) = key.find(']') {
131                if open_bracket < close_bracket {
132                    let parent_key = &key[0..open_bracket];
133                    let nested_key = &key[(open_bracket + 1)..close_bracket];
134                    let remainder = &key[(close_bracket + 1)..];
135
136                    let nested = self
137                        .nested
138                        .entry(parent_key.to_string())
139                        .or_insert_with(NestedValues::new);
140
141                    if remainder.is_empty() {
142                        // Simple case: user[name]=value
143                        nested.flat.insert(nested_key.to_string(), value);
144                    } else {
145                        // Handle deeply nested case like user[address][city]=value
146                        let new_key = format!("{}{}", nested_key, remainder);
147                        nested.insert(&new_key, value);
148                    }
149                    return;
150                }
151            }
152        }
153
154        // If we get here, it's a flat key-value pair
155        self.flat.insert(key.to_string(), value);
156    }
157
158    fn get(&self, key: &str) -> Option<&String> {
159        self.flat.get(key)
160    }
161
162    #[expect(dead_code)]
163    fn get_nested(&self, key: &str) -> Option<&NestedValues> {
164        self.nested.get(key)
165    }
166
167    fn keys(&self) -> impl Iterator<Item = &String> {
168        self.flat.keys()
169    }
170
171    #[expect(dead_code)]
172    fn nested_keys(&self) -> impl Iterator<Item = &String> {
173        self.nested.keys()
174    }
175}
176
177/// Deserialize a value recursively using the nested values
178fn deserialize_value<'mem>(
179    wip: Wip<'mem>,
180    values: &NestedValues,
181) -> Result<HeapValue<'mem>, UrlEncodedError> {
182    match wip.shape().def {
183        Def::Struct(_sd) => {
184            trace!("Deserializing struct");
185
186            let mut wip = wip;
187
188            // Process flat fields
189            for key in values.keys() {
190                if let Some(index) = wip.field_index(key) {
191                    let value = values.get(key).unwrap(); // Safe because we're iterating over keys
192                    let field = wip.field(index)?;
193                    wip = deserialize_scalar_field(key, value, field)?;
194                } else {
195                    trace!("Unknown field: {}", key);
196                }
197            }
198
199            // Process nested fields
200            for key in values.nested.keys() {
201                if let Some(index) = wip.field_index(key) {
202                    let nested_values = values.nested.get(key).unwrap(); // Safe because we're iterating over keys
203                    let field = wip.field(index)?;
204                    wip = deserialize_nested_field(key, nested_values, field)?;
205                } else {
206                    trace!("Unknown nested field: {}", key);
207                }
208            }
209
210            trace!("Finished deserializing struct");
211            Ok(wip.build()?)
212        }
213        _ => {
214            error!("Unsupported root type");
215            Err(UrlEncodedError::UnsupportedShape(
216                "Unsupported root type".to_string(),
217            ))
218        }
219    }
220}
221
222/// Helper function to deserialize a scalar field
223fn deserialize_scalar_field<'mem>(
224    key: &str,
225    value: &str,
226    wip: Wip<'mem>,
227) -> Result<Wip<'mem>, UrlEncodedError> {
228    match wip.shape().def {
229        Def::Scalar(_sd) => {
230            let wip = if wip.shape().is_type::<String>() {
231                let s = value.to_string();
232                wip.put(s)?
233            } else if wip.shape().is_type::<u64>() {
234                match value.parse::<u64>() {
235                    Ok(num) => wip.put(num)?,
236                    Err(_) => {
237                        return Err(UrlEncodedError::InvalidNumber(
238                            key.to_string(),
239                            value.to_string(),
240                        ));
241                    }
242                }
243            } else {
244                warn!("Unsupported scalar type: {}", wip.shape());
245                return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
246            };
247            Ok(wip.pop()?)
248        }
249        _ => {
250            error!("Expected scalar field");
251            Err(UrlEncodedError::UnsupportedShape(format!(
252                "Expected scalar for field '{}'",
253                key
254            )))
255        }
256    }
257}
258
259/// Helper function to deserialize a nested field
260fn deserialize_nested_field<'mem>(
261    key: &str,
262    nested_values: &NestedValues,
263    wip: Wip<'mem>,
264) -> Result<Wip<'mem>, UrlEncodedError> {
265    match wip.shape().def {
266        Def::Struct(_sd) => {
267            trace!("Deserializing nested struct field: {}", key);
268
269            let mut current_wip = wip;
270
271            // Process flat fields in the nested structure
272            for nested_key in nested_values.keys() {
273                if let Some(index) = current_wip.field_index(nested_key) {
274                    let value = nested_values.get(nested_key).unwrap(); // Safe because we're iterating over keys
275                    let field_wip = current_wip.field(index)?;
276                    current_wip = deserialize_scalar_field(nested_key, value, field_wip)?
277                }
278            }
279
280            // Process deeper nested fields
281            for nested_key in nested_values.nested.keys() {
282                if let Some(index) = current_wip.field_index(nested_key) {
283                    let deeper_nested = nested_values.nested.get(nested_key).unwrap(); // Safe because we're iterating over keys
284                    let field_wip = current_wip.field(index)?;
285                    current_wip = deserialize_nested_field(nested_key, deeper_nested, field_wip)?;
286                }
287            }
288
289            // Return to parent level
290            Ok(current_wip.pop()?)
291        }
292        _ => {
293            error!("Expected struct field for nested value");
294            Err(UrlEncodedError::UnsupportedShape(format!(
295                "Expected struct for nested field '{}'",
296                key
297            )))
298        }
299    }
300}
301
302/// Errors that can occur during URL encoded form data deserialization.
303#[derive(Debug)]
304#[non_exhaustive]
305pub enum UrlEncodedError {
306    /// The field value couldn't be parsed as a number.
307    InvalidNumber(String, String),
308    /// The shape is not supported for deserialization.
309    UnsupportedShape(String),
310    /// The type is not supported for deserialization.
311    UnsupportedType(String),
312    /// Reflection error
313    ReflectError(facet_reflect::ReflectError),
314}
315
316impl From<facet_reflect::ReflectError> for UrlEncodedError {
317    fn from(err: facet_reflect::ReflectError) -> Self {
318        UrlEncodedError::ReflectError(err)
319    }
320}
321
322impl core::fmt::Display for UrlEncodedError {
323    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
324        match self {
325            UrlEncodedError::InvalidNumber(field, value) => {
326                write!(f, "Invalid number for field '{}': '{}'", field, value)
327            }
328            UrlEncodedError::UnsupportedShape(shape) => {
329                write!(f, "Unsupported shape: {}", shape)
330            }
331            UrlEncodedError::UnsupportedType(ty) => {
332                write!(f, "Unsupported type: {}", ty)
333            }
334            UrlEncodedError::ReflectError(err) => {
335                write!(f, "Reflection error: {}", err)
336            }
337        }
338    }
339}
340
341impl std::error::Error for UrlEncodedError {}