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