use ic_platform::candid::{self as codec, contract::CandidType};
use serde::de::DeserializeOwned;
use thiserror::Error;
#[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, Error)]
pub enum DecodeError {
#[error("Candid reply exceeds its byte limit")]
ReplyBytes,
#[error("Candid reply exceeds the remaining aggregate byte budget")]
AggregateBytes,
#[error("Candid reply could not be decoded: {0}")]
Candid(#[source] codec::contract::Error),
}
pub fn decode_one<T: CandidType + DeserializeOwned>(
bytes: &[u8],
limits: DecodeLimits,
remaining: &mut usize,
) -> Result<T, DecodeError> {
if bytes.len() > limits.reply_bytes {
return Err(DecodeError::ReplyBytes);
}
*remaining = remaining
.checked_sub(bytes.len())
.ok_or(DecodeError::AggregateBytes)?;
codec::decode_one(
bytes,
codec::DecodeLimits {
reply_bytes: limits.reply_bytes,
decoding_quota: limits.decoding_quota,
skipping_quota: limits.skipping_quota,
type_entries: limits.type_entries,
header_bytes: limits.header_bytes,
},
)
.map_err(|error| match error {
codec::DecodeError::ReplyTooLarge { .. } => DecodeError::ReplyBytes,
codec::DecodeError::Candid(source) => DecodeError::Candid(source),
})
}