use crate::types::*;
use crate::Replica;
use candid::utils::{ArgumentDecoder, ArgumentEncoder};
use candid::{decode_args, decode_one, encode_args, encode_one, CandidType};
use ic_kit_sys::types::{CallError, RejectionCode, CANDID_EMPTY_ARG};
use ic_types::Principal;
use serde::de::DeserializeOwned;
#[derive(Clone)]
pub struct CallBuilder<'a> {
replica: &'a Replica,
canister_id: Principal,
method_name: String,
sender: Principal,
payment: u128,
arg: Option<Vec<u8>>,
}
#[derive(Debug)]
pub enum CallReply {
Reply {
data: Vec<u8>,
cycles_refunded: u128,
},
Reject {
rejection_code: RejectionCode,
rejection_message: String,
cycles_refunded: u128,
},
}
impl<'a> CallBuilder<'a> {
pub fn new(replica: &'a Replica, canister_id: Principal, method_name: String) -> Self {
Self {
replica,
canister_id,
sender: Principal::anonymous(),
method_name,
payment: 0,
arg: None,
}
}
pub fn with_args<T: ArgumentEncoder>(mut self, arguments: T) -> Self {
assert!(self.arg.is_none(), "Arguments may only be set once.");
self.arg = Some(encode_args(arguments).unwrap());
self
}
pub fn with_arg<T: CandidType>(mut self, argument: T) -> Self {
assert!(self.arg.is_none(), "Arguments may only be set once.");
self.arg = Some(encode_one(argument).unwrap());
self
}
pub fn with_arg_raw<A: Into<Vec<u8>>>(mut self, argument: A) -> Self {
assert!(self.arg.is_none(), "Arguments may only be set once.");
self.arg = Some(argument.into());
self
}
pub fn with_payment(mut self, cycles: u128) -> Self {
self.payment = cycles;
self
}
pub fn with_caller<I: Into<Principal>>(mut self, caller: I) -> Self {
self.sender = caller.into();
self
}
pub async fn perform(&self) -> CallReply {
self.replica.perform_call(self.into()).await
}
}
impl CallReply {
pub(crate) fn to_message(self, reply_to: OutgoingRequestId) -> Message {
match self {
CallReply::Reply {
data,
cycles_refunded,
} => Message::Reply {
reply_to,
env: Env::default()
.with_entry_mode(EntryMode::ReplyCallback)
.with_raw_args(data)
.with_cycles_refunded(cycles_refunded),
},
CallReply::Reject {
rejection_code,
rejection_message,
cycles_refunded,
} => Message::Reply {
reply_to,
env: Env::default()
.with_entry_mode(EntryMode::RejectCallback)
.with_cycles_refunded(cycles_refunded)
.with_rejection_code(rejection_code)
.with_rejection_message(rejection_message),
},
}
}
pub fn bytes(&self) -> Result<&[u8], CallError> {
self.into()
}
pub fn decode<T: for<'a> ArgumentDecoder<'a>>(&self) -> Result<T, CallError> {
let bytes = self.bytes()?;
match decode_args(bytes) {
Err(_) => Err(CallError::ResponseDeserializationError(bytes.to_vec())),
Ok(r) => Ok(r),
}
}
pub fn decode_one<T>(&self) -> Result<T, CallError>
where
T: DeserializeOwned + CandidType,
{
let bytes = self.bytes()?;
match decode_one(bytes) {
Err(_) => Err(CallError::ResponseDeserializationError(bytes.to_vec())),
Ok(r) => Ok(r),
}
}
pub fn rejection_code(&self) -> RejectionCode {
match &self {
CallReply::Reply { .. } => RejectionCode::NoError,
CallReply::Reject { rejection_code, .. } => *rejection_code,
}
}
pub fn rejection_message(&self) -> Option<&str> {
match &self {
CallReply::Reply { .. } => None,
CallReply::Reject {
rejection_message, ..
} => Some(rejection_message.as_str()),
}
}
pub fn cycles_refunded(&self) -> u128 {
match &self {
CallReply::Reply {
cycles_refunded, ..
} => *cycles_refunded,
CallReply::Reject {
cycles_refunded, ..
} => *cycles_refunded,
}
}
pub fn is_ok(&self) -> bool {
match &self {
CallReply::Reply { .. } => true,
CallReply::Reject { .. } => false,
}
}
pub fn is_error(&self) -> bool {
match &self {
CallReply::Reply { .. } => false,
CallReply::Reject { .. } => true,
}
}
pub fn assert_ok(&self) {
assert!(self.is_ok(), "The call was rejected.");
}
pub fn assert_error(&self) {
assert!(self.is_error(), "Expected a rejection, but got a reply.");
}
}
impl<'a> From<&'a CallReply> for Result<&'a [u8], CallError> {
fn from(reply: &'a CallReply) -> Self {
match reply {
CallReply::Reply { data, .. } => Ok(data.as_slice()),
CallReply::Reject {
rejection_code,
rejection_message,
..
} => Err(CallError::Rejected(
*rejection_code,
rejection_message.clone(),
)),
}
}
}
impl<'a> From<&'a CallBuilder<'a>> for CanisterCall {
fn from(builder: &'a CallBuilder) -> Self {
CanisterCall {
sender: builder.sender,
request_id: RequestId::new(),
callee: builder.canister_id,
method: builder.method_name.clone(),
payment: builder.payment,
arg: builder
.arg
.clone()
.unwrap_or_else(|| CANDID_EMPTY_ARG.to_vec()),
}
}
}