struct_validation_core/lib.rs
1use miette::Diagnostic;
2use thiserror::Error;
3
4/// Represents an error that occurs during validation of a struct's field.
5///
6/// Each `ValidationError` contains the name of the field that failed validation
7/// and an associated error message explaining the reason for the failure.
8
9#[derive(Debug, Error, Diagnostic)]
10#[error("field {field}: {message}")]
11#[diagnostic(
12 help("Ensure '{field}' is valid.")
13)]
14pub struct ValidationError {
15 pub field: String,
16 pub message: String,
17}
18
19impl ValidationError {
20 /// Creates a new `ValidationError` for a specific field with a given message.
21 ///
22 /// # Arguments
23 ///
24 /// * `field` - The name of the field that failed validation.
25 /// * `message` - A description of the validation error.
26 ///
27 /// # Examples
28 ///
29 /// ```
30 /// use struct_validation_core::ValidationError;
31 ///
32 /// let error = ValidationError::new("username", "must not be empty");
33 /// assert_eq!(error.field, "username");
34 /// assert_eq!(error.message, "must not be empty");
35 /// ```
36 pub fn new(field: &str, message: &str) -> Self {
37 Self {
38 field: field.to_string(),
39 message: message.to_string(),
40 }
41 }
42
43 /// Adds a prefix to the field name, separated by a dot.
44 ///
45 /// This can be useful for nested validation errors where the field
46 /// is part of a larger struct.
47 ///
48 /// # Arguments
49 ///
50 /// * `prefix` - The prefix to add to the field name.
51 ///
52 /// # Examples
53 ///
54 /// ```
55 /// use struct_validation_core::ValidationError;
56 ///
57 /// let mut error = ValidationError::new("username", "must not be empty");
58 /// error.add_prefix("user");
59 /// assert_eq!(error.field, "user.username");
60 /// ```
61 pub fn add_prefix(&mut self, prefix: &str) {
62 self.field = format!("{}.{}", prefix, self.field);
63 }
64}
65
66/// A trait for validating structs.
67///
68/// Implement this trait for your structs to define custom validation logic.
69/// The `validate` method should return a vector of `ValidationError`s indicating
70/// any validation failures.
71pub trait Validate {
72 /// Validates the current instance and returns a list of validation errors.
73 ///
74 /// If the instance is valid, the returned vector should be empty.
75 ///
76 /// # Examples
77 ///
78 /// ```
79 /// use struct_validation_core::{Validate, ValidationError};
80 ///
81 /// struct User {
82 /// username: String,
83 /// email: String,
84 /// }
85 ///
86 /// impl Validate for User {
87 /// fn validate(&self) -> Vec<ValidationError> {
88 /// let mut errors = Vec::new();
89 ///
90 /// if self.username.is_empty() {
91 /// errors.push(ValidationError::new("username", "must not be empty"));
92 /// }
93 ///
94 /// if !self.email.contains('@') {
95 /// errors.push(ValidationError::new("email", "must contain '@'"));
96 /// }
97 ///
98 /// errors
99 /// }
100 /// }
101 /// ```
102 fn validate(&self) -> Vec<ValidationError>;
103}
104
105/// A macro to simplify validation checks.
106///
107/// **Usage:** `validate!(vec, (boolean test expression), "field", "message")`
108///
109/// This macro checks the provided boolean test expression, and if it evaluates to `true`,
110/// it pushes a new `ValidationError` onto the provided vector.
111///
112/// # Arguments
113///
114/// * `$vec` - The vector to which the `ValidationError` will be added.
115/// * `$test` - A boolean expression that determines whether to add the error.
116/// * `$field_name` - The name of the field related to the validation error.
117/// * `$message` - A message describing the validation error.
118///
119/// # Examples
120///
121/// ```
122/// use struct_validation_core::{validate, ValidationError};
123///
124/// let mut errors = Vec::new();
125/// let username = "";
126/// validate!(errors, username.is_empty(), "username", "must not be empty");
127/// assert_eq!(errors.len(), 1);
128/// assert_eq!(errors[0].field, "username");
129/// assert_eq!(errors[0].message, "must not be empty");
130/// ```
131#[macro_export]
132macro_rules! validate {
133 ($vec:expr, $test:expr, $field_name:expr, $message:expr) => {
134 if $test {
135 $vec.push($crate::ValidationError::new($field_name, $message));
136 }
137 };
138}