Coverage Report

Created: 2025-10-20 10:50

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
173
    pub const fn new_with_messages(code: Code, messages: Vec<String>) -> Self {
72
173
        Self { code, messages }
73
173
    }
74
75
    #[must_use]
76
172
    pub fn new(code: Code, msg: String) -> Self {
77
172
        if msg.is_empty() {
  Branch (77:12): [True: 0, False: 172]
  Branch (77:12): [Folded - Ignored]
78
0
            Self::new_with_messages(code, vec![])
79
        } else {
80
172
            Self::new_with_messages(code, vec![msg])
81
        }
82
172
    }
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
pub trait ResultExt<T> {
287
    /// # Errors
288
    ///
289
    /// Will return `Err` if we can't convert the error.
290
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
291
    where
292
        Self: Sized,
293
        S: ToString,
294
        F: (FnOnce(&Error) -> (Code, S)) + Sized;
295
296
    /// # Errors
297
    ///
298
    /// Will return `Err` if we can't convert the error.
299
    #[inline]
300
278k
    fn err_tip<F, S>(self, tip_fn: F) -> Result<T, Error>
301
278k
    where
302
278k
        Self: Sized,
303
278k
        S: ToString,
304
278k
        F: (FnOnce() -> S) + Sized,
305
    {
306
278k
        self.err_tip_with_code(|e| (
e.code119
,
tip_fn()119
))
307
278k
    }
308
309
    /// # Errors
310
    ///
311
    /// Will return `Err` if we can't merge the errors.
312
0
    fn merge<U>(self, _other: Result<U, Error>) -> Result<U, Error>
313
0
    where
314
0
        Self: Sized,
315
    {
316
0
        unreachable!();
317
    }
318
}
319
320
impl<T, E: Into<Error>> ResultExt<T> for Result<T, E> {
321
    #[inline]
322
277k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
323
277k
    where
324
277k
        Self: Sized,
325
277k
        S: ToString,
326
277k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
327
    {
328
277k
        self.map_err(|e| 
{108
329
108
            let mut error: Error = e.into();
330
108
            let (code, message) = tip_fn(&error);
331
108
            error.code = code;
332
108
            error.messages.push(message.to_string());
333
108
            error
334
108
        })
335
277k
    }
336
337
5.71k
    fn merge<U>(self, other: Result<U, Error>) -> Result<U, Error>
338
5.71k
    where
339
5.71k
        Self: Sized,
340
    {
341
5.71k
        if let Err(
e19
) = self {
  Branch (341:16): [Folded - Ignored]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 1, False: 9]
  Branch (341:16): [True: 4, False: 39]
  Branch (341:16): [True: 4, False: 261]
  Branch (341:16): [True: 0, False: 1]
  Branch (341:16): [True: 0, False: 1]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 1, False: 17]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 1.23k]
  Branch (341:16): [True: 0, False: 1]
  Branch (341:16): [True: 3, False: 3.95k]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 1]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 10]
  Branch (341:16): [True: 0, False: 1]
  Branch (341:16): [True: 2, False: 1]
  Branch (341:16): [True: 0, False: 2]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 25]
  Branch (341:16): [True: 0, False: 12]
  Branch (341:16): [True: 1, False: 3]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 1, False: 1]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [Folded - Ignored]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 3]
  Branch (341:16): [True: 0, False: 1]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 5]
  Branch (341:16): [True: 1, False: 4]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 2]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 49]
  Branch (341:16): [True: 0, False: 5]
  Branch (341:16): [True: 0, False: 4]
  Branch (341:16): [True: 0, False: 1]
  Branch (341:16): [True: 0, False: 5]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 1, False: 46]
  Branch (341:16): [True: 0, False: 4]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
  Branch (341:16): [True: 0, False: 0]
342
19
            let mut e: Error = e.into();
343
19
            if let Err(
other_err17
) = other {
  Branch (343:20): [Folded - Ignored]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 1]
  Branch (343:20): [True: 4, False: 0]
  Branch (343:20): [True: 4, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 1, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 3, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 2, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 1, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 1, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [Folded - Ignored]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 1, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 1]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
  Branch (343:20): [True: 0, False: 0]
344
17
                let mut other_err: Error = other_err;
345
17
                // This will help with knowing which messages are tied to different errors.
346
17
                e.messages.push("---".to_string());
347
17
                e.messages.append(&mut other_err.messages);
348
17
            
}2
349
19
            return Err(e);
350
5.69k
        }
351
5.69k
        other
352
5.71k
    }
353
}
354
355
impl<T> ResultExt<T> for Option<T> {
356
    #[inline]
357
5.62k
    fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error>
358
5.62k
    where
359
5.62k
        Self: Sized,
360
5.62k
        S: ToString,
361
5.62k
        F: (FnOnce(&Error) -> (Code, S)) + Sized,
362
    {
363
5.62k
        self.ok_or_else(|| 
{18
364
18
            let mut error = Error {
365
18
                code: Code::Internal,
366
18
                messages: vec![],
367
18
            };
368
18
            let (code, message) = tip_fn(&error);
369
18
            error.code = code;
370
18
            error.messages.push(message.to_string());
371
18
            error
372
18
        })
373
5.62k
    }
374
}
375
376
trait CodeExt {
377
    fn into_error_kind(self) -> std::io::ErrorKind;
378
}
379
380
impl CodeExt for Code {
381
1
    fn into_error_kind(self) -> std::io::ErrorKind {
382
1
        match self {
383
0
            Self::Aborted => std::io::ErrorKind::Interrupted,
384
0
            Self::AlreadyExists => std::io::ErrorKind::AlreadyExists,
385
0
            Self::DeadlineExceeded => std::io::ErrorKind::TimedOut,
386
0
            Self::InvalidArgument => std::io::ErrorKind::InvalidInput,
387
0
            Self::NotFound => std::io::ErrorKind::NotFound,
388
0
            Self::PermissionDenied => std::io::ErrorKind::PermissionDenied,
389
0
            Self::Unavailable => std::io::ErrorKind::ConnectionRefused,
390
1
            _ => std::io::ErrorKind::Other,
391
        }
392
1
    }
393
}
394
395
trait ErrorKindExt {
396
    fn into_code(self) -> Code;
397
}
398
399
impl ErrorKindExt for std::io::ErrorKind {
400
12
    fn into_code(self) -> Code {
401
12
        match self {
402
11
            Self::NotFound => Code::NotFound,
403
0
            Self::PermissionDenied => Code::PermissionDenied,
404
            Self::ConnectionRefused | Self::ConnectionReset | Self::ConnectionAborted => {
405
0
                Code::Unavailable
406
            }
407
0
            Self::AlreadyExists => Code::AlreadyExists,
408
0
            Self::InvalidInput | Self::InvalidData => Code::InvalidArgument,
409
0
            Self::TimedOut => Code::DeadlineExceeded,
410
0
            Self::Interrupted => Code::Aborted,
411
            Self::NotConnected
412
            | Self::AddrInUse
413
            | Self::AddrNotAvailable
414
            | Self::BrokenPipe
415
            | Self::WouldBlock
416
            | Self::WriteZero
417
            | Self::Other
418
1
            | Self::UnexpectedEof => Code::Internal,
419
0
            _ => Code::Unknown,
420
        }
421
12
    }
422
}
423
424
// Serde definition for tonic::Code. See: https://serde.rs/remote-derive.html
425
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
426
#[serde(remote = "Code")]
427
pub enum CodeDef {
428
    Ok = 0,
429
    Cancelled = 1,
430
    Unknown = 2,
431
    InvalidArgument = 3,
432
    DeadlineExceeded = 4,
433
    NotFound = 5,
434
    AlreadyExists = 6,
435
    PermissionDenied = 7,
436
    ResourceExhausted = 8,
437
    FailedPrecondition = 9,
438
    Aborted = 10,
439
    OutOfRange = 11,
440
    Unimplemented = 12,
441
    Internal = 13,
442
    Unavailable = 14,
443
    DataLoss = 15,
444
    Unauthenticated = 16,
445
    // NOTE: Additional codes must be added to stores.rs in ErrorCodes and also
446
    // in both match statements in retry.rs.
447
}