conjure_error/
error.rs

1// Copyright 2019 Palantir Technologies, Inc.
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
15use conjure_object::Any;
16use serde::Serialize;
17use std::borrow::Cow;
18use std::collections::hash_map::{self, HashMap};
19use std::fmt;
20use std::ops::Index;
21use std::time::Duration;
22use std::{backtrace, error};
23
24use crate::{ErrorType, Internal, SerializableError};
25
26/// Information about a throttle error.
27#[derive(Debug)]
28pub struct ThrottleError {
29    duration: Option<Duration>,
30}
31
32impl ThrottleError {
33    /// Returns the amount of time the client should wait before retrying, if provided.
34    #[inline]
35    pub fn duration(&self) -> Option<Duration> {
36        self.duration
37    }
38}
39
40/// Information about an unavailable error.
41#[derive(Debug)]
42pub struct UnavailableError(());
43
44/// Information about the specific type of an `Error`.
45#[derive(Debug)]
46#[non_exhaustive]
47pub enum ErrorKind {
48    /// A general service error.
49    Service(SerializableError),
50    /// A QoS error indicating that the client should throttle itself.
51    Throttle(ThrottleError),
52    /// A QoS error indicating that the server was unable to handle the request.
53    Unavailable(UnavailableError),
54}
55
56#[derive(Debug)]
57struct Inner {
58    cause: Box<dyn error::Error + Sync + Send>,
59    cause_safe: bool,
60    kind: ErrorKind,
61    safe_params: HashMap<Cow<'static, str>, Any>,
62    unsafe_params: HashMap<Cow<'static, str>, Any>,
63    backtraces: Vec<Backtrace>,
64}
65
66/// A standard error type for network services.
67///
68/// An error consists of several components:
69///
70/// * The cause of the error, represented as a type implementing the Rust `Error` trait. The cause can either be
71///   declared safe or unsafe to log.
72/// * The error's kind, indicating how the service should handle the error e.g. in a response to a client.
73/// * Backtraces, including one taken at the time the error was created.
74/// * Parameters adding extra context about the error. They can be declared either safe or unsafe to log.
75///
76/// Note that this type does *not* implement the standard library's `Error` trait.
77#[derive(Debug)]
78pub struct Error(Box<Inner>);
79
80impl Error {
81    /// Creates a service error with an unsafe cause.
82    pub fn service<E, T>(cause: E, error_type: T) -> Error
83    where
84        E: Into<Box<dyn error::Error + Sync + Send>>,
85        T: ErrorType + Serialize,
86    {
87        Error::service_inner(
88            cause.into(),
89            false,
90            crate::encode(&error_type),
91            T::safe_args(),
92        )
93    }
94
95    /// Creates a service error with a safe cause.
96    pub fn service_safe<E, T>(cause: E, error_type: T) -> Error
97    where
98        E: Into<Box<dyn error::Error + Sync + Send>>,
99        T: ErrorType + Serialize,
100    {
101        Error::service_inner(
102            cause.into(),
103            true,
104            crate::encode(&error_type),
105            T::safe_args(),
106        )
107    }
108
109    /// Creates a service error from a propagated error description and an unsafe cause.
110    pub fn propagated_service<E>(cause: E, error: SerializableError) -> Error
111    where
112        E: Into<Box<dyn error::Error + Sync + Send>>,
113    {
114        Error::service_inner(cause.into(), false, error, &[])
115    }
116
117    /// Creates a service error from a propagated error description and a safe cause.
118    pub fn propagated_service_safe<E>(cause: E, error: SerializableError) -> Error
119    where
120        E: Into<Box<dyn error::Error + Sync + Send>>,
121    {
122        Error::service_inner(cause.into(), true, error, &[])
123    }
124
125    fn service_inner(
126        cause: Box<dyn error::Error + Sync + Send>,
127        cause_safe: bool,
128        error: SerializableError,
129        safe_args: &[&str],
130    ) -> Error {
131        let mut safe_params = HashMap::new();
132        let mut unsafe_params = HashMap::new();
133
134        for (key, value) in error.parameters() {
135            let key = Cow::Owned(key.clone());
136            let value = Any::new(value).unwrap();
137            if safe_args.contains(&&*key) {
138                safe_params.insert(key, value);
139            } else {
140                unsafe_params.insert(key, value);
141            }
142        }
143
144        let mut error = Error::new(cause, cause_safe, ErrorKind::Service(error));
145        error.0.safe_params = safe_params;
146        error.0.unsafe_params = unsafe_params;
147        error
148    }
149
150    /// Creates an error indicating that the client should throttle itself with an unsafe cause.
151    pub fn throttle<E>(cause: E) -> Error
152    where
153        E: Into<Box<dyn error::Error + Sync + Send>>,
154    {
155        Error::new(
156            cause.into(),
157            false,
158            ErrorKind::Throttle(ThrottleError { duration: None }),
159        )
160    }
161
162    /// Creates an error indicating that the client should throttle itself with a safe cause.
163    pub fn throttle_safe<E>(cause: E) -> Error
164    where
165        E: Into<Box<dyn error::Error + Sync + Send>>,
166    {
167        Error::new(
168            cause.into(),
169            true,
170            ErrorKind::Throttle(ThrottleError { duration: None }),
171        )
172    }
173
174    /// Creates an error indicating that the client should throttle itself for a specific duration with an unsafe
175    /// cause.
176    pub fn throttle_for<E>(cause: E, duration: Duration) -> Error
177    where
178        E: Into<Box<dyn error::Error + Sync + Send>>,
179    {
180        Error::new(
181            cause.into(),
182            false,
183            ErrorKind::Throttle(ThrottleError {
184                duration: Some(duration),
185            }),
186        )
187    }
188
189    /// Creates an error indicating that the client should throttle itself for a specific duration with a safe
190    /// cause.
191    pub fn throttle_for_safe<E>(cause: E, duration: Duration) -> Error
192    where
193        E: Into<Box<dyn error::Error + Sync + Send>>,
194    {
195        Error::new(
196            cause.into(),
197            true,
198            ErrorKind::Throttle(ThrottleError {
199                duration: Some(duration),
200            }),
201        )
202    }
203
204    /// Creates an error indicating that the server was unable to serve the client's request with an unsafe cause.
205    pub fn unavailable<E>(cause: E) -> Error
206    where
207        E: Into<Box<dyn error::Error + Sync + Send>>,
208    {
209        Error::new(
210            cause.into(),
211            false,
212            ErrorKind::Unavailable(UnavailableError(())),
213        )
214    }
215
216    /// Creates an error indicating that the server was unable to serve the client's request with a safe cause.
217    pub fn unavailable_safe<E>(cause: E) -> Error
218    where
219        E: Into<Box<dyn error::Error + Sync + Send>>,
220    {
221        Error::new(
222            cause.into(),
223            true,
224            ErrorKind::Unavailable(UnavailableError(())),
225        )
226    }
227
228    /// A convenience function to construct an internal service error with an unsafe cause.
229    pub fn internal<E>(cause: E) -> Error
230    where
231        E: Into<Box<dyn error::Error + Sync + Send>>,
232    {
233        Error::service(cause, Internal::new())
234    }
235
236    /// A convenience function to construct an internal service error with a safe cause.
237    pub fn internal_safe<E>(cause: E) -> Error
238    where
239        E: Into<Box<dyn error::Error + Sync + Send>>,
240    {
241        Error::service_safe(cause, Internal::new())
242    }
243
244    fn new(cause: Box<dyn error::Error + Sync + Send>, cause_safe: bool, kind: ErrorKind) -> Error {
245        let inner = Inner {
246            cause,
247            cause_safe,
248            kind,
249            safe_params: HashMap::new(),
250            unsafe_params: HashMap::new(),
251            backtraces: vec![],
252        };
253        Error(Box::new(inner)).with_backtrace()
254    }
255
256    /// Returns the error's cause.
257    ///
258    /// Use the `cause_safe` method to determine if the error is safe or not.
259    #[inline]
260    pub fn cause(&self) -> &(dyn error::Error + 'static + Sync + Send) {
261        &*self.0.cause
262    }
263
264    /// Returns whether or not the error's cause is considered safe.
265    #[inline]
266    pub fn cause_safe(&self) -> bool {
267        self.0.cause_safe
268    }
269
270    /// Returns kind-specific error information.
271    #[inline]
272    pub fn kind(&self) -> &ErrorKind {
273        &self.0.kind
274    }
275
276    /// Adds a new safe parameter to the error.
277    ///
278    /// # Panics
279    ///
280    /// Panics if the value fails to serialize.
281    pub fn with_safe_param<T>(mut self, key: &'static str, value: T) -> Error
282    where
283        T: Serialize,
284    {
285        let value = Any::new(value).expect("value failed to serialize");
286        self.0.safe_params.insert(Cow::Borrowed(key), value);
287        self
288    }
289
290    /// Adds a new unsafe parameter to the error.
291    ///
292    /// # Panics
293    ///
294    /// Panics if the value fails to serialize.
295    pub fn with_unsafe_param<T>(mut self, key: &'static str, value: T) -> Error
296    where
297        T: Serialize,
298    {
299        let value = Any::new(value).expect("value failed to serialize");
300        self.0.unsafe_params.insert(Cow::Borrowed(key), value);
301        self
302    }
303
304    /// Returns the error's safe parameters.
305    #[inline]
306    pub fn safe_params(&self) -> Params<'_> {
307        Params(&self.0.safe_params)
308    }
309
310    /// Returns the error's unsafe parameters.
311    #[inline]
312    pub fn unsafe_params(&self) -> Params<'_> {
313        Params(&self.0.unsafe_params)
314    }
315
316    /// Adds a new backtrace to the error.
317    ///
318    /// An error always takes a backtrace at the time of its construction, but this method can be used to add extra
319    /// backtraces to it. For example, this might be used when transferring an error from one thread to another.
320    #[inline]
321    pub fn with_backtrace(mut self) -> Error {
322        self.0.backtraces.push(Backtrace::new());
323        self
324    }
325
326    /// Adds a new custom backtrace to the error which is safe to log
327    #[inline]
328    pub fn with_custom_safe_backtrace(mut self, backtrace: String) -> Error {
329        self.0.backtraces.push(Backtrace::custom(backtrace));
330        self
331    }
332
333    /// Returns the error's backtraces, ordered from oldest to newest.
334    #[inline]
335    pub fn backtraces(&self) -> &[Backtrace] {
336        &self.0.backtraces
337    }
338}
339
340/// A collection of error parameters, either safe or unsafe.
341#[derive(Debug)]
342pub struct Params<'a>(&'a HashMap<Cow<'static, str>, Any>);
343
344impl<'a> Params<'a> {
345    /// Returns an iterator over the key-value parameter pairs.
346    #[inline]
347    pub fn iter(&self) -> ParamsIter<'a> {
348        ParamsIter(self.0.iter())
349    }
350
351    /// Returns the number of parameters.
352    #[inline]
353    pub fn len(&self) -> usize {
354        self.0.len()
355    }
356
357    /// Determines if there are no parameters.
358    #[inline]
359    pub fn is_empty(&self) -> bool {
360        self.0.is_empty()
361    }
362}
363
364impl Index<&str> for Params<'_> {
365    type Output = Any;
366
367    #[inline]
368    fn index(&self, key: &str) -> &Any {
369        &self.0[key]
370    }
371}
372
373impl<'a> IntoIterator for &Params<'a> {
374    type Item = (&'a str, &'a Any);
375    type IntoIter = ParamsIter<'a>;
376
377    #[inline]
378    fn into_iter(self) -> ParamsIter<'a> {
379        self.iter()
380    }
381}
382
383/// An iterator over the parameters of an error.
384pub struct ParamsIter<'a>(hash_map::Iter<'a, Cow<'static, str>, Any>);
385
386impl<'a> Iterator for ParamsIter<'a> {
387    type Item = (&'a str, &'a Any);
388
389    #[inline]
390    fn next(&mut self) -> Option<(&'a str, &'a Any)> {
391        self.0.next().map(|(a, b)| (&**a, b))
392    }
393
394    #[inline]
395    fn size_hint(&self) -> (usize, Option<usize>) {
396        self.0.size_hint()
397    }
398}
399
400/// A backtrace associated with an `Error`.
401pub struct Backtrace(BacktraceInner);
402
403impl fmt::Debug for Backtrace {
404    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
405        match &self.0 {
406            BacktraceInner::Rust(b) => fmt::Display::fmt(b, fmt),
407            BacktraceInner::Custom(b) => fmt::Display::fmt(b, fmt),
408        }
409    }
410}
411
412impl Backtrace {
413    #[inline]
414    fn new() -> Backtrace {
415        Backtrace(BacktraceInner::Rust(backtrace::Backtrace::force_capture()))
416    }
417
418    #[inline]
419    fn custom(s: String) -> Backtrace {
420        Backtrace(BacktraceInner::Custom(s))
421    }
422}
423
424enum BacktraceInner {
425    Rust(backtrace::Backtrace),
426    Custom(String),
427}