Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

ohkami-rs/serdev

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

[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 ).

Morty Proxy This is a proxified and sanitized view of the page, visit original site.