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