Coverage Report

Created: 2025-06-24 08:57

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