conjure_error/lib.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
15//! Runtime support for Conjure error types.
16//!
17//! In a networked service, the error objects that are propagated through its codebase are responsible for two things:
18//!
19//! * Collecting useful information that a developer can use to diagnose whatever problem caused the error.
20//! * Controlling how the error is presented to the client.
21//!
22//! Services implemented using Conjure's frameworks use the [`Error`] type defined in this crate as the single error
23//! type throughout the codebase. [`Error`]s store:
24//!
25//! * Developer facing:
26//! * [`Error::cause`] - the underlying cause of the error. This can be a type implementing the Rust
27//! [`std::error::Error`] trait, or just a [`str`] or [`String`] containing a description of what happened. When
28//! an [`Error`] is logged, the cause (and its chain of sources via [`std::error::Error::source`]) are included as a
29//! parameter. The log-safety of that cause information is identified by the choice of constructor of the [`Error`].
30//! * [`Error::safe_params`] and [`Error::unsafe_params`] - key-value pairs that can be added to the error to provide
31//! context. When an [`Error`] is logged, these are included in the service log's parameters. When a service
32//! [`Error`] is created, all of the parameters of its associated Conjure error are automatically included as
33//! params, with [`ErrorType::safe_args`] used to partition the parameters between safe and unsafe. Additional
34//! params can be added via [`Error::with_safe_param`] and [`Error::with_unsafe_param`].
35//! * [`Error::backtraces`] - a sequence of backtraces to annotate the error with the state of the function call
36//! stack. A backtrace is automatically taken when the [`Error`] is created, and additional backtraces can be added
37//! with the [`Error::with_backtrace`] method. This can be used when, for example, an [`Error`] transfers from one
38//! thread to another. When an [`Error`] is logged, its backtraces will be included in the stacktrace field.
39//! * Client facing:
40//! * [`Error::kind`] - how the error should be reported to the client. There are currently three kinds:
41//! * [`ErrorKind::Service`] - a standard service error. These are constructed from a type implementing
42//! [`Serialize`] and [`ErrorType`]. The value is expected to serialize as a struct, with the struct's fields
43//! being the parameters of the error. Errors defined in Conjure APIs will generate types implementing these
44//! traits. This will generate an HTTP response following the [Conjure wire spec]. Service errors are created
45//! with the [`Error::service`], [`Error::service_safe`], [`Error::internal`], and [`Error::internal_safe`]
46//! functions.
47//! * [`ErrorKind::Throttle`] - an indication that the client is making too many requests and should throttle
48//! itself. This will generate a `429 Too Many Requests` HTTP response. Throttle errors are created with the
49//! [`Error::throttle`], [`Error::throttle_safe`], [`Error::throttle_for`], and [`Error::throttle_for_safe`]
50//! functions.
51//! * [`ErrorKind::Unavailable`] - an indication that the server is unable to handle the request. This will
52//! generate a `503 Service Unavailable` HTTP response. Unavailable errors are created with the
53//! [`Error::unavailable`] and [`Error::unavailable_safe`] functions.
54//!
55//! [Conjure wire spec]: https://github.com/palantir/conjure/blob/master/docs/spec/wire.md#34-conjure-errors
56//!
57//! ## Examples
58//!
59//! Mapping a [`std::error::Error`] returned by a stdlib API into a generic internal service error:
60//!
61//! ```rust,no_run
62//! use conjure_error::Error;
63//! use std::fs::File;
64//!
65//! # fn foo() -> Result<(), Error> {
66//! let file = File::open("var/data/database.csv").map_err(Error::internal_safe)?;
67//! # Ok(()) }
68//! ```
69//!
70//! Doing the same, but including the filename as an extra parameter:
71//!
72//! ```rust,no_run
73//! use conjure_error::Error;
74//! use std::fs::File;
75//!
76//! # fn foo() -> Result<(), Error> {
77//! let filename = "var/data/database.csv";
78//! let file = File::open(filename).map_err(|e| {
79//! Error::internal_safe(e).with_safe_param("filename", filename)
80//! })?;
81//! # Ok(()) }
82//! ```
83//!
84//! Returning a specific Conjure error when there is no existing error cause:
85//!
86//! ```yaml
87//! types:
88//! definitions:
89//! errors:
90//! ObjectNotFound:
91//! namespace: MyService
92//! code: INVALID_ARGUMENT
93//! safe-args:
94//! objectRid: rid
95//! ```
96//!
97//! ```rust,ignore
98//! use conjure_error::Error;
99//! use my_service_api::errors::ObjectNotFound;
100//!
101//! if !object_was_found {
102//! return Err(Error::service_safe("failed to find object", ObjectNotFound::new(object_rid)));
103//! }
104//! ```
105#![warn(clippy::all, missing_docs)]
106
107extern crate self as conjure_error;
108
109use conjure_object::Uuid;
110use serde::{Serialize, Serializer};
111
112use crate::ser::{ParametersSerializer, StringSeed};
113
114pub use crate::error::*;
115pub use crate::types::errors::*;
116pub use crate::types::objects::*;
117use serde::de::DeserializeSeed;
118
119mod error;
120mod ser;
121#[allow(clippy::all, missing_docs)]
122#[rustfmt::skip]
123mod types;
124
125impl ErrorCode {
126 /// Returns the HTTP status code associated with the error code.
127 #[inline]
128 pub fn status_code(&self) -> u16 {
129 match self {
130 ErrorCode::PermissionDenied => 403,
131 ErrorCode::InvalidArgument => 400,
132 ErrorCode::NotFound => 404,
133 ErrorCode::Conflict => 409,
134 ErrorCode::RequestEntityTooLarge => 413,
135 ErrorCode::FailedPrecondition => 500,
136 ErrorCode::Internal => 500,
137 ErrorCode::Timeout => 500,
138 ErrorCode::CustomClient => 400,
139 ErrorCode::CustomServer => 500,
140 }
141 }
142}
143
144/// A trait implemented by Conjure error types.
145pub trait ErrorType {
146 /// Returns the error's code.
147 fn code() -> ErrorCode;
148
149 /// Returns the error's name.
150 ///
151 /// The name must be formatted like `NamespaceName:ErrorName`.
152 fn name() -> &'static str;
153
154 /// Returns a sorted slice of the names of the error's safe parameters.
155 fn safe_args() -> &'static [&'static str];
156
157 /// Returns the error's instance ID, if it stores one.
158 ///
159 /// The default implementation returns `None`.
160 #[inline]
161 fn instance_id(&self) -> Option<Uuid> {
162 None
163 }
164
165 /// Wraps the error in another that overrides its instance ID.
166 #[inline]
167 fn with_instance_id(self, instance_id: Uuid) -> WithInstanceId<Self>
168 where
169 Self: Sized,
170 {
171 WithInstanceId {
172 error: self,
173 instance_id,
174 }
175 }
176}
177
178impl<T> ErrorType for &T
179where
180 T: ?Sized + ErrorType,
181{
182 #[inline]
183 fn code() -> ErrorCode {
184 T::code()
185 }
186
187 #[inline]
188 fn name() -> &'static str {
189 T::name()
190 }
191
192 #[inline]
193 fn instance_id(&self) -> Option<Uuid> {
194 (**self).instance_id()
195 }
196
197 #[inline]
198 fn safe_args() -> &'static [&'static str] {
199 T::safe_args()
200 }
201}
202
203/// An `ErrorType` which wraps another and overrides its instance ID.
204pub struct WithInstanceId<T> {
205 error: T,
206 instance_id: Uuid,
207}
208
209impl<T> ErrorType for WithInstanceId<T>
210where
211 T: ErrorType,
212{
213 fn code() -> ErrorCode {
214 T::code()
215 }
216
217 fn name() -> &'static str {
218 T::name()
219 }
220
221 fn safe_args() -> &'static [&'static str] {
222 T::safe_args()
223 }
224
225 fn instance_id(&self) -> Option<Uuid> {
226 Some(self.instance_id)
227 }
228}
229
230impl<T> Serialize for WithInstanceId<T>
231where
232 T: Serialize,
233{
234 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
235 where
236 S: Serializer,
237 {
238 self.error.serialize(s)
239 }
240}
241
242/// Encodes a Conjure error into its serialized form.
243///
244/// The error's instance ID will be randomly generated.
245///
246/// # Panics
247///
248/// Panics if the error type does not serialize as a struct.
249pub fn encode<T>(error: &T) -> SerializableError
250where
251 T: ErrorType + Serialize,
252{
253 let parameters = error
254 .serialize(ParametersSerializer)
255 .expect("failed to serialize error parameters");
256
257 SerializableError::builder()
258 .error_code(T::code())
259 .error_name(T::name())
260 .error_instance_id(error.instance_id().unwrap_or_else(Uuid::new_v4))
261 .parameters(parameters)
262 .build()
263}
264
265/// Re-serializes the parameters of a [`SerializableError`] in the legacy stringified format.
266///
267/// Scalar parameters will be converted to their string representations and composite parameters
268/// will be dropped.
269pub fn stringify_parameters(error: SerializableError) -> SerializableError {
270 let mut stringified_parameters = vec![];
271
272 for (key, value) in error.parameters() {
273 if let Ok(value) = StringSeed.deserialize(value.clone()) {
274 stringified_parameters.push((key.clone(), value));
275 }
276 }
277
278 serializable_error::Builder::from(error)
279 .parameters(stringified_parameters)
280 .build()
281}