Coverage Report

Created: 2025-11-07 13:29

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