| Crates.io | serdev |
| lib.rs | serdev |
| version | 0.3.2 |
| created_at | 2024-08-25 01:32:24.128765+00 |
| updated_at | 2026-01-10 11:09:26.578312+00 |
| description | SerdeV - Serde with Validation |
| homepage | https://crates.io/crates/serdev |
| repository | https://github.com/ohkami-rs/serdev |
| max_upload_size | |
| id | 1350697 |
| size | 26,819 |
serde::{Serialize, Deserialize} for your structs,#[serde(validate = "...")] for declarative validation in #[derive(Deserialize)].serdev? Do you know "Parse, don't validate"?A manual implementation for "Parse, don't validate" without serdev will be like:
#[derive(serde::Deserialize)]
struct Point {
x: i32,
y: i32
}
#[derive(serde::Deserialize)]
#[serde(try_from = "Point")]
struct ValidPoint(Point);
impl TryFrom<Point> for ValidPoint {
// ...
}
Actually, this is (almost) exactly what serdev does!
Such manual implementation may be a trigger of mistakes like using Point directly for parsing user's input.
serdev eliminates such kind of mistakes, automatically performing the specified validation.
Or, manual Deserialize impl?:
struct Point {
x: i32,
y: i32
}
impl<'de> serde::Deserialize<'de> for Point {
// ...
}
Indeed this doesn't cause such misuses, but produces boilerplate... (more and more boilerplate in complex situation)
#[serde(validate)] makes, for a struct having complicated conditions, its Deserialize itself the valid parser of the struct,
with near-zero boilerplate.
If you have no pain on this, you may not need serdev.
[dependencies]
serdev = { version = "0.3", features = ["derive"] }
serde_json = "1.0"
use serdev::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(validate = "Self::validate")]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn validate(&self) -> Result<(), impl std::fmt::Display> {
if self.x * self.y > 100 {
return Err("x * y must not exceed 100")
}
Ok(())
}
}
fn main() {
let point = serde_json::from_str::<Point>(r#"
{ "x" : 1, "y" : 2 }
"#).unwrap();
// Prints point = Point { x: 1, y: 2 }
println!("point = {point:?}");
let error = serde_json::from_str::<Point>(r#"
{ "x" : 10, "y" : 20 }
"#).unwrap_err();
// Prints error = x * y must not exceed 100
println!("error = {error}");
}
#[serde(validate = "...")] works with:
other validation tools like validator crate or something similar.
(working example: validator.rs)
use serdev::Deserialize;
use validator::{Validate, ValidationError};
#[derive(Deserialize, Debug, PartialEq, Validate)]
#[serde(validate = "Validate::validate")]
struct SignupData {
#[validate(email)]
mail: String,
#[validate(url)]
site: String,
#[validate(length(min = 1), custom(function = "validate_unique_username"))]
#[serde(rename = "firstName")]
first_name: String,
#[validate(range(min = 18, max = 20))]
age: u32,
#[validate(range(min = 0.0, max = 100.0))]
height: f32,
}
fn validate_unique_username(username: &str) -> Result<(), ValidationError> {
if username == "xXxShad0wxXx" {
// the value of the username will automatically be added later
return Err(ValidationError::new("terrible_username"));
}
Ok(())
}
inlined closure like |p| if p.x * p.y <= 100 {Ok(())} else {Err("...")}, not only a method path.
(working example: closure.rs)
use serdev::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(validate = r#"|p| (p.x * p.y <= 100).then_some(()).ok_or("x * y must not exceed 100")"#)]
struct Point {
x: i32,
y: i32,
}
#[serde(validate = "function")]
Automatically validate the deserialized struct by the function. The function must be an expression that is
callable as type fn(&self) -> Result<(), impl Display> (of course the error type must be known at compile time).
(expression: an inlined closure as above, or name/path to a fn or a method, or even a block expression or function calling
or anything that are finally evaluated as fn(&self) -> Result<(), impl Display>)
Errors are internally converted to a String and passed to serde::de::Error::custom.
#[serde(validate(by = "function", error = "Type"))]
Using given Type for the validation error, without conversion.
The function signature must be fn(&self) -> Result<(), Type>.
This will be preferred in no-std use, or, maybe when you need better performance in error cases.
Both "function" and "Type" above accept path e.g. "Self::validate" or "crate::util::validate".
Additionally, #[serdev(crate = "path::to::serdev")] is supported for reexport from another crate.
Licensed under MIT LICENSE ( LICENSE or https://opensource.org/licenses/MIT ).