Coverage Report

Created: 2026-05-01 12:32

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