Skip to content

Commit

Permalink
fix: remove argument builder from ic-utils (#465)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenyan-dfinity authored Aug 30, 2023
1 parent 728f395 commit 65e0900
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 156 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/provision-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ sudo apt-get install --yes bats

# Install Bats support.
version=0.3.0
wget https://github.com/ztombol/bats-support/archive/v$version.tar.gz
curl --location --output bats-support.tar.gz https://github.com/ztombol/bats-support/archive/v$version.tar.gz
sudo mkdir /usr/local/lib/bats-support
sudo tar --directory /usr/local/lib/bats-support --extract --file v$version.tar.gz --strip-components 1
rm v$version.tar.gz
sudo tar --directory /usr/local/lib/bats-support --extract --file bats-support.tar.gz --strip-components 1
rm bats-support.tar.gz

# Install DFINITY SDK.
wget --output-document install-dfx.sh "https://sdk.dfinity.org/install.sh"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

* Breaking change: Remove argument builder form `ic-utils`. `CallBuilder::with_arg` sets a single argument, instead of pushing a new argument to the list. This function can be called at most once. If it's called multiple times, it panics. If you have multiple arguments, use `CallBuilder::with_args((arg1, arg2))` or `CallBuilder::set_raw_arg(candid::Encode!(arg1, arg2)?)`.
* feat: Added `public_key`, `sign_arbitrary`, `sign_delegation` functions to `Identity`.
* Add `From` trait to coerce `candid::Error` into `ic_agent::AgentError`.
* Add `Agent::set_arc_identity` method to switch identity.
Expand Down
23 changes: 8 additions & 15 deletions ic-utils/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,10 @@ use ic_agent::{agent::UpdateBuilder, export::Principal, Agent, AgentError, Reque
use serde::de::DeserializeOwned;
use std::fmt;
use std::future::Future;
use std::pin::Pin;

mod expiry;
pub use expiry::Expiry;

#[cfg(target_family = "wasm")]
pub(crate) type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[cfg(not(target_family = "wasm"))]
pub(crate) type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a + Send>>;

/// A type that implements synchronous calls (ie. 'query' calls).
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
Expand Down Expand Up @@ -102,15 +96,14 @@ where
/// .create_canister()
/// .as_provisional_create_with_amount(None)
/// .with_effective_canister_id(effective_id)
/// .and_then(|(canister_id,)| {
/// let call = management_canister
/// .install_code(&canister_id, canister_wasm)
/// .build()
/// .unwrap();
/// async move {
/// call.call_and_wait().await?;
/// Ok((canister_id,))
/// }
/// .and_then(|(canister_id,)| async move {
/// management_canister
/// .install_code(&canister_id, canister_wasm)
/// .build()
/// .unwrap()
/// .call_and_wait()
/// .await?;
/// Ok((canister_id,))
/// })
/// .call_and_wait()
/// .await?;
Expand Down
149 changes: 55 additions & 94 deletions ic-utils/src/canister.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::call::{AsyncCaller, SyncCaller};
use candid::utils::ArgumentEncoder;
use candid::{ser::IDLBuilder, types::value::IDLValue, utils::ArgumentDecoder, CandidType};
use candid::{ser::IDLBuilder, types::value::IDLValue, utils::ArgumentDecoder, CandidType, Encode};
use ic_agent::{export::Principal, Agent, AgentError, RequestId};
use std::convert::TryInto;
use std::fmt;
use thiserror::Error;

/// An error happened while building a canister.
Expand Down Expand Up @@ -130,89 +129,48 @@ impl<'agent> Canister<'agent> {
}
}

/// The type of argument passed to a canister call. This can either be a raw argument,
/// in which case it's a vector of bytes that will be passed verbatim, or an IDL
/// Builder which will result in an error or a raw argument at the call site.
///
/// This enumeration is meant to be private. You should use [Argument] for holding
/// argument values.
enum ArgumentType {
Raw(Vec<u8>),
Idl(IDLBuilder),
}

impl fmt::Debug for ArgumentType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Raw(v) => f.debug_tuple("ArgumentType::Raw").field(v).finish(),
Self::Idl(_) => f.debug_struct("ArgumentType::Idl").finish_non_exhaustive(),
}
}
}

/// A builder for a canister argument, allowing you to append elements to an argument tuple with chaining syntax.
#[derive(Debug)]
pub struct Argument(Result<ArgumentType, AgentError>);
/// A buffer to hold canister argument blob.
#[derive(Debug, Default)]
pub struct Argument(pub(crate) Option<Result<Vec<u8>, AgentError>>);

impl Argument {
/// Add an IDL Argument. If the current value of Argument is Raw, will set the
/// result to an error. If the current value is an error, will do nothing.
pub fn push_idl_arg<A: CandidType>(&mut self, arg: A) {
/// Set an IDL Argument. Can only be called at most once.
pub fn set_idl_arg<A: CandidType>(&mut self, arg: A) {
match self.0 {
Ok(ArgumentType::Idl(ref mut idl_builder)) => {
let result = idl_builder.arg(&arg);
if let Err(e) = result {
self.0 = Err(AgentError::CandidError(Box::new(e)))
}
}
Ok(ArgumentType::Raw(_)) => {
self.0 = Err(AgentError::MessageError(
"Cannot overwrite a Raw Argument with a non-raw argument.".to_owned(),
))
}
_ => {}
None => self.0 = Some(Encode!(&arg).map_err(|e| e.into())),
Some(_) => panic!("argument is being set more than once"),
}
}

/// Add an IDLValue Argument. If the current value of Argument is Raw, will set the
/// result to an error. If the current value is an error, will do nothing.
pub fn push_value_arg(&mut self, arg: IDLValue) {
/// Set an IDLValue Argument. Can only be called at most once.
pub fn set_value_arg(&mut self, arg: IDLValue) {
match self.0 {
Ok(ArgumentType::Idl(ref mut idl_builder)) => {
let result = idl_builder.value_arg(&arg);
if let Err(e) = result {
self.0 = Err(AgentError::CandidError(Box::new(e)))
}
}
Ok(ArgumentType::Raw(_)) => {
self.0 = Err(AgentError::MessageError(
"Cannot overwrite a Raw Argument with a non-raw argument.".to_owned(),
))
None => {
let mut builder = IDLBuilder::new();
let result = builder
.value_arg(&arg)
.and_then(|builder| builder.serialize_to_vec())
.map_err(|e| e.into());
self.0 = Some(result);
}
_ => {}
Some(_) => panic!("argument is being set more than once"),
}
}

/// Set the argument as raw, replacing any value that was there before. If the
/// current argument was an error, does nothing.
/// Set the argument as raw. Can only be called at most once.
pub fn set_raw_arg(&mut self, arg: Vec<u8>) {
if self.0.is_ok() {
self.0 = Ok(ArgumentType::Raw(arg));
match self.0 {
None => self.0 = Some(Ok(arg)),
Some(_) => panic!("argument is being set more than once"),
}
}

/// Encodes the completed argument into an IDL blob.
/// Return the argument blob.
pub fn serialize(self) -> Result<Vec<u8>, AgentError> {
match self.0 {
Ok(ArgumentType::Idl(mut idl_builder)) => idl_builder
.serialize_to_vec()
.map_err(|e| AgentError::CandidError(Box::new(e))),
Ok(ArgumentType::Raw(vec)) => Ok(vec),
Err(e) => Err(e),
}
self.0.unwrap_or_else(|| Ok(Encode!()?))
}

/// Resets the argument to an empty builder.
/// Resets the argument to an empty message.
pub fn reset(&mut self) {
*self = Default::default();
}
Expand All @@ -224,24 +182,17 @@ impl Argument {

/// Creates an argument from an arbitrary blob. Equivalent to [`set_raw_arg`](Argument::set_raw_arg).
pub fn from_raw(raw: Vec<u8>) -> Self {
Self(Ok(ArgumentType::Raw(raw)))
Self(Some(Ok(raw)))
}

/// Creates an argument from an existing Candid ArgumentEncoder.
pub fn from_candid(tuple: impl ArgumentEncoder) -> Self {
let mut builder = IDLBuilder::new();
Self(
tuple
.encode(&mut builder)
.map(|_| ArgumentType::Idl(builder))
.map_err(|e| AgentError::CandidError(Box::new(e))),
)
}
}

impl Default for Argument {
fn default() -> Self {
Self(Ok(ArgumentType::Idl(IDLBuilder::new())))
let result = tuple
.encode(&mut builder)
.and_then(|_| builder.serialize_to_vec())
.map_err(|e| e.into());
Self(Some(result))
}
}

Expand Down Expand Up @@ -272,28 +223,32 @@ impl<'agent, 'canister: 'agent> SyncCallBuilder<'agent, 'canister> {
}

impl<'agent, 'canister: 'agent> SyncCallBuilder<'agent, 'canister> {
/// Add an argument to the candid argument list. This requires Candid arguments, if
/// there is a raw argument set (using [`with_arg_raw`](SyncCallBuilder::with_arg_raw)),
/// this will fail.
/// Set the argument with candid argument. Can be called at most once.
pub fn with_arg<Argument>(mut self, arg: Argument) -> SyncCallBuilder<'agent, 'canister>
where
Argument: CandidType + Sync + Send,
{
self.arg.push_idl_arg(arg);
self.arg.set_idl_arg(arg);
self
}
/// Set the argument with multiple arguments as tuple. Can be called at most once.
pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> SyncCallBuilder<'agent, 'canister> {
if self.arg.0.is_some() {
panic!("argument is being set more than once");
}
self.arg = Argument::from_candid(tuple);
self
}

/// Add an argument to the candid argument list. This requires Candid arguments, if
/// there is a raw argument set (using [`with_arg_raw`](SyncCallBuilder::with_arg_raw)), this will fail.
/// Set the argument with IDLValue argument. Can be called at most once.
///
/// TODO: make this method unnecessary ([#132](https://github.com/dfinity/agent-rs/issues/132))
pub fn with_value_arg(mut self, arg: IDLValue) -> SyncCallBuilder<'agent, 'canister> {
self.arg.push_value_arg(arg);
self.arg.set_value_arg(arg);
self
}

/// Replace the argument with raw argument bytes. This will overwrite the current
/// argument set, so calling this method twice will discard the first argument.
/// Set the argument with raw argument bytes. Can be called at most once.
pub fn with_arg_raw(mut self, arg: Vec<u8>) -> SyncCallBuilder<'agent, 'canister> {
self.arg.set_raw_arg(arg);
self
Expand Down Expand Up @@ -353,18 +308,24 @@ impl<'agent, 'canister: 'agent> AsyncCallBuilder<'agent, 'canister> {
}

impl<'agent, 'canister: 'agent> AsyncCallBuilder<'agent, 'canister> {
/// Add an argument to the candid argument list. This requires Candid arguments, if
/// there is a raw argument set (using [`with_arg_raw`](AsyncCallBuilder::with_arg_raw)), this will fail.
/// Set the argument with Candid argument. Can be called at most once.
pub fn with_arg<Argument>(mut self, arg: Argument) -> AsyncCallBuilder<'agent, 'canister>
where
Argument: CandidType + Sync + Send,
{
self.arg.push_idl_arg(arg);
self.arg.set_idl_arg(arg);
self
}
/// Set the argument with multiple arguments as tuple. Can be called at most once.
pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> AsyncCallBuilder<'agent, 'canister> {
if self.arg.0.is_some() {
panic!("argument is being set more than once");
}
self.arg = Argument::from_candid(tuple);
self
}

/// Replace the argument with raw argument bytes. This will overwrite the current
/// argument set, so calling this method twice will discard the first argument.
/// Set the argument with raw argument bytes. Can be called at most once.
pub fn with_arg_raw(mut self, arg: Vec<u8>) -> AsyncCallBuilder<'agent, 'canister> {
self.arg.set_raw_arg(arg);
self
Expand Down
42 changes: 21 additions & 21 deletions ic-utils/src/interfaces/management_canister/builders.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
//! Builder interfaces for some method calls of the management canister.

use crate::{
call::{AsyncCall, BoxFuture},
canister::Argument,
interfaces::management_canister::MgmtMethod,
Canister,
call::AsyncCall, canister::Argument, interfaces::management_canister::MgmtMethod, Canister,
};
use async_trait::async_trait;
use candid::{CandidType, Deserialize, Nat};
Expand Down Expand Up @@ -389,17 +386,25 @@ impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
}
}

/// Add an argument to the installation, which will be passed to the init
/// method of the canister.
/// Set the argument to the installation, which will be passed to the init
/// method of the canister. Can be called at most once.
pub fn with_arg<Argument: CandidType + Sync + Send>(
mut self,
arg: Argument,
) -> InstallCodeBuilder<'agent, 'canister> {
self.arg.push_idl_arg(arg);
self.arg.set_idl_arg(arg);
self
}

/// Override the argument passed in to the canister with raw bytes.
/// Set the argument with multiple arguments as tuple to the installation,
/// which will be passed to the init method of the canister. Can be called at most once.
pub fn with_args(mut self, tuple: impl candid::utils::ArgumentEncoder) -> Self {
if self.arg.0.is_some() {
panic!("argument is being set more than once");
}
self.arg = Argument::from_candid(tuple);
self
}
/// Set the argument passed in to the canister with raw bytes. Can be called at most once.
pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
self.arg.set_raw_arg(arg);
self
Expand Down Expand Up @@ -440,20 +445,15 @@ impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
}
}

#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl<'agent, 'canister: 'agent> AsyncCall<()> for InstallCodeBuilder<'agent, 'canister> {
fn call<'async_trait>(self) -> BoxFuture<'async_trait, Result<RequestId, AgentError>>
where
Self: 'async_trait,
{
let call_res = self.build();
Box::pin(async move { call_res?.call().await })
async fn call(self) -> Result<RequestId, AgentError> {
self.build()?.call().await
}
fn call_and_wait<'async_trait>(self) -> BoxFuture<'async_trait, Result<(), AgentError>>
where
Self: 'async_trait,
{
let call_res = self.build();
Box::pin(async move { call_res?.call_and_wait().await })

async fn call_and_wait(self) -> Result<(), AgentError> {
self.build()?.call_and_wait().await
}
}

Expand Down
Loading

0 comments on commit 65e0900

Please sign in to comment.