1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum ErrorKind {
9 InvalidRequest,
11 Unauthorized,
13 Forbidden,
15 ContentTooLarge,
17 StoragePermissionDenied,
19 NotSupported,
21 NotFound,
23 MethodNotAllowed,
25 Gone,
27 AlreadyExists,
29 Conflict,
32 DeadlineExceeded,
35 Unavailable,
37 OutcomeUnknown,
40 DataCorruption,
42 Internal,
44}
45
46macro_rules! error_codes {
53 (@count) => { 0 };
54 (@count $head:ident $($tail:ident)*) => { 1 + error_codes!(@count $($tail)*) };
55 ($($variant:ident => $wire:literal),+ $(,)?) => {
56 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
60 #[non_exhaustive]
61 pub enum ErrorCode {
62 $(
63 #[doc = concat!("Carries the stable wire code `", $wire, "`.")]
64 $variant,
65 )+
66 }
67
68 impl ErrorCode {
69 pub const ALL: [ErrorCode; error_codes!(@count $($variant)+)] =
71 [$(ErrorCode::$variant,)+];
72
73 pub fn as_str(self) -> &'static str {
75 match self {
76 $(ErrorCode::$variant => $wire,)+
77 }
78 }
79
80 pub fn parse(value: &str) -> Option<ErrorCode> {
83 match value {
84 $($wire => Some(ErrorCode::$variant),)+
85 _ => None,
86 }
87 }
88 }
89
90 impl serde::Serialize for ErrorCode {
91 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
92 serializer.serialize_str(self.as_str())
93 }
94 }
95
96 impl<'de> serde::Deserialize<'de> for ErrorCode {
97 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
102 let value = String::deserialize(deserializer)?;
103 ErrorCode::parse(&value).ok_or_else(|| {
104 serde::de::Error::custom(format_args!("unknown error code `{value}`"))
105 })
106 }
107 }
108 };
109}
110
111error_codes! {
112 InvalidRequest => "invalid_request",
113 Unauthorized => "unauthorized",
114 Forbidden => "forbidden",
115 StoragePermissionDenied => "storage_permission_denied",
116 ContentTooLarge => "content_too_large",
117 NotSupported => "not_supported",
118 RouteNotFound => "route_not_found",
119 MethodNotAllowed => "method_not_allowed",
120 NamespaceNotFound => "namespace_not_found",
121 NamespaceDeleted => "namespace_deleted",
122 NamespaceExists => "namespace_exists",
123 CheckpointNotFound => "checkpoint_not_found",
124 SnapshotNotFound => "snapshot_not_found",
125 SnapshotGone => "snapshot_gone",
126 SnapshotQuotaExceeded => "snapshot_quota_exceeded",
127 ContentNotPrepared => "content_not_prepared",
128 PathNotFound => "path_not_found",
129 InodeNotFound => "inode_not_found",
130 RevisionNotFound => "revision_not_found",
131 PathConflict => "path_conflict",
132 DirectoryNotEmpty => "directory_not_empty",
133 StaleHead => "stale_head",
134 StaleRevision => "stale_revision",
135 StaleAttributes => "stale_attributes",
136 StaleAccess => "stale_access",
137 NamespaceUnrestricted => "namespace_unrestricted",
138 BindingGenerationMismatch => "binding_generation_mismatch",
139 NotDeleted => "not_deleted",
140 WriterFenced => "writer_fenced",
141 WouldCycle => "would_cycle",
142 CommitIdReuseConflict => "commit_id_reuse_conflict",
143 CommitOutcomeUnknown => "commit_outcome_unknown",
144 CommitQueueFull => "commit_queue_full",
145 WriterSessionClosed => "writer_session_closed",
146 WriterCapacityExceeded => "writer_capacity_exceeded",
147 ServerBusy => "server_busy",
148 ShuttingDown => "shutting_down",
149 DeadlineExceeded => "deadline_exceeded",
150 CheckpointUnavailable => "checkpoint_unavailable",
151 ContentNotMaterialized => "content_not_materialized",
152 MaintenanceRequired => "maintenance_required",
153 UploadNotFound => "upload_not_found",
154 UploadAlreadyCompleted => "upload_already_completed",
155 UploadContentConflict => "upload_content_conflict",
156 RebootstrapRequired => "rebootstrap_required",
157 QueryUnindexable => "query_unindexable",
158 IndexLagging => "index_lagging",
159 IndexCorrupt => "index_corrupt",
160 NamespaceCorrupt => "namespace_corrupt",
161 ServerError => "server_error",
162}
163
164impl ErrorCode {
165 pub fn kind(self) -> ErrorKind {
170 match self {
171 ErrorCode::InvalidRequest | ErrorCode::QueryUnindexable => ErrorKind::InvalidRequest,
175 ErrorCode::Unauthorized => ErrorKind::Unauthorized,
176 ErrorCode::Forbidden => ErrorKind::Forbidden,
177 ErrorCode::StoragePermissionDenied => ErrorKind::StoragePermissionDenied,
180 ErrorCode::ContentTooLarge => ErrorKind::ContentTooLarge,
181 ErrorCode::NotSupported => ErrorKind::NotSupported,
182 ErrorCode::NamespaceNotFound
183 | ErrorCode::CheckpointNotFound
184 | ErrorCode::SnapshotNotFound
185 | ErrorCode::PathNotFound
186 | ErrorCode::InodeNotFound
187 | ErrorCode::RevisionNotFound
188 | ErrorCode::UploadNotFound
189 | ErrorCode::RouteNotFound => ErrorKind::NotFound,
190 ErrorCode::MethodNotAllowed => ErrorKind::MethodNotAllowed,
191 ErrorCode::NamespaceDeleted | ErrorCode::SnapshotGone => ErrorKind::Gone,
192 ErrorCode::NamespaceExists => ErrorKind::AlreadyExists,
193 ErrorCode::DeadlineExceeded => ErrorKind::DeadlineExceeded,
194 ErrorCode::CommitQueueFull
195 | ErrorCode::WriterSessionClosed
196 | ErrorCode::WriterCapacityExceeded
197 | ErrorCode::ServerBusy
198 | ErrorCode::ShuttingDown
199 | ErrorCode::CheckpointUnavailable
200 | ErrorCode::ContentNotMaterialized
201 | ErrorCode::IndexLagging
202 | ErrorCode::MaintenanceRequired => ErrorKind::Unavailable,
203 ErrorCode::CommitOutcomeUnknown => ErrorKind::OutcomeUnknown,
204 ErrorCode::IndexCorrupt | ErrorCode::NamespaceCorrupt => ErrorKind::DataCorruption,
205 ErrorCode::ServerError => ErrorKind::Internal,
206 ErrorCode::ContentNotPrepared
211 | ErrorCode::PathConflict
212 | ErrorCode::DirectoryNotEmpty
213 | ErrorCode::StaleHead
214 | ErrorCode::StaleRevision
215 | ErrorCode::StaleAttributes
219 | ErrorCode::StaleAccess
221 | ErrorCode::NamespaceUnrestricted
223 | ErrorCode::BindingGenerationMismatch
224 | ErrorCode::NotDeleted
227 | ErrorCode::WriterFenced
228 | ErrorCode::WouldCycle
229 | ErrorCode::CommitIdReuseConflict
230 | ErrorCode::UploadAlreadyCompleted
231 | ErrorCode::UploadContentConflict
232 | ErrorCode::RebootstrapRequired
233 | ErrorCode::SnapshotQuotaExceeded => ErrorKind::Conflict,
234 }
235 }
236
237 pub fn retryable_without_operator_action(self) -> bool {
245 match self {
246 ErrorCode::CommitQueueFull | ErrorCode::ServerBusy | ErrorCode::ShuttingDown => true,
247 ErrorCode::InvalidRequest
248 | ErrorCode::Unauthorized
249 | ErrorCode::Forbidden
250 | ErrorCode::StoragePermissionDenied
251 | ErrorCode::ContentTooLarge
252 | ErrorCode::NotSupported
253 | ErrorCode::RouteNotFound
254 | ErrorCode::MethodNotAllowed
255 | ErrorCode::NamespaceNotFound
256 | ErrorCode::NamespaceDeleted
257 | ErrorCode::NamespaceExists
258 | ErrorCode::CheckpointNotFound
259 | ErrorCode::SnapshotNotFound
260 | ErrorCode::SnapshotGone
261 | ErrorCode::SnapshotQuotaExceeded
262 | ErrorCode::ContentNotPrepared
263 | ErrorCode::PathNotFound
264 | ErrorCode::InodeNotFound
265 | ErrorCode::RevisionNotFound
266 | ErrorCode::PathConflict
267 | ErrorCode::DirectoryNotEmpty
268 | ErrorCode::StaleHead
269 | ErrorCode::StaleRevision
270 | ErrorCode::StaleAttributes
271 | ErrorCode::StaleAccess
272 | ErrorCode::NamespaceUnrestricted
273 | ErrorCode::BindingGenerationMismatch
274 | ErrorCode::NotDeleted
275 | ErrorCode::WriterFenced
276 | ErrorCode::WouldCycle
277 | ErrorCode::CommitIdReuseConflict
278 | ErrorCode::CommitOutcomeUnknown
279 | ErrorCode::WriterSessionClosed
280 | ErrorCode::WriterCapacityExceeded
281 | ErrorCode::DeadlineExceeded
282 | ErrorCode::CheckpointUnavailable
283 | ErrorCode::ContentNotMaterialized
284 | ErrorCode::MaintenanceRequired
285 | ErrorCode::UploadNotFound
286 | ErrorCode::UploadAlreadyCompleted
287 | ErrorCode::UploadContentConflict
288 | ErrorCode::RebootstrapRequired
289 | ErrorCode::QueryUnindexable
290 | ErrorCode::IndexLagging
291 | ErrorCode::IndexCorrupt
292 | ErrorCode::NamespaceCorrupt
293 | ErrorCode::ServerError => false,
294 }
295 }
296}
297
298impl fmt::Display for ErrorCode {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 f.write_str(self.as_str())
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::ErrorCode;
307
308 #[test]
309 fn error_codes_serde_uses_the_wire_strings() {
310 for code in ErrorCode::ALL {
311 let value = serde_json::to_value(code).expect("serialize error code");
312 assert_eq!(value, serde_json::Value::String(code.as_str().to_owned()));
313 let parsed: ErrorCode = serde_json::from_value(value).expect("deserialize error code");
314 assert_eq!(parsed, code);
315 }
316 assert!(serde_json::from_str::<ErrorCode>("\"not_a_code\"").is_err());
317 }
318
319 #[test]
320 fn retryability_is_limited_to_self_clearing_admission_conditions() {
321 let retryable: Vec<_> = ErrorCode::ALL
322 .into_iter()
323 .filter(|code| code.retryable_without_operator_action())
324 .collect();
325
326 assert_eq!(
327 retryable,
328 [
329 ErrorCode::CommitQueueFull,
330 ErrorCode::ServerBusy,
331 ErrorCode::ShuttingDown,
332 ]
333 );
334 }
335}