use candid::{
CandidType,
de::{DecoderConfig, IDLDeserialize},
};
use serde::Deserialize;
use std::{error::Error, fmt};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DecodeLimits {
pub reply_bytes: usize,
pub decoding_quota: usize,
pub skipping_quota: usize,
pub type_entries: usize,
pub header_bytes: usize,
}
#[derive(Debug)]
pub enum DecodeError {
ReplyTooLarge {
actual: usize,
maximum: usize,
},
Candid(candid::Error),
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ReplyTooLarge { actual, maximum } => {
write!(f, "Candid reply is {actual} bytes, maximum is {maximum}")
}
Self::Candid(error) => write!(f, "Candid reply decoding failed: {error}"),
}
}
}
impl Error for DecodeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::ReplyTooLarge { .. } => None,
Self::Candid(error) => Some(error),
}
}
}
fn config(bytes: &[u8], limits: DecodeLimits) -> Result<DecoderConfig, DecodeError> {
if bytes.len() > limits.reply_bytes {
return Err(DecodeError::ReplyTooLarge {
actual: bytes.len(),
maximum: limits.reply_bytes,
});
}
let mut config = DecoderConfig::new();
config
.set_decoding_quota(limits.decoding_quota)
.set_skipping_quota(limits.skipping_quota)
.set_max_type_len(limits.type_entries)
.set_max_header_len(limits.header_bytes);
Ok(config)
}
pub fn decode_one<'a, T: Deserialize<'a> + CandidType>(
bytes: &'a [u8],
limits: DecodeLimits,
) -> Result<T, DecodeError> {
candid::decode_one_with_config(bytes, &config(bytes, limits)?).map_err(DecodeError::Candid)
}
pub fn decode_exactly_one<'a, T: Deserialize<'a> + CandidType>(
bytes: &'a [u8],
limits: DecodeLimits,
) -> Result<T, DecodeError> {
let config = config(bytes, limits)?;
let mut decoder =
IDLDeserialize::new_with_config(bytes, &config).map_err(DecodeError::Candid)?;
if decoder.is_done() {
return Err(DecodeError::Candid(candid::Error::msg(
"expected exactly one Candid result",
)));
}
let value = decoder.get_value::<T>().map_err(DecodeError::Candid)?;
if !decoder.is_done() {
return Err(DecodeError::Candid(candid::Error::msg(
"expected exactly one Candid result",
)));
}
decoder.done().map_err(DecodeError::Candid)?;
Ok(value)
}