facet_urlencoded/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use facet_core::{Facet, Opaque};
5use facet_reflect::{PokeStruct, PokeUninit};
6use log::*;
7
8#[cfg(test)]
9mod tests;
10
11/// Deserializes a URL encoded form data string into a value of type `T` that implements `Facet`.
12///
13/// This function supports parsing both flat structures and nested structures using the common
14/// bracket notation. For example, a form field like `user[name]` will be deserialized into
15/// a struct with a field named `user` that contains a field named `name`.
16///
17/// # Nested Structure Format
18///
19/// For nested structures, the library supports the standard bracket notation used in most web frameworks:
20/// - Simple nested objects: `object[field]=value`
21/// - Deeply nested objects: `object[field1][field2]=value`
22///
23/// # Basic Example
24///
25/// ```
26/// use facet::Facet;
27/// use facet_urlencoded::from_str;
28///
29/// #[derive(Debug, Facet, PartialEq)]
30/// struct SearchParams {
31///     query: String,
32///     page: u64,
33/// }
34///
35/// let query_string = "query=rust+programming&page=2";
36///
37/// let params: SearchParams = from_str(query_string).expect("Failed to parse URL encoded data");
38/// assert_eq!(params, SearchParams { query: "rust programming".to_string(), page: 2 });
39/// ```
40///
41/// # Nested Structure Example
42///
43/// ```
44/// use facet::Facet;
45/// use facet_urlencoded::from_str;
46///
47/// #[derive(Debug, Facet, PartialEq)]
48/// struct Address {
49///     street: String,
50///     city: String,
51/// }
52///
53/// #[derive(Debug, Facet, PartialEq)]
54/// struct User {
55///     name: String,
56///     address: Address,
57/// }
58///
59/// let query_string = "name=John+Doe&address[street]=123+Main+St&address[city]=Anytown";
60///
61/// let user: User = from_str(query_string).expect("Failed to parse URL encoded data");
62/// assert_eq!(user, User {
63///     name: "John Doe".to_string(),
64///     address: Address {
65///         street: "123 Main St".to_string(),
66///         city: "Anytown".to_string(),
67///     },
68/// });
69/// ```
70pub fn from_str<T: Facet>(urlencoded: &str) -> Result<T, UrlEncodedError> {
71    let (poke, _guard) = PokeUninit::alloc::<T>();
72    let opaque = from_str_opaque(poke, urlencoded)?;
73    Ok(unsafe { opaque.read::<T>() })
74}
75
76/// Deserializes a URL encoded form data string into an `Opaque` value.
77///
78/// This is the lower-level function that works with `Poke` directly.
79fn from_str_opaque<'mem>(
80    poke: PokeUninit<'mem>,
81    urlencoded: &str,
82) -> Result<Opaque<'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    // Process the deserialization
95    deserialize_value(poke, &nested_values)
96}
97
98/// Internal helper struct to represent nested values from URL-encoded data
99struct NestedValues {
100    // Root level key-value pairs
101    flat: std::collections::HashMap<String, String>,
102    // Nested structures: key -> nested map
103    nested: std::collections::HashMap<String, NestedValues>,
104}
105
106impl NestedValues {
107    fn new() -> Self {
108        Self {
109            flat: std::collections::HashMap::new(),
110            nested: std::collections::HashMap::new(),
111        }
112    }
113
114    fn insert(&mut self, key: &str, value: String) {
115        // For bracket notation like user[name] or user[address][city]
116        if let Some(open_bracket) = key.find('[') {
117            if let Some(close_bracket) = key.find(']') {
118                if open_bracket < close_bracket {
119                    let parent_key = &key[0..open_bracket];
120                    let nested_key = &key[(open_bracket + 1)..close_bracket];
121                    let remainder = &key[(close_bracket + 1)..];
122
123                    let nested = self
124                        .nested
125                        .entry(parent_key.to_string())
126                        .or_insert_with(NestedValues::new);
127
128                    if remainder.is_empty() {
129                        // Simple case: user[name]=value
130                        nested.flat.insert(nested_key.to_string(), value);
131                    } else {
132                        // Handle deeply nested case like user[address][city]=value
133                        let new_key = format!("{}{}", nested_key, remainder);
134                        nested.insert(&new_key, value);
135                    }
136                    return;
137                }
138            }
139        }
140
141        // If we get here, it's a flat key-value pair
142        self.flat.insert(key.to_string(), value);
143    }
144
145    fn get(&self, key: &str) -> Option<&String> {
146        self.flat.get(key)
147    }
148
149    fn get_nested(&self, key: &str) -> Option<&NestedValues> {
150        self.nested.get(key)
151    }
152
153    fn keys(&self) -> impl Iterator<Item = &String> {
154        self.flat.keys()
155    }
156
157    fn nested_keys(&self) -> impl Iterator<Item = &String> {
158        self.nested.keys()
159    }
160}
161
162/// Deserialize a value recursively using the nested values
163fn deserialize_value<'mem>(
164    poke: PokeUninit<'mem>,
165    values: &NestedValues,
166) -> Result<Opaque<'mem>, UrlEncodedError> {
167    match poke {
168        PokeUninit::Struct(mut ps) => {
169            trace!("Deserializing struct");
170
171            // Process flat fields
172            for key in values.keys() {
173                if let Ok((index, field_poke)) = ps.field_by_name(key) {
174                    let value = values.get(key).unwrap(); // Safe because we're iterating over keys
175                    deserialize_scalar_field(key, value, field_poke, index, &mut ps)?;
176                } else {
177                    warn!("Unknown field: {}", key);
178                    // Skip unknown fields
179                }
180            }
181
182            // Process nested fields
183            for key in values.nested_keys() {
184                if let Ok((index, field_poke)) = ps.field_by_name(key) {
185                    if let Some(nested_values) = values.get_nested(key) {
186                        match field_poke {
187                            PokeUninit::Struct(_) => {
188                                let _nested_opaque = deserialize_value(field_poke, nested_values)?;
189                                unsafe {
190                                    ps.mark_initialized(index);
191                                }
192                            }
193                            _ => {
194                                return Err(UrlEncodedError::UnsupportedShape(format!(
195                                    "Expected struct for nested field '{}'",
196                                    key
197                                )));
198                            }
199                        }
200                    }
201                } else {
202                    warn!("Unknown nested field: {}", key);
203                    // Skip unknown fields
204                }
205            }
206
207            trace!("Finished deserializing struct");
208            Ok(ps.build_in_place())
209        }
210        _ => {
211            error!("Unsupported root type");
212            Err(UrlEncodedError::UnsupportedShape(
213                "Unsupported root type".to_string(),
214            ))
215        }
216    }
217}
218
219/// Helper function to deserialize a scalar field
220fn deserialize_scalar_field<'mem>(
221    key: &str,
222    value: &str,
223    field_poke: PokeUninit<'mem>,
224    index: usize,
225    ps: &mut PokeStruct<'mem>,
226) -> Result<(), UrlEncodedError> {
227    match field_poke {
228        PokeUninit::Scalar(ps_scalar) => {
229            if ps_scalar.shape().is_type::<String>() {
230                let s = value.to_string();
231                ps_scalar.put(s);
232            } else if ps_scalar.shape().is_type::<u64>() {
233                match value.parse::<u64>() {
234                    Ok(num) => {
235                        ps_scalar.put(num);
236                    }
237                    Err(_) => {
238                        return Err(UrlEncodedError::InvalidNumber(
239                            key.to_string(),
240                            value.to_string(),
241                        ));
242                    }
243                }
244            } else {
245                warn!("Unsupported scalar type: {}", ps_scalar.shape());
246                return Err(UrlEncodedError::UnsupportedType(format!(
247                    "{}",
248                    ps_scalar.shape()
249                )));
250            }
251            unsafe { ps.mark_initialized(index) };
252            Ok(())
253        }
254        _ => {
255            error!("Expected scalar field");
256            Err(UrlEncodedError::UnsupportedShape(format!(
257                "Expected scalar for field '{}'",
258                key
259            )))
260        }
261    }
262}
263
264/// Errors that can occur during URL encoded form data deserialization.
265#[derive(Debug)]
266#[non_exhaustive]
267pub enum UrlEncodedError {
268    /// The field value couldn't be parsed as a number.
269    InvalidNumber(String, String),
270    /// The shape is not supported for deserialization.
271    UnsupportedShape(String),
272    /// The type is not supported for deserialization.
273    UnsupportedType(String),
274}
275
276impl core::fmt::Display for UrlEncodedError {
277    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
278        match self {
279            UrlEncodedError::InvalidNumber(field, value) => {
280                write!(f, "Invalid number for field '{}': '{}'", field, value)
281            }
282            UrlEncodedError::UnsupportedShape(shape) => {
283                write!(f, "Unsupported shape: {}", shape)
284            }
285            UrlEncodedError::UnsupportedType(ty) => {
286                write!(f, "Unsupported type: {}", ty)
287            }
288        }
289    }
290}
291
292impl std::error::Error for UrlEncodedError {}