use candid::{
CandidType,
de::{DecoderConfig, IDLDeserialize},
};
use serde::Deserialize;
pub mod arguments;
pub mod decoder;
#[cfg(feature = "candid-endpoint")]
pub mod endpoint;
pub mod nesting;
pub mod protocols;
pub mod reserved;
pub mod sequence;
pub mod contract {
pub use crate::principal::ExternalPrincipal as Principal;
pub use candid::de::DecoderConfig;
pub use candid::utils::{ArgumentDecoder, ArgumentEncoder, encode_args_ref};
pub use candid::{
CandidType, Decode, Encode, Error, Int, Nat, Reserved, TypeEnv, decode_args_with_config,
encode_args, encode_one, idl_hash,
};
#[cfg(feature = "candid-service")]
pub use candid::{candid_method, export_service};
pub use serde;
#[cfg(feature = "candid-service")]
pub mod pretty;
pub mod types {
pub mod internal;
pub mod subtype;
pub use candid::types::{
CandidType, Compound, Field, FieldDoc, FuncMode, Function, Label, Serializer, Type,
TypeDoc, TypeId, TypeInner,
};
}
}
#[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, thiserror::Error)]
pub enum DecodeError {
#[error("Candid reply is {actual} bytes, maximum is {maximum}")]
ReplyTooLarge {
actual: usize,
maximum: usize,
},
#[error("Candid reply decoding failed: {0}")]
Candid(#[source] candid::Error),
}
impl DecodeError {
pub fn nesting_error(&self) -> Option<&nesting::NestingError> {
match self {
Self::Candid(candid::Error::Custom(error)) => error.downcast_ref(),
_ => None,
}
}
}
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,
});
}
nesting::check(bytes, limits)
.map_err(|error| DecodeError::Candid(candid::Error::Custom(error.into())))?;
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)
.set_full_error_message(false);
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_args<'a, T: candid::utils::ArgumentDecoder<'a>>(
bytes: &'a [u8],
limits: DecodeLimits,
) -> Result<T, DecodeError> {
candid::decode_args_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> {
with_decoder(bytes, limits, |decoder| {
exact_arity(
decoder,
true,
(
"expected exactly one Candid result",
"expected exactly one Candid result",
),
|decoder| decoder.get_value::<T>().map_err(DecodeError::Candid),
)
})
}
fn exact_arity<'a, T>(
decoder: &mut IDLDeserialize<'a>,
requires_value: bool,
messages: (&str, &str),
decode: impl FnOnce(&mut IDLDeserialize<'a>) -> Result<T, DecodeError>,
) -> Result<T, DecodeError> {
if requires_value && decoder.is_done() {
return Err(DecodeError::Candid(candid::Error::msg(messages.0)));
}
let value = decode(decoder)?;
if !decoder.is_done() {
return Err(DecodeError::Candid(candid::Error::msg(messages.1)));
}
Ok(value)
}
fn with_decoder<'a, T>(
bytes: &'a [u8],
limits: DecodeLimits,
decode: impl FnOnce(&mut IDLDeserialize<'a>) -> Result<T, DecodeError>,
) -> Result<T, DecodeError> {
let config = config(bytes, limits)?;
let mut decoder =
IDLDeserialize::new_with_config(bytes, &config).map_err(DecodeError::Candid)?;
let value = decode(&mut decoder)?;
decoder.done().map_err(DecodeError::Candid)?;
Ok(value)
}