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