- Just a wrapper of Serde (100% compatible),
- implementing
serde::{Serialize, Deserialize}for your structs, - with providing
#[serde(validate = "...")]for declarative validation in#[derive(Deserialize)].
Why 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
validatorcrate 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. Thefunctionmust be an expression that is callable as typefn(&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
fnor a method, or even a block expression or function calling or anything that are finally evaluated asfn(&self) -> Result<(), impl Display>)Errors are internally converted to a
Stringand passed toserde::de::Error::custom. -
#[serde(validate(by = "function", error = "Type"))]Using given
Typefor the validation error, without conversion. Thefunctionsignature must befn(&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 ).