cjtoolkit_structured_validator/common/
validation_collector.rs

1//! This module contains structures and traits for working with validation errors.
2
3use crate::common::locale::LocaleMessage;
4use blake3::Hash;
5use std::fmt::Debug;
6use std::sync::Arc;
7
8/// `ValidateErrorStore` is a structure used to store validation errors, where each error consists
9/// of a `String` key and an associated `Box<dyn LocaleMessage>` value. The key represents
10/// an identifier (e.g., field name or error code), while the `LocaleMessage` represents
11/// a localizable message for the associated validation error.
12///
13/// This structure is designed to be `Default` and makes use of an `Arc<[]>` to share ownership
14/// of the data, enabling efficient cloning and concurrent usage in multithreaded contexts.
15///
16/// # Fields
17/// - `0`: A reference-counted array (`Arc<[]>`) of tuples containing:
18///   - `String`: The identifier for the validation error.
19///   - `Box<dyn LocaleMessage>`: A boxed trait object to represent a localizable message dynamically.
20///
21/// # Traits
22/// The struct derives the `Default` trait so it can be initialized with an empty error store.
23///
24#[derive(Default)]
25pub struct ValidateErrorStore(pub Arc<[(String, Box<dyn LocaleMessage>)]>);
26
27impl Debug for ValidateErrorStore {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        for (i, error) in self.0.iter().enumerate() {
30            if i > 0 {
31                write!(f, ", ")?;
32            }
33            write!(f, "{:?}", error.0)?;
34        }
35        Ok(())
36    }
37}
38
39impl PartialEq for ValidateErrorStore {
40    fn eq(&self, other: &Self) -> bool {
41        self.hash() == other.hash()
42    }
43}
44
45impl Clone for ValidateErrorStore {
46    fn clone(&self) -> Self {
47        Self(Arc::clone(&self.0))
48    }
49}
50
51impl ValidateErrorStore {
52    /// Converts the internal vector representation of the original message
53    /// into an `Arc<[String]>`, a thread-safe, shared, and reference-counted slice of strings.
54    ///
55    /// # Returns
56    /// An `Arc<[String]>` containing the original message as a shared reference. The
57    /// `Arc` allows multiple threads to safely share ownership of the message data without
58    /// requiring additional cloning or copying of the underlying strings.
59    ///
60    /// # Usage
61    /// This method can be used when you need a thread-safe, immutable reference
62    /// to the original message structure, allowing for efficient sharing of data
63    /// across threads.
64    ///
65    /// # Implementation
66    /// Internally, the method calls `as_original_message_vec()` to retrieve the original
67    /// message as a `Vec<String>` and then converts it to an `Arc<[String]>`.
68    ///
69    pub fn as_original_message(&self) -> Arc<[String]> {
70        self.as_original_message_vec().into()
71    }
72
73    /// Converts the current collection into a `Vec<String>` containing the original messages.
74    ///
75    /// # Description
76    /// This method iterates over the elements of the internal collection, extracts the original
77    /// message (assumed to be stored as the first element of each component, `e.0`), clones it,
78    /// and collects all the cloned messages into a new `Vec<String>`.
79    ///
80    /// # Returns
81    /// A `Vec<String>` containing the cloned original messages from each element in the collection.
82    ///
83    /// # Notes
84    /// - The internal structure `self.0` must be iterable, and each element must have a
85    ///   `0` field containing a `String`-like value.
86    /// - The result is a completely new vector and does not modify the internal state of the
87    ///   current collection.
88    ///
89    /// # Complexity
90    /// This method has a time complexity of O(n), where n is the number of elements in
91    /// the internal collection `self.0`.
92    pub fn as_original_message_vec(&self) -> Vec<String> {
93        self.0.iter().map(|e| e.0.clone()).collect()
94    }
95
96    /// Converts the current instance into a `ValidateErrorCollector`.
97    ///
98    /// This method takes the current object, clones it, and converts it into a
99    /// `ValidateErrorCollector` instance. It is useful when you want to transform
100    /// the current object to a `ValidateErrorCollector` for further processing or
101    /// validation error handling.
102    ///
103    /// # Returns
104    ///
105    /// A `ValidateErrorCollector` created by cloning and converting the current object.
106    pub fn as_validate_error_collector(&self) -> ValidateErrorCollector {
107        self.clone().into()
108    }
109
110    fn hash(&self) -> Hash {
111        let mut hasher = blake3::Hasher::new();
112        for error in self.0.iter() {
113            hasher.update(error.0.as_bytes());
114        }
115        hasher.finalize()
116    }
117}
118
119impl Into<ValidateErrorCollector> for ValidateErrorStore {
120    fn into(self) -> ValidateErrorCollector {
121        let mut errors: Vec<(String, Box<dyn LocaleMessage>)> = vec![];
122        for error in self.0.iter() {
123            errors.push((error.0.clone(), Box::new(error.1.get_locale_data())));
124        }
125        ValidateErrorCollector(errors)
126    }
127}
128
129/// A struct for collecting validation errors in a list.
130///
131/// `ValidateErrorCollector` is used to gather validation errors that can be
132/// associated with a specific field or key. Each error is stored as a tuple containing:
133/// - A `String` representing the field or key name where the error occurred.
134/// - A `Box<dyn LocaleMessage>` representing a localized error message.
135///
136/// # Fields
137/// - `0`: A vector of tuples, each tuple containing a field name as `String` and a
138///   localized error message as `Box<dyn LocaleMessage>`.
139///
140/// Note: The `LocaleMessage` trait is used to encapsulate errors with localization support.
141/// Implementations of `LocaleMessage` should provide mechanisms for translating error messages
142/// to various locales.
143#[derive(Default)]
144pub struct ValidateErrorCollector(pub Vec<(String, Box<dyn LocaleMessage>)>);
145
146impl Into<ValidateErrorStore> for ValidateErrorCollector {
147    fn into(self) -> ValidateErrorStore {
148        ValidateErrorStore(self.0.into())
149    }
150}
151
152impl ValidateErrorCollector {
153    /// Creates a new instance of the struct with an empty `Vec`.
154    ///
155    /// # Returns
156    /// A new instance of the struct containing an empty `Vec`.
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use cjtoolkit_structured_validator::common::locale::ValidateErrorCollector;
162    /// let instance = ValidateErrorCollector::new();
163    /// assert!(instance.0.is_empty());
164    /// ```
165    pub fn new() -> Self {
166        Self(vec![])
167    }
168
169    /// Checks whether the container is empty.
170    ///
171    /// This method returns `true` if the container has no elements, and `false` otherwise.
172    ///
173    /// # Returns
174    /// * `true` - If the container is empty.
175    /// * `false` - If the container contains one or more elements.
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// use cjtoolkit_structured_validator::common::locale::ValidateErrorCollector;
181    /// let container = ValidateErrorCollector::new();
182    /// assert!(container.is_empty());
183    /// ```
184    pub fn is_empty(&self) -> bool {
185        self.0.is_empty()
186    }
187
188    ///
189    /// Adds an error item to the collection.
190    ///
191    /// # Parameters
192    /// - `error`: A tuple containing:
193    ///   - A `String` representing the error message or identifier.
194    ///   - A `Box<dyn LocaleMessage>` which encapsulates a trait object implementing `LocaleMessage`.
195    ///     This provides localized details for the error.
196    ///
197    /// # Behavior
198    /// Appends the given `error` tuple to the internal vector storing errors.
199    ///
200    pub fn push(&mut self, error: (String, Box<dyn LocaleMessage>)) {
201        self.0.push(error);
202    }
203
204    /// Returns the number of elements in the collection.
205    ///
206    /// This method provides the length of the underlying collection by
207    /// delegating the call to the `.len()` method of the inner data structure.
208    ///
209    /// # Returns
210    /// * `usize` - The number of elements currently contained in the collection.
211    ///
212    pub fn len(&self) -> usize {
213        self.0.len()
214    }
215}
216
217/// A trait that provides an abstraction to interact with and retrieve validation-related data
218/// such as error stores, error collectors, and original messages.
219///
220/// Implementors of this trait are expected to provide a mechanism to convert or extract
221/// their underlying structure into a `ValidateErrorStore`, which can then be used to
222/// access detailed validation-related data.
223///
224/// This trait also provides default implementations for retrieving validation error
225/// collectors and original messages, relying on the `ValidateErrorStore` for such operations.
226pub trait AsValidateErrorStore {
227    fn as_validate_store(&self) -> ValidateErrorStore;
228
229    fn as_validate_error_collector(&self) -> ValidateErrorCollector {
230        self.as_validate_store().as_validate_error_collector()
231    }
232
233    fn as_original_message_vec(&self) -> Vec<String> {
234        self.as_validate_store().as_original_message_vec()
235    }
236
237    fn as_original_message(&self) -> Arc<[String]> {
238        self.as_validate_store().as_original_message()
239    }
240}
241
242impl<T, E> AsValidateErrorStore for Result<T, E>
243where
244    for<'a> &'a E: Into<ValidateErrorStore>,
245{
246    fn as_validate_store(&self) -> ValidateErrorStore {
247        self.as_ref().err().map(Into::into).unwrap_or_default()
248    }
249}