| Crates.io | webformd |
| lib.rs | webformd |
| version | 0.1.3 |
| created_at | 2025-09-17 16:28:27.630807+00 |
| updated_at | 2025-09-17 16:31:05.307072+00 |
| description | trait for webformd_macro to deserialize actix_web::web::Form for structs with Option |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1843569 |
| size | 9,548 |
webformd: Web Form Deserializerwebformd is a Rust crate that provides a derive macro to deserialize web form data (application/x-www-form-urlencoded) into your structs.
It is designed to easily handle form data, especially in common web development scenarios like those found when using frameworks such as Actix Web. The crate can deserialize fields into various primitive types, String, Vec<T>, and Option<T>, making it very flexible for all kinds of form inputs. It also supports parsing into any type that implements the FromStr trait.
#[derive(WebformDeserialize)] to your struct.String and numeric types (i32, f64, etc.) for single value fields.Option<T> for optional fields.Vec<T> for fields that can have multiple values (like checkboxes).Option<Vec<T>> for optional, multiple-value fields.String, i32) is missing or can't be parsed.actix_web::web::Form).Add webformd to your Cargo.toml file:
[dependencies]
webformd = "0.1.0"
Define your struct and derive WebformDeserialize from webformd. Then, you can use the deserialize method provided by the WebFomData trait.
Imagine you have an HTML form like this:
<form action="/submit" method="post">
<!-- Required field -->
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
<!-- Required numeric field -->
<label for="age">Age:</label>
<input type="number" id="age" name="age" required>
<!-- Optional field -->
<label for="email">Email (optional):</label>
<input type="email" id="email" name="email">
<!-- Checkboxes (multiple values) -->
<p>Interests:</p>
<input type="checkbox" name="interests" value="rust"> Rust
<input type="checkbox" name="interests" value="webdev"> Web Dev
<input type="checkbox" name="interests" value="gamedev"> Game Dev
<button type="submit">Submit</button>
</form>
You can deserialize the data from this form into a Rust struct as follows:
use webformd::{WebFomData, WebformDeserialize};
#[derive(Debug, PartialEq, WebformDeserialize)]
struct MyForm {
name: String, // Required by default
age: u32,
email: Option<String>,
interests: Option<Vec<String>>, // Use Option<Vec<String>> because no interests might be selected
}
fn main() {
// Simulated web form data (like what you'd get in an Actix Web handler)
let form_data = vec![
("name".to_string(), "Alice".to_string()), // email is missing, so it will be None
("age".to_string(), "30".to_string()),
("interests".to_string(), "rust".to_string()),
("interests".to_string(), "webdev".to_string()),
];
let my_form = MyForm::deserialize(&form_data).unwrap();
assert_eq!(my_form, MyForm {
name: "Alice".to_string(),
age: 30,
email: None,
interests: Some(vec!["rust".to_string(), "webdev".to_string()]),
});
println!("Deserialized successfully: {:?}", my_form);
}
#[webformd(from_str)]For fields that are not String but implement the FromStr trait (like u32, f64, etc.), the WebformDeserialize macro will automatically try to parse them.
In the first example, the age: u32 field is deserialized from a String ("30") thanks to this internal logic. If the conversion fails (for example, if "thirty" is received instead of "30"), deserialize will return an error.
You can also use it for Vec<T> where T implements FromStr by explicitly adding the #[webformd(from_str)] attribute.
use webformd::{WebFomData, WebformDeserialize};
use std::str::FromStr;
#[derive(Debug, PartialEq, WebformDeserialize)]
struct ProductSelection {
// Each "product_ids" value will be parsed into a u32
#[webformd(from_str)]
product_ids: Vec<u32>,
// This also works with custom enums
#[webformd(from_str)]
categories: Option<Vec<ProductCategory>>,
}
#[derive(Debug, PartialEq)]
enum ProductCategory {
Electronics,
Books,
Clothing,
}
impl FromStr for ProductCategory {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"electronics" => Ok(ProductCategory::Electronics),
"books" => Ok(ProductCategory::Books),
"clothing" => Ok(ProductCategory::Clothing),
_ => Err(format!("'{}' is not a valid category", s)),
}
}
}
fn main() {
let form_data = vec![
("product_ids".to_string(), "101".to_string()),
("product_ids".to_string(), "202".to_string()),
("categories".to_string(), "electronics".to_string()),
("categories".to_string(), "books".to_string()),
];
let selection = ProductSelection::deserialize(&form_data).unwrap();
assert_eq!(selection, ProductSelection {
product_ids: vec![101, 202],
categories: Some(vec![ProductCategory::Electronics, ProductCategory::Books]),
});
}
Integration with actix_web::web::Form is straightforward. The Vec<(String, String)> format is compatible with what Form extracts.
// In your Actix Web handler:
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use webformd::{WebFomData, WebformDeserialize};
#[derive(Debug, WebformDeserialize)]
struct MyForm {
name: String,
age: u32,
email: Option<String>,
interests: Option<Vec<String>>,
}
async fn handle_form(form: web::Form<Vec<(String, String)>>) -> HttpResponse {
match MyForm::deserialize(&form.into_inner()) {
Ok(data) => {
println!("Deserialized form data: {:?}", data);
HttpResponse::Ok().body(format!("Hello, {}! You are {} years old.", data.name, data.age))
}
Err(e) => HttpResponse::BadRequest().body(e),
}
}
This project is licensed under the CC0 1.0 Universal license.