Crate preprocess

source ·
Expand description

§Preprocess

A crate to help you preprocess your structs and enums. Can be used to validate data, or to transform it.

There are two kinds of preprocessors:

  • Validators: They check if the given field is valid and don’t modify the value. For example: a validator could check if a string is a valid email address.
  • Preprocessors: These allow you to modify the value (and possibly type) of a field. For example: a preprocessor could trim a string, or convert it to uppercase.

§Example usage

use preprocess::prelude::*;

#[preprocess::sync]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserSignUpRequest {
    // First trims the email, then converts it to lowercase, then validates it as an email address.
    #[preprocess(trim, lowercase, email)]
    pub email: String,
    // First trims the password, then checks if it's at least 8 characters long.
    #[preprocess(trim, length(min = 8))]
    pub password: String,
}
 
let processed_value = raw_value.preprocess()?;

§Inheriting derive attributes

Since the crate uses an attribute macro, it must always be the first attribute on the struct or enum. A new struct / enum will be generated with the name {original_name}Processed. The derive macro will inherit all the derive attributes from the original struct / enum. For example:

use preprocess::prelude::*;

#[preprocess::sync]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserSignUpRequest {
    #[preprocess(trim, lowercase, email)]
    #[serde(default)]
    pub email: String,
    #[serde(alias = "pass")]
    #[preprocess(trim, length(min = 8))]
    pub password: String,
}

The above code will generate:

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserSignUpRequestProcessed {
    #[serde(default)]
    pub email: String,
    #[serde(alias = "pass")]
    pub password: String,
}

This way, any custom derive attributes you use (like Serde) will be inherited by the generated struct / enum. This also ensures that you can preprocess your struct / enum and send the preprocessed version to the client, without having to write any extra code.

§List of allowed preprocessors

PreprocessorDescription
emailValidates a string to be a valid email address.
domainValidates a string to be a valid domain name.
ipValidates a string to be a valid IP Address.
urlValidates a string to be a valid URL.
lengthValidates the length of a string.
rangeValidates the range of a number.
containsValidates if a string contains a substring.
does_not_containValidates if a string does not contain a substring.
regexValidates a string using a regex.
typeEnforces the type of a value using TryFrom.
trimTrims a string.
lowercaseConverts a string to lowercase.
uppercaseConverts a string to uppercase.
customValidates a string using a custom function.

More details about each preprocessor can be found in the respective module documentation of preprocessors and validators.

§Custom preprocessors

You can use a custom function as a preprocessor. The function must have the following signature:

fn custom_preprocessor<T>(value: T) -> Result<T, Error>;

The function must return a Result with the same type as the input. If the function returns an Err, the error will be returned as the error of the preprocessor. If the function returns an Ok, the value will be returned as the output of the preprocessor.

pub fn custom_preprocessor(value: String) -> Result<String, Error> {
    if value.len() < 8 {
        return Err(Error::new(
            "Password must be at least 8 characters long",
        ));
    }
    Ok(value)
}

#[preprocess::sync]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserSignUpRequest {
    #[preprocess(custom = "custom_preprocessor")]
    pub password: String,
}

§Enforcing the type of a value

You can use the type preprocessor to enforce the type of a value. This is useful when you want to convert a value to a different type. For example, you might want to convert a string to an integer. You can use the type preprocessor to do this. The type preprocessor uses TryFrom to convert the value to the desired type. If the conversion fails, the preprocessor will return an error.

#[preprocess::sync]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserSignUpRequest {
    #[preprocess(type = "i32")]
    pub age: i16,
}

§Usage

Add this to your Cargo.toml:

[dependencies]
preprocess = "<version>"

Then, you can import the crate and use it like this:

use preprocess::prelude::*;

#[preprocess::sync]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserSignUpRequest {
    // First trims the email, then converts it to lowercase, then validates it as an email address.
    #[preprocess(trim, lowercase, email)]
    pub email: String,
    // First trims the password, then checks if it's at least 8 characters long.
    #[preprocess(trim, length(min = 8))]
    pub password: String,
}

§MSRV

There is no MSRV as such, and to be honest, I don’t see the point of an MSRV, with how easy rust is to upgrade. I just use the latest version of rust on my machine. That being said, I don’t think I’ve used any new rust features. So it should work on older versions of rust as well. Please open an issue if you’re facing any, well, issues.

Re-exports§

  • pub use crate::utils::Error;

Modules§

  • Prelude module for the library. This module re-exports all the important types and traits from the library. This module is useful when you want to use the library without importing the individual modules.
  • List of all the preprocessors that mutates the given field, including changing the type if required. A list of all the preprocessors that preprocess the given field, mutating it if required. The type of the field may be changed. For example, the lowercase preprocessor will change the type of the field to String.
  • A list of all the types that are re-exported from supporting crates. Used by the preprocessor to set the types for a field if required.
  • Utility module for the library.
  • List of all the validators that validates the given field without mutating it. The type of the field may still be changed. For example, the ip validator will change the type of the field to IpAddr. A list of all the validators that only validates the given field without mutating it. The type of the field may still be changed. For example, the ip validator will change the type of the field to IpAddr.

Traits§

  • A trait that can be implemented by any type to allow it to be preprocessed. This trait is automatically implemented for all types that use the #[preprocess::sync] macro.

Attribute Macros§

  • An attribute macro for preprocessing structs