Crate validity

source ·
Expand description

Type safe validation of arbitrary data

Provides the Valid<T> struct which wraps some data, after verifiying that it meets some criteria:

#[derive(Debug)]
struct PhoneNumber(String);

enum InvalidPhoneNumber {
  NonDigit,
  WrongLength,
}

impl Validate for PhoneNumber {
  type Context<'a> = ();
  type Error = InvalidPhoneNumber;

  fn is_valid(&self, _ctx: Self::Context<'_>) -> Result<(), Self::Error> {
    if self.0.len() == 11 {
        return Err(InvalidPhoneNumber::WrongLength);
    }

    if self.0.chars().any(|c| !c.is_digit(10)) {
        return Err(InvalidPhoneNumber::NonDigit);
    }

    Ok(())
  }
}

fn main() {
  let number = PhoneNumber("01234567890".to_string());
  if let Ok(number) = number.validate() {
    handle_phone_number(number);
  } else {
    println!("error!");
  }
}

fn handle_phone_number(number: Valid<PhoneNumber>) {
  println!("This is a definitely valid phone number: {:?}", number.into_inner());
}

Some validation requires access to some context. For example, you may want to validate that an email address exists in your database. For that, you can pass this context via the Context associated type.

When validating, you can call foo.validate_with(context):

#[derive(Debug)]
struct PhoneNumber(String);

enum InvalidPhoneNumber {
  NonDigit,
  WrongLength,
  NotInDatabase,
}

impl Validate for PhoneNumber {
  type Context<'a> = Database;
  type Error = InvalidPhoneNumber;

  fn is_valid(&self, db: Self::Context<'_>) -> Result<(), Self::Error> {
    if self.0.len() == 11 {
        return Err(InvalidPhoneNumber::WrongLength);
    }

    if self.0.chars().any(|c| !c.is_digit(10)) {
        return Err(InvalidPhoneNumber::NonDigit);
    }

    if !db.check_phone_exists(self) {
        return Err(InvalidPhoneNumber::NotInDatabase);
    }

    Ok(())
  }
}

You can then call this with:

let db = Database::new();
let phone = PhoneNumber("01234567890".to_string());
phone.validate_with(db);

Structs

A struct representing a failure to validate a value
A thin wrapper around a value that guarantees that it is “valid”

Traits

A trait which defines what it means for a type to be “valid”