Skip to main content

candid/
error.rs

1//! `candid::Result<T> = Result<T, candid::Error>>`
2
3use serde::{de, ser};
4use std::collections::TryReserveError;
5use std::{io, num::ParseIntError};
6use thiserror::Error;
7
8pub type Result<T = ()> = std::result::Result<T, Error>;
9
10#[derive(Debug)]
11pub struct Label {
12    pos: usize,
13    message: String,
14}
15
16#[derive(Debug, Error)]
17pub enum Error {
18    #[error("binary parser error: {}", .0.first().map_or_else(|| "io error".to_string(), |f| format!("{} at byte offset {}", f.message, f.pos/2)))]
19    Binread(Vec<Label>),
20
21    #[error(transparent)]
22    Reserve(#[from] TryReserveError),
23
24    #[error("Subtyping error: {0}")]
25    Subtype(String),
26
27    #[error(transparent)]
28    Custom(#[from] anyhow::Error),
29}
30
31impl Error {
32    pub fn msg<T: ToString>(msg: T) -> Self {
33        Error::Custom(anyhow::anyhow!(msg.to_string()))
34    }
35    pub fn subtype<T: ToString>(msg: T) -> Self {
36        Error::Subtype(msg.to_string())
37    }
38}
39
40fn get_binread_labels(e: &binrw::Error) -> Vec<Label> {
41    use binrw::Error::*;
42    match e {
43        BadMagic { pos, .. } => {
44            let pos = (*pos * 2) as usize;
45            vec![Label {
46                pos,
47                message: "Unexpected bytes".to_string(),
48            }]
49        }
50        Custom { pos, err } => {
51            let pos = (*pos * 2) as usize;
52            // `Truncated` carries the same field name as the plain `&str` case; it is a
53            // distinct type only so a read that ran out of input stays distinguishable
54            // from a malformed one. Both render identically here.
55            let message = err
56                .downcast_ref::<&str>()
57                .map(|s| (*s).to_string())
58                .or_else(|| {
59                    err.downcast_ref::<crate::binary_parser::Truncated>()
60                        .map(|t| t.0.to_string())
61                })
62                .unwrap_or_else(|| "unknown error (there's a bug in error reporting)".to_string());
63            vec![Label { pos, message }]
64        }
65        EnumErrors {
66            pos,
67            variant_errors,
68        } => {
69            let pos = (*pos * 2) as usize;
70            let variant = variant_errors
71                .iter()
72                .find(|(_, e)| !matches!(e, BadMagic { .. }));
73            // Should have at most one non-magic error
74            match variant {
75                None => vec![Label {
76                    pos,
77                    message: "Unknown opcode".to_string(),
78                }],
79                Some((id, e)) => {
80                    let mut labels = get_binread_labels(e);
81                    labels.push(Label {
82                        pos,
83                        message: (*id).to_string(),
84                    });
85                    labels
86                }
87            }
88        }
89        NoVariantMatch { pos } => {
90            let pos = (*pos * 2) as usize;
91            vec![Label {
92                pos,
93                message: "No variant match".to_string(),
94            }]
95        }
96        AssertFail { pos, message } => {
97            let pos = (*pos * 2) as usize;
98            vec![Label {
99                pos,
100                message: message.to_string(),
101            }]
102        }
103        Io(_) => vec![],
104        Backtrace(backtrace) => get_binread_labels(&backtrace.error),
105        // `binrw::Error` is `#[non_exhaustive]`. Decoding runs on untrusted
106        // input, so degrade to a label-less error instead of panicking if a
107        // future version of binrw adds a variant.
108        _ => vec![],
109    }
110}
111
112impl ser::Error for Error {
113    fn custom<T: std::fmt::Display>(msg: T) -> Self {
114        Error::msg(format!("Serialize error: {msg}"))
115    }
116}
117
118impl de::Error for Error {
119    fn custom<T: std::fmt::Display>(msg: T) -> Self {
120        let msg = msg.to_string();
121        if let Some(msg) = msg.strip_prefix("Subtyping error: ") {
122            Error::Subtype(msg.to_string())
123        } else {
124            Error::msg(format!("Deserialize error: {msg}"))
125        }
126    }
127    fn invalid_type(_: de::Unexpected<'_>, exp: &dyn de::Expected) -> Self {
128        Error::Subtype(format!("{exp}"))
129    }
130}
131
132impl From<io::Error> for Error {
133    fn from(e: io::Error) -> Error {
134        Error::msg(format!("io error: {e}"))
135    }
136}
137
138impl From<binrw::Error> for Error {
139    fn from(e: binrw::Error) -> Error {
140        Error::Binread(get_binread_labels(&e))
141    }
142}
143
144impl From<ParseIntError> for Error {
145    fn from(e: ParseIntError) -> Error {
146        Error::msg(format!("ParseIntError: {e}"))
147    }
148}