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}