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