Coverage Report

Created: 2026-07-01 11:13

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
35
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87
35
        let mut builder = f.debug_struct("Error");
88
35
        builder.field("code", &self.code);
89
35
        if !self.messages.is_empty() {
90
35
            builder.field("messages", &self.messages);
91
35
        
}0
92
35
        if !self.context.is_none() {
93
0
            builder.field("context", &self.context);
94
35
        }
95
35
        builder.finish()
96
35
    }
97
}
98
99
impl ErrorContext {
100
    #[inline]
101
    #[must_use]
102
37
    pub const fn is_none(&self) -> bool {
103
37
        
matches!0
(self, Self::None)
104
37
    }
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
268
    pub const fn new_with_messages(code: Code, messages: Vec<String>) -> Self {
120
268
        Self {
121
268
            code,
122
268
            messages,
123
268
            context: ErrorContext::None,
124
268
        }
125
268
    }
126
127
    #[must_use]
128
216
    pub fn new(code: Code, msg: String) -> Self {
129
216
        if msg.is_empty() {
130
0
            Self::new_with_messages(code, vec![])
131
        } else {
132
216
            Self::new_with_messages(code, vec![msg])
133
        }
134
216
    }
135
136
    #[must_use]
137
49
    pub fn from_std_err(code: Code, mut err: &dyn core::error::Error) -> Self {
138
49
        let mut messages = vec![format!("{err}")];
139
55
        while let Some(
src6
) = err.source() {
140
6
            messages.push(format!("{src}"));
141
6
            err = src;
142
6
        }
143
49
        messages.reverse();
144
49
        Self::new_with_messages(code, messages)
145
49
    }
146
147
    #[inline]
148
    #[must_use]
149
56
    pub fn append<S: Into<String>>(mut self, msg: S) -> Self {
150
56
        self.messages.push(msg.into());
151
56
        self
152
56
    }
153
154
    #[inline]
155
    #[must_use]
156
3
    pub fn with_context(mut self, context: ErrorContext) -> Self {
157
3
        self.context = context;
158
3
        self
159
3
    }
160
161
    #[must_use]
162
5
    pub fn merge<E: Into<Self>>(mut self, other: E) -> Self {
163
5
        let mut other: Self = other.into();
164
        // This will help with knowing which messages are tied to different errors.
165
5
        self.messages.push("---".to_string());
166
5
        self.messages.append(&mut other.messages);
167
5
        self
168
5
    }
169
170
    #[must_use]
171
21
    pub fn merge_option<T: Into<Self>, U: Into<Self>>(
172
21
        this: Option<T>,
173
21
        other: Option<U>,
174
21
    ) -> Option<Self> {
175
21
        if let Some(
this5
) = this {
176
5
            if let Some(
other1
) = other {
177
1
                return Some(this.into().merge(other));
178
4
            }
179
4
            return Some(this.into());
180
16
        }
181
16
        other.map(Into::into)
182
21
    }
183
184
    #[must_use]
185
2
    pub fn to_std_err(self) -> std::io::Error {
186
2
        std::io::Error::new(self.code.into_error_kind(), self.messages.join(" : "))
187
2
    }
188
189
    #[must_use]
190
23
    pub fn message_string(&self) -> String {
191
23
        self.messages.join(" : ")
192
23
    }
193
}
194
195
impl core::error::Error for Error {}
196
197
impl From<Error> for nativelink_proto::google::rpc::Status {
198
15
    fn from(val: Error) -> Self {
199
15
        Self {
200
15
            code: val.code as i32,
201
15
            message: val.message_string(),
202
15
            details: vec![],
203
15
        }
204
15
    }
205
}
206
207
impl From<nativelink_proto::google::rpc::Status> for Error {
208
3
    fn from(val: nativelink_proto::google::rpc::Status) -> Self {
209
3
        Self {
210
3
            code: val.code.into(),
211
3
            messages: vec![val.message],
212
3
            context: ErrorContext::None,
213
3
        }
214
3
    }
215
}
216
217
impl core::fmt::Display for Error {
218
31
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219
        // A manual impl to reduce the noise of frequently empty fields.
220
31
        let mut builder = f.debug_struct("Error");
221
222
31
        builder.field("code", &self.code);
223
224
31
        if !self.messages.is_empty() {
225
31
            builder.field("messages", &self.messages);
226
31
        
}0
227
228
31
        builder.finish()
229
31
    }
230
}
231
232
impl From<prost::DecodeError> for Error {
233
1
    fn from(err: prost::DecodeError) -> Self {
234
1
        Self::from_std_err(Code::Internal, &err)
235
1
    }
236
}
237
238
impl From<prost::EncodeError> for Error {
239
0
    fn from(err: prost::EncodeError) -> Self {
240
0
        Self::from_std_err(Code::Internal, &err)
241
0
    }
242
}
243
244
impl From<prost::UnknownEnumValue> for Error {
245
0
    fn from(err: prost::UnknownEnumValue) -> Self {
246
0
        Self::from_std_err(Code::Internal, &err)
247
0
    }
248
}
249
250
impl From<core::num::TryFromIntError> for Error {
251
1
    fn from(err: core::num::TryFromIntError) -> Self {
252
1
        Self::from_std_err(Code::InvalidArgument, &err)
253
1
    }
254
}
255
256
impl From<tokio::task::JoinError> for Error {
257
1
    fn from(err: tokio::task::JoinError) -> Self {
258
1
        Self::from_std_err(Code::Internal, &err)
259
1
    }
260
}
261
262
impl<T> From<PoisonError<MutexGuard<'_, T>>> for Error {
263
0
    fn from(err: PoisonError<MutexGuard<'_, T>>) -> Self {
264
0
        Self::from_std_err(Code::Internal, &err)
265
0
    }
266
}
267
268
impl From<serde_json5::Error> for Error {
269
0
    fn from(err: serde_json5::Error) -> Self {
270
0
        match err {
271
0
            serde_json5::Error::Message { msg, location } => {
272
0
                if let Some(has_location) = location {
273
0
                    make_err!(
274
0
                        Code::Internal,
275
                        "line {}, column {} - {}",
276
                        has_location.line,
277
                        has_location.column,
278
                        msg
279
                    )
280
                } else {
281
0
                    Self::new(Code::Internal, msg)
282
                }
283
            }
284
        }
285
0
    }
286
}
287
288
impl From<core::num::ParseIntError> for Error {
289
1
    fn from(err: core::num::ParseIntError) -> Self {
290
1
        Self::from_std_err(Code::InvalidArgument, &err)
291
1
    }
292
}
293
294
impl From<core::convert::Infallible> for Error {
295
0
    fn from(_err: core::convert::Infallible) -> Self {
296
        // Infallible is an error type that can never happen.
297
0
        unreachable!();
298
    }
299
}
300
301
impl From<TimestampError> for Error {
302
0
    fn from(err: TimestampError) -> Self {
303
0
        Self::from_std_err(Code::InvalidArgument, &err)
304
0
    }
305
}
306
307
impl From<AcquireError> for Error {
308
0
    fn from(err: AcquireError) -> Self {
309
0
        Self::from_std_err(Code::Internal, &err)
310
0
    }
311
}
312
313
impl From<Utf8Error> for Error {
314
0
    fn from(err: Utf8Error) -> Self {
315
0
        Self::from_std_err(Code::Internal, &err)
316
0
    }
317
}
318
319
impl From<std::io::Error> for Error {
320
30
    fn from(err: std::io::Error) -> Self {
321
30
        Self {
322
30
            code: err.kind().into_code(),
323
30
            messages: vec![err.to_string()],
324
30
            context: ErrorContext::None,
325
30
        }
326
30
    }
327
}
328
329
impl From<redis::RedisError> for Error {
330
12
    fn from(error: redis::RedisError) -> Self {
331
        use redis::ErrorKind::{
332
            AuthenticationFailed, InvalidClientConfig, Io as IoError, Parse as ParseError,
333
            UnexpectedReturnType,
334
        };
335
336
        // Conversions here are based on https://grpc.github.io/grpc/core/md_doc_statuscodes.html.
337
12
        let code = match error.kind() {
338
1
            AuthenticationFailed => Code::PermissionDenied,
339
1
            ParseError | UnexpectedReturnType | InvalidClientConfig => Code::InvalidArgument,
340
            IoError => {
341
4
                if error.is_timeout() {
342
3
                    Code::DeadlineExceeded
343
                } else {
344
1
                    Code::Internal
345
                }
346
            }
347
6
            _ => Code::Unknown,
348
        };
349
350
12
        let kind = error.kind();
351
12
        make_err!(code, "{kind:?}: {error}")
352
12
    }
353
}
354
355
impl From<tonic::Status> for Error {
356
3
    fn from(status: tonic::Status) -> Self {
357
3
        Self::new(status.code(), status.to_string())
358
3
    }
359
}
360
361
impl From<Error> for tonic::Status {
362
14
    fn from(val: Error) -> Self {
363
14
        Self::new(val.code, val.messages.join(" : "))
364
14
    }
365
}
366
367
impl From<walkdir::Error> for Error {
368
1
    fn from(err: walkdir::Error) -> Self {
369
1
        Self::from_std_err(Code::Internal, &err)
370
1
    }
371
}
372
373
impl From<uuid::Error> for Error {
374
0
    fn from(err: uuid::Error) -> Self {
375
0
        Self::from_std_err(Code::Internal, &err)
376
0
    }
377
}
378
379
impl From<rustls_pki_types::pem::Error> for Error {
380
0
    fn from(err: rustls_pki_types::pem::Error) -> Self {
381
0
        Self::from_std_err(Code::Internal, &err)
382
0
    }
383
}
384
385
impl From<tokio::time::error::Elapsed> for Error {
386
0
    fn from(err: tokio::time::error::Elapsed) -> Self {
387
0
        Self::from_std_err(Code::DeadlineExceeded, &err)
388
0
    }
389
}
390
391
impl From<url::ParseError> for Error {
392
0
    fn from(err: url::ParseError) -> Self {
393
0
        Self::from_std_err(Code::Internal, &err)
394
0
    }
395
}
396
397
impl From<mongodb::error::Error> for Error {
398
0
    fn from(err: mongodb::error::Error) -> Self {
399
0
        Self::from_std_err(Code::Internal, &err)
400
0
    }
401
}
402
403
impl From<reqwest::Error> for Error {
404
0
    fn from(err: reqwest::Error) -> Self {
405
0
        Self::from_std_err(Code::Internal, &err)
406
0
    }
407
}
408
409
impl From<zip::result::ZipError> for Error {
410
0
    fn from(err: zip::result::ZipError) -> Self {
411
0
        Self::from_std_err(Code::Internal, &err)
412
0
    }
413
}
414
415
impl From<std::ffi::NulError> for Error {
416
0
    fn from(err: std::ffi::NulError) -> Self {
417
0
        Self::from_std_err(Code::Internal, &err)
418
0
    }
419
}
420
421
impl From<base64::DecodeError> for Error {
422
0
    fn from(err: base64::DecodeError) -> Self {
423
0
        Self::from_std_err(Code::Internal, &err)
424
0
    }
425
}
426
427
pub trait ResultExt<T> {
428
    /// # Errors
429
    ///
430
    /// Will return `Err` if we can't convert the error.
431
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
432
    where
433
        Self: Sized,
434
        S: ToString,
435
        F: (FnOnce(&Error) -> (Code, S)) + Sized;
436
437
    /// # Errors
438
    ///
439
    /// Will return `Err` if we can't convert the error.
440
    #[inline]
441
278k
    fn err_tip<F, S>(self, tip_fn: F) -> Result<T, Error>
442
278k
    where
443
278k
        Self: Sized,
444
278k
        S: ToString,
445
278k
        F: (FnOnce() -> S) + Sized,
446
    {
447
278k
        self.err_tip_with_code(|e| (
e.code167
,
tip_fn()167
))
448
278k
    }
449
450
    /// # Errors
451
    ///
452
    /// Will return `Err` if we can't merge the errors.
453
0
    fn merge<U>(self, _other: Result<U, Error>) -> Result<U, Error>
454
0
    where
455
0
        Self: Sized,
456
    {
457
0
        unreachable!();
458
    }
459
}
460
461
impl<T, E: Into<Error>> ResultExt<T> for Result<T, E> {
462
    #[inline]
463
276k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
464
276k
    where
465
276k
        Self: Sized,
466
276k
        S: ToString,
467
276k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
468
    {
469
276k
        self.map_err(|e| 
{156
470
156
            let mut error: Error = e.into();
471
156
            let (code, message) = tip_fn(&error);
472
156
            error.code = code;
473
156
            error.messages.push(message.to_string());
474
156
            error
475
156
        })
476
276k
    }
477
478
6.10k
    fn merge<U>(self, other: Result<U, Error>) -> Result<U, Error>
479
6.10k
    where
480
6.10k
        Self: Sized,
481
    {
482
6.10k
        if let Err(
e25
) = self {
483
25
            let mut e: Error = e.into();
484
25
            if let Err(
other_err23
) = other {
485
23
                let mut other_err: Error = other_err;
486
23
                // This will help with knowing which messages are tied to different errors.
487
23
                e.messages.push("---".to_string());
488
23
                e.messages.append(&mut other_err.messages);
489
23
            
}2
490
25
            return Err(e);
491
6.07k
        }
492
6.07k
        other
493
6.10k
    }
494
}
495
496
impl<T> ResultExt<T> for Option<T> {
497
    #[inline]
498
5.96k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
499
5.96k
    where
500
5.96k
        Self: Sized,
501
5.96k
        S: ToString,
502
5.96k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
503
    {
504
5.96k
        self.ok_or_else(|| 
{23
505
23
            let mut error = Error {
506
23
                code: Code::Internal,
507
23
                messages: vec![],
508
23
                context: ErrorContext::None,
509
23
            };
510
23
            let (code, message) = tip_fn(&error);
511
23
            error.code = code;
512
23
            error.messages.push(message.to_string());
513
23
            error
514
23
        })
515
5.96k
    }
516
}
517
518
trait CodeExt {
519
    fn into_error_kind(self) -> std::io::ErrorKind;
520
}
521
522
impl CodeExt for Code {
523
2
    fn into_error_kind(self) -> std::io::ErrorKind {
524
2
        match self {
525
0
            Self::Aborted => std::io::ErrorKind::Interrupted,
526
0
            Self::AlreadyExists => std::io::ErrorKind::AlreadyExists,
527
0
            Self::DeadlineExceeded => std::io::ErrorKind::TimedOut,
528
0
            Self::InvalidArgument => std::io::ErrorKind::InvalidInput,
529
0
            Self::NotFound => std::io::ErrorKind::NotFound,
530
0
            Self::PermissionDenied => std::io::ErrorKind::PermissionDenied,
531
0
            Self::Unavailable => std::io::ErrorKind::ConnectionRefused,
532
2
            _ => std::io::ErrorKind::Other,
533
        }
534
2
    }
535
}
536
537
trait ErrorKindExt {
538
    fn into_code(self) -> Code;
539
}
540
541
impl ErrorKindExt for std::io::ErrorKind {
542
30
    fn into_code(self) -> Code {
543
30
        match self {
544
28
            Self::NotFound => Code::NotFound,
545
0
            Self::PermissionDenied => Code::PermissionDenied,
546
            Self::ConnectionRefused | Self::ConnectionReset | Self::ConnectionAborted => {
547
0
                Code::Unavailable
548
            }
549
0
            Self::AlreadyExists => Code::AlreadyExists,
550
0
            Self::InvalidInput | Self::InvalidData => Code::InvalidArgument,
551
0
            Self::TimedOut => Code::DeadlineExceeded,
552
0
            Self::Interrupted => Code::Aborted,
553
            Self::NotConnected
554
            | Self::AddrInUse
555
            | Self::AddrNotAvailable
556
            | Self::BrokenPipe
557
            | Self::WouldBlock
558
            | Self::WriteZero
559
            | Self::Other
560
1
            | Self::UnexpectedEof => Code::Internal,
561
1
            _ => Code::Unknown,
562
        }
563
30
    }
564
}
565
566
// Serde definition for tonic::Code. See: https://serde.rs/remote-derive.html
567
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
568
#[serde(remote = "Code")]
569
pub enum CodeDef {
570
    Ok = 0,
571
    Cancelled = 1,
572
    Unknown = 2,
573
    InvalidArgument = 3,
574
    DeadlineExceeded = 4,
575
    NotFound = 5,
576
    AlreadyExists = 6,
577
    PermissionDenied = 7,
578
    ResourceExhausted = 8,
579
    FailedPrecondition = 9,
580
    Aborted = 10,
581
    OutOfRange = 11,
582
    Unimplemented = 12,
583
    Internal = 13,
584
    Unavailable = 14,
585
    DataLoss = 15,
586
    Unauthenticated = 16,
587
    // NOTE: Additional codes must be added to stores.rs in ErrorCodes and also
588
    // in both match statements in retry.rs.
589
}