1use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum ErrorKind {
15 InvalidRequest,
17 Unauthorized,
19 ContentTooLarge,
23 StoragePermissionDenied,
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 StoragePermissionDenied => "storage_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 SnapshotNotFound => "snapshot_not_found",
142 SnapshotGone => "snapshot_gone",
143 SnapshotQuotaExceeded => "snapshot_quota_exceeded",
144 ContentNotPrepared => "content_not_prepared",
145 PathNotFound => "path_not_found",
146 InodeNotFound => "inode_not_found",
147 RevisionNotFound => "revision_not_found",
148 PathConflict => "path_conflict",
149 DirectoryNotEmpty => "directory_not_empty",
150 StaleHead => "stale_head",
151 StaleRevision => "stale_revision",
152 StaleAttributes => "stale_attributes",
153 BindingGenerationMismatch => "binding_generation_mismatch",
154 NotDeleted => "not_deleted",
155 WriterFenced => "writer_fenced",
156 WouldCycle => "would_cycle",
157 CommitIdReuseConflict => "commit_id_reuse_conflict",
158 CommitOutcomeUnknown => "commit_outcome_unknown",
159 CommitQueueFull => "commit_queue_full",
160 ServerBusy => "server_busy",
161 ShuttingDown => "shutting_down",
162 DeadlineExceeded => "deadline_exceeded",
163 CheckpointUnavailable => "checkpoint_unavailable",
164 MaintenanceRequired => "maintenance_required",
165 UploadNotFound => "upload_not_found",
166 UploadAlreadyCompleted => "upload_already_completed",
167 UploadContentConflict => "upload_content_conflict",
168 RebootstrapRequired => "rebootstrap_required",
169 QueryUnindexable => "query_unindexable",
170 IndexLagging => "index_lagging",
171 IndexCorrupt => "index_corrupt",
172 NamespaceCorrupt => "namespace_corrupt",
173 ServerError => "server_error",
174}
175
176impl ErrorCode {
177 pub fn kind(self) -> ErrorKind {
182 match self {
183 ErrorCode::InvalidRequest | ErrorCode::QueryUnindexable => ErrorKind::InvalidRequest,
187 ErrorCode::Unauthorized => ErrorKind::Unauthorized,
188 ErrorCode::StoragePermissionDenied => ErrorKind::StoragePermissionDenied,
191 ErrorCode::ContentTooLarge => ErrorKind::ContentTooLarge,
192 ErrorCode::NotSupported => ErrorKind::NotSupported,
193 ErrorCode::NamespaceNotFound
194 | ErrorCode::SnapshotNotFound
195 | ErrorCode::PathNotFound
196 | ErrorCode::InodeNotFound
197 | ErrorCode::RevisionNotFound
198 | ErrorCode::UploadNotFound
199 | ErrorCode::RouteNotFound => ErrorKind::NotFound,
200 ErrorCode::MethodNotAllowed => ErrorKind::MethodNotAllowed,
201 ErrorCode::NamespaceDeleted | ErrorCode::SnapshotGone => ErrorKind::Gone,
202 ErrorCode::NamespaceExists => ErrorKind::AlreadyExists,
203 ErrorCode::DeadlineExceeded => ErrorKind::DeadlineExceeded,
204 ErrorCode::CommitQueueFull
205 | ErrorCode::ServerBusy
206 | ErrorCode::ShuttingDown
207 | ErrorCode::CheckpointUnavailable
208 | ErrorCode::IndexLagging
209 | ErrorCode::MaintenanceRequired => ErrorKind::Unavailable,
210 ErrorCode::CommitOutcomeUnknown => ErrorKind::OutcomeUnknown,
211 ErrorCode::IndexCorrupt | ErrorCode::NamespaceCorrupt => ErrorKind::DataCorruption,
212 ErrorCode::ServerError => ErrorKind::Internal,
213 ErrorCode::ContentNotPrepared
218 | ErrorCode::PathConflict
219 | ErrorCode::DirectoryNotEmpty
220 | ErrorCode::StaleHead
221 | ErrorCode::StaleRevision
222 | ErrorCode::StaleAttributes
226 | ErrorCode::BindingGenerationMismatch
227 | ErrorCode::NotDeleted
230 | ErrorCode::WriterFenced
231 | ErrorCode::WouldCycle
232 | ErrorCode::CommitIdReuseConflict
233 | ErrorCode::UploadAlreadyCompleted
234 | ErrorCode::UploadContentConflict
235 | ErrorCode::RebootstrapRequired
236 | ErrorCode::SnapshotQuotaExceeded => ErrorKind::Conflict,
237 }
238 }
239
240 pub fn retryable_without_operator_action(self) -> bool {
248 match self {
249 ErrorCode::CommitQueueFull | ErrorCode::ServerBusy | ErrorCode::ShuttingDown => true,
250 ErrorCode::InvalidRequest
251 | ErrorCode::Unauthorized
252 | ErrorCode::StoragePermissionDenied
253 | ErrorCode::ContentTooLarge
254 | ErrorCode::NotSupported
255 | ErrorCode::RouteNotFound
256 | ErrorCode::MethodNotAllowed
257 | ErrorCode::NamespaceNotFound
258 | ErrorCode::NamespaceDeleted
259 | ErrorCode::NamespaceExists
260 | ErrorCode::SnapshotNotFound
261 | ErrorCode::SnapshotGone
262 | ErrorCode::SnapshotQuotaExceeded
263 | ErrorCode::ContentNotPrepared
264 | ErrorCode::PathNotFound
265 | ErrorCode::InodeNotFound
266 | ErrorCode::RevisionNotFound
267 | ErrorCode::PathConflict
268 | ErrorCode::DirectoryNotEmpty
269 | ErrorCode::StaleHead
270 | ErrorCode::StaleRevision
271 | ErrorCode::StaleAttributes
272 | ErrorCode::BindingGenerationMismatch
273 | ErrorCode::NotDeleted
274 | ErrorCode::WriterFenced
275 | ErrorCode::WouldCycle
276 | ErrorCode::CommitIdReuseConflict
277 | ErrorCode::CommitOutcomeUnknown
278 | ErrorCode::DeadlineExceeded
279 | ErrorCode::CheckpointUnavailable
280 | ErrorCode::MaintenanceRequired
281 | ErrorCode::UploadNotFound
282 | ErrorCode::UploadAlreadyCompleted
283 | ErrorCode::UploadContentConflict
284 | ErrorCode::RebootstrapRequired
285 | ErrorCode::QueryUnindexable
286 | ErrorCode::IndexLagging
287 | ErrorCode::IndexCorrupt
288 | ErrorCode::NamespaceCorrupt
289 | ErrorCode::ServerError => false,
290 }
291 }
292}
293
294impl fmt::Display for ErrorCode {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 f.write_str(self.as_str())
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::ErrorCode;
303
304 #[test]
305 fn error_codes_serde_uses_the_wire_strings() {
306 for code in ErrorCode::ALL {
307 let value = serde_json::to_value(code).expect("serialize error code");
308 assert_eq!(value, serde_json::Value::String(code.as_str().to_owned()));
309 let parsed: ErrorCode = serde_json::from_value(value).expect("deserialize error code");
310 assert_eq!(parsed, code);
311 }
312 assert!(serde_json::from_str::<ErrorCode>("\"not_a_code\"").is_err());
313 }
314
315 #[test]
316 fn retryability_is_limited_to_self_clearing_admission_conditions() {
317 let retryable: Vec<_> = ErrorCode::ALL
318 .into_iter()
319 .filter(|code| code.retryable_without_operator_action())
320 .collect();
321
322 assert_eq!(
323 retryable,
324 [
325 ErrorCode::CommitQueueFull,
326 ErrorCode::ServerBusy,
327 ErrorCode::ShuttingDown,
328 ]
329 );
330 }
331}