opentitanlib/proxy/
errors.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use erased_serde::Serialize;
6
7#[doc(hidden)]
8pub use inventory::submit;
9
10#[doc(hidden)]
11pub struct SerializableErrorRegistration {
12    pub try_convert: fn(anyhow::Error) -> Result<SerializedError, anyhow::Error>,
13}
14inventory::collect!(SerializableErrorRegistration);
15
16/// `SerializableError` is a trait which represents an error type that can
17/// be sent over the wire by the proxy protocol.
18#[typetag::serde(tag = "error_type")]
19pub trait SerializableError: Serialize + std::error::Error + std::fmt::Debug + Send + Sync {
20    fn as_anyhow_error(self: Box<Self>) -> anyhow::Error;
21}
22
23/// `impl_serializable_error` needs to be invoked for every error type
24/// that can be sent over the wire.
25#[macro_export]
26macro_rules! impl_serializable_error {
27    ($t:ty) => {
28        const _: () = {
29            #[typetag::serde]
30            impl $crate::proxy::errors::SerializableError for $t {
31                fn as_anyhow_error(self: Box<$t>) -> anyhow::Error {
32                    self.into()
33                }
34            }
35
36            $crate::proxy::errors::submit! {
37                $crate::proxy::errors::SerializableErrorRegistration {
38                    try_convert: |err| {
39                        if !err.is::<$t>() {
40                            return Err(err);
41                        }
42
43                        let description = err.to_string();
44                        let backtrace = format!("{:#?}", err.backtrace());
45                        let downcast = err.downcast::<$t>().unwrap();
46                        Ok($crate::proxy::errors::SerializedError {
47                            description,
48                            backtrace,
49                            error: Some(Box::new(downcast)),
50                        })
51                    }
52                }
53            };
54        };
55    };
56}
57
58/// `SerializedError` is the wire form of errors that can be sent through the proxy.
59#[derive(serde::Serialize, serde::Deserialize, Debug)]
60pub struct SerializedError {
61    pub description: String,
62    pub backtrace: String,
63    pub error: Option<Box<dyn SerializableError>>,
64}
65
66/// Converts a `SerializedError` back into a real error type.
67impl From<SerializedError> for anyhow::Error {
68    fn from(ser: SerializedError) -> anyhow::Error {
69        let error = if let Some(error) = ser.error {
70            error.as_anyhow_error()
71        } else {
72            anyhow::Error::msg(ser.description)
73        };
74        if ser.backtrace == "<disabled>" {
75            error
76        } else {
77            error.context(format!("Server Error.\nRemote {}", ser.backtrace))
78        }
79    }
80}
81
82/// Converts any error into a `SerializedError`.
83impl From<anyhow::Error> for SerializedError {
84    fn from(mut error: anyhow::Error) -> SerializedError {
85        for reg in inventory::iter::<SerializableErrorRegistration> {
86            match (reg.try_convert)(error) {
87                Ok(v) => return v,
88                Err(e) => error = e,
89            }
90        }
91
92        let description = error.to_string();
93        let backtrace = format!("{:#?}", error.backtrace());
94        SerializedError {
95            description,
96            backtrace,
97            error: None,
98        }
99    }
100}