Skip to main content

reliakit_validate/
lib.rs

1//! Composable validation traits and error types for Rust structs and values.
2//!
3//! `reliakit-validate` provides a small, focused toolkit for expressing
4//! validation rules as types. The core pieces are:
5//!
6//! - [`Validate`] — a trait that types implement to describe their validity
7//!   rules.
8//! - [`Valid<T>`] — a zero-cost wrapper that carries proof of successful
9//!   validation in the type system.
10//! - [`ValidationError`] — an error type that collects one or more
11//!   [`Violation`]s, useful for validating multiple fields at once and
12//!   returning all failures together.
13//!
14//! # Examples
15//!
16//! ## Single-field validation
17//!
18//! ```
19//! use reliakit_validate::{Validate, Valid, ValidationError};
20//!
21//! struct Username(String);
22//!
23//! impl Validate for Username {
24//!     type Error = ValidationError;
25//!
26//!     fn validate(&self) -> Result<(), Self::Error> {
27//!         if self.0.is_empty() {
28//!             return Err(ValidationError::new("username must not be empty"));
29//!         }
30//!         if self.0.len() > 32 {
31//!             return Err(ValidationError::new("username must not exceed 32 characters"));
32//!         }
33//!         Ok(())
34//!     }
35//! }
36//!
37//! let user = Valid::new(Username("alice".into())).unwrap();
38//! assert_eq!(user.0, "alice");
39//! ```
40//!
41//! ## Multi-field struct validation
42//!
43//! ```
44//! use reliakit_validate::{Validate, ValidationError, Violation};
45//!
46//! struct CreateUser {
47//!     name: String,
48//!     age: u8,
49//! }
50//!
51//! impl Validate for CreateUser {
52//!     type Error = ValidationError;
53//!
54//!     fn validate(&self) -> Result<(), Self::Error> {
55//!         let mut errors = ValidationError::empty();
56//!
57//!         if self.name.is_empty() {
58//!             errors.push(Violation::with_field("name", "must not be empty"));
59//!         }
60//!         if self.age < 18 {
61//!             errors.push(Violation::with_field("age", "must be at least 18"));
62//!         }
63//!
64//!         if errors.is_empty() { Ok(()) } else { Err(errors) }
65//!     }
66//! }
67//!
68//! let result = CreateUser { name: String::new(), age: 15 }.validate();
69//! assert!(result.is_err());
70//! assert_eq!(result.unwrap_err().len(), 2);
71//! ```
72//!
73//! ## One error list for an API response
74//!
75//! Collecting every [`Violation`] lets a request handler report all field
76//! problems at once (e.g. as an HTTP 422 body) instead of making the client fix
77//! one error per round-trip:
78//!
79//! ```
80//! use reliakit_validate::{Validate, ValidationError, Violation};
81//!
82//! struct Signup {
83//!     email: String,
84//!     password: String,
85//! }
86//!
87//! impl Validate for Signup {
88//!     type Error = ValidationError;
89//!
90//!     fn validate(&self) -> Result<(), Self::Error> {
91//!         let mut errors = ValidationError::empty();
92//!         if !self.email.contains('@') {
93//!             errors.push(Violation::with_field("email", "must contain @"));
94//!         }
95//!         if self.password.len() < 8 {
96//!             errors.push(Violation::with_field("password", "must be at least 8 characters"));
97//!         }
98//!         if errors.is_empty() { Ok(()) } else { Err(errors) }
99//!     }
100//! }
101//!
102//! let bad = Signup { email: "nope".into(), password: "x".into() };
103//! let errors = bad.validate().unwrap_err();
104//!
105//! // Render to the (field, message) pairs a JSON error body would carry.
106//! let body: Vec<(&str, &str)> = errors
107//!     .violations()
108//!     .iter()
109//!     .map(|v| (v.field.unwrap_or("(root)"), v.message))
110//!     .collect();
111//! assert_eq!(
112//!     body,
113//!     vec![("email", "must contain @"), ("password", "must be at least 8 characters")]
114//! );
115//! ```
116//!
117//! For ready-made typed fields to validate — email, port, percentages, bounded
118//! strings, and more — pair this crate with
119//! [`reliakit-primitives`](https://docs.rs/reliakit-primitives). The
120//! `config_check` example in the `reliakit` umbrella crate shows primitives,
121//! validate, and secret working together on one config.
122
123//! # Feature flags
124//!
125//! - `std` (default) enables `std::error::Error` for [`ValidationError`] and
126//!   implies `alloc`.
127//! - `alloc` enables [`ValidationError`] and [`ValidateResult`], which collect
128//!   multiple [`Violation`]s in a `Vec`.
129//!
130//! # `no_std`
131//!
132//! The crate supports `no_std`. The [`Validate`] trait, [`Valid<T>`], and
133//! [`Violation`] are available without `alloc`; implement [`Validate`] with your
134//! own error type in allocation-free contexts. [`ValidationError`] and
135//! [`ValidateResult`] require the `alloc` feature (enabled by default via `std`).
136
137#![cfg_attr(not(feature = "std"), no_std)]
138#![forbid(unsafe_code)]
139
140#[cfg(feature = "alloc")]
141extern crate alloc;
142
143mod error;
144mod valid;
145
146pub use error::Violation;
147#[cfg(feature = "alloc")]
148pub use error::{ValidateResult, ValidationError};
149pub use valid::Valid;
150
151/// A type that can validate itself.
152///
153/// Implement this trait to express the validity rules of a type. Use
154/// [`Valid<T>`] to wrap validated values and carry the proof in the type
155/// system.
156///
157/// # Example
158///
159/// ```
160/// use reliakit_validate::{Validate, ValidationError};
161///
162/// struct Score(u8);
163///
164/// impl Validate for Score {
165///     type Error = ValidationError;
166///
167///     fn validate(&self) -> Result<(), Self::Error> {
168///         if self.0 > 100 {
169///             return Err(ValidationError::new("score must not exceed 100"));
170///         }
171///         Ok(())
172///     }
173/// }
174///
175/// assert!(Score(100).validate().is_ok());
176/// assert!(Score(101).validate().is_err());
177/// ```
178pub trait Validate {
179    /// The error type returned when validation fails.
180    type Error;
181
182    /// Checks whether `self` satisfies its validity rules.
183    ///
184    /// Returns `Ok(())` if valid, or an error describing what failed.
185    fn validate(&self) -> Result<(), Self::Error>;
186}