facet_urlencoded/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use facet_core::{Def, Facet, Type, UserType};
5use facet_reflect::{Partial, ReflectError};
6use log::*;
7
8#[cfg(test)]
9mod tests;
10
11mod form;
12pub use form::Form;
13
14mod query;
15pub use query::Query;
16
17#[cfg(feature = "axum")]
18mod axum;
19#[cfg(feature = "axum")]
20pub use self::axum::{FormRejection, QueryRejection};
21
22/// Deserializes a URL encoded form data string into a value of type `T` that implements `Facet`.
23///
24/// This function supports parsing both flat structures and nested structures using the common
25/// bracket notation. For example, a form field like `user[name]` will be deserialized into
26/// a struct with a field named `user` that contains a field named `name`.
27///
28/// # Nested Structure Format
29///
30/// For nested structures, the library supports the standard bracket notation used in most web frameworks:
31/// - Simple nested objects: `object[field]=value`
32/// - Deeply nested objects: `object[field1][field2]=value`
33///
34/// # Basic Example
35///
36/// ```
37/// use facet::Facet;
38/// use facet_urlencoded::from_str;
39///
40/// #[derive(Debug, Facet, PartialEq)]
41/// struct SearchParams {
42///     query: String,
43///     page: u64,
44/// }
45///
46/// let query_string = "query=rust+programming&page=2";
47///
48/// let params: SearchParams = from_str(query_string).expect("Failed to parse URL encoded data");
49/// assert_eq!(params, SearchParams { query: "rust programming".to_string(), page: 2 });
50/// ```
51///
52/// # Nested Structure Example
53///
54/// ```
55/// use facet::Facet;
56/// use facet_urlencoded::from_str;
57///
58/// #[derive(Debug, Facet, PartialEq)]
59/// struct Address {
60///     street: String,
61///     city: String,
62/// }
63///
64/// #[derive(Debug, Facet, PartialEq)]
65/// struct User {
66///     name: String,
67///     address: Address,
68/// }
69///
70/// let query_string = "name=John+Doe&address[street]=123+Main+St&address[city]=Anytown";
71///
72/// let user: User = from_str(query_string).expect("Failed to parse URL encoded data");
73/// assert_eq!(user, User {
74///     name: "John Doe".to_string(),
75///     address: Address {
76///         street: "123 Main St".to_string(),
77///         city: "Anytown".to_string(),
78///     },
79/// });
80/// ```
81pub fn from_str<'input: 'facet, 'facet, T: Facet<'facet>>(
82    urlencoded: &'input str,
83) -> Result<T, UrlEncodedError> {
84    let partial = Partial::alloc::<T>()?;
85    let partial = from_str_value(partial, urlencoded)?;
86    let result: T = partial.build()?.materialize()?;
87    Ok(result)
88}
89
90/// Deserializes a URL encoded form data string into an owned value of type `T`.
91///
92/// This is similar to [`from_str`] but works with types that implement `Facet<'static>`,
93/// which means they don't borrow from the input. This is useful when the input is
94/// temporary (e.g., from an HTTP request body) but you need an owned result.
95///
96/// # Example
97///
98/// ```
99/// use facet::Facet;
100/// use facet_urlencoded::from_str_owned;
101///
102/// #[derive(Debug, Facet, PartialEq)]
103/// struct SearchParams {
104///     query: String,
105///     page: u64,
106/// }
107///
108/// let query_string = "query=rust&page=1";
109/// let params: SearchParams = from_str_owned(query_string).expect("Failed to parse");
110/// assert_eq!(params, SearchParams { query: "rust".to_string(), page: 1 });
111/// ```
112pub fn from_str_owned<T: Facet<'static>>(urlencoded: &str) -> Result<T, UrlEncodedError> {
113    let partial = Partial::alloc::<T>()?;
114    let partial = from_str_value(partial, urlencoded)?;
115    let result: T = partial.build()?.materialize()?;
116    Ok(result)
117}
118
119/// Deserializes a URL encoded form data string into an heap-allocated value.
120///
121/// This is the lower-level function that works with `Partial` directly.
122fn from_str_value<'mem>(
123    mut wip: Partial<'mem>,
124    urlencoded: &str,
125) -> Result<Partial<'mem>, UrlEncodedError> {
126    trace!("Starting URL encoded form data deserialization");
127
128    // Parse the URL encoded string into key-value pairs
129    let pairs = form_urlencoded::parse(urlencoded.as_bytes());
130
131    // Process the input into a nested structure
132    let mut nested_values = NestedValues::new();
133    for (key, value) in pairs {
134        nested_values.insert(&key, value.to_string());
135    }
136
137    // Create pre-initialized structure so that we have all the required fields
138    // for better error reporting when fields are missing
139    initialize_nested_structures(&mut nested_values);
140
141    // Process the deserialization
142    wip = deserialize_value(wip, &nested_values)?;
143    Ok(wip)
144}
145
146/// Ensures that all nested structures have entries in the NestedValues
147/// This helps ensure we get better error reporting when fields are missing
148fn initialize_nested_structures(nested: &mut NestedValues) {
149    // Go through each nested value and recursively initialize it
150    for nested_value in nested.nested.values_mut() {
151        initialize_nested_structures(nested_value);
152    }
153}
154
155/// Internal helper struct to represent nested values from URL-encoded data
156struct NestedValues {
157    // Root level key-value pairs
158    flat: std::collections::HashMap<String, String>,
159    // Nested structures: key -> nested map
160    nested: std::collections::HashMap<String, NestedValues>,
161}
162
163impl NestedValues {
164    fn new() -> Self {
165        Self {
166            flat: std::collections::HashMap::new(),
167            nested: std::collections::HashMap::new(),
168        }
169    }
170
171    fn insert(&mut self, key: &str, value: String) {
172        // For bracket notation like user[name] or user[address][city]
173        if let Some(open_bracket) = key.find('[')
174            && let Some(close_bracket) = key.find(']')
175            && open_bracket < close_bracket
176        {
177            let parent_key = &key[0..open_bracket];
178            let nested_key = &key[(open_bracket + 1)..close_bracket];
179            let remainder = &key[(close_bracket + 1)..];
180
181            let nested = self
182                .nested
183                .entry(parent_key.to_string())
184                .or_insert_with(NestedValues::new);
185
186            if remainder.is_empty() {
187                // Simple case: user[name]=value
188                nested.flat.insert(nested_key.to_string(), value);
189            } else {
190                // Handle deeply nested case like user[address][city]=value
191                let new_key = format!("{nested_key}{remainder}");
192                nested.insert(&new_key, value);
193            }
194            return;
195        }
196
197        // If we get here, it's a flat key-value pair
198        self.flat.insert(key.to_string(), value);
199    }
200
201    fn get(&self, key: &str) -> Option<&String> {
202        self.flat.get(key)
203    }
204
205    #[expect(dead_code)]
206    fn get_nested(&self, key: &str) -> Option<&NestedValues> {
207        self.nested.get(key)
208    }
209
210    fn keys(&self) -> impl Iterator<Item = &String> {
211        self.flat.keys()
212    }
213
214    #[expect(dead_code)]
215    fn nested_keys(&self) -> impl Iterator<Item = &String> {
216        self.nested.keys()
217    }
218}
219
220/// Deserialize a value recursively using the nested values
221fn deserialize_value<'mem>(
222    mut wip: Partial<'mem>,
223    values: &NestedValues,
224) -> Result<Partial<'mem>, UrlEncodedError> {
225    let shape = wip.shape();
226    match shape.ty {
227        Type::User(UserType::Struct(_)) => {
228            trace!("Deserializing struct");
229
230            // Process flat fields
231            for key in values.keys() {
232                if let Some(index) = wip.field_index(key) {
233                    let value = values.get(key).unwrap(); // Safe because we're iterating over keys
234                    wip = wip.begin_nth_field(index)?;
235                    wip = deserialize_scalar_field(key, value, wip)?;
236                    wip = wip.end()?;
237                } else {
238                    trace!("Unknown field: {key}");
239                }
240            }
241
242            // Process nested fields
243            for key in values.nested.keys() {
244                if let Some(index) = wip.field_index(key) {
245                    let nested_values = values.nested.get(key).unwrap(); // Safe because we're iterating over keys
246                    wip = wip.begin_nth_field(index)?;
247                    wip = deserialize_nested_field(key, nested_values, wip)?;
248                    wip = wip.end()?;
249                } else {
250                    trace!("Unknown nested field: {key}");
251                }
252            }
253
254            trace!("Finished deserializing struct");
255            Ok(wip)
256        }
257        _ => {
258            error!("Unsupported root type");
259            Err(UrlEncodedError::UnsupportedShape(
260                "Unsupported root type".to_string(),
261            ))
262        }
263    }
264}
265
266/// Helper function to deserialize a scalar field
267fn deserialize_scalar_field<'mem>(
268    key: &str,
269    value: &str,
270    mut wip: Partial<'mem>,
271) -> Result<Partial<'mem>, UrlEncodedError> {
272    match wip.shape().def {
273        Def::Scalar => {
274            if wip.shape().is_type::<String>() {
275                let s = value.to_string();
276                wip = wip.set(s)?;
277            } else if wip.shape().is_type::<u64>() {
278                match value.parse::<u64>() {
279                    Ok(num) => wip = wip.set(num)?,
280                    Err(_) => {
281                        return Err(UrlEncodedError::InvalidNumber(
282                            key.to_string(),
283                            value.to_string(),
284                        ));
285                    }
286                };
287            } else {
288                warn!("facet-yaml: unsupported scalar type: {}", wip.shape());
289                return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
290            }
291            Ok(wip)
292        }
293        _ => {
294            error!("Expected scalar field");
295            Err(UrlEncodedError::UnsupportedShape(format!(
296                "Expected scalar for field '{key}'"
297            )))
298        }
299    }
300}
301
302/// Helper function to deserialize a nested field
303fn deserialize_nested_field<'mem>(
304    key: &str,
305    nested_values: &NestedValues,
306    mut wip: Partial<'mem>,
307) -> Result<Partial<'mem>, UrlEncodedError> {
308    let shape = wip.shape();
309    match shape.ty {
310        Type::User(UserType::Struct(_)) => {
311            trace!("Deserializing nested struct field: {key}");
312
313            // Process flat fields in the nested structure
314            for nested_key in nested_values.keys() {
315                if let Some(index) = wip.field_index(nested_key) {
316                    let value = nested_values.get(nested_key).unwrap(); // Safe because we're iterating over keys
317                    wip = wip.begin_nth_field(index)?;
318                    wip = deserialize_scalar_field(nested_key, value, wip)?;
319                    wip = wip.end()?;
320                }
321            }
322
323            // Process deeper nested fields
324            for nested_key in nested_values.nested.keys() {
325                if let Some(index) = wip.field_index(nested_key) {
326                    let deeper_nested = nested_values.nested.get(nested_key).unwrap(); // Safe because we're iterating over keys
327                    wip = wip.begin_nth_field(index)?;
328                    wip = deserialize_nested_field(nested_key, deeper_nested, wip)?;
329                    wip = wip.end()?;
330                }
331            }
332
333            Ok(wip)
334        }
335        _ => {
336            error!("Expected struct field for nested value");
337            Err(UrlEncodedError::UnsupportedShape(format!(
338                "Expected struct for nested field '{key}'"
339            )))
340        }
341    }
342}
343
344/// Errors that can occur during URL encoded form data deserialization.
345#[derive(Debug)]
346pub enum UrlEncodedError {
347    /// The field value couldn't be parsed as a number.
348    InvalidNumber(String, String),
349    /// The shape is not supported for deserialization.
350    UnsupportedShape(String),
351    /// The type is not supported for deserialization.
352    UnsupportedType(String),
353    /// Reflection error
354    ReflectError(ReflectError),
355}
356
357impl From<ReflectError> for UrlEncodedError {
358    fn from(err: ReflectError) -> Self {
359        UrlEncodedError::ReflectError(err)
360    }
361}
362
363impl core::fmt::Display for UrlEncodedError {
364    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
365        match self {
366            UrlEncodedError::InvalidNumber(field, value) => {
367                write!(f, "Invalid number for field '{field}': '{value}'")
368            }
369            UrlEncodedError::UnsupportedShape(shape) => {
370                write!(f, "Unsupported shape: {shape}")
371            }
372            UrlEncodedError::UnsupportedType(ty) => {
373                write!(f, "Unsupported type: {ty}")
374            }
375            UrlEncodedError::ReflectError(err) => {
376                write!(f, "Reflection error: {err}")
377            }
378        }
379    }
380}
381
382impl std::error::Error for UrlEncodedError {}