Skip to main content

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 minimum array length.
24    #[error("min array length `{0}` items")]
25    MinArraySize(usize),
26
27    /// Field does not meet non-empty condition.
28    #[error("the array cannot be empty")]
29    EmptyArray,
30
31    /// Field does not meet format condition.
32    #[error("should be in the format `{0}`")]
33    InvalidFormat(&'static str),
34
35    /// Field exceeds maximum integer value.
36    #[error("max value is `{0}")]
37    MaxIntegerValue(i64),
38
39    /// Field does not meet minimum integer value.
40    #[error("min value is `{0}")]
41    MinIntegerValue(i64),
42
43    /// Field must be greater than zero.
44    #[error("value must be greater than zero")]
45    MustBeGreaterThanZero,
46
47    /// Both fields are provided but only one is allowed.
48    #[error("cannot provide both {0} and {1}")]
49    ExclusiveField(&'static str, &'static str),
50
51    /// Either field is required but none is provided.
52    #[error("required either {0} or {1}")]
53    EitherRequired(&'static str, &'static str),
54
55    #[error("at least one of {0}, {1}, {2}, or {3} is required")]
56    AtLeastOneOf4(&'static str, &'static str, &'static str, &'static str),
57
58    /// At least one field is required but none is provided.
59    #[error("required at least one field")]
60    NoFieldProvided,
61
62    /// Rich text block should have exactly one element.
63    #[error("rich text block should have exactly one element")]
64    RichTextSingleElement,
65
66    /// Rich text cannot be used for a table header row.
67    #[error("rich text cannot be used for a table header row")]
68    RichTextTableHeader,
69
70    /// Each series within a chart must have a unique name.
71    #[error("each series within a chart must have a unique name")]
72    UniqueSeriesName,
73
74    /// Every data point label in every series must match a value in axis config categories.
75    #[error("every data point label in every series must match a value in axis config categories")]
76    DataPointLabelMatching,
77}
78
79/// Validation error from single field or across fields.
80///
81/// ValidationError can be either:
82/// - AcrossFields: Errors that involve multiple fields.
83/// - SingleField: Errors that pertain to a specific field.
84#[derive(Debug, Clone, PartialEq, Error)]
85pub enum ValidationError {
86    /// Errors that involve multiple fields.
87    #[error("AcrossFieldsError {0:?}")]
88    AcrossFields(Vec<ValidationErrorKind>),
89
90    /// Errors that pertain to a specific field.
91    #[error("SingleField {{ field: {field:}, errors: {errors:?} }}")]
92    SingleField {
93        field: Cow<'static, str>,
94        errors: Vec<ValidationErrorKind>,
95    },
96}
97
98impl ValidationError {
99    pub(crate) fn new_across_fields(inner: Vec<ValidationErrorKind>) -> Option<Self> {
100        if inner.is_empty() {
101            None
102        } else {
103            Some(Self::AcrossFields(inner))
104        }
105    }
106
107    pub(crate) fn new_single_field(
108        field: &'static str,
109        inner: Vec<ValidationErrorKind>,
110    ) -> Option<Self> {
111        if inner.is_empty() {
112            None
113        } else {
114            Some(Self::SingleField {
115                field: Cow::Borrowed(field),
116                errors: inner,
117            })
118        }
119    }
120
121    /// Returns field name of the error. If it is an across field error, this method returns None.
122    pub fn field(&self) -> Option<&str> {
123        if let Self::SingleField { field, .. } = self {
124            Some(field)
125        } else {
126            None
127        }
128    }
129
130    /// Returns all error variants.
131    pub fn errors(&self) -> &[ValidationErrorKind] {
132        match self {
133            Self::AcrossFields(errors) => errors,
134            Self::SingleField { errors, .. } => errors,
135        }
136    }
137}
138
139/// Validation errors objects that every builder object can return as Result::Err
140/// when validation fails.
141#[derive(Debug, Clone, PartialEq, Error)]
142#[error("Validation Error {{ object: {object:}, errors: {errors:?} }}")]
143pub struct ValidationErrors {
144    /// Name of the source object of the error.
145    pub object: Cow<'static, str>,
146    /// All validation errors of the object.
147    pub errors: Vec<ValidationError>,
148}
149
150impl ValidationErrors {
151    /// Returns the source object name of the error.
152    pub fn object(&self) -> &str {
153        &self.object
154    }
155
156    /// Returns all validation errors of the object includes.
157    pub fn errors(&self) -> &[ValidationError] {
158        &self.errors
159    }
160}
161
162#[cfg(test)]
163mod test_helpers {
164    use super::*;
165
166    pub struct ErrorKinds(Vec<ValidationErrorKind>);
167
168    impl<'a> FromIterator<&'a ValidationErrorKind> for ErrorKinds {
169        fn from_iter<T: IntoIterator<Item = &'a ValidationErrorKind>>(iter: T) -> Self {
170            Self(iter.into_iter().copied().collect())
171        }
172    }
173
174    impl ErrorKinds {
175        pub fn includes(&self, error: ValidationErrorKind) -> bool {
176            self.0.contains(&error)
177        }
178    }
179
180    impl ValidationErrors {
181        pub fn field(&self, field: &'static str) -> ErrorKinds {
182            self.filter_errors(|e| e.field().is_some_and(|f| f == field))
183        }
184
185        pub fn across_fields(&self) -> ErrorKinds {
186            self.filter_errors(|e| matches!(e, ValidationError::AcrossFields(_)))
187        }
188
189        fn filter_errors(&self, predicate: impl Fn(&ValidationError) -> bool) -> ErrorKinds {
190            self.errors()
191                .iter()
192                .filter_map(move |e| if predicate(e) { Some(e.errors()) } else { None })
193                .flatten()
194                .collect()
195        }
196    }
197}