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 TooManyRequests(Duration),
19 Unavailable(String),
20 Internal {
21 context: String,
22 source: Option<Box<dyn std::error::Error + Send + Sync>>,
23 },
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct FieldError {
28 pub field: String,
29 pub key: Cow<'static, str>,
30 pub arguments: Vec<(Cow<'static, str>, String)>,
31 pub message: String,
32}
33
34impl FieldError {
35 pub fn new(
36 field: impl Into<String>,
37 key: impl Into<Cow<'static, str>>,
38 message: impl Into<String>,
39 ) -> Self {
40 Self {
41 field: field.into(),
42 key: key.into(),
43 arguments: Vec::new(),
44 message: message.into(),
45 }
46 }
47
48 pub fn required(field: impl Into<String>) -> Self {
49 Self::new(field, REQUIRED, "is required")
50 }
51
52 pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
53 Self::new(field, INVALID, message)
54 }
55
56 #[must_use]
57 pub fn with(mut self, name: impl Into<Cow<'static, str>>, value: impl Into<String>) -> Self {
58 self.arguments.push((name.into(), value.into()));
59 self
60 }
61}
62
63const INVALID: &str = "validation.invalid";
64const REQUIRED: &str = "validation.required";
65
66pub type Result<T, E = Error> = std::result::Result<T, E>;
67
68impl Error {
69 pub fn internal(
70 context: impl Into<String>,
71 source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
72 ) -> Self {
73 Self::Internal {
74 context: context.into(),
75 source: Some(source.into()),
76 }
77 }
78
79 pub fn internal_message(context: impl Into<String>) -> Self {
80 Self::Internal {
81 context: context.into(),
82 source: None,
83 }
84 }
85
86 pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
87 Self::Invalid(vec![FieldError::invalid(field, message)])
88 }
89
90 #[must_use]
92 pub fn into_deserialize_error<E: serde::de::Error>(self) -> E {
93 E::custom(format_args!("{self:#}"))
94 }
95
96 fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 Self::BadRequest(message) | Self::Rule(message) => f.write_str(message),
99 Self::Invalid(errors) => {
100 let messages: Vec<&str> =
101 errors.iter().map(|error| error.message.as_str()).collect();
102 f.write_str(&messages.join("; "))
103 }
104 _ => write!(f, "{self}"),
105 }
106 }
107}
108
109impl fmt::Display for Error {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 if f.alternate() {
113 return self.fmt_message(f);
114 }
115 match self {
116 Self::NotFound => f.write_str("not found"),
117 Self::Moved { from, to } => write!(f, "moved from `{from}` to `{to}`"),
118 Self::Conflict => f.write_str("the record changed since it was loaded"),
119 Self::BadRequest(message) => write!(f, "bad request: {message}"),
120 Self::Invalid(errors) => {
121 f.write_str("invalid input:")?;
122 for error in errors {
123 write!(f, " {}: {};", error.field, error.message)?;
124 }
125 Ok(())
126 }
127 Self::Rule(message) => write!(f, "refused: {message}"),
128 Self::TooManyRequests(wait) => {
129 write!(f, "too many requests; try again in {wait:?}")
130 }
131 Self::Unavailable(message) => write!(f, "unavailable: {message}"),
132 Self::Internal { context, source } => match source {
133 Some(source) => write!(f, "{context}: {source}"),
134 None => f.write_str(context),
135 },
136 }
137 }
138}
139
140impl std::error::Error for Error {
141 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
142 match self {
143 Self::Internal {
144 source: Some(source),
145 ..
146 } => Some(source.as_ref()),
147 _ => None,
148 }
149 }
150}