Coverage Report

Created: 2026-03-02 14:58

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