Skip to main content

Validator

Struct Validator 

Source
pub struct Validator { /* private fields */ }
Expand description

Collects field errors with Rails-style messages; finish fails with all of them.

Every check adds at most one error per failed rule and returns &mut Self, so checks chain. Nothing stops at the first failure: the user sees every problem at once, as Error::Invalid (422). Pure Rust: no binding call, no D1 rows. Uniqueness and foreign keys need the database; generated models check them with Db::exists and check.

§Examples

use ocre::{Error, Validator};

let mut v = Validator::new();
v.required("title", "  ").max_length("title", "  ", 100).range("pages", 0, 1..=10_000);
let Err(Error::Invalid(errors)) = v.finish() else { panic!("expected errors") };
let messages: Vec<String> = errors.iter().map(|e| e.full_message()).collect();
assert_eq!(messages, ["Title can't be blank", "Pages must be greater than or equal to 1"]);

Implementations§

Source§

impl Validator

Source

pub fn file_content(&mut self, field: &str, upload: &Upload) -> &mut Self

Checks that an upload’s bytes match its declared content type (Active Storage’s content-type identification).

Adds “has content that does not match image/png” when the declared type is one analyze recognizes but the bytes do not carry its signature (a script renamed .png), or when the bytes are a recognized type other than the declared one (a PDF sent as text/plain). Text types without a signature pass. Use it after Validator::file, which limits the declared type; costs microseconds (see analyze).

§Examples
use ocre::{Validator, storage::Upload};

let fake = Upload::new("cat.png", "image/png", "<script>alert(1)</script>");
let err = Validator::new().file_content("photo", &fake).finish().unwrap_err();
assert_eq!(err.to_string(), "invalid: Photo has content that does not match image/png");

let text = Upload::new("notes.txt", "text/plain", "hello");
assert!(Validator::new().file_content("notes", &text).finish().is_ok());
Source§

impl Validator

Source

pub fn file(&mut self, field: &str, upload: &Upload, rules: &Rules) -> &mut Self

Checks an uploaded file against rules: its size and its content type.

Adds “is too large (maximum is 5 MB)” when the file is over Rules::max_bytes and “has an unsupported type (allowed: image/png, image/jpeg)” when Rules::allows refuses its type; both can be reported at once. Run it before store, so a refused file costs no R2 operation. Returns self for chaining; finish turns the collected messages into a 422.

§Examples
use ocre::{Validator, storage::{Rules, Upload}};

const DOC: Rules = Rules { max_bytes: 4, content_types: &["text/plain"] };
let upload = Upload { filename: "a.html".into(), content_type: "text/html".into(), bytes: "<p>hello</p>".into() };
let err = Validator::new().file("doc", &upload, &DOC).finish().unwrap_err();
assert_eq!(
    err.to_string(),
    "invalid: Doc is too large (maximum is 4 bytes), Doc has an unsupported type (allowed: text/plain)"
);

let note = Upload { filename: "a.txt".into(), content_type: "text/plain".into(), bytes: "ok".into() };
assert!(Validator::new().file("doc", &note, &DOC).finish().is_ok());
Source§

impl Validator

Source

pub fn new() -> Self

Creates a validator with no errors.

§Examples
assert!(ocre::Validator::new().is_valid());
Source

pub fn check( &mut self, field: &str, failed: bool, message: impl Into<String>, ) -> &mut Self

Adds message for field when failed is true: the building block for custom rules.

§Examples
let mut v = ocre::Validator::new();
v.check("ends_at", 10 < 5, "must be after the start").check("title", true, "is reserved");
assert!(!v.is_valid());
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Title is reserved");
Source

pub fn required(&mut self, field: &str, value: &str) -> &mut Self

Checks that value is not empty after trimming whitespace (“can’t be blank”).

§Examples
let mut v = ocre::Validator::new();
v.required("title", "Hello");
assert!(v.is_valid());
v.required("body", " \n");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Body can't be blank");
Source

pub fn max_length(&mut self, field: &str, value: &str, max: usize) -> &mut Self

Checks that value has at most max characters (Unicode scalar values, not bytes).

Message: “is too long (maximum is N characters)”.

§Examples
let mut v = ocre::Validator::new();
v.max_length("title", "été", 3);
assert!(v.is_valid());
v.max_length("title", "summer", 3);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Title is too long (maximum is 3 characters)");
Source

pub fn min_length(&mut self, field: &str, value: &str, min: usize) -> &mut Self

Checks that value has at least min characters (Unicode scalar values, not bytes).

Message: “is too short (minimum is N characters)”.

§Examples
let mut v = ocre::Validator::new();
v.min_length("password", "hunter2", 12);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Password is too short (minimum is 12 characters)");
Source

pub fn range<T: PartialOrd + Display>( &mut self, field: &str, value: T, range: RangeInclusive<T>, ) -> &mut Self

Checks that value is within range, bounds included.

Messages: “must be greater than or equal to MIN” or “must be less than or equal to MAX”.

§Examples
let mut v = ocre::Validator::new();
v.range("rating", 3, 1..=5).range("price", 12.5, 0.0..=10.0);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Price must be less than or equal to 10");
Source

pub fn safe_integer(&mut self, field: &str, value: i64) -> &mut Self

Checks that value is an integer D1 can store and return exactly (±2^53 - 1).

See MAX_SAFE_INTEGER; same messages as range.

§Examples
let mut v = ocre::Validator::new();
v.safe_integer("views", ocre::MAX_SAFE_INTEGER);
assert!(v.is_valid());
v.safe_integer("views", i64::MAX);
assert!(!v.is_valid());
Source

pub fn inclusion( &mut self, field: &str, value: &str, allowed: &[&str], ) -> &mut Self

Checks that value is one of allowed (“is not included in the list”).

§Examples
let mut v = ocre::Validator::new();
v.inclusion("status", "draft", &["draft", "published"]);
assert!(v.is_valid());
v.inclusion("status", "archived", &["draft", "published"]);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Status is not included in the list");
Source

pub fn exclusion( &mut self, field: &str, value: &str, forbidden: &[&str], ) -> &mut Self

Checks that value is not one of forbidden (“is reserved”), like Rails’ exclusion.

§Examples
let mut v = ocre::Validator::new();
v.exclusion("username", "ada", &["admin", "root"]);
assert!(v.is_valid());
v.exclusion("username", "admin", &["admin", "root"]);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Username is reserved");
Source

pub fn length(&mut self, field: &str, value: &str, length: usize) -> &mut Self

Checks that value has exactly length characters (Unicode scalar values): “is the wrong length (should be N characters)”. For a range (Rails’ in:), chain min_length and max_length.

§Examples
let mut v = ocre::Validator::new();
v.length("zip", "75001", 5);
assert!(v.is_valid());
v.length("zip", "7500", 5);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Zip is the wrong length (should be 5 characters)");
Source

pub fn greater_than<T: PartialOrd + Display>( &mut self, field: &str, value: T, than: T, ) -> &mut Self

Checks that value > than (“must be greater than N”), like Rails’ comparison.

Works for numbers and for YYYY-MM-DD dates or datetimes as text, which compare in time order.

§Examples
let mut v = ocre::Validator::new();
v.greater_than("ends_on", "2026-10-02", "2026-10-01").greater_than("quantity", 0, 0);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Quantity must be greater than 0");
Source

pub fn greater_than_or_equal_to<T: PartialOrd + Display>( &mut self, field: &str, value: T, min: T, ) -> &mut Self

Checks that value >= min (“must be greater than or equal to N”).

§Examples
let mut v = ocre::Validator::new();
v.greater_than_or_equal_to("age", 17, 18);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Age must be greater than or equal to 18");
Source

pub fn less_than<T: PartialOrd + Display>( &mut self, field: &str, value: T, than: T, ) -> &mut Self

Checks that value < than (“must be less than N”).

§Examples
let mut v = ocre::Validator::new();
v.less_than("discount", 1.0, 1.0);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Discount must be less than 1");
Source

pub fn less_than_or_equal_to<T: PartialOrd + Display>( &mut self, field: &str, value: T, max: T, ) -> &mut Self

Checks that value <= max (“must be less than or equal to N”).

§Examples
let mut v = ocre::Validator::new();
v.less_than_or_equal_to("seats", 9, 8);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Seats must be less than or equal to 8");
Source

pub fn other_than<T: PartialEq + Display>( &mut self, field: &str, value: T, other: T, ) -> &mut Self

Checks that value != other (“must be other than N”).

§Examples
let mut v = ocre::Validator::new();
v.other_than("parent_id", 4, 4);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Parent must be other than 4");
Source

pub fn confirmation( &mut self, field: &str, value: &str, confirmation: &str, ) -> &mut Self

Checks that confirmation equals value, like Rails’ confirmation: the error goes on <field>_confirmation (“doesn’t match Password”).

§Examples
let mut v = ocre::Validator::new();
v.confirmation("password", "s3cret-pass", "s3cret-pass");
assert!(v.is_valid());
v.confirmation("password", "s3cret-pass", "typo");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Password confirmation doesn't match Password");
Source

pub fn acceptance(&mut self, field: &str, accepted: bool) -> &mut Self

Checks that a checkbox was ticked (“must be accepted”), like Rails’ acceptance.

The value is not stored: take it as a bool in the form or JSON struct (#[serde(default)], since an unticked checkbox sends nothing).

§Examples
let mut v = ocre::Validator::new();
v.acceptance("terms_of_service", false);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Terms of service must be accepted");
Source

pub fn absence(&mut self, field: &str, value: &str) -> &mut Self

Checks that value is blank: empty or only whitespace (“must be blank”), like Rails’ absence.

For an Option, check value.is_some() with check.

§Examples
let mut v = ocre::Validator::new();
v.absence("nickname", " ");
assert!(v.is_valid());
v.absence("nickname", "bot");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Nickname must be blank");
Source

pub fn format( &mut self, field: &str, value: &str, allowed: impl Fn(char) -> bool, ) -> &mut Self

Checks that every character of value passes allowed (“is invalid”): Rails’ format, without regular expressions (no regex engine in the WebAssembly binary). Combine with min_length to refuse an empty value, and with check for position rules (value.starts_with(..)).

§Examples
let mut v = ocre::Validator::new();
let slug = |c: char| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-';
v.format("slug", "hello-2026", slug);
assert!(v.is_valid());
v.format("slug", "Hello World", slug);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Slug is invalid");
Source

pub fn message(&mut self, message: impl Into<String>) -> &mut Self

Replaces the message of the check just before, if it failed (Rails’ message: option).

Only the last check counts: call it right after the check it changes.

§Examples
let mut v = ocre::Validator::new();
v.required("title", "Dune").message("needs a title");
v.required("body", "").message("write something first");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Body write something first");
Source

pub fn errors(&self) -> &[FieldError]

The errors collected so far, in the order the checks ran (Rails’ errors).

§Examples
let mut v = ocre::Validator::new();
v.required("title", "").required("body", "");
let fields: Vec<&str> = v.errors().iter().map(|e| e.field.as_str()).collect();
assert_eq!(fields, ["title", "body"]);
Source

pub fn email(&mut self, field: &str, value: &str) -> &mut Self

Checks that value looks like an e-mail address (“is invalid”).

One @, text on both sides, a dot in the domain, no spaces or <>,. Delivery is the only real check. mail::send applies the same rule.

§Examples
let mut v = ocre::Validator::new();
v.email("email", "ada@example.com");
assert!(v.is_valid());
v.email("email", "ada@localhost");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Email is invalid");
Source

pub fn number<T: FromStr>(&mut self, field: &str, text: &str) -> Option<T>

Parses a required number typed as text (HTML forms), adding “is not a number” when it does not parse.

Surrounding whitespace is ignored. Returns the parsed value, or None after adding the error, so parsing and validation happen in one pass.

§Examples
let mut v = ocre::Validator::new();
assert_eq!(v.number::<i64>("pages", " 42 "), Some(42));
assert_eq!(v.number::<i64>("year", ""), None);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Year is not a number");
Source

pub fn optional_number<T: FromStr>( &mut self, field: &str, text: &str, ) -> Option<T>

Parses an optional number typed as text: blank text is None without an error.

Otherwise like number.

§Examples
let mut v = ocre::Validator::new();
assert_eq!(v.optional_number::<u32>("pages", "  "), None);
assert!(v.is_valid());
assert_eq!(v.optional_number::<u32>("pages", "-1"), None);
assert!(!v.is_valid());
Source

pub fn one_of<T: FromStr>(&mut self, field: &str, text: &str) -> Option<T>

Parses form text into one of an enum’s values, like number: None plus “is not included in the list” when T::from_str refuses it. Generated scaffolds use it for enum fields.

§Examples
#[derive(Debug, PartialEq)]
enum Status {
    Draft,
}

impl std::str::FromStr for Status {
    type Err = ();
    fn from_str(text: &str) -> Result<Self, ()> {
        if text == "draft" { Ok(Status::Draft) } else { Err(()) }
    }
}

let mut v = ocre::Validator::new();
assert_eq!(v.one_of::<Status>("status", "draft"), Some(Status::Draft));
assert_eq!(v.one_of::<Status>("status", "archived"), None);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Status is not included in the list");
Source

pub fn optional_one_of<T: FromStr>( &mut self, field: &str, text: &str, ) -> Option<T>

Like one_of for an optional field: blank text is None without error.

§Examples
let mut v = ocre::Validator::new();
assert_eq!(v.optional_one_of::<bool>("flag", " "), None);
assert_eq!(v.optional_one_of::<bool>("flag", "true"), Some(true));
assert!(v.is_valid());
Source

pub fn json(&mut self, field: &str, text: &str) -> Option<Value>

Parses a required JSON value typed as text (a <textarea>), adding “is not valid JSON” when it does not parse.

Returns the parsed value, or None after adding the error.

§Examples
let mut v = ocre::Validator::new();
assert_eq!(v.json("metadata", r#"{"a": 1}"#), Some(serde_json::json!({"a": 1})));
assert_eq!(v.json("metadata", "{a: 1}"), None);
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Metadata is not valid JSON");
Source

pub fn optional_json(&mut self, field: &str, text: &str) -> Option<Value>

Parses an optional JSON value typed as text: blank text is None without an error.

Otherwise like json.

§Examples
let mut v = ocre::Validator::new();
assert_eq!(v.optional_json("metadata", ""), None);
assert!(v.is_valid());
Source

pub fn date(&mut self, field: &str, value: &str) -> &mut Self

Checks that value is a real calendar date written YYYY-MM-DD (“is not a valid date”).

Month lengths and leap years are checked.

§Examples
let mut v = ocre::Validator::new();
v.date("born_on", "2024-02-29");
assert!(v.is_valid());
v.date("born_on", "2023-02-29");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Born on is not a valid date");
Source

pub fn datetime(&mut self, field: &str, value: &str) -> &mut Self

Checks that value is YYYY-MM-DD HH:MM[:SS], with a space or T (HTML datetime-local).

Message: “is not a valid date and time”. No time zone is accepted.

§Examples
let mut v = ocre::Validator::new();
v.datetime("starts_at", "2026-09-29T18:30").datetime("ends_at", "2026-09-29 19:00:00");
assert!(v.is_valid());
v.datetime("starts_at", "2026-09-29");
assert!(!v.is_valid());
Source

pub fn time(&mut self, field: &str, value: &str) -> &mut Self

Checks that value is a time of day written HH:MM or HH:MM:SS (HTML <input type="time">).

Message: “is not a valid time”. No time zone is accepted.

§Examples
let mut v = ocre::Validator::new();
v.time("opens_at", "09:30").time("closes_at", "18:00:00");
assert!(v.is_valid());
v.time("opens_at", "24:00");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Opens at is not a valid time");
Source

pub fn uuid(&mut self, field: &str, value: &str) -> &mut Self

Checks that value is a UUID in its hyphenated form, any case (“is not a valid UUID”).

§Examples
let mut v = ocre::Validator::new();
v.uuid("token", "67e55044-10b1-426f-9247-bb680e5fe0c8");
assert!(v.is_valid());
v.uuid("token", "67e55044");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Token is not a valid UUID");
Source

pub fn decimal(&mut self, field: &str, value: &str) -> &mut Self

Checks that value is an exact decimal number such as -12.50 (“is not a decimal number”).

An optional sign, digits, then optionally a dot and digits: no exponent, no spaces. Decimal columns are stored as this text, so money keeps every digit (a REAL would round 0.1 + 0.2).

§Examples
let mut v = ocre::Validator::new();
v.decimal("price", "19.99").decimal("balance", "-3");
assert!(v.is_valid());
v.decimal("price", "1e3");
assert_eq!(v.finish().unwrap_err().to_string(), "invalid: Price is not a decimal number");
Source

pub fn merge(&mut self, other: Validator) -> &mut Self

Adds the errors collected by other, e.g. a model’s validate() after parsing a form.

§Examples
let mut model = ocre::Validator::new();
model.required("title", "");
let mut form = ocre::Validator::new();
form.merge(model);
assert!(!form.is_valid());
Source

pub fn is_valid(&self) -> bool

Whether no error has been collected so far.

§Examples
let mut v = ocre::Validator::new();
assert!(v.is_valid());
v.required("title", "");
assert!(!v.is_valid());
Source

pub fn finish(&mut self) -> Result<(), Error>

Returns Ok(()) when every check passed, and takes the collected errors otherwise.

The validator is empty afterwards, so it can be reused.

§Errors

Error::Invalid (422) with every collected FieldError, in the order the checks ran.

§Examples
use ocre::{Error, FieldError, Validator};

let mut v = Validator::new();
v.required("title", "");
let Err(Error::Invalid(errors)) = v.finish() else { panic!("expected errors") };
assert_eq!(errors, [FieldError::new("title", "can't be blank")]);
assert!(v.finish().is_ok());

Trait Implementations§

Source§

impl Debug for Validator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Validator

Source§

fn default() -> Validator

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<S, T> Upcast<T> for S
where T: UpcastFrom<S> + ?Sized, S: ?Sized,

Source§

fn upcast(&self) -> &T
where Self: ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider ref type within the Wasm bindgen generics type system. Read more
Source§

fn upcast_into(self) -> T
where Self: Sized + ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider type within the Wasm bindgen generics type system. Read more
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V