Coverage Report

Created: 2025-12-16 15:31

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