facet_urlencoded/
lib.rs

1#![warn(missing_docs)]
2#![forbid(unsafe_code)]
3#![doc = include_str!("../README.md")]
4
5use facet_core::{Def, Facet, Type, UserType};
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<'facet>> {
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, 'shape>(
82    wip: Wip<'mem, 'shape>,
83    urlencoded: &str,
84) -> Result<HeapValue<'mem, 'shape>, UrlEncodedError<'shape>> {
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, 'shape>(
181    wip: Wip<'mem, 'shape>,
182    values: &NestedValues,
183) -> Result<HeapValue<'mem, 'shape>, UrlEncodedError<'shape>> {
184    let shape = wip.shape();
185    match shape.ty {
186        Type::User(UserType::Struct(_)) => {
187            trace!("Deserializing struct");
188
189            let mut wip = wip;
190
191            // Process flat fields
192            for key in values.keys() {
193                if let Some(index) = wip.field_index(key) {
194                    let value = values.get(key).unwrap(); // Safe because we're iterating over keys
195                    let field = wip.field(index)?;
196                    wip = deserialize_scalar_field(key, value, field)?;
197                } else {
198                    trace!("Unknown field: {}", key);
199                }
200            }
201
202            // Process nested fields
203            for key in values.nested.keys() {
204                if let Some(index) = wip.field_index(key) {
205                    let nested_values = values.nested.get(key).unwrap(); // Safe because we're iterating over keys
206                    let field = wip.field(index)?;
207                    wip = deserialize_nested_field(key, nested_values, field)?;
208                } else {
209                    trace!("Unknown nested field: {}", key);
210                }
211            }
212
213            trace!("Finished deserializing struct");
214            Ok(wip.build()?)
215        }
216        _ => {
217            error!("Unsupported root type");
218            Err(UrlEncodedError::UnsupportedShape(
219                "Unsupported root type".to_string(),
220            ))
221        }
222    }
223}
224
225/// Helper function to deserialize a scalar field
226fn deserialize_scalar_field<'mem, 'shape>(
227    key: &str,
228    value: &str,
229    wip: Wip<'mem, 'shape>,
230) -> Result<Wip<'mem, 'shape>, UrlEncodedError<'shape>> {
231    match wip.shape().def {
232        Def::Scalar(_sd) => {
233            let wip = if wip.shape().is_type::<String>() {
234                let s = value.to_string();
235                wip.put(s)?
236            } else if wip.shape().is_type::<u64>() {
237                match value.parse::<u64>() {
238                    Ok(num) => wip.put(num)?,
239                    Err(_) => {
240                        return Err(UrlEncodedError::InvalidNumber(
241                            key.to_string(),
242                            value.to_string(),
243                        ));
244                    }
245                }
246            } else {
247                warn!("facet-yaml: unsupported scalar type: {}", wip.shape());
248                return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
249            };
250            Ok(wip.pop()?)
251        }
252        _ => {
253            error!("Expected scalar field");
254            Err(UrlEncodedError::UnsupportedShape(format!(
255                "Expected scalar for field '{}'",
256                key
257            )))
258        }
259    }
260}
261
262/// Helper function to deserialize a nested field
263fn deserialize_nested_field<'mem, 'shape>(
264    key: &str,
265    nested_values: &NestedValues,
266    wip: Wip<'mem, 'shape>,
267) -> Result<Wip<'mem, 'shape>, UrlEncodedError<'shape>> {
268    let shape = wip.shape();
269    match shape.ty {
270        Type::User(UserType::Struct(_)) => {
271            trace!("Deserializing nested struct field: {}", key);
272
273            let mut current_wip = wip;
274
275            // Process flat fields in the nested structure
276            for nested_key in nested_values.keys() {
277                if let Some(index) = current_wip.field_index(nested_key) {
278                    let value = nested_values.get(nested_key).unwrap(); // Safe because we're iterating over keys
279                    let field_wip = current_wip.field(index)?;
280                    current_wip = deserialize_scalar_field(nested_key, value, field_wip)?
281                }
282            }
283
284            // Process deeper nested fields
285            for nested_key in nested_values.nested.keys() {
286                if let Some(index) = current_wip.field_index(nested_key) {
287                    let deeper_nested = nested_values.nested.get(nested_key).unwrap(); // Safe because we're iterating over keys
288                    let field_wip = current_wip.field(index)?;
289                    current_wip = deserialize_nested_field(nested_key, deeper_nested, field_wip)?;
290                }
291            }
292
293            // Return to parent level
294            Ok(current_wip.pop()?)
295        }
296        _ => {
297            error!("Expected struct field for nested value");
298            Err(UrlEncodedError::UnsupportedShape(format!(
299                "Expected struct for nested field '{}'",
300                key
301            )))
302        }
303    }
304}
305
306/// Errors that can occur during URL encoded form data deserialization.
307#[derive(Debug)]
308#[non_exhaustive]
309pub enum UrlEncodedError<'shape> {
310    /// The field value couldn't be parsed as a number.
311    InvalidNumber(String, String),
312    /// The shape is not supported for deserialization.
313    UnsupportedShape(String),
314    /// The type is not supported for deserialization.
315    UnsupportedType(String),
316    /// Reflection error
317    ReflectError(facet_reflect::ReflectError<'shape>),
318}
319
320impl<'shape> From<facet_reflect::ReflectError<'shape>> for UrlEncodedError<'shape> {
321    fn from(err: facet_reflect::ReflectError<'shape>) -> Self {
322        UrlEncodedError::ReflectError(err)
323    }
324}
325
326impl<'shape> core::fmt::Display for UrlEncodedError<'shape> {
327    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328        match self {
329            UrlEncodedError::InvalidNumber(field, value) => {
330                write!(f, "Invalid number for field '{}': '{}'", field, value)
331            }
332            UrlEncodedError::UnsupportedShape(shape) => {
333                write!(f, "Unsupported shape: {}", shape)
334            }
335            UrlEncodedError::UnsupportedType(ty) => {
336                write!(f, "Unsupported type: {}", ty)
337            }
338            UrlEncodedError::ReflectError(err) => {
339                write!(f, "Reflection error: {}", err)
340            }
341        }
342    }
343}
344
345impl<'shape> std::error::Error for UrlEncodedError<'shape> {}