slack_messaging/
errors.rs

1use std::borrow::Cow;
2use thiserror::Error;
3
4/// Validation error variants.
5#[derive(Debug, Clone, Copy, PartialEq, Error)]
6pub enum ValidationErrorKind {
7    /// Field is required but not provided.
8    #[error("required")]
9    Required,
10
11    /// Field exceeds maximum text length.
12    #[error("max text length `{0}` characters")]
13    MaxTextLength(usize),
14
15    /// Field does not meet minimum text length.
16    #[error("min text length `{0}` characters")]
17    MinTextLength(usize),
18
19    /// Field exceeds maximum array length.
20    #[error("max array length `{0}` items")]
21    MaxArraySize(usize),
22
23    /// Field does not meet non-empty condition.
24    #[error("the array cannot be empty")]
25    EmptyArray,
26
27    /// Field does not meet format condition.
28    #[error("should be in the format `{0}`")]
29    InvalidFormat(&'static str),
30
31    /// Field exceeds maximum integer value.
32    #[error("max value is `{0}")]
33    MaxIntegerValue(i64),
34
35    /// Field does not meet minimum integer value.
36    #[error("min value is `{0}")]
37    MinIntegerValue(i64),
38
39    /// Both fields are provided but only one is allowed.
40    #[error("cannot provide both {0} and {1}")]
41    ExclusiveField(&'static str, &'static str),
42
43    /// Either field is required but none is provided.
44    #[error("required either {0} or {1}")]
45    EitherRequired(&'static str, &'static str),
46
47    /// At least one field is required but none is provided.
48    #[error("required at least one field")]
49    NoFieldProvided,
50}
51
52/// Validation error from single field or across fields.
53///
54/// ValidationError can be either:
55/// - AcrossFields: Errors that involve multiple fields.
56/// - SingleField: Errors that pertain to a specific field.
57#[derive(Debug, Clone, PartialEq, Error)]
58pub enum ValidationError {
59    /// Errors that involve multiple fields.
60    #[error("AcrossFieldsError {0:?}")]
61    AcrossFields(Vec<ValidationErrorKind>),
62
63    /// Errors that pertain to a specific field.
64    #[error("SingleField {{ field: {field:}, errors: {errors:?} }}")]
65    SingleField {
66        field: Cow<'static, str>,
67        errors: Vec<ValidationErrorKind>,
68    },
69}
70
71impl ValidationError {
72    pub(crate) fn new_across_fields(inner: Vec<ValidationErrorKind>) -> Option<Self> {
73        if inner.is_empty() {
74            None
75        } else {
76            Some(Self::AcrossFields(inner))
77        }
78    }
79
80    pub(crate) fn new_single_field(
81        field: &'static str,
82        inner: Vec<ValidationErrorKind>,
83    ) -> Option<Self> {
84        if inner.is_empty() {
85            None
86        } else {
87            Some(Self::SingleField {
88                field: Cow::Borrowed(field),
89                errors: inner,
90            })
91        }
92    }
93
94    /// Returns field name of the error. If it is an across field error, this method returns None.
95    pub fn field(&self) -> Option<&str> {
96        if let Self::SingleField { field, .. } = self {
97            Some(field)
98        } else {
99            None
100        }
101    }
102
103    /// Returns all error variants.
104    pub fn errors(&self) -> &[ValidationErrorKind] {
105        match self {
106            Self::AcrossFields(errors) => errors,
107            Self::SingleField { errors, .. } => errors,
108        }
109    }
110}
111
112/// Validation errors objects that every builder object can return as Result::Err
113/// when validation fails.
114#[derive(Debug, Clone, PartialEq, Error)]
115#[error("Validation Error {{ object: {object:}, errors: {errors:?} }}")]
116pub struct ValidationErrors {
117    /// Name of the source object of the error.
118    pub object: Cow<'static, str>,
119    /// All validation errors of the object.
120    pub errors: Vec<ValidationError>,
121}
122
123impl ValidationErrors {
124    /// Returns the source object name of the error.
125    pub fn object(&self) -> &str {
126        &self.object
127    }
128
129    /// Returns all validation errors of the object includes.
130    pub fn errors(&self) -> &[ValidationError] {
131        &self.errors
132    }
133}
134
135#[cfg(test)]
136mod test_helpers {
137    use super::*;
138
139    pub struct ErrorKinds(Vec<ValidationErrorKind>);
140
141    impl<'a> FromIterator<&'a ValidationErrorKind> for ErrorKinds {
142        fn from_iter<T: IntoIterator<Item = &'a ValidationErrorKind>>(iter: T) -> Self {
143            Self(iter.into_iter().copied().collect())
144        }
145    }
146
147    impl ErrorKinds {
148        pub fn includes(&self, error: ValidationErrorKind) -> bool {
149            self.0.contains(&error)
150        }
151    }
152
153    impl ValidationErrors {
154        pub fn field(&self, field: &'static str) -> ErrorKinds {
155            self.filter_errors(|e| e.field().is_some_and(|f| f == field))
156        }
157
158        pub fn across_fields(&self) -> ErrorKinds {
159            self.filter_errors(|e| matches!(e, ValidationError::AcrossFields(_)))
160        }
161
162        fn filter_errors(&self, predicate: impl Fn(&ValidationError) -> bool) -> ErrorKinds {
163            self.errors()
164                .iter()
165                .filter_map(move |e| if predicate(e) { Some(e.errors()) } else { None })
166                .flatten()
167                .collect()
168        }
169    }
170}