facet_urlencoded/
lib.rs

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