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    /// Both fields are provided but only one is allowed.
44    #[error("cannot provide both {0} and {1}")]
45    ExclusiveField(&'static str, &'static str),
46
47    /// Either field is required but none is provided.
48    #[error("required either {0} or {1}")]
49    EitherRequired(&'static str, &'static str),
50
51    #[error("at least one of {0}, {1}, {2}, or {3} is required")]
52    AtLeastOneOf4(&'static str, &'static str, &'static str, &'static str),
53
54    /// At least one field is required but none is provided.
55    #[error("required at least one field")]
56    NoFieldProvided,
57
58    /// Rich text block should have exactly one element.
59    #[error("rich text block should have exactly one element")]
60    RichTextSingleElement,
61}
62
63/// Validation error from single field or across fields.
64///
65/// ValidationError can be either:
66/// - AcrossFields: Errors that involve multiple fields.
67/// - SingleField: Errors that pertain to a specific field.
68#[derive(Debug, Clone, PartialEq, Error)]
69pub enum ValidationError {
70    /// Errors that involve multiple fields.
71    #[error("AcrossFieldsError {0:?}")]
72    AcrossFields(Vec<ValidationErrorKind>),
73
74    /// Errors that pertain to a specific field.
75    #[error("SingleField {{ field: {field:}, errors: {errors:?} }}")]
76    SingleField {
77        field: Cow<'static, str>,
78        errors: Vec<ValidationErrorKind>,
79    },
80}
81
82impl ValidationError {
83    pub(crate) fn new_across_fields(inner: Vec<ValidationErrorKind>) -> Option<Self> {
84        if inner.is_empty() {
85            None
86        } else {
87            Some(Self::AcrossFields(inner))
88        }
89    }
90
91    pub(crate) fn new_single_field(
92        field: &'static str,
93        inner: Vec<ValidationErrorKind>,
94    ) -> Option<Self> {
95        if inner.is_empty() {
96            None
97        } else {
98            Some(Self::SingleField {
99                field: Cow::Borrowed(field),
100                errors: inner,
101            })
102        }
103    }
104
105    /// Returns field name of the error. If it is an across field error, this method returns None.
106    pub fn field(&self) -> Option<&str> {
107        if let Self::SingleField { field, .. } = self {
108            Some(field)
109        } else {
110            None
111        }
112    }
113
114    /// Returns all error variants.
115    pub fn errors(&self) -> &[ValidationErrorKind] {
116        match self {
117            Self::AcrossFields(errors) => errors,
118            Self::SingleField { errors, .. } => errors,
119        }
120    }
121}
122
123/// Validation errors objects that every builder object can return as Result::Err
124/// when validation fails.
125#[derive(Debug, Clone, PartialEq, Error)]
126#[error("Validation Error {{ object: {object:}, errors: {errors:?} }}")]
127pub struct ValidationErrors {
128    /// Name of the source object of the error.
129    pub object: Cow<'static, str>,
130    /// All validation errors of the object.
131    pub errors: Vec<ValidationError>,
132}
133
134impl ValidationErrors {
135    /// Returns the source object name of the error.
136    pub fn object(&self) -> &str {
137        &self.object
138    }
139
140    /// Returns all validation errors of the object includes.
141    pub fn errors(&self) -> &[ValidationError] {
142        &self.errors
143    }
144}
145
146#[cfg(test)]
147mod test_helpers {
148    use super::*;
149
150    pub struct ErrorKinds(Vec<ValidationErrorKind>);
151
152    impl<'a> FromIterator<&'a ValidationErrorKind> for ErrorKinds {
153        fn from_iter<T: IntoIterator<Item = &'a ValidationErrorKind>>(iter: T) -> Self {
154            Self(iter.into_iter().copied().collect())
155        }
156    }
157
158    impl ErrorKinds {
159        pub fn includes(&self, error: ValidationErrorKind) -> bool {
160            self.0.contains(&error)
161        }
162    }
163
164    impl ValidationErrors {
165        pub fn field(&self, field: &'static str) -> ErrorKinds {
166            self.filter_errors(|e| e.field().is_some_and(|f| f == field))
167        }
168
169        pub fn across_fields(&self) -> ErrorKinds {
170            self.filter_errors(|e| matches!(e, ValidationError::AcrossFields(_)))
171        }
172
173        fn filter_errors(&self, predicate: impl Fn(&ValidationError) -> bool) -> ErrorKinds {
174            self.errors()
175                .iter()
176                .filter_map(move |e| if predicate(e) { Some(e.errors()) } else { None })
177                .flatten()
178                .collect()
179        }
180    }
181}