#![warn(missing_docs)]
#![doc = include_str!("../readme-footer.md")]
use facet_core::{Def, Facet, Type, UserType};
use facet_reflect::{AllocError, Partial, ReflectError, ShapeMismatchError, TypePlan};
use log::*;
#[cfg(test)]
mod tests;
mod form;
pub use form::Form;
mod query;
pub use query::Query;
#[cfg(feature = "axum")]
mod axum;
#[cfg(feature = "axum")]
pub use self::axum::{FormRejection, QueryRejection};
pub fn from_str<'input: 'facet, 'facet, T: Facet<'facet>>(
urlencoded: &'input str,
) -> Result<T, UrlEncodedError> {
let plan = TypePlan::<T>::build()?;
let partial = plan.partial()?;
let partial = from_str_value(partial, urlencoded)?;
let result: T = partial.build()?.materialize()?;
Ok(result)
}
pub fn from_str_owned<T: Facet<'static>>(urlencoded: &str) -> Result<T, UrlEncodedError> {
let plan = TypePlan::<T>::build()?;
let partial = plan.partial_owned()?;
let partial = from_str_value(partial, urlencoded)?;
let result: T = partial.build()?.materialize()?;
Ok(result)
}
fn from_str_value<'facet, const BORROW: bool>(
mut wip: Partial<'facet, BORROW>,
urlencoded: &str,
) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
trace!("Starting URL encoded form data deserialization");
let pairs = form_urlencoded::parse(urlencoded.as_bytes());
let mut nested_values = NestedValues::new();
for (key, value) in pairs {
nested_values.insert(&key, value.to_string());
}
initialize_nested_structures(&mut nested_values);
wip = deserialize_value(wip, &nested_values)?;
Ok(wip)
}
fn initialize_nested_structures(nested: &mut NestedValues) {
for nested_value in nested.nested.values_mut() {
initialize_nested_structures(nested_value);
}
}
struct NestedValues {
flat: std::collections::HashMap<String, String>,
nested: std::collections::HashMap<String, NestedValues>,
}
impl NestedValues {
fn new() -> Self {
Self {
flat: std::collections::HashMap::new(),
nested: std::collections::HashMap::new(),
}
}
fn insert(&mut self, key: &str, value: String) {
if let Some(open_bracket) = key.find('[')
&& let Some(close_bracket) = key.find(']')
&& open_bracket < close_bracket
{
let parent_key = &key[0..open_bracket];
let nested_key = &key[(open_bracket + 1)..close_bracket];
let remainder = &key[(close_bracket + 1)..];
let nested = self
.nested
.entry(parent_key.to_string())
.or_insert_with(NestedValues::new);
if remainder.is_empty() {
nested.flat.insert(nested_key.to_string(), value);
} else {
let new_key = format!("{nested_key}{remainder}");
nested.insert(&new_key, value);
}
return;
}
self.flat.insert(key.to_string(), value);
}
fn get(&self, key: &str) -> Option<&String> {
self.flat.get(key)
}
#[expect(dead_code)]
fn get_nested(&self, key: &str) -> Option<&NestedValues> {
self.nested.get(key)
}
fn keys(&self) -> impl Iterator<Item = &String> {
self.flat.keys()
}
#[expect(dead_code)]
fn nested_keys(&self) -> impl Iterator<Item = &String> {
self.nested.keys()
}
}
fn deserialize_value<'facet, const BORROW: bool>(
mut wip: Partial<'facet, BORROW>,
values: &NestedValues,
) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
let shape = wip.shape();
match shape.ty {
Type::User(UserType::Struct(_)) => {
trace!("Deserializing struct");
for key in values.keys() {
if let Some(index) = wip.field_index(key) {
let value = values.get(key).unwrap(); wip = wip.begin_nth_field(index)?;
wip = deserialize_scalar_field(key, value, wip)?;
wip = wip.end()?;
} else {
trace!("Unknown field: {key}");
}
}
for key in values.nested.keys() {
if let Some(index) = wip.field_index(key) {
let nested_values = values.nested.get(key).unwrap(); wip = wip.begin_nth_field(index)?;
wip = deserialize_nested_field(key, nested_values, wip)?;
wip = wip.end()?;
} else {
trace!("Unknown nested field: {key}");
}
}
trace!("Finished deserializing struct");
Ok(wip)
}
_ => {
error!("Unsupported root type");
Err(UrlEncodedError::UnsupportedShape(
"Unsupported root type".to_string(),
))
}
}
}
fn deserialize_scalar_field<'facet, const BORROW: bool>(
key: &str,
value: &str,
mut wip: Partial<'facet, BORROW>,
) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
match wip.shape().def {
Def::Scalar => {
if wip.shape().is_type::<String>() {
let s = value.to_string();
wip = wip.set(s)?;
} else if wip.shape().is_type::<u64>() {
match value.parse::<u64>() {
Ok(num) => wip = wip.set(num)?,
Err(_) => {
return Err(UrlEncodedError::InvalidNumber(
key.to_string(),
value.to_string(),
));
}
};
} else {
warn!("facet-yaml: unsupported scalar type: {}", wip.shape());
return Err(UrlEncodedError::UnsupportedType(format!("{}", wip.shape())));
}
Ok(wip)
}
_ => {
error!("Expected scalar field");
Err(UrlEncodedError::UnsupportedShape(format!(
"Expected scalar for field '{key}'"
)))
}
}
}
fn deserialize_nested_field<'facet, const BORROW: bool>(
key: &str,
nested_values: &NestedValues,
mut wip: Partial<'facet, BORROW>,
) -> Result<Partial<'facet, BORROW>, UrlEncodedError> {
let shape = wip.shape();
match shape.ty {
Type::User(UserType::Struct(_)) => {
trace!("Deserializing nested struct field: {key}");
for nested_key in nested_values.keys() {
if let Some(index) = wip.field_index(nested_key) {
let value = nested_values.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
wip = deserialize_scalar_field(nested_key, value, wip)?;
wip = wip.end()?;
}
}
for nested_key in nested_values.nested.keys() {
if let Some(index) = wip.field_index(nested_key) {
let deeper_nested = nested_values.nested.get(nested_key).unwrap(); wip = wip.begin_nth_field(index)?;
wip = deserialize_nested_field(nested_key, deeper_nested, wip)?;
wip = wip.end()?;
}
}
Ok(wip)
}
_ => {
error!("Expected struct field for nested value");
Err(UrlEncodedError::UnsupportedShape(format!(
"Expected struct for nested field '{key}'"
)))
}
}
}
#[derive(Debug)]
pub enum UrlEncodedError {
InvalidNumber(String, String),
UnsupportedShape(String),
UnsupportedType(String),
ReflectError(ReflectError),
}
impl From<ReflectError> for UrlEncodedError {
fn from(err: ReflectError) -> Self {
UrlEncodedError::ReflectError(err)
}
}
impl From<ShapeMismatchError> for UrlEncodedError {
fn from(err: ShapeMismatchError) -> Self {
UrlEncodedError::UnsupportedShape(format!(
"shape mismatch: expected {}, got {}",
err.expected, err.actual
))
}
}
impl From<AllocError> for UrlEncodedError {
fn from(err: AllocError) -> Self {
UrlEncodedError::UnsupportedShape(format!(
"allocation failed for {}: {}",
err.shape, err.operation
))
}
}
impl core::fmt::Display for UrlEncodedError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
UrlEncodedError::InvalidNumber(field, value) => {
write!(f, "Invalid number for field '{field}': '{value}'")
}
UrlEncodedError::UnsupportedShape(shape) => {
write!(f, "Unsupported shape: {shape}")
}
UrlEncodedError::UnsupportedType(ty) => {
write!(f, "Unsupported type: {ty}")
}
UrlEncodedError::ReflectError(err) => {
write!(f, "Reflection error: {err}")
}
}
}
}
impl std::error::Error for UrlEncodedError {}