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