1use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum ErrorKind {
15 InvalidRequest,
17 Unauthorized,
19 ContentTooLarge,
23 PermissionDenied,
26 NotSupported,
29 NotFound,
31 MethodNotAllowed,
34 Gone,
37 AlreadyExists,
39 Conflict,
43 DeadlineExceeded,
46 Unavailable,
52 OutcomeUnknown,
56 DataCorruption,
58 Internal,
60}
61
62macro_rules! error_codes {
69 (@count) => { 0 };
70 (@count $head:ident $($tail:ident)*) => { 1 + error_codes!(@count $($tail)*) };
71 ($($variant:ident => $wire:literal),+ $(,)?) => {
72 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
79 #[non_exhaustive]
80 pub enum ErrorCode {
81 $(
82 #[doc = concat!("Carries the stable wire code `", $wire, "`.")]
83 $variant,
84 )+
85 }
86
87 impl ErrorCode {
88 pub const ALL: [ErrorCode; error_codes!(@count $($variant)+)] =
90 [$(ErrorCode::$variant,)+];
91
92 pub fn as_str(self) -> &'static str {
94 match self {
95 $(ErrorCode::$variant => $wire,)+
96 }
97 }
98
99 pub fn parse(value: &str) -> Option<ErrorCode> {
102 match value {
103 $($wire => Some(ErrorCode::$variant),)+
104 _ => None,
105 }
106 }
107 }
108
109 impl serde::Serialize for ErrorCode {
110 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
111 serializer.serialize_str(self.as_str())
112 }
113 }
114
115 impl<'de> serde::Deserialize<'de> for ErrorCode {
116 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
121 let value = String::deserialize(deserializer)?;
122 ErrorCode::parse(&value).ok_or_else(|| {
123 serde::de::Error::custom(format_args!("unknown error code `{value}`"))
124 })
125 }
126 }
127 };
128}
129
130error_codes! {
131 InvalidRequest => "invalid_request",
132 Unauthorized => "unauthorized",
133 PermissionDenied => "permission_denied",
134 ContentTooLarge => "content_too_large",
135 NotSupported => "not_supported",
136 RouteNotFound => "route_not_found",
137 MethodNotAllowed => "method_not_allowed",
138 NamespaceNotFound => "namespace_not_found",
139 NamespaceDeleted => "namespace_deleted",
140 NamespaceExists => "namespace_exists",
141 ContentNotPrepared => "content_not_prepared",
142 PathNotFound => "path_not_found",
143 InodeNotFound => "inode_not_found",
144 RevisionNotFound => "revision_not_found",
145 PathConflict => "path_conflict",
146 DirectoryNotEmpty => "directory_not_empty",
147 StaleHead => "stale_head",
148 StaleRevision => "stale_revision",
149 StaleAttributes => "stale_attributes",
150 NotDeleted => "not_deleted",
151 WriterFenced => "writer_fenced",
152 WouldCycle => "would_cycle",
153 CommitIdReuseConflict => "commit_id_reuse_conflict",
154 CommitOutcomeUnknown => "commit_outcome_unknown",
155 CommitQueueFull => "commit_queue_full",
156 ServerBusy => "server_busy",
157 ShuttingDown => "shutting_down",
158 DeadlineExceeded => "deadline_exceeded",
159 CheckpointUnavailable => "checkpoint_unavailable",
160 MaintenanceRequired => "maintenance_required",
161 UploadNotFound => "upload_not_found",
162 UploadAlreadyCompleted => "upload_already_completed",
163 UploadContentConflict => "upload_content_conflict",
164 RebootstrapRequired => "rebootstrap_required",
165 QueryUnindexable => "query_unindexable",
166 IndexLagging => "index_lagging",
167 IndexCorrupt => "index_corrupt",
168 NamespaceCorrupt => "namespace_corrupt",
169 ServerError => "server_error",
170}
171
172impl ErrorCode {
173 pub fn kind(self) -> ErrorKind {
178 match self {
179 ErrorCode::InvalidRequest | ErrorCode::QueryUnindexable => ErrorKind::InvalidRequest,
183 ErrorCode::Unauthorized => ErrorKind::Unauthorized,
184 ErrorCode::PermissionDenied => ErrorKind::PermissionDenied,
188 ErrorCode::ContentTooLarge => ErrorKind::ContentTooLarge,
189 ErrorCode::NotSupported => ErrorKind::NotSupported,
190 ErrorCode::NamespaceNotFound
191 | ErrorCode::PathNotFound
192 | ErrorCode::InodeNotFound
193 | ErrorCode::RevisionNotFound
194 | ErrorCode::UploadNotFound
195 | ErrorCode::RouteNotFound => ErrorKind::NotFound,
196 ErrorCode::MethodNotAllowed => ErrorKind::MethodNotAllowed,
197 ErrorCode::NamespaceDeleted => ErrorKind::Gone,
198 ErrorCode::NamespaceExists => ErrorKind::AlreadyExists,
199 ErrorCode::DeadlineExceeded => ErrorKind::DeadlineExceeded,
200 ErrorCode::CommitQueueFull
201 | ErrorCode::ServerBusy
202 | ErrorCode::ShuttingDown
203 | ErrorCode::CheckpointUnavailable
204 | ErrorCode::IndexLagging
205 | ErrorCode::MaintenanceRequired => ErrorKind::Unavailable,
206 ErrorCode::CommitOutcomeUnknown => ErrorKind::OutcomeUnknown,
207 ErrorCode::IndexCorrupt | ErrorCode::NamespaceCorrupt => ErrorKind::DataCorruption,
208 ErrorCode::ServerError => ErrorKind::Internal,
209 ErrorCode::ContentNotPrepared
214 | ErrorCode::PathConflict
215 | ErrorCode::DirectoryNotEmpty
216 | ErrorCode::StaleHead
217 | ErrorCode::StaleRevision
218 | ErrorCode::StaleAttributes
222 | ErrorCode::NotDeleted
225 | ErrorCode::WriterFenced
226 | ErrorCode::WouldCycle
227 | ErrorCode::CommitIdReuseConflict
228 | ErrorCode::UploadAlreadyCompleted
229 | ErrorCode::UploadContentConflict
230 | ErrorCode::RebootstrapRequired => ErrorKind::Conflict,
231 }
232 }
233
234 pub fn retryable_without_operator_action(self) -> bool {
242 match self {
243 ErrorCode::CommitQueueFull | ErrorCode::ServerBusy | ErrorCode::ShuttingDown => true,
244 ErrorCode::InvalidRequest
245 | ErrorCode::Unauthorized
246 | ErrorCode::PermissionDenied
247 | ErrorCode::ContentTooLarge
248 | ErrorCode::NotSupported
249 | ErrorCode::RouteNotFound
250 | ErrorCode::MethodNotAllowed
251 | ErrorCode::NamespaceNotFound
252 | ErrorCode::NamespaceDeleted
253 | ErrorCode::NamespaceExists
254 | ErrorCode::ContentNotPrepared
255 | ErrorCode::PathNotFound
256 | ErrorCode::InodeNotFound
257 | ErrorCode::RevisionNotFound
258 | ErrorCode::PathConflict
259 | ErrorCode::DirectoryNotEmpty
260 | ErrorCode::StaleHead
261 | ErrorCode::StaleRevision
262 | ErrorCode::StaleAttributes
263 | ErrorCode::NotDeleted
264 | ErrorCode::WriterFenced
265 | ErrorCode::WouldCycle
266 | ErrorCode::CommitIdReuseConflict
267 | ErrorCode::CommitOutcomeUnknown
268 | ErrorCode::DeadlineExceeded
269 | ErrorCode::CheckpointUnavailable
270 | ErrorCode::MaintenanceRequired
271 | ErrorCode::UploadNotFound
272 | ErrorCode::UploadAlreadyCompleted
273 | ErrorCode::UploadContentConflict
274 | ErrorCode::RebootstrapRequired
275 | ErrorCode::QueryUnindexable
276 | ErrorCode::IndexLagging
277 | ErrorCode::IndexCorrupt
278 | ErrorCode::NamespaceCorrupt
279 | ErrorCode::ServerError => false,
280 }
281 }
282}
283
284impl fmt::Display for ErrorCode {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 f.write_str(self.as_str())
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::ErrorCode;
293
294 #[test]
295 fn error_codes_round_trip_through_their_strings() {
296 for code in ErrorCode::ALL {
297 assert_eq!(ErrorCode::parse(code.as_str()), Some(code));
298 }
299 assert_eq!(ErrorCode::parse("not_a_code"), None);
300 }
301
302 #[test]
303 fn error_codes_serde_uses_the_wire_strings() {
304 for code in ErrorCode::ALL {
305 let value = serde_json::to_value(code).expect("serialize error code");
306 assert_eq!(value, serde_json::Value::String(code.as_str().to_owned()));
307 let parsed: ErrorCode = serde_json::from_value(value).expect("deserialize error code");
308 assert_eq!(parsed, code);
309 }
310 assert!(serde_json::from_str::<ErrorCode>("\"not_a_code\"").is_err());
311 }
312
313 #[test]
314 fn retryability_is_limited_to_self_clearing_admission_conditions() {
315 let retryable: Vec<_> = ErrorCode::ALL
316 .into_iter()
317 .filter(|code| code.retryable_without_operator_action())
318 .collect();
319
320 assert_eq!(
321 retryable,
322 [
323 ErrorCode::CommitQueueFull,
324 ErrorCode::ServerBusy,
325 ErrorCode::ShuttingDown,
326 ]
327 );
328 }
329}