blob: d6ae0ce4f3c0ceadb2fcfe14eac9faca8fe9365d [file] [log] [blame]
Janis Danisevskis7d77a762020-07-20 13:03:31 -07001// Copyright 2020, The Android Open Source Project
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//! Keystore error provides convenience methods and types for Keystore error handling.
16//! Clients of Keystore expect one of two error codes, i.e., a Keystore ResponseCode as
17//! defined by the Keystore AIDL interface, or a Keymint ErrorCode as defined by
18//! the Keymint HAL specification.
19//! This crate provides `Error` which can wrap both. It is to be used
20//! internally by Keystore to diagnose error conditions that need to be reported to
21//! the client. To report the error condition to the client the Keystore AIDL
22//! interface defines a wire type `Result` which is distinctly different from Rust's
23//! `enum Result<T,E>`.
24//!
25//! This crate provides the convenience method `map_or_log_err` to convert `anyhow::Error`
26//! into this wire type. In addition to handling the conversion of `Error`
27//! to the `Result` wire type it handles any other error by mapping it to
Janis Danisevskisc5b210b2020-09-11 13:27:37 -070028//! `ResponseCode::SYSTEM_ERROR` and logs any error condition.
Janis Danisevskis7d77a762020-07-20 13:03:31 -070029//!
30//! Keystore functions should use `anyhow::Result` to return error conditions, and
31//! context should be added every time an error is forwarded.
32
Shawn Willden708744a2020-12-11 13:05:27 +000033pub use android_hardware_security_keymint::aidl::android::hardware::security::keymint::ErrorCode::ErrorCode;
Janis Danisevskisc5b210b2020-09-11 13:27:37 -070034pub use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
Janis Danisevskisc5b210b2020-09-11 13:27:37 -070035use android_system_keystore2::binder::{
Janis Danisevskisba998992020-12-29 16:08:40 -080036 ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode,
Janis Danisevskis017d2092020-09-02 10:15:52 -070037};
Janis Danisevskis2ee014b2021-05-05 14:29:08 -070038use keystore2_selinux as selinux;
39use std::cmp::PartialEq;
Janis Danisevskisea03cff2021-12-16 08:10:17 -080040use std::ffi::CString;
Janis Danisevskis7d77a762020-07-20 13:03:31 -070041
42/// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated
43/// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant.
Chris Wailes263de9f2022-08-11 15:00:51 -070044#[derive(Debug, thiserror::Error, PartialEq, Eq)]
Janis Danisevskis7d77a762020-07-20 13:03:31 -070045pub enum Error {
46 /// Wraps a Keystore `ResponseCode` as defined by the Keystore AIDL interface specification.
47 #[error("Error::Rc({0:?})")]
Janis Danisevskise24f3472020-08-12 17:58:49 -070048 Rc(ResponseCode),
Janis Danisevskis7d77a762020-07-20 13:03:31 -070049 /// Wraps a Keymint `ErrorCode` as defined by the Keymint AIDL interface specification.
50 #[error("Error::Km({0:?})")]
Janis Danisevskise24f3472020-08-12 17:58:49 -070051 Km(ErrorCode),
Janis Danisevskis017d2092020-09-02 10:15:52 -070052 /// Wraps a Binder exception code other than a service specific exception.
53 #[error("Binder exception code {0:?}, {1:?}")]
54 Binder(ExceptionCode, i32),
Janis Danisevskisba998992020-12-29 16:08:40 -080055 /// Wraps a Binder status code.
56 #[error("Binder transaction error {0:?}")]
57 BinderTransaction(StatusCode),
Janis Danisevskis7d77a762020-07-20 13:03:31 -070058}
59
60impl Error {
Janis Danisevskisc5b210b2020-09-11 13:27:37 -070061 /// Short hand for `Error::Rc(ResponseCode::SYSTEM_ERROR)`
Janis Danisevskis7d77a762020-07-20 13:03:31 -070062 pub fn sys() -> Self {
Janis Danisevskisc5b210b2020-09-11 13:27:37 -070063 Error::Rc(ResponseCode::SYSTEM_ERROR)
Janis Danisevskis7d77a762020-07-20 13:03:31 -070064 }
65
Seth Moore7ee79f92021-12-07 11:42:49 -080066 /// Short hand for `Error::Rc(ResponseCode::PERMISSION_DENIED)`
Janis Danisevskis7d77a762020-07-20 13:03:31 -070067 pub fn perm() -> Self {
Janis Danisevskisc5b210b2020-09-11 13:27:37 -070068 Error::Rc(ResponseCode::PERMISSION_DENIED)
Janis Danisevskis7d77a762020-07-20 13:03:31 -070069 }
70}
71
Janis Danisevskis017d2092020-09-02 10:15:52 -070072/// Helper function to map the binder status we get from calls into KeyMint
73/// to a Keystore Error. We don't create an anyhow error here to make
74/// it easier to evaluate KeyMint errors, which we must do in some cases, e.g.,
75/// when diagnosing authentication requirements, update requirements, and running
76/// out of operation slots.
77pub fn map_km_error<T>(r: BinderResult<T>) -> Result<T, Error> {
78 r.map_err(|s| {
79 match s.exception_code() {
80 ExceptionCode::SERVICE_SPECIFIC => {
81 let se = s.service_specific_error();
82 if se < 0 {
83 // Negative service specific errors are KM error codes.
Janis Danisevskisc5b210b2020-09-11 13:27:37 -070084 Error::Km(ErrorCode(s.service_specific_error()))
Janis Danisevskis017d2092020-09-02 10:15:52 -070085 } else {
86 // Non negative error codes cannot be KM error codes.
87 // So we create an `Error::Binder` variant to preserve
88 // the service specific error code for logging.
89 // `map_or_log_err` will map this on a system error,
90 // but not before logging the details to logcat.
91 Error::Binder(ExceptionCode::SERVICE_SPECIFIC, se)
92 }
93 }
94 // We create `Error::Binder` to preserve the exception code
95 // for logging.
96 // `map_or_log_err` will map this on a system error.
97 e_code => Error::Binder(e_code, 0),
98 }
99 })
100}
101
Janis Danisevskisba998992020-12-29 16:08:40 -0800102/// This function is similar to map_km_error only that we don't expect
103/// any KeyMint error codes, we simply preserve the exception code and optional
104/// service specific exception.
105pub fn map_binder_status<T>(r: BinderResult<T>) -> Result<T, Error> {
106 r.map_err(|s| match s.exception_code() {
107 ExceptionCode::SERVICE_SPECIFIC => {
108 let se = s.service_specific_error();
109 Error::Binder(ExceptionCode::SERVICE_SPECIFIC, se)
110 }
111 ExceptionCode::TRANSACTION_FAILED => {
112 let e = s.transaction_error();
113 Error::BinderTransaction(e)
114 }
115 e_code => Error::Binder(e_code, 0),
116 })
117}
118
119/// This function maps a status code onto a Keystore Error.
120pub fn map_binder_status_code<T>(r: Result<T, StatusCode>) -> Result<T, Error> {
121 r.map_err(Error::BinderTransaction)
122}
123
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700124/// This function should be used by Keystore service calls to translate error conditions
Janis Danisevskis8ea5f552020-11-20 11:22:59 -0800125/// into service specific exceptions.
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700126///
Hasini Gunasinghee1d1bbd2021-04-20 18:13:25 +0000127/// All error conditions get logged by this function, except for KEY_NOT_FOUND error.
Janis Danisevskis8ea5f552020-11-20 11:22:59 -0800128///
129/// All `Error::Rc(x)` and `Error::Km(x)` variants get mapped onto a service specific error
130/// code of x. This is possible because KeyMint `ErrorCode` errors are always negative and
131/// `ResponseCode` codes are always positive.
132/// `selinux::Error::PermissionDenied` is mapped on `ResponseCode::PERMISSION_DENIED`.
133///
134/// All non `Error` error conditions and the Error::Binder variant get mapped onto
135/// ResponseCode::SYSTEM_ERROR`.
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700136///
137/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
Janis Danisevskis8ea5f552020-11-20 11:22:59 -0800138/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
139/// typically returns Ok(value).
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700140///
141/// # Examples
142///
143/// ```
Janis Danisevskis8ea5f552020-11-20 11:22:59 -0800144/// fn loadKey() -> anyhow::Result<Vec<u8>> {
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700145/// if (good_but_auth_required) {
Janis Danisevskis8ea5f552020-11-20 11:22:59 -0800146/// Ok(vec!['k', 'e', 'y'])
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700147/// } else {
Janis Danisevskis8ea5f552020-11-20 11:22:59 -0800148/// Err(anyhow!(Error::Rc(ResponseCode::KEY_NOT_FOUND)))
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700149/// }
150/// }
151///
Janis Danisevskis8ea5f552020-11-20 11:22:59 -0800152/// map_or_log_err(loadKey(), Ok)
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700153/// ```
Janis Danisevskise24f3472020-08-12 17:58:49 -0700154pub fn map_or_log_err<T, U, F>(result: anyhow::Result<U>, handle_ok: F) -> BinderResult<T>
155where
156 F: FnOnce(U) -> BinderResult<T>,
157{
Janis Danisevskis778245c2021-03-04 15:40:23 -0800158 map_err_with(
159 result,
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700160 |e| {
Hasini Gunasinghee1d1bbd2021-04-20 18:13:25 +0000161 // Make the key not found errors silent.
162 if !matches!(
163 e.root_cause().downcast_ref::<Error>(),
164 Some(Error::Rc(ResponseCode::KEY_NOT_FOUND))
165 ) {
166 log::error!("{:?}", e);
167 }
Janis Danisevskis778245c2021-03-04 15:40:23 -0800168 e
169 },
170 handle_ok,
171 )
172}
173
Janis Danisevskisea03cff2021-12-16 08:10:17 -0800174/// This function turns an anyhow error into an optional CString.
175/// This is especially useful to add a message string to a service specific error.
176/// If the formatted string was not convertible because it contained a nul byte,
177/// None is returned and a warning is logged.
178pub fn anyhow_error_to_cstring(e: &anyhow::Error) -> Option<CString> {
179 match CString::new(format!("{:?}", e)) {
180 Ok(msg) => Some(msg),
181 Err(_) => {
182 log::warn!("Cannot convert error message to CStr. It contained a nul byte.");
183 None
184 }
185 }
186}
187
Janis Danisevskis778245c2021-03-04 15:40:23 -0800188/// This function behaves similar to map_or_log_error, but it does not log the errors, instead
189/// it calls map_err on the error before mapping it to a binder result allowing callers to
190/// log or transform the error before mapping it.
191pub fn map_err_with<T, U, F1, F2>(
192 result: anyhow::Result<U>,
193 map_err: F1,
194 handle_ok: F2,
195) -> BinderResult<T>
196where
197 F1: FnOnce(anyhow::Error) -> anyhow::Error,
198 F2: FnOnce(U) -> BinderResult<T>,
199{
200 result.map_or_else(
201 |e| {
202 let e = map_err(e);
Hasini Gunasingheb7142972021-02-20 03:11:27 +0000203 let rc = get_error_code(&e);
Janis Danisevskisea03cff2021-12-16 08:10:17 -0800204 Err(BinderStatus::new_service_specific_error(
205 rc,
206 anyhow_error_to_cstring(&e).as_deref(),
207 ))
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700208 },
209 handle_ok,
210 )
211}
212
Hasini Gunasingheb7142972021-02-20 03:11:27 +0000213/// Returns the error code given a reference to the error
214pub fn get_error_code(e: &anyhow::Error) -> i32 {
215 let root_cause = e.root_cause();
216 match root_cause.downcast_ref::<Error>() {
217 Some(Error::Rc(rcode)) => rcode.0,
218 Some(Error::Km(ec)) => ec.0,
Hasini Gunasingheb7142972021-02-20 03:11:27 +0000219 // If an Error::Binder reaches this stage we report a system error.
220 // The exception code and possible service specific error will be
221 // printed in the error log above.
222 Some(Error::Binder(_, _)) | Some(Error::BinderTransaction(_)) => {
223 ResponseCode::SYSTEM_ERROR.0
224 }
225 None => match root_cause.downcast_ref::<selinux::Error>() {
226 Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0,
227 _ => ResponseCode::SYSTEM_ERROR.0,
228 },
229 }
230}
231
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700232#[cfg(test)]
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000233pub mod tests {
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700234
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700235 use super::*;
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700236 use android_system_keystore2::binder::{
Janis Danisevskis017d2092020-09-02 10:15:52 -0700237 ExceptionCode, Result as BinderResult, Status as BinderStatus,
238 };
Janis Danisevskise24f3472020-08-12 17:58:49 -0700239 use anyhow::{anyhow, Context};
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700240
Janis Danisevskise24f3472020-08-12 17:58:49 -0700241 fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700242 Err(anyhow!(Error::Rc(rc))).context("nested nested rc")
243 }
244
Janis Danisevskise24f3472020-08-12 17:58:49 -0700245 fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700246 nested_nested_rc(rc).context("nested rc")
247 }
248
249 fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
250 Err(anyhow!(Error::Km(ec))).context("nested nested ec")
251 }
252
253 fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
254 nested_nested_ec(ec).context("nested ec")
255 }
256
Janis Danisevskise24f3472020-08-12 17:58:49 -0700257 fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700258 Ok(rc)
259 }
260
Janis Danisevskise24f3472020-08-12 17:58:49 -0700261 fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700262 nested_nested_ok(rc).context("nested ok")
263 }
264
Janis Danisevskisce995432020-07-21 12:22:34 -0700265 fn nested_nested_selinux_perm() -> anyhow::Result<()> {
266 Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied")
267 }
268
269 fn nested_selinux_perm() -> anyhow::Result<()> {
270 nested_nested_selinux_perm().context("nested selinux permission denied")
271 }
272
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700273 #[derive(Debug, thiserror::Error)]
274 enum TestError {
275 #[error("TestError::Fail")]
276 Fail = 0,
277 }
278
279 fn nested_nested_other_error() -> anyhow::Result<()> {
280 Err(anyhow!(TestError::Fail)).context("nested nested other error")
281 }
282
283 fn nested_other_error() -> anyhow::Result<()> {
284 nested_nested_other_error().context("nested other error")
285 }
286
Janis Danisevskis017d2092020-09-02 10:15:52 -0700287 fn binder_sse_error(sse: i32) -> BinderResult<()> {
288 Err(BinderStatus::new_service_specific_error(sse, None))
289 }
290
291 fn binder_exception(ex: ExceptionCode) -> BinderResult<()> {
292 Err(BinderStatus::new_exception(ex, None))
293 }
294
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700295 #[test]
296 fn keystore_error_test() -> anyhow::Result<(), String> {
297 android_logger::init_once(
298 android_logger::Config::default()
299 .with_tag("keystore_error_tests")
300 .with_min_level(log::Level::Debug),
301 );
Janis Danisevskise24f3472020-08-12 17:58:49 -0700302 // All Error::Rc(x) get mapped on a service specific error
303 // code of x.
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700304 for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 {
Janis Danisevskise24f3472020-08-12 17:58:49 -0700305 assert_eq!(
306 Result::<(), i32>::Err(rc),
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700307 map_or_log_err(nested_rc(ResponseCode(rc)), |_| Err(BinderStatus::ok()))
Janis Danisevskise24f3472020-08-12 17:58:49 -0700308 .map_err(|s| s.service_specific_error())
309 );
310 }
311
Janis Danisevskis017d2092020-09-02 10:15:52 -0700312 // All Keystore Error::Km(x) get mapped on a service
Janis Danisevskise24f3472020-08-12 17:58:49 -0700313 // specific error of x.
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700314 for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
Janis Danisevskise24f3472020-08-12 17:58:49 -0700315 assert_eq!(
316 Result::<(), i32>::Err(ec),
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700317 map_or_log_err(nested_ec(ErrorCode(ec)), |_| Err(BinderStatus::ok()))
Janis Danisevskise24f3472020-08-12 17:58:49 -0700318 .map_err(|s| s.service_specific_error())
319 );
320 }
321
Janis Danisevskis017d2092020-09-02 10:15:52 -0700322 // All Keymint errors x received through a Binder Result get mapped on
323 // a service specific error of x.
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700324 for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
Janis Danisevskis017d2092020-09-02 10:15:52 -0700325 assert_eq!(
326 Result::<(), i32>::Err(ec),
327 map_or_log_err(
328 map_km_error(binder_sse_error(ec))
329 .with_context(|| format!("Km error code: {}.", ec)),
330 |_| Err(BinderStatus::ok())
331 )
332 .map_err(|s| s.service_specific_error())
333 );
334 }
335
336 // map_km_error creates an Error::Binder variant storing
337 // ExceptionCode::SERVICE_SPECIFIC and the given
338 // service specific error.
339 let sse = map_km_error(binder_sse_error(1));
340 assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse);
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700341 // map_or_log_err then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
Janis Danisevskis017d2092020-09-02 10:15:52 -0700342 assert_eq!(
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700343 Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
Janis Danisevskis017d2092020-09-02 10:15:52 -0700344 map_or_log_err(sse.context("Non negative service specific error."), |_| Err(
345 BinderStatus::ok()
346 ))
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700347 .map_err(|s| ResponseCode(s.service_specific_error()))
Janis Danisevskis017d2092020-09-02 10:15:52 -0700348 );
349
350 // map_km_error creates a Error::Binder variant storing the given exception code.
351 let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED));
352 assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception);
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700353 // map_or_log_err then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
Janis Danisevskis017d2092020-09-02 10:15:52 -0700354 assert_eq!(
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700355 Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
Janis Danisevskis017d2092020-09-02 10:15:52 -0700356 map_or_log_err(binder_exception.context("Binder Exception."), |_| Err(
357 BinderStatus::ok()
358 ))
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700359 .map_err(|s| ResponseCode(s.service_specific_error()))
Janis Danisevskis017d2092020-09-02 10:15:52 -0700360 );
361
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700362 // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700363 assert_eq!(
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700364 Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED),
Janis Danisevskise24f3472020-08-12 17:58:49 -0700365 map_or_log_err(nested_selinux_perm(), |_| Err(BinderStatus::ok()))
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700366 .map_err(|s| ResponseCode(s.service_specific_error()))
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700367 );
368
Janis Danisevskise24f3472020-08-12 17:58:49 -0700369 // All other errors get mapped on System Error.
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700370 assert_eq!(
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700371 Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
Janis Danisevskise24f3472020-08-12 17:58:49 -0700372 map_or_log_err(nested_other_error(), |_| Err(BinderStatus::ok()))
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700373 .map_err(|s| ResponseCode(s.service_specific_error()))
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700374 );
375
376 // Result::Ok variants get passed to the ok handler.
Janis Danisevskisc5b210b2020-09-11 13:27:37 -0700377 assert_eq!(Ok(ResponseCode::LOCKED), map_or_log_err(nested_ok(ResponseCode::LOCKED), Ok));
378 assert_eq!(
379 Ok(ResponseCode::SYSTEM_ERROR),
380 map_or_log_err(nested_ok(ResponseCode::SYSTEM_ERROR), Ok)
381 );
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700382
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700383 Ok(())
384 }
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000385
386 //Helper function to test whether error cases are handled as expected.
Janis Danisevskise24f3472020-08-12 17:58:49 -0700387 pub fn check_result_contains_error_string<T>(
388 result: anyhow::Result<T>,
389 expected_error_string: &str,
390 ) {
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000391 let error_str = format!(
392 "{:#?}",
393 result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string))
394 );
395 assert!(
396 error_str.contains(expected_error_string),
397 "The string \"{}\" should contain \"{}\"",
398 error_str,
399 expected_error_string
400 );
401 }
Janis Danisevskis7d77a762020-07-20 13:03:31 -0700402} // mod tests