Skip to main content

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