Coverage Report

Created: 2026-10-29 17:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-error/src/lib.rs
Line
Count
Source
1
// Copyright 2024 The NativeLink Authors. All rights reserved.
2
//
3
// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    See LICENSE file for details
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use core::convert::Into;
16
use core::str::Utf8Error;
17
use std::sync::{MutexGuard, PoisonError};
18
19
use nativelink_metric::{
20
    MetricFieldData, MetricKind, MetricPublishKnownKindData, MetricsComponent,
21
};
22
use prost_types::TimestampError;
23
use serde::{Deserialize, Serialize};
24
use tokio::sync::AcquireError;
25
// Reexport of tonic's error codes which we use as "nativelink_error::Code".
26
pub use tonic::Code;
27
28
#[macro_export]
29
macro_rules! make_err {
30
    ($code:expr, $($arg:tt)+) => {{
31
        $crate::Error::new(
32
            $code,
33
            format!("{}", format_args!($($arg)+)),
34
        )
35
    }};
36
}
37
38
#[macro_export]
39
macro_rules! make_input_err {
40
    ($($arg:tt)+) => {{
41
        $crate::make_err!($crate::Code::InvalidArgument, $($arg)+)
42
    }};
43
}
44
45
#[macro_export]
46
macro_rules! error_if {
47
    ($cond:expr, $($arg:tt)+) => {{
48
        if $cond {
49
            Err($crate::make_err!($crate::Code::InvalidArgument, $($arg)+))?;
50
        }
51
    }};
52
}
53
54
/// Typed metadata that travels with an [`Error`].
55
///
56
/// Used in place of string-parsing error messages when the producer
57
/// has structured information the consumer needs to act on. The
58
/// motivating case is missing-blob errors from `fast_slow_store` —
59
/// `to_execute_response` reads [`ErrorContext::MissingDigest`] and
60
/// surfaces a `FAILED_PRECONDITION` with a `PreconditionFailure`
61
/// detail naming the digest, which Bazel auto-retries on.
62
///
63
/// Default is [`ErrorContext::None`]; existing call sites that
64
/// construct [`Error`] via `make_err!` / `Error::new` do not need to
65
/// be updated.
66
#[derive(Default, Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
67
pub enum ErrorContext {
68
    #[default]
69
    None,
70
    /// The error refers to a specific CAS blob that could not be
71
    /// located. `hash` and `size` together form the digest the client
72
    /// should re-upload (`REv2` `blobs/{hash}/{size}`).
73
    MissingDigest { hash: String, size: i64 },
74
}
75
76
#[derive(Eq, PartialEq, Clone, Serialize, Deserialize)]
77
pub struct Error {
78
    #[serde(with = "CodeDef")]
79
    pub code: Code,
80
    pub messages: Vec<String>,
81
    #[serde(default, skip_serializing_if = "ErrorContext::is_none")]
82
    pub context: ErrorContext,
83
}
84
85
impl core::fmt::Debug for Error {
86
186
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87
186
        let mut builder = f.debug_struct("Error");
88
186
        builder.field("code", &self.code);
89
186
        if !self.messages.is_empty() {
90
186
            builder.field("messages", &self.messages);
91
186
        
}0
92
186
        if !self.context.is_none() {
93
5
            builder.field("context", &self.context);
94
181
        }
95
186
        builder.finish()
96
186
    }
97
}
98
99
impl ErrorContext {
100
    #[inline]
101
    #[must_use]
102
190
    pub const fn is_none(&self) -> bool {
103
190
        
matches!5
(self, Self::None)
104
190
    }
105
}
106
107
impl MetricsComponent for Error {
108
1
    fn publish(
109
1
        &self,
110
1
        kind: MetricKind,
111
1
        field_metadata: MetricFieldData,
112
1
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
113
1
        self.to_string().publish(kind, field_metadata)
114
1
    }
115
}
116
117
impl Error {
118
    #[must_use]
119
1.18k
    pub const fn new_with_messages(code: Code, messages: Vec<String>) -> Self {
120
1.18k
        Self {
121
1.18k
            code,
122
1.18k
            messages,
123
1.18k
            context: ErrorContext::None,
124
1.18k
        }
125
1.18k
    }
126
127
    #[must_use]
128
1.12k
    pub fn new(code: Code, msg: String) -> Self {
129
1.12k
        if msg.is_empty() {
130
0
            Self::new_with_messages(code, vec![])
131
        } else {
132
1.12k
            Self::new_with_messages(code, vec![msg])
133
        }
134
1.12k
    }
135
136
    #[must_use]
137
53
    pub fn from_std_err(code: Code, mut err: &dyn core::error::Error) -> Self {
138
53
        let mut messages = vec![format!("{err}")];
139
59
        while let Some(
src6
) = err.source() {
140
6
            messages.push(format!("{src}"));
141
6
            err = src;
142
6
        }
143
53
        messages.reverse();
144
53
        Self::new_with_messages(code, messages)
145
53
    }
146
147
    #[inline]
148
    #[must_use]
149
146
    pub fn append<S: Into<String>>(mut self, msg: S) -> Self {
150
146
        self.messages.push(msg.into());
151
146
        self
152
146
    }
153
154
    #[inline]
155
    #[must_use]
156
0
    pub fn with_context(mut self, context: ErrorContext) -> Self {
157
0
        self.context = context;
158
0
        self
159
0
    }
160
161
    #[must_use]
162
13
    pub fn merge<E: Into<Self>>(mut self, other: E) -> Self {
163
13
        let mut other: Self = other.into();
164
        // This will help with knowing which messages are tied to different errors.
165
13
        self.messages.push("---".to_string());
166
13
        self.messages.append(&mut other.messages);
167
13
        self
168
13
    }
169
170
    #[must_use]
171
58
    pub fn merge_option<T: Into<Self>, U: Into<Self>>(
172
58
        this: Option<T>,
173
58
        other: Option<U>,
174
58
    ) -> Option<Self> {
175
58
        if let Some(
this13
) = this {
176
13
            if let Some(
other1
) = other {
177
1
                return Some(this.into().merge(other));
178
12
            }
179
12
            return Some(this.into());
180
45
        }
181
45
        other.map(Into::into)
182
58
    }
183
184
    #[must_use]
185
1
    pub fn to_std_err(self) -> std::io::Error {
186
1
        std::io::Error::new(self.code.into_error_kind(), self.messages.join(" : "))
187
1
    }
188
189
    #[must_use]
190
179
    pub fn message_string(&self) -> String {
191
179
        self.messages.join(" : ")
192
179
    }
193
}
194
195
impl core::error::Error for Error {}
196
197
impl From<Error> for nativelink_proto::google::rpc::Status {
198
41
    fn from(val: Error) -> Self {
199
41
        Self {
200
41
            code: val.code as i32,
201
41
            message: val.message_string(),
202
41
            details: vec![],
203
41
        }
204
41
    }
205
}
206
207
impl From<nativelink_proto::google::rpc::Status> for Error {
208
8
    fn from(val: nativelink_proto::google::rpc::Status) -> Self {
209
8
        let context = missing_digest_from_details(&val.details);
210
8
        Self {
211
8
            code: val.code.into(),
212
8
            messages: vec![val.message],
213
8
            context,
214
8
        }
215
8
    }
216
}
217
218
/// The `REv2` missing-blob violation in a status's details, as the
219
/// [`ErrorContext`] that produced it. A status that crosses a gRPC hop
220
/// (a CAS forwarding `Execute` to a separate scheduler) would otherwise
221
/// lose it, and with it the `PreconditionFailure` the client needs to
222
/// upload the blob again and retry.
223
8
fn missing_digest_from_details(details: &[prost_types::Any]) -> ErrorContext {
224
    const PRECONDITION_FAILURE: &str = "type.googleapis.com/google.rpc.PreconditionFailure";
225
8
    details
226
8
        .iter()
227
8
        .filter(|any| 
any.type_url1
==
PRECONDITION_FAILURE1
)
228
8
        .filter_map(|any| 
{1
229
1
            <nativelink_proto::google::rpc::PreconditionFailure as prost::Message>::decode(
230
1
                any.value.as_slice(),
231
            )
232
1
            .ok()
233
1
        })
234
8
        .flat_map(|failure| failure.violations)
235
8
        .filter(|violation| 
violation.r#type1
==
"MISSING"1
)
236
8
        .find_map(|violation| 
{1
237
1
            let rest = violation.subject.strip_prefix("blobs/")
?0
;
238
1
            let (hash, size) = rest.split_once('/')
?0
;
239
            Some(ErrorContext::MissingDigest {
240
1
                hash: hash.to_string(),
241
1
                size: size.parse().ok()
?0
,
242
            })
243
1
        })
244
8
        .unwrap_or_default()
245
8
}
246
247
impl core::fmt::Display for Error {
248
123
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249
        // A manual impl to reduce the noise of frequently empty fields.
250
123
        let mut builder = f.debug_struct("Error");
251
252
123
        builder.field("code", &self.code);
253
254
123
        if !self.messages.is_empty() {
255
123
            builder.field("messages", &self.messages);
256
123
        
}0
257
258
123
        builder.finish()
259
123
    }
260
}
261
262
impl From<prost::DecodeError> for Error {
263
2
    fn from(err: prost::DecodeError) -> Self {
264
2
        Self::from_std_err(Code::Internal, &err)
265
2
    }
266
}
267
268
impl From<prost::EncodeError> for Error {
269
0
    fn from(err: prost::EncodeError) -> Self {
270
0
        Self::from_std_err(Code::Internal, &err)
271
0
    }
272
}
273
274
impl From<prost::UnknownEnumValue> for Error {
275
0
    fn from(err: prost::UnknownEnumValue) -> Self {
276
0
        Self::from_std_err(Code::Internal, &err)
277
0
    }
278
}
279
280
impl From<core::num::TryFromIntError> for Error {
281
1
    fn from(err: core::num::TryFromIntError) -> Self {
282
1
        Self::from_std_err(Code::InvalidArgument, &err)
283
1
    }
284
}
285
286
impl From<tokio::task::JoinError> for Error {
287
1
    fn from(err: tokio::task::JoinError) -> Self {
288
1
        Self::from_std_err(Code::Internal, &err)
289
1
    }
290
}
291
292
impl<T> From<PoisonError<MutexGuard<'_, T>>> for Error {
293
0
    fn from(err: PoisonError<MutexGuard<'_, T>>) -> Self {
294
0
        Self::from_std_err(Code::Internal, &err)
295
0
    }
296
}
297
298
impl From<serde_json5::Error> for Error {
299
0
    fn from(err: serde_json5::Error) -> Self {
300
0
        match err {
301
0
            serde_json5::Error::Message { msg, location } => {
302
0
                if let Some(has_location) = location {
303
0
                    make_err!(
304
0
                        Code::Internal,
305
                        "line {}, column {} - {}",
306
                        has_location.line,
307
                        has_location.column,
308
                        msg
309
                    )
310
                } else {
311
0
                    Self::new(Code::Internal, msg)
312
                }
313
            }
314
        }
315
0
    }
316
}
317
318
impl From<core::num::ParseIntError> for Error {
319
2
    fn from(err: core::num::ParseIntError) -> Self {
320
2
        Self::from_std_err(Code::InvalidArgument, &err)
321
2
    }
322
}
323
324
impl From<core::convert::Infallible> for Error {
325
0
    fn from(_err: core::convert::Infallible) -> Self {
326
        // Infallible is an error type that can never happen.
327
0
        unreachable!();
328
    }
329
}
330
331
impl From<TimestampError> for Error {
332
0
    fn from(err: TimestampError) -> Self {
333
0
        Self::from_std_err(Code::InvalidArgument, &err)
334
0
    }
335
}
336
337
impl From<AcquireError> for Error {
338
0
    fn from(err: AcquireError) -> Self {
339
0
        Self::from_std_err(Code::Internal, &err)
340
0
    }
341
}
342
343
impl From<Utf8Error> for Error {
344
0
    fn from(err: Utf8Error) -> Self {
345
0
        Self::from_std_err(Code::Internal, &err)
346
0
    }
347
}
348
349
impl From<std::io::Error> for Error {
350
1.35k
    fn from(err: std::io::Error) -> Self {
351
1.35k
        Self {
352
1.35k
            code: err.kind().into_code(),
353
1.35k
            messages: vec![err.to_string()],
354
1.35k
            context: ErrorContext::None,
355
1.35k
        }
356
1.35k
    }
357
}
358
359
impl From<redis::RedisError> for Error {
360
33
    fn from(error: redis::RedisError) -> Self {
361
        use redis::ErrorKind::{
362
            AuthenticationFailed, ClusterConnectionNotFound, EmptySentinelList, Extension,
363
            InvalidClientConfig, Io as IoError, MasterNameNotFoundBySentinel,
364
            NoValidReplicasFoundBySentinel, Parse as ParseError, RESP3NotSupported, Server,
365
            UnexpectedReturnType,
366
        };
367
        use redis::ServerErrorKind::{
368
            BusyLoading, ClusterDown, MasterDown, NoPerm, ReadOnly, TryAgain,
369
        };
370
371
        // Conversions here are based on https://grpc.github.io/grpc/core/md_doc_statuscodes.html.
372
        //
373
        // The distinction that matters most is retryable vs not. These statuses
374
        // reach REAPI clients directly, and Bazel treats INVALID_ARGUMENT as
375
        // permanent: it fails the build instantly rather than retrying. So
376
        // anything caused by the state of the Redis deployment — a failover in
377
        // progress, a dropped connection, a node still loading its dataset —
378
        // must NOT land there, or a routine blip becomes a failed CI run.
379
33
        let code = match error.kind() {
380
3
            AuthenticationFailed | Server(NoPerm) => Code::PermissionDenied,
381
382
            // Topology in flux. Sentinel failover makes these normal and
383
            // brief: the master moved and the client has not caught up yet.
384
            // Surfacing MasterNameNotFoundBySentinel as INVALID_ARGUMENT is
385
            // what turned a 1-2 minute sentinel failover into a dead build.
386
            MasterNameNotFoundBySentinel
387
            | NoValidReplicasFoundBySentinel
388
            | ClusterConnectionNotFound
389
            | Server(ClusterDown | MasterDown | TryAgain | BusyLoading | ReadOnly) => {
390
10
                Code::Unavailable
391
            }
392
393
            // A timeout is worth distinguishing from a dead connection, but
394
            // both are transient and both are retryable.
395
            IoError => {
396
6
                if error.is_timeout() {
397
4
                    Code::DeadlineExceeded
398
                } else {
399
2
                    Code::Unavailable
400
                }
401
            }
402
403
            // Genuine misconfiguration by the operator — retrying cannot help.
404
4
            InvalidClientConfig | EmptySentinelList | RESP3NotSupported => Code::InvalidArgument,
405
406
            // Server-side or protocol faults. Not the caller's argument, so
407
            // not InvalidArgument: a malformed reply is our problem or the
408
            // server's, and it is at least worth a retry.
409
2
            ParseError | UnexpectedReturnType => Code::Internal,
410
411
            // A server at its maxmemory with noeviction refuses every write
412
            // with OOM. The client knows no kind for it, so it arrives as an
413
            // extension code; it is the server out of room, nothing wrong
414
            // with the command.
415
4
            Extension if error.code() == Some("OOM"
)1
=>
Code::ResourceExhausted1
,
416
417
7
            _ => Code::Unknown,
418
        };
419
420
33
        let kind = error.kind();
421
33
        make_err!(code, "{kind:?}: {error}")
422
33
    }
423
}
424
425
impl From<tonic::Status> for Error {
426
22
    fn from(status: tonic::Status) -> Self {
427
        // tonic codes a mid-stream transport/`hyper` failure (a connection reset
428
        // or broken pipe during a large streaming write) as `Code::Unknown`
429
        // with message "transport error". But a broken connection is transient
430
        // and retryable — the gRPC status model reserves `UNKNOWN` for genuinely
431
        // unmapped errors and codes transport failures `UNAVAILABLE`. Left as
432
        // `Unknown`, such a blip falls outside both our own `Retrier`'s and most
433
        // clients' retry sets, so a retryable failure becomes terminal and
434
        // cacheable uploads are silently dropped. Remap transport-originated
435
        // `Unknown` to `Unavailable`.
436
22
        let code = if status.code() == Code::Unknown && 
is_transport_status6
(
&status6
) {
437
4
            Code::Unavailable
438
        } else {
439
18
            status.code()
440
        };
441
22
        Self::new(code, status.to_string())
442
22
    }
443
}
444
445
/// Whether a `tonic::Status` originates from a transport-layer failure (a
446
/// reset/broken connection), which tonic surfaces as `Code::Unknown`.
447
///
448
/// tonic's own `Status::from_error` already maps the failures it recognizes
449
/// (for example a connect refusal) to `Unavailable`; the `Unknown` shape
450
/// arises for transport errors it does not, and for statuses that crossed a
451
/// process boundary. Detection is downcast-first: precise when the concrete
452
/// transport error is still reachable in the source chain; falls back to the
453
/// message otherwise.
454
6
fn is_transport_status(status: &tonic::Status) -> bool {
455
6
    let mut source = std::error::Error::source(status);
456
6
    while let Some(
err1
) = source {
457
1
        if err.is::<tonic::transport::Error>() {
458
1
            return true;
459
0
        }
460
0
        source = err.source();
461
    }
462
    // Fallback for a `Status` reconstructed from the wire, where the concrete
463
    // transport error is no longer in the source chain and only the message
464
    // survives. `tonic::transport::Error` renders as exactly "transport
465
    // error" (no detail suffix), and this crate's own `From<Error> for
466
    // Status` re-encodes a proxied one as "transport error : <appended
467
    // context>". Anchor to those two shapes — an unanchored `contains` would
468
    // reclassify any app-level `Unknown` whose message merely mentions
469
    // "transport error" as retryable `Unavailable`.
470
5
    let msg = status.message();
471
5
    msg == "transport error" || 
msg3
.
starts_with3
("transport error : ")
472
6
}
473
474
impl From<Error> for tonic::Status {
475
41
    fn from(val: Error) -> Self {
476
41
        Self::new(val.code, val.messages.join(" : "))
477
41
    }
478
}
479
480
impl From<walkdir::Error> for Error {
481
1
    fn from(err: walkdir::Error) -> Self {
482
1
        Self::from_std_err(Code::Internal, &err)
483
1
    }
484
}
485
486
impl From<uuid::Error> for Error {
487
0
    fn from(err: uuid::Error) -> Self {
488
0
        Self::from_std_err(Code::Internal, &err)
489
0
    }
490
}
491
492
impl From<rustls_pki_types::pem::Error> for Error {
493
0
    fn from(err: rustls_pki_types::pem::Error) -> Self {
494
0
        Self::from_std_err(Code::Internal, &err)
495
0
    }
496
}
497
498
impl From<tokio::time::error::Elapsed> for Error {
499
0
    fn from(err: tokio::time::error::Elapsed) -> Self {
500
0
        Self::from_std_err(Code::DeadlineExceeded, &err)
501
0
    }
502
}
503
504
impl From<url::ParseError> for Error {
505
0
    fn from(err: url::ParseError) -> Self {
506
0
        Self::from_std_err(Code::Internal, &err)
507
0
    }
508
}
509
510
impl From<mongodb::error::Error> for Error {
511
0
    fn from(err: mongodb::error::Error) -> Self {
512
0
        Self::from_std_err(Code::Internal, &err)
513
0
    }
514
}
515
516
impl From<reqwest::Error> for Error {
517
0
    fn from(err: reqwest::Error) -> Self {
518
0
        Self::from_std_err(Code::Internal, &err)
519
0
    }
520
}
521
522
impl From<zip::result::ZipError> for Error {
523
0
    fn from(err: zip::result::ZipError) -> Self {
524
0
        Self::from_std_err(Code::Internal, &err)
525
0
    }
526
}
527
528
impl From<std::ffi::NulError> for Error {
529
0
    fn from(err: std::ffi::NulError) -> Self {
530
0
        Self::from_std_err(Code::Internal, &err)
531
0
    }
532
}
533
534
impl From<base64::DecodeError> for Error {
535
0
    fn from(err: base64::DecodeError) -> Self {
536
0
        Self::from_std_err(Code::Internal, &err)
537
0
    }
538
}
539
540
pub trait ResultExt<T> {
541
    /// # Errors
542
    ///
543
    /// Will return `Err` if we can't convert the error.
544
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
545
    where
546
        Self: Sized,
547
        S: ToString,
548
        F: (FnOnce(&Error) -> (Code, S)) + Sized;
549
550
    /// # Errors
551
    ///
552
    /// Will return `Err` if we can't convert the error.
553
    #[inline]
554
457k
    fn err_tip<F, S>(self, tip_fn: F) -> Result<T, Error>
555
457k
    where
556
457k
        Self: Sized,
557
457k
        S: ToString,
558
457k
        F: (FnOnce() -> S) + Sized,
559
    {
560
457k
        self.err_tip_with_code(|e| (
e.code688
,
tip_fn()688
))
561
457k
    }
562
563
    /// # Errors
564
    ///
565
    /// Will return `Err` if we can't merge the errors.
566
0
    fn merge<U>(self, _other: Result<U, Error>) -> Result<U, Error>
567
0
    where
568
0
        Self: Sized,
569
    {
570
0
        unreachable!();
571
    }
572
}
573
574
impl<T, E: Into<Error>> ResultExt<T> for Result<T, E> {
575
    #[inline]
576
452k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
577
452k
    where
578
452k
        Self: Sized,
579
452k
        S: ToString,
580
452k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
581
    {
582
452k
        self.map_err(|e| 
{670
583
670
            let mut error: Error = e.into();
584
670
            let (code, message) = tip_fn(&error);
585
670
            error.code = code;
586
670
            error.messages.push(message.to_string());
587
670
            error
588
670
        })
589
452k
    }
590
591
13.6k
    fn merge<U>(self, other: Result<U, Error>) -> Result<U, Error>
592
13.6k
    where
593
13.6k
        Self: Sized,
594
    {
595
13.6k
        if let Err(
e141
) = self {
596
141
            let mut e: Error = e.into();
597
141
            if let Err(
other_err136
) = other {
598
136
                let mut other_err: Error = other_err;
599
136
                // This will help with knowing which messages are tied to different errors.
600
136
                e.messages.push("---".to_string());
601
136
                e.messages.append(&mut other_err.messages);
602
136
            
}5
603
141
            return Err(e);
604
13.5k
        }
605
13.5k
        other
606
13.6k
    }
607
}
608
609
impl<T> ResultExt<T> for Option<T> {
610
    #[inline]
611
12.6k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
612
12.6k
    where
613
12.6k
        Self: Sized,
614
12.6k
        S: ToString,
615
12.6k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
616
    {
617
12.6k
        self.ok_or_else(|| 
{44
618
44
            let mut error = Error {
619
44
                code: Code::Internal,
620
44
                messages: vec![],
621
44
                context: ErrorContext::None,
622
44
            };
623
44
            let (code, message) = tip_fn(&error);
624
44
            error.code = code;
625
44
            error.messages.push(message.to_string());
626
44
            error
627
44
        })
628
12.6k
    }
629
}
630
631
trait CodeExt {
632
    fn into_error_kind(self) -> std::io::ErrorKind;
633
}
634
635
impl CodeExt for Code {
636
1
    fn into_error_kind(self) -> std::io::ErrorKind {
637
1
        match self {
638
0
            Self::Aborted => std::io::ErrorKind::Interrupted,
639
0
            Self::AlreadyExists => std::io::ErrorKind::AlreadyExists,
640
0
            Self::DeadlineExceeded => std::io::ErrorKind::TimedOut,
641
0
            Self::InvalidArgument => std::io::ErrorKind::InvalidInput,
642
0
            Self::NotFound => std::io::ErrorKind::NotFound,
643
0
            Self::PermissionDenied => std::io::ErrorKind::PermissionDenied,
644
0
            Self::Unavailable => std::io::ErrorKind::ConnectionRefused,
645
1
            _ => std::io::ErrorKind::Other,
646
        }
647
1
    }
648
}
649
650
trait ErrorKindExt {
651
    fn into_code(self) -> Code;
652
}
653
654
impl ErrorKindExt for std::io::ErrorKind {
655
1.35k
    fn into_code(self) -> Code {
656
1.35k
        match self {
657
1.34k
            Self::NotFound => Code::NotFound,
658
1
            Self::PermissionDenied => Code::PermissionDenied,
659
            Self::ConnectionRefused | Self::ConnectionReset | Self::ConnectionAborted => {
660
1
                Code::Unavailable
661
            }
662
1
            Self::AlreadyExists => Code::AlreadyExists,
663
0
            Self::InvalidInput | Self::InvalidData => Code::InvalidArgument,
664
0
            Self::TimedOut => Code::DeadlineExceeded,
665
0
            Self::Interrupted => Code::Aborted,
666
            Self::NotConnected
667
            | Self::AddrInUse
668
            | Self::AddrNotAvailable
669
            | Self::BrokenPipe
670
            | Self::WouldBlock
671
            | Self::WriteZero
672
            | Self::Other
673
3
            | Self::UnexpectedEof => Code::Internal,
674
3
            _ => Code::Unknown,
675
        }
676
1.35k
    }
677
}
678
679
// Serde definition for tonic::Code. See: https://serde.rs/remote-derive.html
680
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
681
#[serde(remote = "Code")]
682
pub enum CodeDef {
683
    Ok = 0,
684
    Cancelled = 1,
685
    Unknown = 2,
686
    InvalidArgument = 3,
687
    DeadlineExceeded = 4,
688
    NotFound = 5,
689
    AlreadyExists = 6,
690
    PermissionDenied = 7,
691
    ResourceExhausted = 8,
692
    FailedPrecondition = 9,
693
    Aborted = 10,
694
    OutOfRange = 11,
695
    Unimplemented = 12,
696
    Internal = 13,
697
    Unavailable = 14,
698
    DataLoss = 15,
699
    Unauthenticated = 16,
700
    // NOTE: Additional codes must be added to stores.rs in ErrorCodes and also
701
    // in both match statements in retry.rs.
702
}