shapely_urlencoded/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use shapely::{Partial, error, trace, warn};
5
6#[cfg(test)]
7mod tests;
8
9/// Deserializes URL encoded form data into a Shapely Partial.
10///
11/// # Example
12///
13/// ```
14/// use shapely::Shapely;
15/// use shapely_urlencoded::from_urlencoded;
16///
17/// #[derive(Debug, Shapely, PartialEq)]
18/// struct SearchParams {
19///     query: String,
20///     page: u64,
21/// }
22///
23/// let query_string = "query=rust+programming&page=2";
24///
25/// let mut partial = SearchParams::partial();
26/// from_urlencoded(&mut partial, query_string).expect("Failed to parse URL encoded data");
27///
28/// let params = partial.build::<SearchParams>();
29/// assert_eq!(params, SearchParams { query: "rust programming".to_string(), page: 2 });
30/// ```
31pub fn from_urlencoded(partial: &mut Partial, input: &str) -> Result<(), UrlEncodedError> {
32    use shapely::{Innards, Scalar};
33
34    trace!("Starting URL encoded form data deserialization");
35
36    // Parse the URL encoded string into key-value pairs
37    let pairs = form_urlencoded::parse(input.as_bytes());
38
39    // Create a map to store the parsed key-value pairs
40    let mut pairs_map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
41    for (key, value) in pairs {
42        pairs_map.insert(key.to_string(), value.to_string());
43    }
44
45    // Process the struct fields
46    let shape_desc = partial.shape();
47    let shape = shape_desc.get();
48    trace!("Deserializing value with shape:\n{:?}", shape);
49
50    match &shape.innards {
51        Innards::Struct { .. } => {
52            trace!("Deserializing \x1b[1;36mstruct\x1b[0m");
53
54            for (key, value) in pairs_map {
55                trace!("Processing field key: \x1b[1;33m{}\x1b[0m", key);
56
57                let slot = match partial.slot_by_name(&key) {
58                    Ok(slot) => slot,
59                    Err(_) => {
60                        warn!("Unknown field: {}", key);
61                        continue; // Skip unknown fields
62                    }
63                };
64
65                let slot_shape = slot.shape();
66                let slot_shape_ref = slot_shape.get();
67
68                match &slot_shape_ref.innards {
69                    Innards::Scalar(scalar) => {
70                        let mut partial_field = Partial::alloc(slot.shape());
71                        let field_slot = partial_field.scalar_slot().expect("Scalar slot");
72
73                        match scalar {
74                            Scalar::String => {
75                                field_slot.fill(value);
76                            }
77                            Scalar::U64 => match value.parse::<u64>() {
78                                Ok(num) => field_slot.fill(num),
79                                Err(_) => {
80                                    return Err(UrlEncodedError::InvalidNumber(key.clone(), value));
81                                }
82                            },
83                            // Add other scalar types as needed
84                            _ => {
85                                warn!("Unsupported scalar type: {:?}", scalar);
86                                return Err(UrlEncodedError::UnsupportedType(format!(
87                                    "{:?}",
88                                    scalar
89                                )));
90                            }
91                        }
92
93                        slot.fill_from_partial(partial_field);
94                    }
95                    // Add support for other shapes (Array, Transparent) as needed
96                    _ => {
97                        error!("Unsupported shape: {:?}", slot_shape_ref.innards);
98                        return Err(UrlEncodedError::UnsupportedShape(format!(
99                            "{:?}",
100                            slot_shape_ref.innards
101                        )));
102                    }
103                }
104            }
105
106            trace!("Finished deserializing \x1b[1;36mstruct\x1b[0m");
107        }
108        _ => {
109            error!("Unsupported shape: {:?}", shape.innards);
110            return Err(UrlEncodedError::UnsupportedShape(format!(
111                "{:?}",
112                shape.innards
113            )));
114        }
115    }
116
117    trace!(
118        "Successfully deserialized URL encoded form data for shape: \x1b[1;32m{}\x1b[0m at address \x1b[1;34m{:?}\x1b[0m\n",
119        shape,
120        partial.addr()
121    );
122    Ok(())
123}
124
125/// Errors that can occur during URL encoded form data deserialization.
126#[derive(Debug)]
127pub enum UrlEncodedError {
128    /// The field value couldn't be parsed as a number.
129    InvalidNumber(String, String),
130    /// The shape is not supported for deserialization.
131    UnsupportedShape(String),
132    /// The type is not supported for deserialization.
133    UnsupportedType(String),
134}
135
136impl std::fmt::Display for UrlEncodedError {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        match self {
139            UrlEncodedError::InvalidNumber(field, value) => {
140                write!(f, "Invalid number for field '{}': '{}'", field, value)
141            }
142            UrlEncodedError::UnsupportedShape(shape) => {
143                write!(f, "Unsupported shape: {}", shape)
144            }
145            UrlEncodedError::UnsupportedType(ty) => {
146                write!(f, "Unsupported type: {}", ty)
147            }
148        }
149    }
150}
151
152impl std::error::Error for UrlEncodedError {}