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