4 releases (2 breaking)

Uses new Rust 2024

0.3.2 Dec 13, 2025
0.3.1 Dec 12, 2025
0.3.0 Dec 12, 2025
0.2.0 Sep 13, 2024
0.1.0 Aug 25, 2024

#678 in Encoding

Download history 226/week @ 2025-09-03 197/week @ 2025-09-10 328/week @ 2025-09-17 294/week @ 2025-09-24 207/week @ 2025-10-01 161/week @ 2025-10-08 143/week @ 2025-10-15 198/week @ 2025-10-22 128/week @ 2025-10-29 198/week @ 2025-11-05 287/week @ 2025-11-12 195/week @ 2025-11-19 192/week @ 2025-11-26 338/week @ 2025-12-03 318/week @ 2025-12-10

894 downloads per month

MIT license

10KB

SerdeV

SerdeV - Serde with Validation

  • 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.

Example

[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,
    }
    

Attribute

  • #[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.

License

Licensed under MIT LICENSE ( LICENSE or https://opensource.org/licenses/MIT ).

Dependencies

~155–460KB