1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use facet_core::{Def, Facet, Type, UserType};
5use facet_reflect::{AllocError, Partial, ReflectError, ShapeMismatchError, TypePlan};
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
22pub fn from_str<'input: 'facet, 'facet, T: Facet<'facet>>(
82 urlencoded: &'input str,
83) -> Result<T, UrlEncodedError> {
84 let plan = TypePlan::<T>::build()?;
85 let partial = plan.partial()?;
86 let partial = from_str_value(partial, urlencoded)?;
87 let result: T = partial.build()?.materialize()?;
88 Ok(result)
89}
90
91pub fn from_str_owned<T: Facet<'static>>(urlencoded: &str) -> Result<T, UrlEncodedError> {
114 let plan = TypePlan::<T>::build()?;
115 let partial = plan.partial_owned()?;
116 let partial = from_str_value(partial, urlencoded)?;
117 let result: T = partial.build()?.materialize()?;
118 Ok(result)
119}
120
121fn from_str_value<'facet, const BORROW: bool>(
125 mut wip: Partial<'facet, BORROW>,
126 urlencoded: &str,
127) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
128 trace!("Starting URL encoded form data deserialization");
129
130 let pairs = form_urlencoded::parse(urlencoded.as_bytes());
132
133 let mut nested_values = NestedValues::new();
135 for (key, value) in pairs {
136 nested_values.insert(&key, value.to_string());
137 }
138
139 initialize_nested_structures(&mut nested_values);
142
143 wip = deserialize_value(wip, &nested_values)?;
145 Ok(wip)
146}
147
148fn initialize_nested_structures(nested: &mut NestedValues) {
151 for nested_value in nested.nested.values_mut() {
153 initialize_nested_structures(nested_value);
154 }
155}
156
157struct NestedValues {
159 flat: std::collections::HashMap<String, String>,
161 nested: std::collections::HashMap<String, NestedValues>,
163}
164
165impl NestedValues {
166 fn new() -> Self {
167 Self {
168 flat: std::collections::HashMap::new(),
169 nested: std::collections::HashMap::new(),
170 }
171 }
172
173 fn insert(&mut self, key: &str, value: String) {
174 if let Some(open_bracket) = key.find('[')
176 && let Some(close_bracket) = key.find(']')
177 && open_bracket < close_bracket
178 {
179 let parent_key = &key[0..open_bracket];
180 let nested_key = &key[(open_bracket + 1)..close_bracket];
181 let remainder = &key[(close_bracket + 1)..];
182
183 let nested = self
184 .nested
185 .entry(parent_key.to_string())
186 .or_insert_with(NestedValues::new);
187
188 if remainder.is_empty() {
189 nested.flat.insert(nested_key.to_string(), value);
191 } else {
192 let new_key = format!("{nested_key}{remainder}");
194 nested.insert(&new_key, value);
195 }
196 return;
197 }
198
199 self.flat.insert(key.to_string(), value);
201 }
202
203 fn get(&self, key: &str) -> Option<&String> {
204 self.flat.get(key)
205 }
206
207 #[expect(dead_code)]
208 fn get_nested(&self, key: &str) -> Option<&NestedValues> {
209 self.nested.get(key)
210 }
211
212 fn keys(&self) -> impl Iterator<Item = &String> {
213 self.flat.keys()
214 }
215
216 #[expect(dead_code)]
217 fn nested_keys(&self) -> impl Iterator<Item = &String> {
218 self.nested.keys()
219 }
220}
221
222fn deserialize_value<'facet, const BORROW: bool>(
224 mut wip: Partial<'facet, BORROW>,
225 values: &NestedValues,
226) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
227 let shape = wip.shape();
228 match shape.ty {
229 Type::User(UserType::Struct(_)) => {
230 trace!("Deserializing struct");
231
232 for key in values.keys() {
234 if let Some(index) = wip.field_index(key) {
235 let value = values.get(key).unwrap(); wip = wip.begin_nth_field(index)?;
237 wip = deserialize_scalar_field(key, value, wip)?;
238 wip = wip.end()?;
239 } else {
240 trace!("Unknown field: {key}");
241 }
242 }
243
244 for key in values.nested.keys() {
246 if let Some(index) = wip.field_index(key) {
247 let nested_values = values.nested.get(key).unwrap(); wip = wip.begin_nth_field(index)?;
249 wip = deserialize_nested_field(key, nested_values, wip)?;
250 wip = wip.end()?;
251 } else {
252 trace!("Unknown nested field: {key}");
253 }
254 }
255
256 trace!("Finished deserializing struct");
257 Ok(wip)
258 }
259 _ => {
260 error!("Unsupported root type");
261 Err(UrlEncodedError::UnsupportedShape(
262 "Unsupported root type".to_string(),
263 ))
264 }
265 }
266}
267
268fn deserialize_scalar_field<'facet, const BORROW: bool>(
270 key: &str,
271 value: &str,
272 mut wip: Partial<'facet, BORROW>,
273) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
274 match wip.shape().def {
275 Def::Scalar => {
276 if wip.shape().is_type::<String>() {
277 let s = value.to_string();
278 wip = wip.set(s)?;
279 } else if wip.shape().is_type::<u64>() {
280 match value.parse::<u64>() {
281 Ok(num) => wip = wip.set(num)?,
282 Err(_) => {
283 return Err(UrlEncodedError::InvalidNumber(
284 key.to_string(),
285 value.to_string(),
286 ));
287 }
288 };
289 } else {
290 warn!("facet-yaml: unsupported scalar type: {}", wip.shape());
291 return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
292 }
293 Ok(wip)
294 }
295 _ => {
296 error!("Expected scalar field");
297 Err(UrlEncodedError::UnsupportedShape(format!(
298 "Expected scalar for field '{key}'"
299 )))
300 }
301 }
302}
303
304fn deserialize_nested_field<'facet, const BORROW: bool>(
306 key: &str,
307 nested_values: &NestedValues,
308 mut wip: Partial<'facet, BORROW>,
309) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
310 let shape = wip.shape();
311 match shape.ty {
312 Type::User(UserType::Struct(_)) => {
313 trace!("Deserializing nested struct field: {key}");
314
315 for nested_key in nested_values.keys() {
317 if let Some(index) = wip.field_index(nested_key) {
318 let value = nested_values.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
320 wip = deserialize_scalar_field(nested_key, value, wip)?;
321 wip = wip.end()?;
322 }
323 }
324
325 for nested_key in nested_values.nested.keys() {
327 if let Some(index) = wip.field_index(nested_key) {
328 let deeper_nested = nested_values.nested.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
330 wip = deserialize_nested_field(nested_key, deeper_nested, wip)?;
331 wip = wip.end()?;
332 }
333 }
334
335 Ok(wip)
336 }
337 _ => {
338 error!("Expected struct field for nested value");
339 Err(UrlEncodedError::UnsupportedShape(format!(
340 "Expected struct for nested field '{key}'"
341 )))
342 }
343 }
344}
345
346#[derive(Debug)]
348pub enum UrlEncodedError {
349 InvalidNumber(String, String),
351 UnsupportedShape(String),
353 UnsupportedType(String),
355 ReflectError(ReflectError),
357}
358
359impl From<ReflectError> for UrlEncodedError {
360 fn from(err: ReflectError) -> Self {
361 UrlEncodedError::ReflectError(err)
362 }
363}
364
365impl From<ShapeMismatchError> for UrlEncodedError {
366 fn from(err: ShapeMismatchError) -> Self {
367 UrlEncodedError::UnsupportedShape(format!(
368 "shape mismatch: expected {}, got {}",
369 err.expected, err.actual
370 ))
371 }
372}
373
374impl From<AllocError> for UrlEncodedError {
375 fn from(err: AllocError) -> Self {
376 UrlEncodedError::UnsupportedShape(format!(
377 "allocation failed for {}: {}",
378 err.shape, err.operation
379 ))
380 }
381}
382
383impl core::fmt::Display for UrlEncodedError {
384 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
385 match self {
386 UrlEncodedError::InvalidNumber(field, value) => {
387 write!(f, "Invalid number for field '{field}': '{value}'")
388 }
389 UrlEncodedError::UnsupportedShape(shape) => {
390 write!(f, "Unsupported shape: {shape}")
391 }
392 UrlEncodedError::UnsupportedType(ty) => {
393 write!(f, "Unsupported type: {ty}")
394 }
395 UrlEncodedError::ReflectError(err) => {
396 write!(f, "Reflection error: {err}")
397 }
398 }
399 }
400}
401
402impl std::error::Error for UrlEncodedError {}