/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 | | /// Typed metadata that travels with an [`Error`]. |
55 | | /// |
56 | | /// Used in place of string-parsing error messages when the producer |
57 | | /// has structured information the consumer needs to act on. The |
58 | | /// motivating case is missing-blob errors from `fast_slow_store` — |
59 | | /// `to_execute_response` reads [`ErrorContext::MissingDigest`] and |
60 | | /// surfaces a `FAILED_PRECONDITION` with a `PreconditionFailure` |
61 | | /// detail naming the digest, which Bazel auto-retries on. |
62 | | /// |
63 | | /// Default is [`ErrorContext::None`]; existing call sites that |
64 | | /// construct [`Error`] via `make_err!` / `Error::new` do not need to |
65 | | /// be updated. |
66 | | #[derive(Default, Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] |
67 | | pub enum ErrorContext { |
68 | | #[default] |
69 | | None, |
70 | | /// The error refers to a specific CAS blob that could not be |
71 | | /// located. `hash` and `size` together form the digest the client |
72 | | /// should re-upload (`REv2` `blobs/{hash}/{size}`). |
73 | | MissingDigest { hash: String, size: i64 }, |
74 | | } |
75 | | |
76 | | #[derive(Eq, PartialEq, Clone, Serialize, Deserialize)] |
77 | | pub struct Error { |
78 | | #[serde(with = "CodeDef")] |
79 | | pub code: Code, |
80 | | pub messages: Vec<String>, |
81 | | #[serde(default, skip_serializing_if = "ErrorContext::is_none")] |
82 | | pub context: ErrorContext, |
83 | | } |
84 | | |
85 | | impl core::fmt::Debug for Error { |
86 | 186 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
87 | 186 | let mut builder = f.debug_struct("Error"); |
88 | 186 | builder.field("code", &self.code); |
89 | 186 | if !self.messages.is_empty() { |
90 | 186 | builder.field("messages", &self.messages); |
91 | 186 | }0 |
92 | 186 | if !self.context.is_none() { |
93 | 5 | builder.field("context", &self.context); |
94 | 181 | } |
95 | 186 | builder.finish() |
96 | 186 | } |
97 | | } |
98 | | |
99 | | impl ErrorContext { |
100 | | #[inline] |
101 | | #[must_use] |
102 | 190 | pub const fn is_none(&self) -> bool { |
103 | 190 | matches!5 (self, Self::None) |
104 | 190 | } |
105 | | } |
106 | | |
107 | | impl MetricsComponent for Error { |
108 | 1 | fn publish( |
109 | 1 | &self, |
110 | 1 | kind: MetricKind, |
111 | 1 | field_metadata: MetricFieldData, |
112 | 1 | ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> { |
113 | 1 | self.to_string().publish(kind, field_metadata) |
114 | 1 | } |
115 | | } |
116 | | |
117 | | impl Error { |
118 | | #[must_use] |
119 | 1.18k | pub const fn new_with_messages(code: Code, messages: Vec<String>) -> Self { |
120 | 1.18k | Self { |
121 | 1.18k | code, |
122 | 1.18k | messages, |
123 | 1.18k | context: ErrorContext::None, |
124 | 1.18k | } |
125 | 1.18k | } |
126 | | |
127 | | #[must_use] |
128 | 1.12k | pub fn new(code: Code, msg: String) -> Self { |
129 | 1.12k | if msg.is_empty() { |
130 | 0 | Self::new_with_messages(code, vec![]) |
131 | | } else { |
132 | 1.12k | Self::new_with_messages(code, vec![msg]) |
133 | | } |
134 | 1.12k | } |
135 | | |
136 | | #[must_use] |
137 | 53 | pub fn from_std_err(code: Code, mut err: &dyn core::error::Error) -> Self { |
138 | 53 | let mut messages = vec![format!("{err}")]; |
139 | 59 | while let Some(src6 ) = err.source() { |
140 | 6 | messages.push(format!("{src}")); |
141 | 6 | err = src; |
142 | 6 | } |
143 | 53 | messages.reverse(); |
144 | 53 | Self::new_with_messages(code, messages) |
145 | 53 | } |
146 | | |
147 | | #[inline] |
148 | | #[must_use] |
149 | 146 | pub fn append<S: Into<String>>(mut self, msg: S) -> Self { |
150 | 146 | self.messages.push(msg.into()); |
151 | 146 | self |
152 | 146 | } |
153 | | |
154 | | #[inline] |
155 | | #[must_use] |
156 | 0 | pub fn with_context(mut self, context: ErrorContext) -> Self { |
157 | 0 | self.context = context; |
158 | 0 | self |
159 | 0 | } |
160 | | |
161 | | #[must_use] |
162 | 13 | pub fn merge<E: Into<Self>>(mut self, other: E) -> Self { |
163 | 13 | let mut other: Self = other.into(); |
164 | | // This will help with knowing which messages are tied to different errors. |
165 | 13 | self.messages.push("---".to_string()); |
166 | 13 | self.messages.append(&mut other.messages); |
167 | 13 | self |
168 | 13 | } |
169 | | |
170 | | #[must_use] |
171 | 58 | pub fn merge_option<T: Into<Self>, U: Into<Self>>( |
172 | 58 | this: Option<T>, |
173 | 58 | other: Option<U>, |
174 | 58 | ) -> Option<Self> { |
175 | 58 | if let Some(this13 ) = this { |
176 | 13 | if let Some(other1 ) = other { |
177 | 1 | return Some(this.into().merge(other)); |
178 | 12 | } |
179 | 12 | return Some(this.into()); |
180 | 45 | } |
181 | 45 | other.map(Into::into) |
182 | 58 | } |
183 | | |
184 | | #[must_use] |
185 | 1 | pub fn to_std_err(self) -> std::io::Error { |
186 | 1 | std::io::Error::new(self.code.into_error_kind(), self.messages.join(" : ")) |
187 | 1 | } |
188 | | |
189 | | #[must_use] |
190 | 179 | pub fn message_string(&self) -> String { |
191 | 179 | self.messages.join(" : ") |
192 | 179 | } |
193 | | } |
194 | | |
195 | | impl core::error::Error for Error {} |
196 | | |
197 | | impl From<Error> for nativelink_proto::google::rpc::Status { |
198 | 41 | fn from(val: Error) -> Self { |
199 | 41 | Self { |
200 | 41 | code: val.code as i32, |
201 | 41 | message: val.message_string(), |
202 | 41 | details: vec![], |
203 | 41 | } |
204 | 41 | } |
205 | | } |
206 | | |
207 | | impl From<nativelink_proto::google::rpc::Status> for Error { |
208 | 8 | fn from(val: nativelink_proto::google::rpc::Status) -> Self { |
209 | 8 | let context = missing_digest_from_details(&val.details); |
210 | 8 | Self { |
211 | 8 | code: val.code.into(), |
212 | 8 | messages: vec![val.message], |
213 | 8 | context, |
214 | 8 | } |
215 | 8 | } |
216 | | } |
217 | | |
218 | | /// The `REv2` missing-blob violation in a status's details, as the |
219 | | /// [`ErrorContext`] that produced it. A status that crosses a gRPC hop |
220 | | /// (a CAS forwarding `Execute` to a separate scheduler) would otherwise |
221 | | /// lose it, and with it the `PreconditionFailure` the client needs to |
222 | | /// upload the blob again and retry. |
223 | 8 | fn missing_digest_from_details(details: &[prost_types::Any]) -> ErrorContext { |
224 | | const PRECONDITION_FAILURE: &str = "type.googleapis.com/google.rpc.PreconditionFailure"; |
225 | 8 | details |
226 | 8 | .iter() |
227 | 8 | .filter(|any| any.type_url1 == PRECONDITION_FAILURE1 ) |
228 | 8 | .filter_map(|any| {1 |
229 | 1 | <nativelink_proto::google::rpc::PreconditionFailure as prost::Message>::decode( |
230 | 1 | any.value.as_slice(), |
231 | | ) |
232 | 1 | .ok() |
233 | 1 | }) |
234 | 8 | .flat_map(|failure| failure.violations) |
235 | 8 | .filter(|violation| violation.r#type1 == "MISSING"1 ) |
236 | 8 | .find_map(|violation| {1 |
237 | 1 | let rest = violation.subject.strip_prefix("blobs/")?0 ; |
238 | 1 | let (hash, size) = rest.split_once('/')?0 ; |
239 | | Some(ErrorContext::MissingDigest { |
240 | 1 | hash: hash.to_string(), |
241 | 1 | size: size.parse().ok()?0 , |
242 | | }) |
243 | 1 | }) |
244 | 8 | .unwrap_or_default() |
245 | 8 | } |
246 | | |
247 | | impl core::fmt::Display for Error { |
248 | 123 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
249 | | // A manual impl to reduce the noise of frequently empty fields. |
250 | 123 | let mut builder = f.debug_struct("Error"); |
251 | | |
252 | 123 | builder.field("code", &self.code); |
253 | | |
254 | 123 | if !self.messages.is_empty() { |
255 | 123 | builder.field("messages", &self.messages); |
256 | 123 | }0 |
257 | | |
258 | 123 | builder.finish() |
259 | 123 | } |
260 | | } |
261 | | |
262 | | impl From<prost::DecodeError> for Error { |
263 | 2 | fn from(err: prost::DecodeError) -> Self { |
264 | 2 | Self::from_std_err(Code::Internal, &err) |
265 | 2 | } |
266 | | } |
267 | | |
268 | | impl From<prost::EncodeError> for Error { |
269 | 0 | fn from(err: prost::EncodeError) -> Self { |
270 | 0 | Self::from_std_err(Code::Internal, &err) |
271 | 0 | } |
272 | | } |
273 | | |
274 | | impl From<prost::UnknownEnumValue> for Error { |
275 | 0 | fn from(err: prost::UnknownEnumValue) -> Self { |
276 | 0 | Self::from_std_err(Code::Internal, &err) |
277 | 0 | } |
278 | | } |
279 | | |
280 | | impl From<core::num::TryFromIntError> for Error { |
281 | 1 | fn from(err: core::num::TryFromIntError) -> Self { |
282 | 1 | Self::from_std_err(Code::InvalidArgument, &err) |
283 | 1 | } |
284 | | } |
285 | | |
286 | | impl From<tokio::task::JoinError> for Error { |
287 | 1 | fn from(err: tokio::task::JoinError) -> Self { |
288 | 1 | Self::from_std_err(Code::Internal, &err) |
289 | 1 | } |
290 | | } |
291 | | |
292 | | impl<T> From<PoisonError<MutexGuard<'_, T>>> for Error { |
293 | 0 | fn from(err: PoisonError<MutexGuard<'_, T>>) -> Self { |
294 | 0 | Self::from_std_err(Code::Internal, &err) |
295 | 0 | } |
296 | | } |
297 | | |
298 | | impl From<serde_json5::Error> for Error { |
299 | 0 | fn from(err: serde_json5::Error) -> Self { |
300 | 0 | match err { |
301 | 0 | serde_json5::Error::Message { msg, location } => { |
302 | 0 | if let Some(has_location) = location { |
303 | 0 | make_err!( |
304 | 0 | Code::Internal, |
305 | | "line {}, column {} - {}", |
306 | | has_location.line, |
307 | | has_location.column, |
308 | | msg |
309 | | ) |
310 | | } else { |
311 | 0 | Self::new(Code::Internal, msg) |
312 | | } |
313 | | } |
314 | | } |
315 | 0 | } |
316 | | } |
317 | | |
318 | | impl From<core::num::ParseIntError> for Error { |
319 | 2 | fn from(err: core::num::ParseIntError) -> Self { |
320 | 2 | Self::from_std_err(Code::InvalidArgument, &err) |
321 | 2 | } |
322 | | } |
323 | | |
324 | | impl From<core::convert::Infallible> for Error { |
325 | 0 | fn from(_err: core::convert::Infallible) -> Self { |
326 | | // Infallible is an error type that can never happen. |
327 | 0 | unreachable!(); |
328 | | } |
329 | | } |
330 | | |
331 | | impl From<TimestampError> for Error { |
332 | 0 | fn from(err: TimestampError) -> Self { |
333 | 0 | Self::from_std_err(Code::InvalidArgument, &err) |
334 | 0 | } |
335 | | } |
336 | | |
337 | | impl From<AcquireError> for Error { |
338 | 0 | fn from(err: AcquireError) -> Self { |
339 | 0 | Self::from_std_err(Code::Internal, &err) |
340 | 0 | } |
341 | | } |
342 | | |
343 | | impl From<Utf8Error> for Error { |
344 | 0 | fn from(err: Utf8Error) -> Self { |
345 | 0 | Self::from_std_err(Code::Internal, &err) |
346 | 0 | } |
347 | | } |
348 | | |
349 | | impl From<std::io::Error> for Error { |
350 | 1.35k | fn from(err: std::io::Error) -> Self { |
351 | 1.35k | Self { |
352 | 1.35k | code: err.kind().into_code(), |
353 | 1.35k | messages: vec![err.to_string()], |
354 | 1.35k | context: ErrorContext::None, |
355 | 1.35k | } |
356 | 1.35k | } |
357 | | } |
358 | | |
359 | | impl From<redis::RedisError> for Error { |
360 | 33 | fn from(error: redis::RedisError) -> Self { |
361 | | use redis::ErrorKind::{ |
362 | | AuthenticationFailed, ClusterConnectionNotFound, EmptySentinelList, Extension, |
363 | | InvalidClientConfig, Io as IoError, MasterNameNotFoundBySentinel, |
364 | | NoValidReplicasFoundBySentinel, Parse as ParseError, RESP3NotSupported, Server, |
365 | | UnexpectedReturnType, |
366 | | }; |
367 | | use redis::ServerErrorKind::{ |
368 | | BusyLoading, ClusterDown, MasterDown, NoPerm, ReadOnly, TryAgain, |
369 | | }; |
370 | | |
371 | | // Conversions here are based on https://grpc.github.io/grpc/core/md_doc_statuscodes.html. |
372 | | // |
373 | | // The distinction that matters most is retryable vs not. These statuses |
374 | | // reach REAPI clients directly, and Bazel treats INVALID_ARGUMENT as |
375 | | // permanent: it fails the build instantly rather than retrying. So |
376 | | // anything caused by the state of the Redis deployment — a failover in |
377 | | // progress, a dropped connection, a node still loading its dataset — |
378 | | // must NOT land there, or a routine blip becomes a failed CI run. |
379 | 33 | let code = match error.kind() { |
380 | 3 | AuthenticationFailed | Server(NoPerm) => Code::PermissionDenied, |
381 | | |
382 | | // Topology in flux. Sentinel failover makes these normal and |
383 | | // brief: the master moved and the client has not caught up yet. |
384 | | // Surfacing MasterNameNotFoundBySentinel as INVALID_ARGUMENT is |
385 | | // what turned a 1-2 minute sentinel failover into a dead build. |
386 | | MasterNameNotFoundBySentinel |
387 | | | NoValidReplicasFoundBySentinel |
388 | | | ClusterConnectionNotFound |
389 | | | Server(ClusterDown | MasterDown | TryAgain | BusyLoading | ReadOnly) => { |
390 | 10 | Code::Unavailable |
391 | | } |
392 | | |
393 | | // A timeout is worth distinguishing from a dead connection, but |
394 | | // both are transient and both are retryable. |
395 | | IoError => { |
396 | 6 | if error.is_timeout() { |
397 | 4 | Code::DeadlineExceeded |
398 | | } else { |
399 | 2 | Code::Unavailable |
400 | | } |
401 | | } |
402 | | |
403 | | // Genuine misconfiguration by the operator — retrying cannot help. |
404 | 4 | InvalidClientConfig | EmptySentinelList | RESP3NotSupported => Code::InvalidArgument, |
405 | | |
406 | | // Server-side or protocol faults. Not the caller's argument, so |
407 | | // not InvalidArgument: a malformed reply is our problem or the |
408 | | // server's, and it is at least worth a retry. |
409 | 2 | ParseError | UnexpectedReturnType => Code::Internal, |
410 | | |
411 | | // A server at its maxmemory with noeviction refuses every write |
412 | | // with OOM. The client knows no kind for it, so it arrives as an |
413 | | // extension code; it is the server out of room, nothing wrong |
414 | | // with the command. |
415 | 4 | Extension if error.code() == Some("OOM")1 => Code::ResourceExhausted1 , |
416 | | |
417 | 7 | _ => Code::Unknown, |
418 | | }; |
419 | | |
420 | 33 | let kind = error.kind(); |
421 | 33 | make_err!(code, "{kind:?}: {error}") |
422 | 33 | } |
423 | | } |
424 | | |
425 | | impl From<tonic::Status> for Error { |
426 | 22 | fn from(status: tonic::Status) -> Self { |
427 | | // tonic codes a mid-stream transport/`hyper` failure (a connection reset |
428 | | // or broken pipe during a large streaming write) as `Code::Unknown` |
429 | | // with message "transport error". But a broken connection is transient |
430 | | // and retryable — the gRPC status model reserves `UNKNOWN` for genuinely |
431 | | // unmapped errors and codes transport failures `UNAVAILABLE`. Left as |
432 | | // `Unknown`, such a blip falls outside both our own `Retrier`'s and most |
433 | | // clients' retry sets, so a retryable failure becomes terminal and |
434 | | // cacheable uploads are silently dropped. Remap transport-originated |
435 | | // `Unknown` to `Unavailable`. |
436 | 22 | let code = if status.code() == Code::Unknown && is_transport_status6 (&status6 ) { |
437 | 4 | Code::Unavailable |
438 | | } else { |
439 | 18 | status.code() |
440 | | }; |
441 | 22 | Self::new(code, status.to_string()) |
442 | 22 | } |
443 | | } |
444 | | |
445 | | /// Whether a `tonic::Status` originates from a transport-layer failure (a |
446 | | /// reset/broken connection), which tonic surfaces as `Code::Unknown`. |
447 | | /// |
448 | | /// tonic's own `Status::from_error` already maps the failures it recognizes |
449 | | /// (for example a connect refusal) to `Unavailable`; the `Unknown` shape |
450 | | /// arises for transport errors it does not, and for statuses that crossed a |
451 | | /// process boundary. Detection is downcast-first: precise when the concrete |
452 | | /// transport error is still reachable in the source chain; falls back to the |
453 | | /// message otherwise. |
454 | 6 | fn is_transport_status(status: &tonic::Status) -> bool { |
455 | 6 | let mut source = std::error::Error::source(status); |
456 | 6 | while let Some(err1 ) = source { |
457 | 1 | if err.is::<tonic::transport::Error>() { |
458 | 1 | return true; |
459 | 0 | } |
460 | 0 | source = err.source(); |
461 | | } |
462 | | // Fallback for a `Status` reconstructed from the wire, where the concrete |
463 | | // transport error is no longer in the source chain and only the message |
464 | | // survives. `tonic::transport::Error` renders as exactly "transport |
465 | | // error" (no detail suffix), and this crate's own `From<Error> for |
466 | | // Status` re-encodes a proxied one as "transport error : <appended |
467 | | // context>". Anchor to those two shapes — an unanchored `contains` would |
468 | | // reclassify any app-level `Unknown` whose message merely mentions |
469 | | // "transport error" as retryable `Unavailable`. |
470 | 5 | let msg = status.message(); |
471 | 5 | msg == "transport error" || msg3 .starts_with3 ("transport error : ") |
472 | 6 | } |
473 | | |
474 | | impl From<Error> for tonic::Status { |
475 | 41 | fn from(val: Error) -> Self { |
476 | 41 | Self::new(val.code, val.messages.join(" : ")) |
477 | 41 | } |
478 | | } |
479 | | |
480 | | impl From<walkdir::Error> for Error { |
481 | 1 | fn from(err: walkdir::Error) -> Self { |
482 | 1 | Self::from_std_err(Code::Internal, &err) |
483 | 1 | } |
484 | | } |
485 | | |
486 | | impl From<uuid::Error> for Error { |
487 | 0 | fn from(err: uuid::Error) -> Self { |
488 | 0 | Self::from_std_err(Code::Internal, &err) |
489 | 0 | } |
490 | | } |
491 | | |
492 | | impl From<rustls_pki_types::pem::Error> for Error { |
493 | 0 | fn from(err: rustls_pki_types::pem::Error) -> Self { |
494 | 0 | Self::from_std_err(Code::Internal, &err) |
495 | 0 | } |
496 | | } |
497 | | |
498 | | impl From<tokio::time::error::Elapsed> for Error { |
499 | 0 | fn from(err: tokio::time::error::Elapsed) -> Self { |
500 | 0 | Self::from_std_err(Code::DeadlineExceeded, &err) |
501 | 0 | } |
502 | | } |
503 | | |
504 | | impl From<url::ParseError> for Error { |
505 | 0 | fn from(err: url::ParseError) -> Self { |
506 | 0 | Self::from_std_err(Code::Internal, &err) |
507 | 0 | } |
508 | | } |
509 | | |
510 | | impl From<mongodb::error::Error> for Error { |
511 | 0 | fn from(err: mongodb::error::Error) -> Self { |
512 | 0 | Self::from_std_err(Code::Internal, &err) |
513 | 0 | } |
514 | | } |
515 | | |
516 | | impl From<reqwest::Error> for Error { |
517 | 0 | fn from(err: reqwest::Error) -> Self { |
518 | 0 | Self::from_std_err(Code::Internal, &err) |
519 | 0 | } |
520 | | } |
521 | | |
522 | | impl From<zip::result::ZipError> for Error { |
523 | 0 | fn from(err: zip::result::ZipError) -> Self { |
524 | 0 | Self::from_std_err(Code::Internal, &err) |
525 | 0 | } |
526 | | } |
527 | | |
528 | | impl From<std::ffi::NulError> for Error { |
529 | 0 | fn from(err: std::ffi::NulError) -> Self { |
530 | 0 | Self::from_std_err(Code::Internal, &err) |
531 | 0 | } |
532 | | } |
533 | | |
534 | | impl From<base64::DecodeError> for Error { |
535 | 0 | fn from(err: base64::DecodeError) -> Self { |
536 | 0 | Self::from_std_err(Code::Internal, &err) |
537 | 0 | } |
538 | | } |
539 | | |
540 | | pub trait ResultExt<T> { |
541 | | /// # Errors |
542 | | /// |
543 | | /// Will return `Err` if we can't convert the error. |
544 | | fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error> |
545 | | where |
546 | | Self: Sized, |
547 | | S: ToString, |
548 | | F: (FnOnce(&Error) -> (Code, S)) + Sized; |
549 | | |
550 | | /// # Errors |
551 | | /// |
552 | | /// Will return `Err` if we can't convert the error. |
553 | | #[inline] |
554 | 457k | fn err_tip<F, S>(self, tip_fn: F) -> Result<T, Error> |
555 | 457k | where |
556 | 457k | Self: Sized, |
557 | 457k | S: ToString, |
558 | 457k | F: (FnOnce() -> S) + Sized, |
559 | | { |
560 | 457k | self.err_tip_with_code(|e| (e.code688 , tip_fn()688 )) |
561 | 457k | } |
562 | | |
563 | | /// # Errors |
564 | | /// |
565 | | /// Will return `Err` if we can't merge the errors. |
566 | 0 | fn merge<U>(self, _other: Result<U, Error>) -> Result<U, Error> |
567 | 0 | where |
568 | 0 | Self: Sized, |
569 | | { |
570 | 0 | unreachable!(); |
571 | | } |
572 | | } |
573 | | |
574 | | impl<T, E: Into<Error>> ResultExt<T> for Result<T, E> { |
575 | | #[inline] |
576 | 452k | fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error> |
577 | 452k | where |
578 | 452k | Self: Sized, |
579 | 452k | S: ToString, |
580 | 452k | F: (FnOnce(&Error) -> (Code, S)) + Sized, |
581 | | { |
582 | 452k | self.map_err(|e| {670 |
583 | 670 | let mut error: Error = e.into(); |
584 | 670 | let (code, message) = tip_fn(&error); |
585 | 670 | error.code = code; |
586 | 670 | error.messages.push(message.to_string()); |
587 | 670 | error |
588 | 670 | }) |
589 | 452k | } |
590 | | |
591 | 13.6k | fn merge<U>(self, other: Result<U, Error>) -> Result<U, Error> |
592 | 13.6k | where |
593 | 13.6k | Self: Sized, |
594 | | { |
595 | 13.6k | if let Err(e141 ) = self { |
596 | 141 | let mut e: Error = e.into(); |
597 | 141 | if let Err(other_err136 ) = other { |
598 | 136 | let mut other_err: Error = other_err; |
599 | 136 | // This will help with knowing which messages are tied to different errors. |
600 | 136 | e.messages.push("---".to_string()); |
601 | 136 | e.messages.append(&mut other_err.messages); |
602 | 136 | }5 |
603 | 141 | return Err(e); |
604 | 13.5k | } |
605 | 13.5k | other |
606 | 13.6k | } |
607 | | } |
608 | | |
609 | | impl<T> ResultExt<T> for Option<T> { |
610 | | #[inline] |
611 | 12.6k | fn err_tip_with_code<F, S>(self, tip_fn: F) -> Result<T, Error> |
612 | 12.6k | where |
613 | 12.6k | Self: Sized, |
614 | 12.6k | S: ToString, |
615 | 12.6k | F: (FnOnce(&Error) -> (Code, S)) + Sized, |
616 | | { |
617 | 12.6k | self.ok_or_else(|| {44 |
618 | 44 | let mut error = Error { |
619 | 44 | code: Code::Internal, |
620 | 44 | messages: vec![], |
621 | 44 | context: ErrorContext::None, |
622 | 44 | }; |
623 | 44 | let (code, message) = tip_fn(&error); |
624 | 44 | error.code = code; |
625 | 44 | error.messages.push(message.to_string()); |
626 | 44 | error |
627 | 44 | }) |
628 | 12.6k | } |
629 | | } |
630 | | |
631 | | trait CodeExt { |
632 | | fn into_error_kind(self) -> std::io::ErrorKind; |
633 | | } |
634 | | |
635 | | impl CodeExt for Code { |
636 | 1 | fn into_error_kind(self) -> std::io::ErrorKind { |
637 | 1 | match self { |
638 | 0 | Self::Aborted => std::io::ErrorKind::Interrupted, |
639 | 0 | Self::AlreadyExists => std::io::ErrorKind::AlreadyExists, |
640 | 0 | Self::DeadlineExceeded => std::io::ErrorKind::TimedOut, |
641 | 0 | Self::InvalidArgument => std::io::ErrorKind::InvalidInput, |
642 | 0 | Self::NotFound => std::io::ErrorKind::NotFound, |
643 | 0 | Self::PermissionDenied => std::io::ErrorKind::PermissionDenied, |
644 | 0 | Self::Unavailable => std::io::ErrorKind::ConnectionRefused, |
645 | 1 | _ => std::io::ErrorKind::Other, |
646 | | } |
647 | 1 | } |
648 | | } |
649 | | |
650 | | trait ErrorKindExt { |
651 | | fn into_code(self) -> Code; |
652 | | } |
653 | | |
654 | | impl ErrorKindExt for std::io::ErrorKind { |
655 | 1.35k | fn into_code(self) -> Code { |
656 | 1.35k | match self { |
657 | 1.34k | Self::NotFound => Code::NotFound, |
658 | 1 | Self::PermissionDenied => Code::PermissionDenied, |
659 | | Self::ConnectionRefused | Self::ConnectionReset | Self::ConnectionAborted => { |
660 | 1 | Code::Unavailable |
661 | | } |
662 | 1 | Self::AlreadyExists => Code::AlreadyExists, |
663 | 0 | Self::InvalidInput | Self::InvalidData => Code::InvalidArgument, |
664 | 0 | Self::TimedOut => Code::DeadlineExceeded, |
665 | 0 | Self::Interrupted => Code::Aborted, |
666 | | Self::NotConnected |
667 | | | Self::AddrInUse |
668 | | | Self::AddrNotAvailable |
669 | | | Self::BrokenPipe |
670 | | | Self::WouldBlock |
671 | | | Self::WriteZero |
672 | | | Self::Other |
673 | 3 | | Self::UnexpectedEof => Code::Internal, |
674 | 3 | _ => Code::Unknown, |
675 | | } |
676 | 1.35k | } |
677 | | } |
678 | | |
679 | | // Serde definition for tonic::Code. See: https://serde.rs/remote-derive.html |
680 | | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] |
681 | | #[serde(remote = "Code")] |
682 | | pub enum CodeDef { |
683 | | Ok = 0, |
684 | | Cancelled = 1, |
685 | | Unknown = 2, |
686 | | InvalidArgument = 3, |
687 | | DeadlineExceeded = 4, |
688 | | NotFound = 5, |
689 | | AlreadyExists = 6, |
690 | | PermissionDenied = 7, |
691 | | ResourceExhausted = 8, |
692 | | FailedPrecondition = 9, |
693 | | Aborted = 10, |
694 | | OutOfRange = 11, |
695 | | Unimplemented = 12, |
696 | | Internal = 13, |
697 | | Unavailable = 14, |
698 | | DataLoss = 15, |
699 | | Unauthenticated = 16, |
700 | | // NOTE: Additional codes must be added to stores.rs in ErrorCodes and also |
701 | | // in both match statements in retry.rs. |
702 | | } |