Coverage Report

Created: 2026-06-02 19:54

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
28
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87
28
        let mut builder = f.debug_struct("Error");
88
28
        builder.field("code", &self.code);
89
28
        if !self.messages.is_empty() {
90
28
            builder.field("messages", &self.messages);
91
28
        
}0
92
28
        if !self.context.is_none() {
93
0
            builder.field("context", &self.context);
94
28
        }
95
28
        builder.finish()
96
28
    }
97
}
98
99
impl ErrorContext {
100
    #[inline]
101
    #[must_use]
102
30
    pub const fn is_none(&self) -> bool {
103
30
        
matches!0
(self, Self::None)
104
30
    }
105
}
106
107
impl MetricsComponent for Error {
108
0
    fn publish(
109
0
        &self,
110
0
        kind: MetricKind,
111
0
        field_metadata: MetricFieldData,
112
0
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
113
0
        self.to_string().publish(kind, field_metadata)
114
0
    }
115
}
116
117
impl Error {
118
    #[must_use]
119
223
    pub const fn new_with_messages(code: Code, messages: Vec<String>) -> Self {
120
223
        Self {
121
223
            code,
122
223
            messages,
123
223
            context: ErrorContext::None,
124
223
        }
125
223
    }
126
127
    #[must_use]
128
175
    pub fn new(code: Code, msg: String) -> Self {
129
175
        if msg.is_empty() {
130
0
            Self::new_with_messages(code, vec![])
131
        } else {
132
175
            Self::new_with_messages(code, vec![msg])
133
        }
134
175
    }
135
136
    #[must_use]
137
45
    pub fn from_std_err(code: Code, mut err: &dyn core::error::Error) -> Self {
138
45
        let mut messages = vec![format!("{err}")];
139
51
        while let Some(
src6
) = err.source() {
140
6
            messages.push(format!("{src}"));
141
6
            err = src;
142
6
        }
143
45
        messages.reverse();
144
45
        Self::new_with_messages(code, messages)
145
45
    }
146
147
    #[inline]
148
    #[must_use]
149
54
    pub fn append<S: Into<String>>(mut self, msg: S) -> Self {
150
54
        self.messages.push(msg.into());
151
54
        self
152
54
    }
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
3
    pub fn merge<E: Into<Self>>(mut self, other: E) -> Self {
163
3
        let mut other: Self = other.into();
164
        // This will help with knowing which messages are tied to different errors.
165
3
        self.messages.push("---".to_string());
166
3
        self.messages.append(&mut other.messages);
167
3
        self
168
3
    }
169
170
    #[must_use]
171
20
    pub fn merge_option<T: Into<Self>, U: Into<Self>>(
172
20
        this: Option<T>,
173
20
        other: Option<U>,
174
20
    ) -> Option<Self> {
175
20
        if let Some(
this4
) = this {
176
4
            if let Some(
other0
) = other {
177
0
                return Some(this.into().merge(other));
178
4
            }
179
4
            return Some(this.into());
180
16
        }
181
16
        other.map(Into::into)
182
20
    }
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
21
    pub fn message_string(&self) -> String {
191
21
        self.messages.join(" : ")
192
21
    }
193
}
194
195
impl core::error::Error for Error {}
196
197
impl From<Error> for nativelink_proto::google::rpc::Status {
198
14
    fn from(val: Error) -> Self {
199
14
        Self {
200
14
            code: val.code as i32,
201
14
            message: val.message_string(),
202
14
            details: vec![],
203
14
        }
204
14
    }
205
}
206
207
impl From<nativelink_proto::google::rpc::Status> for Error {
208
2
    fn from(val: nativelink_proto::google::rpc::Status) -> Self {
209
2
        Self {
210
2
            code: val.code.into(),
211
2
            messages: vec![val.message],
212
2
            context: ErrorContext::None,
213
2
        }
214
2
    }
215
}
216
217
impl core::fmt::Display for Error {
218
30
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219
        // A manual impl to reduce the noise of frequently empty fields.
220
30
        let mut builder = f.debug_struct("Error");
221
222
30
        builder.field("code", &self.code);
223
224
30
        if !self.messages.is_empty() {
225
30
            builder.field("messages", &self.messages);
226
30
        
}0
227
228
30
        builder.finish()
229
30
    }
230
}
231
232
impl From<prost::DecodeError> for Error {
233
0
    fn from(err: prost::DecodeError) -> Self {
234
0
        Self::from_std_err(Code::Internal, &err)
235
0
    }
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
0
    fn from(err: core::num::TryFromIntError) -> Self {
252
0
        Self::from_std_err(Code::InvalidArgument, &err)
253
0
    }
254
}
255
256
impl From<tokio::task::JoinError> for Error {
257
0
    fn from(err: tokio::task::JoinError) -> Self {
258
0
        Self::from_std_err(Code::Internal, &err)
259
0
    }
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
0
    fn from(err: core::num::ParseIntError) -> Self {
290
0
        Self::from_std_err(Code::InvalidArgument, &err)
291
0
    }
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
17
    fn from(err: std::io::Error) -> Self {
321
17
        Self {
322
17
            code: err.kind().into_code(),
323
17
            messages: vec![err.to_string()],
324
17
            context: ErrorContext::None,
325
17
        }
326
17
    }
327
}
328
329
impl From<redis::RedisError> for Error {
330
9
    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
9
        let code = match error.kind() {
338
0
            AuthenticationFailed => Code::PermissionDenied,
339
0
            ParseError | UnexpectedReturnType | InvalidClientConfig => Code::InvalidArgument,
340
            IoError => {
341
3
                if error.is_timeout() {
342
3
                    Code::DeadlineExceeded
343
                } else {
344
0
                    Code::Internal
345
                }
346
            }
347
6
            _ => Code::Unknown,
348
        };
349
350
9
        let kind = error.kind();
351
9
        make_err!(code, "{kind:?}: {error}")
352
9
    }
353
}
354
355
impl From<tonic::Status> for Error {
356
2
    fn from(status: tonic::Status) -> Self {
357
2
        Self::new(status.code(), status.to_string())
358
2
    }
359
}
360
361
impl From<Error> for tonic::Status {
362
13
    fn from(val: Error) -> Self {
363
13
        Self::new(val.code, val.messages.join(" : "))
364
13
    }
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
pub trait ResultExt<T> {
422
    /// # Errors
423
    ///
424
    /// Will return `Err` if we can't convert the error.
425
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
426
    where
427
        Self: Sized,
428
        S: ToString,
429
        F: (FnOnce(&Error) -> (Code, S)) + Sized;
430
431
    /// # Errors
432
    ///
433
    /// Will return `Err` if we can't convert the error.
434
    #[inline]
435
276k
    fn err_tip<F, S>(self, tip_fn: F) -> Result<T, Error>
436
276k
    where
437
276k
        Self: Sized,
438
276k
        S: ToString,
439
276k
        F: (FnOnce() -> S) + Sized,
440
    {
441
276k
        self.err_tip_with_code(|e| (
e.code155
,
tip_fn()155
))
442
276k
    }
443
444
    /// # Errors
445
    ///
446
    /// Will return `Err` if we can't merge the errors.
447
0
    fn merge<U>(self, _other: Result<U, Error>) -> Result<U, Error>
448
0
    where
449
0
        Self: Sized,
450
    {
451
0
        unreachable!();
452
    }
453
}
454
455
impl<T, E: Into<Error>> ResultExt<T> for Result<T, E> {
456
    #[inline]
457
275k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
458
275k
    where
459
275k
        Self: Sized,
460
275k
        S: ToString,
461
275k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
462
    {
463
275k
        self.map_err(|e| 
{144
464
144
            let mut error: Error = e.into();
465
144
            let (code, message) = tip_fn(&error);
466
144
            error.code = code;
467
144
            error.messages.push(message.to_string());
468
144
            error
469
144
        })
470
275k
    }
471
472
6.05k
    fn merge<U>(self, other: Result<U, Error>) -> Result<U, Error>
473
6.05k
    where
474
6.05k
        Self: Sized,
475
    {
476
6.05k
        if let Err(
e22
) = self {
477
22
            let mut e: Error = e.into();
478
22
            if let Err(
other_err20
) = other {
479
20
                let mut other_err: Error = other_err;
480
20
                // This will help with knowing which messages are tied to different errors.
481
20
                e.messages.push("---".to_string());
482
20
                e.messages.append(&mut other_err.messages);
483
20
            
}2
484
22
            return Err(e);
485
6.03k
        }
486
6.03k
        other
487
6.05k
    }
488
}
489
490
impl<T> ResultExt<T> for Option<T> {
491
    #[inline]
492
5.91k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
493
5.91k
    where
494
5.91k
        Self: Sized,
495
5.91k
        S: ToString,
496
5.91k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
497
    {
498
5.91k
        self.ok_or_else(|| 
{22
499
22
            let mut error = Error {
500
22
                code: Code::Internal,
501
22
                messages: vec![],
502
22
                context: ErrorContext::None,
503
22
            };
504
22
            let (code, message) = tip_fn(&error);
505
22
            error.code = code;
506
22
            error.messages.push(message.to_string());
507
22
            error
508
22
        })
509
5.91k
    }
510
}
511
512
trait CodeExt {
513
    fn into_error_kind(self) -> std::io::ErrorKind;
514
}
515
516
impl CodeExt for Code {
517
1
    fn into_error_kind(self) -> std::io::ErrorKind {
518
1
        match self {
519
0
            Self::Aborted => std::io::ErrorKind::Interrupted,
520
0
            Self::AlreadyExists => std::io::ErrorKind::AlreadyExists,
521
0
            Self::DeadlineExceeded => std::io::ErrorKind::TimedOut,
522
0
            Self::InvalidArgument => std::io::ErrorKind::InvalidInput,
523
0
            Self::NotFound => std::io::ErrorKind::NotFound,
524
0
            Self::PermissionDenied => std::io::ErrorKind::PermissionDenied,
525
0
            Self::Unavailable => std::io::ErrorKind::ConnectionRefused,
526
1
            _ => std::io::ErrorKind::Other,
527
        }
528
1
    }
529
}
530
531
trait ErrorKindExt {
532
    fn into_code(self) -> Code;
533
}
534
535
impl ErrorKindExt for std::io::ErrorKind {
536
17
    fn into_code(self) -> Code {
537
17
        match self {
538
16
            Self::NotFound => Code::NotFound,
539
0
            Self::PermissionDenied => Code::PermissionDenied,
540
            Self::ConnectionRefused | Self::ConnectionReset | Self::ConnectionAborted => {
541
0
                Code::Unavailable
542
            }
543
0
            Self::AlreadyExists => Code::AlreadyExists,
544
0
            Self::InvalidInput | Self::InvalidData => Code::InvalidArgument,
545
0
            Self::TimedOut => Code::DeadlineExceeded,
546
0
            Self::Interrupted => Code::Aborted,
547
            Self::NotConnected
548
            | Self::AddrInUse
549
            | Self::AddrNotAvailable
550
            | Self::BrokenPipe
551
            | Self::WouldBlock
552
            | Self::WriteZero
553
            | Self::Other
554
1
            | Self::UnexpectedEof => Code::Internal,
555
0
            _ => Code::Unknown,
556
        }
557
17
    }
558
}
559
560
// Serde definition for tonic::Code. See: https://serde.rs/remote-derive.html
561
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
562
#[serde(remote = "Code")]
563
pub enum CodeDef {
564
    Ok = 0,
565
    Cancelled = 1,
566
    Unknown = 2,
567
    InvalidArgument = 3,
568
    DeadlineExceeded = 4,
569
    NotFound = 5,
570
    AlreadyExists = 6,
571
    PermissionDenied = 7,
572
    ResourceExhausted = 8,
573
    FailedPrecondition = 9,
574
    Aborted = 10,
575
    OutOfRange = 11,
576
    Unimplemented = 12,
577
    Internal = 13,
578
    Unavailable = 14,
579
    DataLoss = 15,
580
    Unauthenticated = 16,
581
    // NOTE: Additional codes must be added to stores.rs in ErrorCodes and also
582
    // in both match statements in retry.rs.
583
}