1use std::borrow::Cow;
2use std::fmt;
3use std::time::Duration;
4
5#[derive(Debug)]
6pub enum Error {
7 NotFound,
9 Moved {
11 from: String,
12 to: String,
13 },
14 Conflict,
15 BadRequest(String),
16 Invalid(Vec<FieldError>),
17 Rule(String),
18 KeyedRule(Box<KeyedRule>),
19 TooManyRequests(Duration),
20 Unavailable(String),
21 Internal {
22 context: String,
23 source: Option<Box<dyn std::error::Error + Send + Sync>>,
24 },
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct FieldError {
29 pub field: String,
30 pub key: Cow<'static, str>,
31 pub arguments: Vec<(Cow<'static, str>, String)>,
32 pub message: String,
33}
34
35impl FieldError {
36 pub fn new(
37 field: impl Into<String>,
38 key: impl Into<Cow<'static, str>>,
39 message: impl Into<String>,
40 ) -> Self {
41 Self {
42 field: field.into(),
43 key: key.into(),
44 arguments: Vec::new(),
45 message: message.into(),
46 }
47 }
48
49 pub fn required(field: impl Into<String>) -> Self {
50 Self::new(field, REQUIRED, "is required")
51 }
52
53 pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
54 Self::new(field, INVALID, message)
55 }
56
57 #[must_use]
58 pub fn with(mut self, name: impl Into<Cow<'static, str>>, value: impl Into<String>) -> Self {
59 self.arguments.push((name.into(), value.into()));
60 self
61 }
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct KeyedRule {
66 pub key: Cow<'static, str>,
67 pub arguments: Vec<(Cow<'static, str>, String)>,
68 pub message: String,
69}
70
71#[derive(Clone, Copy, Debug)]
72pub struct RuleText {
73 key: &'static str,
74 message: &'static str,
75}
76
77impl RuleText {
78 #[must_use]
79 pub const fn new(key: &'static str, message: &'static str) -> Self {
80 Self { key, message }
81 }
82}
83
84impl From<RuleText> for Error {
85 fn from(rule: RuleText) -> Self {
86 Self::rule_keyed(rule.key, [], rule.message)
87 }
88}
89
90const INVALID: &str = "validation.invalid";
91const REQUIRED: &str = "validation.required";
92
93pub type Result<T, E = Error> = std::result::Result<T, E>;
94
95impl Error {
96 pub fn internal(
97 context: impl Into<String>,
98 source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
99 ) -> Self {
100 Self::Internal {
101 context: context.into(),
102 source: Some(source.into()),
103 }
104 }
105
106 pub fn internal_message(context: impl Into<String>) -> Self {
107 Self::Internal {
108 context: context.into(),
109 source: None,
110 }
111 }
112
113 pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
114 Self::Invalid(vec![FieldError::invalid(field, message)])
115 }
116
117 pub fn rule_keyed(
118 key: &'static str,
119 arguments: impl IntoIterator<Item = (&'static str, String)>,
120 message: impl Into<String>,
121 ) -> Self {
122 Self::KeyedRule(Box::new(KeyedRule {
123 key: Cow::Borrowed(key),
124 arguments: arguments
125 .into_iter()
126 .map(|(name, value)| (Cow::Borrowed(name), value))
127 .collect(),
128 message: message.into(),
129 }))
130 }
131
132 #[must_use]
134 pub fn into_deserialize_error<E: serde::de::Error>(self) -> E {
135 E::custom(format_args!("{self:#}"))
136 }
137
138 fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 match self {
140 Self::BadRequest(message) | Self::Rule(message) => f.write_str(message),
141 Self::KeyedRule(rule) => f.write_str(&rule.message),
142 Self::Invalid(errors) => {
143 let messages: Vec<&str> =
144 errors.iter().map(|error| error.message.as_str()).collect();
145 f.write_str(&messages.join("; "))
146 }
147 _ => write!(f, "{self}"),
148 }
149 }
150}
151
152impl fmt::Display for Error {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 if f.alternate() {
156 return self.fmt_message(f);
157 }
158 match self {
159 Self::NotFound => f.write_str("not found"),
160 Self::Moved { from, to } => write!(f, "moved from `{from}` to `{to}`"),
161 Self::Conflict => f.write_str("the record changed since it was loaded"),
162 Self::BadRequest(message) => write!(f, "bad request: {message}"),
163 Self::Invalid(errors) => {
164 f.write_str("invalid input:")?;
165 for error in errors {
166 write!(f, " {}: {};", error.field, error.message)?;
167 }
168 Ok(())
169 }
170 Self::Rule(message) => write!(f, "refused: {message}"),
171 Self::KeyedRule(rule) => write!(f, "refused: {}", rule.message),
172 Self::TooManyRequests(wait) => {
173 write!(f, "too many requests; try again in {wait:?}")
174 }
175 Self::Unavailable(message) => write!(f, "unavailable: {message}"),
176 Self::Internal { context, source } => match source {
177 Some(source) => write!(f, "{context}: {source}"),
178 None => f.write_str(context),
179 },
180 }
181 }
182}
183
184impl std::error::Error for Error {
185 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
186 match self {
187 Self::Internal {
188 source: Some(source),
189 ..
190 } => Some(source.as_ref()),
191 _ => None,
192 }
193 }
194}