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
impl Validator
Sourcepub fn file_content(&mut self, field: &str, upload: &Upload) -> &mut Self
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
impl Validator
Sourcepub fn file(&mut self, field: &str, upload: &Upload, rules: &Rules) -> &mut Self
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", ¬e, &DOC).finish().is_ok());Source§impl Validator
impl Validator
Sourcepub fn check(
&mut self,
field: &str,
failed: bool,
message: impl Into<String>,
) -> &mut Self
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");Sourcepub fn required(&mut self, field: &str, value: &str) -> &mut Self
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");Sourcepub fn max_length(&mut self, field: &str, value: &str, max: usize) -> &mut Self
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)");Sourcepub fn min_length(&mut self, field: &str, value: &str, min: usize) -> &mut Self
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)");Sourcepub fn range<T: PartialOrd + Display>(
&mut self,
field: &str,
value: T,
range: RangeInclusive<T>,
) -> &mut Self
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");Sourcepub fn safe_integer(&mut self, field: &str, value: i64) -> &mut Self
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());Sourcepub fn inclusion(
&mut self,
field: &str,
value: &str,
allowed: &[&str],
) -> &mut Self
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");Sourcepub fn exclusion(
&mut self,
field: &str,
value: &str,
forbidden: &[&str],
) -> &mut Self
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");Sourcepub fn length(&mut self, field: &str, value: &str, length: usize) -> &mut Self
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)");Sourcepub fn greater_than<T: PartialOrd + Display>(
&mut self,
field: &str,
value: T,
than: T,
) -> &mut Self
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");Sourcepub fn greater_than_or_equal_to<T: PartialOrd + Display>(
&mut self,
field: &str,
value: T,
min: T,
) -> &mut Self
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");Sourcepub fn less_than<T: PartialOrd + Display>(
&mut self,
field: &str,
value: T,
than: T,
) -> &mut Self
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");Sourcepub fn less_than_or_equal_to<T: PartialOrd + Display>(
&mut self,
field: &str,
value: T,
max: T,
) -> &mut Self
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");Sourcepub fn other_than<T: PartialEq + Display>(
&mut self,
field: &str,
value: T,
other: T,
) -> &mut Self
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");Sourcepub fn confirmation(
&mut self,
field: &str,
value: &str,
confirmation: &str,
) -> &mut Self
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");Sourcepub fn acceptance(&mut self, field: &str, accepted: bool) -> &mut Self
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");Sourcepub fn absence(&mut self, field: &str, value: &str) -> &mut Self
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");Sourcepub fn format(
&mut self,
field: &str,
value: &str,
allowed: impl Fn(char) -> bool,
) -> &mut Self
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");Sourcepub fn message(&mut self, message: impl Into<String>) -> &mut Self
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");Sourcepub fn errors(&self) -> &[FieldError]
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"]);Sourcepub fn email(&mut self, field: &str, value: &str) -> &mut Self
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");Sourcepub fn number<T: FromStr>(&mut self, field: &str, text: &str) -> Option<T>
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");Sourcepub fn optional_number<T: FromStr>(
&mut self,
field: &str,
text: &str,
) -> Option<T>
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());Sourcepub fn one_of<T: FromStr>(&mut self, field: &str, text: &str) -> Option<T>
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");Sourcepub fn json(&mut self, field: &str, text: &str) -> Option<Value>
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");Sourcepub fn date(&mut self, field: &str, value: &str) -> &mut Self
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");Sourcepub fn datetime(&mut self, field: &str, value: &str) -> &mut Self
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());Sourcepub fn time(&mut self, field: &str, value: &str) -> &mut Self
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");Sourcepub fn uuid(&mut self, field: &str, value: &str) -> &mut Self
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");Sourcepub fn decimal(&mut self, field: &str, value: &str) -> &mut Self
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");Sourcepub fn merge(&mut self, other: Validator) -> &mut Self
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());Sourcepub fn is_valid(&self) -> bool
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());Sourcepub fn finish(&mut self) -> Result<(), Error>
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());