slack_messaging/
errors.rs1use std::borrow::Cow;
2use thiserror::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Error)]
6pub enum ValidationErrorKind {
7 #[error("required")]
9 Required,
10
11 #[error("max text length `{0}` characters")]
13 MaxTextLength(usize),
14
15 #[error("min text length `{0}` characters")]
17 MinTextLength(usize),
18
19 #[error("max array length `{0}` items")]
21 MaxArraySize(usize),
22
23 #[error("min array length `{0}` items")]
25 MinArraySize(usize),
26
27 #[error("the array cannot be empty")]
29 EmptyArray,
30
31 #[error("should be in the format `{0}`")]
33 InvalidFormat(&'static str),
34
35 #[error("max value is `{0}")]
37 MaxIntegerValue(i64),
38
39 #[error("min value is `{0}")]
41 MinIntegerValue(i64),
42
43 #[error("cannot provide both {0} and {1}")]
45 ExclusiveField(&'static str, &'static str),
46
47 #[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 #[error("required at least one field")]
56 NoFieldProvided,
57
58 #[error("rich text block should have exactly one element")]
60 RichTextSingleElement,
61}
62
63#[derive(Debug, Clone, PartialEq, Error)]
69pub enum ValidationError {
70 #[error("AcrossFieldsError {0:?}")]
72 AcrossFields(Vec<ValidationErrorKind>),
73
74 #[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 pub fn field(&self) -> Option<&str> {
107 if let Self::SingleField { field, .. } = self {
108 Some(field)
109 } else {
110 None
111 }
112 }
113
114 pub fn errors(&self) -> &[ValidationErrorKind] {
116 match self {
117 Self::AcrossFields(errors) => errors,
118 Self::SingleField { errors, .. } => errors,
119 }
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Error)]
126#[error("Validation Error {{ object: {object:}, errors: {errors:?} }}")]
127pub struct ValidationErrors {
128 pub object: Cow<'static, str>,
130 pub errors: Vec<ValidationError>,
132}
133
134impl ValidationErrors {
135 pub fn object(&self) -> &str {
137 &self.object
138 }
139
140 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}