diff --git a/CHANGELOG.md b/CHANGELOG.md index e0cbcfc2..0a0390b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +* Changed the SyncCall and AsyncCall traits to use an associated type for their output instead of a generic parameter. +* Call builders now generally implement `IntoFuture`, allowing `.call_and_wait().await` to be shortened to `.await`. + ## [0.35.0] - 2024-05-10 * Added a limit to the concurrent requests an agent will make at once. This should make server-side ratelimiting much rarer to encounter, even when sending a high volume of requests (for example, a large `ic_utils::ManagementCanister::install` call). diff --git a/ic-agent/src/agent/mod.rs b/ic-agent/src/agent/mod.rs index e82629bd..2ab74185 100644 --- a/ic-agent/src/agent/mod.rs +++ b/ic-agent/src/agent/mod.rs @@ -48,7 +48,7 @@ use std::{ collections::HashMap, convert::TryFrom, fmt, - future::Future, + future::{Future, IntoFuture}, pin::Pin, sync::{Arc, Mutex, RwLock}, task::{Context, Poll}, @@ -234,7 +234,6 @@ pub enum PollResult { /// let response = agent.update(&management_canister_id, "provisional_create_canister_with_cycles") /// .with_effective_canister_id(effective_canister_id) /// .with_arg(Encode!(&Argument { amount: None })?) -/// .call_and_wait() /// .await?; /// /// let result = Decode!(response.as_slice(), CreateCanisterResult)?; @@ -1614,6 +1613,14 @@ impl<'agent> QueryBuilder<'agent> { } } +impl<'agent> IntoFuture for QueryBuilder<'agent> { + type IntoFuture = AgentFuture<'agent, Vec>; + type Output = Result, AgentError>; + fn into_future(self) -> Self::IntoFuture { + Box::pin(self.call()) + } +} + /// An in-flight canister update call. Useful primarily as a `Future`. pub struct UpdateCall<'agent> { agent: &'agent Agent, @@ -1769,6 +1776,14 @@ impl<'agent> UpdateBuilder<'agent> { } } +impl<'agent> IntoFuture for UpdateBuilder<'agent> { + type IntoFuture = AgentFuture<'agent, Vec>; + type Output = Result, AgentError>; + fn into_future(self) -> Self::IntoFuture { + Box::pin(self.call_and_wait()) + } +} + #[cfg(all(test, feature = "reqwest", not(target_family = "wasm")))] mod offline_tests { use super::*; diff --git a/ic-agent/src/lib.rs b/ic-agent/src/lib.rs index 4937c314..ef10028d 100644 --- a/ic-agent/src/lib.rs +++ b/ic-agent/src/lib.rs @@ -73,7 +73,6 @@ //! let response = agent.update(&management_canister_id, "provisional_create_canister_with_cycles") //! .with_effective_canister_id(effective_canister_id) //! .with_arg(Encode!(&Argument { amount: None})?) -//! .call_and_wait() //! .await?; //! //! let result = Decode!(response.as_slice(), CreateCanisterResult)?; diff --git a/ic-utils/src/call.rs b/ic-utils/src/call.rs index df440c13..cccf69eb 100644 --- a/ic-utils/src/call.rs +++ b/ic-utils/src/call.rs @@ -3,7 +3,9 @@ use candid::{decode_args, decode_one, utils::ArgumentDecoder, CandidType}; use ic_agent::{agent::UpdateBuilder, export::Principal, Agent, AgentError, RequestId}; use serde::de::DeserializeOwned; use std::fmt; -use std::future::Future; +use std::future::{Future, IntoFuture}; +use std::marker::PhantomData; +use std::pin::Pin; mod expiry; pub use expiry::Expiry; @@ -11,20 +13,19 @@ pub use expiry::Expiry; /// 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)] -pub trait SyncCall -where - O: for<'de> ArgumentDecoder<'de> + Send, -{ +pub trait SyncCall: CallIntoFuture> { + /// The return type of the Candid function being called. + type Value: for<'de> ArgumentDecoder<'de> + Send; /// Execute the call, return an array of bytes directly from the canister. #[cfg(feature = "raw")] async fn call_raw(self) -> Result, AgentError>; /// Execute the call, returning either the value returned by the canister, or an /// error returned by the Agent. - async fn call(self) -> Result + async fn call(self) -> Result where Self: Sized + Send, - O: 'async_trait; + Self::Value: 'async_trait; } /// A type that implements asynchronous calls (ie. 'update' calls). @@ -35,10 +36,9 @@ where /// call should be returning. #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -pub trait AsyncCall -where - Out: for<'de> ArgumentDecoder<'de> + Send, -{ +pub trait AsyncCall: CallIntoFuture> { + /// The return type of the Candid function being called. + type Value: for<'de> ArgumentDecoder<'de> + Send; /// Execute the call, but returns the RequestId. Waiting on the request Id must be /// managed by the caller using the Agent directly. /// @@ -52,7 +52,7 @@ where /// Execute the call, and wait for an answer using an exponential-backoff strategy. The return /// type is encoded in the trait. - async fn call_and_wait(self) -> Result; + async fn call_and_wait(self) -> Result; /// Apply a transformation function after the call has been successful. The transformation /// is applied with the result. @@ -101,11 +101,9 @@ where /// .install_code(&canister_id, canister_wasm) /// .build() /// .unwrap() - /// .call_and_wait() /// .await?; /// Ok((canister_id,)) /// }) - /// .call_and_wait() /// .await?; /// /// Ok(canister_id) @@ -117,30 +115,51 @@ where /// eprintln!("{}", canister_id); /// # }); /// ``` - fn and_then( + fn and_then<'a, Out2, R, AndThen>( self, and_then: AndThen, - ) -> AndThenAsyncCaller + ) -> AndThenAsyncCaller<'a, Self::Value, Out2, Self, R, AndThen> where - Self: Sized + Send, - Out2: for<'de> ArgumentDecoder<'de> + Send, - R: Future> + Send, - AndThen: Send + Fn(Out) -> R, + Self: Sized + Send + 'a, + Out2: for<'de> ArgumentDecoder<'de> + Send + 'a, + R: Future> + Send + 'a, + AndThen: Send + Fn(Self::Value) -> R + 'a, { AndThenAsyncCaller::new(self, and_then) } /// Apply a transformation function after the call has been successful. Equivalent to `.and_then(|x| async { map(x) })`. - fn map(self, map: Map) -> MappedAsyncCaller + fn map<'a, Out, Map>(self, map: Map) -> MappedAsyncCaller<'a, Self::Value, Out, Self, Map> where - Self: Sized + Send, - Out2: for<'de> ArgumentDecoder<'de> + Send, - Map: Send + Fn(Out) -> Out2, + Self: Sized + Send + 'a, + Out: for<'de> ArgumentDecoder<'de> + Send + 'a, + Map: Send + Fn(Self::Value) -> Out + 'a, { MappedAsyncCaller::new(self, map) } } +#[cfg(target_family = "wasm")] +pub(crate) type CallFuture<'a, T> = Pin> + 'a>>; +#[cfg(not(target_family = "wasm"))] +pub(crate) type CallFuture<'a, T> = + Pin> + Send + 'a>>; +#[cfg(not(target_family = "wasm"))] +#[doc(hidden)] +pub trait CallIntoFuture: IntoFuture::IntoFuture> { + type IntoFuture: Future + Send; +} +#[cfg(not(target_family = "wasm"))] +impl CallIntoFuture for T +where + T: IntoFuture + ?Sized, + T::IntoFuture: Send, +{ + type IntoFuture = T::IntoFuture; +} +#[cfg(target_family = "wasm")] +use IntoFuture as CallIntoFuture; + /// A synchronous call encapsulation. #[derive(Debug)] pub struct SyncCaller<'agent, Out> @@ -153,7 +172,7 @@ where pub(crate) method_name: String, pub(crate) arg: Result, AgentError>, pub(crate) expiry: Expiry, - pub(crate) phantom_out: std::marker::PhantomData, + pub(crate) phantom_out: PhantomData, } impl<'agent, Out> SyncCaller<'agent, Out> @@ -174,11 +193,12 @@ where #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl<'agent, Out> SyncCall for SyncCaller<'agent, Out> +impl<'agent, Out> SyncCall for SyncCaller<'agent, Out> where Self: Sized, Out: 'agent + for<'de> ArgumentDecoder<'de> + Send, { + type Value = Out; #[cfg(feature = "raw")] async fn call_raw(self) -> Result, AgentError> { Ok(self.call_raw().await?) @@ -191,6 +211,18 @@ where } } +impl<'agent, Out> IntoFuture for SyncCaller<'agent, Out> +where + Self: Sized, + Out: 'agent + for<'de> ArgumentDecoder<'de> + Send, +{ + type IntoFuture = CallFuture<'agent, Out>; + type Output = Result; + fn into_future(self) -> Self::IntoFuture { + SyncCall::call(self) + } +} + /// An async caller, encapsulating a call to an update method. #[derive(Debug)] pub struct AsyncCaller<'agent, Out> @@ -203,12 +235,12 @@ where pub(crate) method_name: String, pub(crate) arg: Result, AgentError>, pub(crate) expiry: Expiry, - pub(crate) phantom_out: std::marker::PhantomData, + pub(crate) phantom_out: PhantomData, } impl<'agent, Out> AsyncCaller<'agent, Out> where - Out: for<'de> ArgumentDecoder<'de> + Send, + Out: for<'de> ArgumentDecoder<'de> + Send + 'agent, { /// Build an UpdateBuilder call that can be used directly with the [Agent]. This is /// essentially downleveling this type into the lower level [ic-agent] abstraction. @@ -246,7 +278,7 @@ where } /// See [`AsyncCall::map`]. - pub fn map(self, map: Map) -> MappedAsyncCaller + pub fn map(self, map: Map) -> MappedAsyncCaller<'agent, Out, Out2, Self, Map> where Out2: for<'de> ArgumentDecoder<'de> + Send, Map: Send + Fn(Out) -> Out2, @@ -257,10 +289,11 @@ where #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl<'agent, Out> AsyncCall for AsyncCaller<'agent, Out> +impl<'agent, Out> AsyncCall for AsyncCaller<'agent, Out> where - Out: for<'de> ArgumentDecoder<'de> + Send, + Out: for<'de> ArgumentDecoder<'de> + Send + 'agent, { + type Value = Out; async fn call(self) -> Result { self.call().await } @@ -269,27 +302,41 @@ where } } +impl<'agent, Out> IntoFuture for AsyncCaller<'agent, Out> +where + Out: for<'de> ArgumentDecoder<'de> + Send + 'agent, +{ + type IntoFuture = CallFuture<'agent, Out>; + type Output = Result; + fn into_future(self) -> Self::IntoFuture { + AsyncCall::call_and_wait(self) + } +} + /// An AsyncCall that applies a transform function to the result of the call. Because of /// constraints on the type system in Rust, both the input and output to the function must be /// deserializable. pub struct AndThenAsyncCaller< + 'a, Out: for<'de> ArgumentDecoder<'de> + Send, Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send, + Inner: AsyncCall + Send + 'a, R: Future> + Send, AndThen: Send + Fn(Out) -> R, > { inner: Inner, and_then: AndThen, - _out: std::marker::PhantomData, - _out2: std::marker::PhantomData, + _out: PhantomData, + _out2: PhantomData, + _lifetime: PhantomData<&'a ()>, } -impl fmt::Debug for AndThenAsyncCaller +impl<'a, Out, Out2, Inner, R, AndThen> fmt::Debug + for AndThenAsyncCaller<'a, Out, Out2, Inner, R, AndThen> where Out: for<'de> ArgumentDecoder<'de> + Send, Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send + fmt::Debug, + Inner: AsyncCall + Send + fmt::Debug + 'a, R: Future> + Send, AndThen: Send + Fn(Out) -> R + fmt::Debug, { @@ -303,21 +350,22 @@ where } } -impl AndThenAsyncCaller +impl<'a, Out, Out2, Inner, R, AndThen> AndThenAsyncCaller<'a, Out, Out2, Inner, R, AndThen> where - Out: for<'de> ArgumentDecoder<'de> + Send, - Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send, - R: Future> + Send, - AndThen: Send + Fn(Out) -> R, + Out: for<'de> ArgumentDecoder<'de> + Send + 'a, + Out2: for<'de> ArgumentDecoder<'de> + Send + 'a, + Inner: AsyncCall + Send + 'a, + R: Future> + Send + 'a, + AndThen: Send + Fn(Out) -> R + 'a, { /// Equivalent to `inner.and_then(and_then)`. pub fn new(inner: Inner, and_then: AndThen) -> Self { Self { inner, and_then, - _out: std::marker::PhantomData, - _out2: std::marker::PhantomData, + _out: PhantomData, + _out2: PhantomData, + _lifetime: PhantomData, } } @@ -338,17 +386,17 @@ where pub fn and_then( self, and_then: AndThen2, - ) -> AndThenAsyncCaller + ) -> AndThenAsyncCaller<'a, Out2, Out3, Self, R2, AndThen2> where - Out3: for<'de> ArgumentDecoder<'de> + Send, - R2: Future> + Send, - AndThen2: Send + Fn(Out2) -> R2, + Out3: for<'de> ArgumentDecoder<'de> + Send + 'a, + R2: Future> + Send + 'a, + AndThen2: Send + Fn(Out2) -> R2 + 'a, { AndThenAsyncCaller::new(self, and_then) } /// See [`AsyncCall::map`]. - pub fn map(self, map: Map) -> MappedAsyncCaller + pub fn map(self, map: Map) -> MappedAsyncCaller<'a, Out2, Out3, Self, Map> where Out3: for<'de> ArgumentDecoder<'de> + Send, Map: Send + Fn(Out2) -> Out3, @@ -359,15 +407,17 @@ where #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl AsyncCall - for AndThenAsyncCaller +impl<'a, Out, Out2, Inner, R, AndThen> AsyncCall + for AndThenAsyncCaller<'a, Out, Out2, Inner, R, AndThen> where - Out: for<'de> ArgumentDecoder<'de> + Send, - Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send, - R: Future> + Send, - AndThen: Send + Fn(Out) -> R, + Out: for<'de> ArgumentDecoder<'de> + Send + 'a, + Out2: for<'de> ArgumentDecoder<'de> + Send + 'a, + Inner: AsyncCall + Send + 'a, + R: Future> + Send + 'a, + AndThen: Send + Fn(Out) -> R + 'a, { + type Value = Out2; + async fn call(self) -> Result { self.call().await } @@ -377,25 +427,43 @@ where } } +impl<'a, Out, Out2, Inner, R, AndThen> IntoFuture + for AndThenAsyncCaller<'a, Out, Out2, Inner, R, AndThen> +where + Out: for<'de> ArgumentDecoder<'de> + Send + 'a, + Out2: for<'de> ArgumentDecoder<'de> + Send + 'a, + Inner: AsyncCall + Send + 'a, + R: Future> + Send + 'a, + AndThen: Send + Fn(Out) -> R + 'a, +{ + type IntoFuture = CallFuture<'a, Out2>; + type Output = Result; + fn into_future(self) -> Self::IntoFuture { + AsyncCall::call_and_wait(self) + } +} + /// A structure that applies a transform function to the result of a call. Because of constraints /// on the type system in Rust, both the input and output to the function must be deserializable. pub struct MappedAsyncCaller< + 'a, Out: for<'de> ArgumentDecoder<'de> + Send, Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send, + Inner: AsyncCall + Send + 'a, Map: Send + Fn(Out) -> Out2, > { inner: Inner, map: Map, - _out: std::marker::PhantomData, - _out2: std::marker::PhantomData, + _out: PhantomData, + _out2: PhantomData, + _lifetime: PhantomData<&'a ()>, } -impl fmt::Debug for MappedAsyncCaller +impl<'a, Out, Out2, Inner, Map> fmt::Debug for MappedAsyncCaller<'a, Out, Out2, Inner, Map> where Out: for<'de> ArgumentDecoder<'de> + Send, Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send + fmt::Debug, + Inner: AsyncCall + Send + fmt::Debug + 'a, Map: Send + Fn(Out) -> Out2 + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -408,11 +476,11 @@ where } } -impl MappedAsyncCaller +impl<'a, Out, Out2, Inner, Map> MappedAsyncCaller<'a, Out, Out2, Inner, Map> where Out: for<'de> ArgumentDecoder<'de> + Send, Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send, + Inner: AsyncCall + Send + 'a, Map: Send + Fn(Out) -> Out2, { /// Equivalent to `inner.map(map)`. @@ -420,8 +488,9 @@ where Self { inner, map, - _out: std::marker::PhantomData, - _out2: std::marker::PhantomData, + _out: PhantomData, + _out2: PhantomData, + _lifetime: PhantomData, } } @@ -440,17 +509,17 @@ where pub fn and_then( self, and_then: AndThen2, - ) -> AndThenAsyncCaller + ) -> AndThenAsyncCaller<'a, Out2, Out3, Self, R2, AndThen2> where - Out3: for<'de> ArgumentDecoder<'de> + Send, - R2: Future> + Send, - AndThen2: Send + Fn(Out2) -> R2, + Out3: for<'de> ArgumentDecoder<'de> + Send + 'a, + R2: Future> + Send + 'a, + AndThen2: Send + Fn(Out2) -> R2 + 'a, { AndThenAsyncCaller::new(self, and_then) } /// See [`AsyncCall::map`]. - pub fn map(self, map: Map2) -> MappedAsyncCaller + pub fn map(self, map: Map2) -> MappedAsyncCaller<'a, Out2, Out3, Self, Map2> where Out3: for<'de> ArgumentDecoder<'de> + Send, Map2: Send + Fn(Out2) -> Out3, @@ -461,13 +530,15 @@ where #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl AsyncCall for MappedAsyncCaller +impl<'a, Out, Out2, Inner, Map> AsyncCall for MappedAsyncCaller<'a, Out, Out2, Inner, Map> where - Out: for<'de> ArgumentDecoder<'de> + Send, - Out2: for<'de> ArgumentDecoder<'de> + Send, - Inner: AsyncCall + Send, - Map: Send + Fn(Out) -> Out2, + Out: for<'de> ArgumentDecoder<'de> + Send + 'a, + Out2: for<'de> ArgumentDecoder<'de> + Send + 'a, + Inner: AsyncCall + Send + 'a, + Map: Send + Fn(Out) -> Out2 + 'a, { + type Value = Out2; + async fn call(self) -> Result { self.call().await } @@ -476,3 +547,18 @@ where self.call_and_wait().await } } + +impl<'a, Out, Out2, Inner, Map> IntoFuture for MappedAsyncCaller<'a, Out, Out2, Inner, Map> +where + Out: for<'de> ArgumentDecoder<'de> + Send + 'a, + Out2: for<'de> ArgumentDecoder<'de> + Send + 'a, + Inner: AsyncCall + Send + 'a, + Map: Send + Fn(Out) -> Out2 + 'a, +{ + type IntoFuture = CallFuture<'a, Out2>; + type Output = Result; + + fn into_future(self) -> Self::IntoFuture { + AsyncCall::call_and_wait(self) + } +} diff --git a/ic-utils/src/canister.rs b/ic-utils/src/canister.rs index dd5127b2..2d12f168 100644 --- a/ic-utils/src/canister.rs +++ b/ic-utils/src/canister.rs @@ -98,6 +98,7 @@ impl<'agent> Canister<'agent> { } /// Create an AsyncCallBuilder to do an update call. + /// Prefer using [`update`](Canister::update) instead. pub fn update_<'canister>( &'canister self, method_name: &str, @@ -106,7 +107,6 @@ impl<'agent> Canister<'agent> { } /// Create an AsyncCallBuilder to do an update call. - /// Prefer using [`update`](Canister::update) instead. pub fn update<'canister>( &'canister self, method_name: &str, diff --git a/ic-utils/src/interfaces/http_request.rs b/ic-utils/src/interfaces/http_request.rs index 8c2cfa7e..fa5ee82f 100644 --- a/ic-utils/src/interfaces/http_request.rs +++ b/ic-utils/src/interfaces/http_request.rs @@ -409,7 +409,7 @@ impl<'agent> HttpRequestCanister<'agent> { >, body: impl AsRef<[u8]>, certificate_version: Option<&u16>, - ) -> impl 'agent + SyncCall<(HttpResponse,)> { + ) -> impl 'agent + SyncCall { self.http_request_custom( method.as_ref(), url.as_ref(), @@ -428,7 +428,7 @@ impl<'agent> HttpRequestCanister<'agent> { headers: H, body: &[u8], certificate_version: Option<&u16>, - ) -> impl 'agent + SyncCall<(HttpResponse,)> + ) -> impl 'agent + SyncCall,)> where H: 'agent + Send + Sync + Clone + ExactSizeIterator>, T: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>, @@ -453,7 +453,7 @@ impl<'agent> HttpRequestCanister<'agent> { url: impl AsRef, headers: impl 'agent + Send + Sync + Clone + ExactSizeIterator>, body: impl AsRef<[u8]>, - ) -> impl 'agent + AsyncCall<(HttpResponse,)> { + ) -> impl 'agent + AsyncCall { self.http_request_update_custom(method.as_ref(), url.as_ref(), headers, body.as_ref()) } @@ -466,7 +466,7 @@ impl<'agent> HttpRequestCanister<'agent> { url: &str, headers: H, body: &[u8], - ) -> impl 'agent + AsyncCall<(HttpResponse,)> + ) -> impl 'agent + AsyncCall,)> where H: 'agent + Send + Sync + Clone + ExactSizeIterator>, T: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>, @@ -487,7 +487,7 @@ impl<'agent> HttpRequestCanister<'agent> { &'canister self, method: impl AsRef, token: Token, - ) -> impl 'agent + SyncCall<(StreamingCallbackHttpResponse,)> { + ) -> impl 'agent + SyncCall { self.query(method.as_ref()).with_value_arg(token.0).build() } @@ -497,7 +497,7 @@ impl<'agent> HttpRequestCanister<'agent> { &'canister self, method: impl AsRef, token: T, - ) -> impl 'agent + SyncCall<(StreamingCallbackHttpResponse,)> + ) -> impl 'agent + SyncCall,)> where T: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>, { diff --git a/ic-utils/src/interfaces/management_canister.rs b/ic-utils/src/interfaces/management_canister.rs index 5b0562fe..de4bfc66 100644 --- a/ic-utils/src/interfaces/management_canister.rs +++ b/ic-utils/src/interfaces/management_canister.rs @@ -287,7 +287,7 @@ impl<'agent> ManagementCanister<'agent> { pub fn canister_status( &self, canister_id: &Principal, - ) -> impl 'agent + AsyncCall<(StatusCallResult,)> { + ) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct In { canister_id: Principal, @@ -309,7 +309,7 @@ impl<'agent> ManagementCanister<'agent> { /// This method deposits the cycles included in this call into the specified canister. /// Only the controller of the canister can deposit cycles. - pub fn deposit_cycles(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<()> { + pub fn deposit_cycles(&self, canister_id: &Principal) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument { canister_id: Principal, @@ -324,7 +324,7 @@ impl<'agent> ManagementCanister<'agent> { } /// Deletes a canister. - pub fn delete_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<()> { + pub fn delete_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument { canister_id: Principal, @@ -346,7 +346,7 @@ impl<'agent> ManagementCanister<'agent> { &self, canister_id: &Principal, amount: u64, - ) -> impl 'agent + AsyncCall<()> { + ) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument { canister_id: Principal, @@ -365,14 +365,14 @@ impl<'agent> ManagementCanister<'agent> { /// This method takes no input and returns 32 pseudo-random bytes to the caller. /// The return value is unknown to any part of the IC at time of the submission of this call. /// A new return value is generated for each call to this method. - pub fn raw_rand(&self) -> impl 'agent + AsyncCall<(Vec,)> { + pub fn raw_rand(&self) -> impl 'agent + AsyncCall,)> { self.update(MgmtMethod::RawRand.as_ref()) .build() .map(|result: (Vec,)| (result.0,)) } /// Starts a canister. - pub fn start_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<()> { + pub fn start_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument { canister_id: Principal, @@ -387,7 +387,7 @@ impl<'agent> ManagementCanister<'agent> { } /// Stop a canister. - pub fn stop_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<()> { + pub fn stop_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument { canister_id: Principal, @@ -408,7 +408,7 @@ impl<'agent> ManagementCanister<'agent> { /// Outstanding responses to the canister will not be processed, even if they arrive after code has been installed again. /// The canister is now empty. In particular, any incoming or queued calls will be rejected. //// A canister after uninstalling retains its cycles balance, controller, status, and allocations. - pub fn uninstall_code(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<()> { + pub fn uninstall_code(&self, canister_id: &Principal) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument { canister_id: Principal, @@ -444,7 +444,7 @@ impl<'agent> ManagementCanister<'agent> { &self, canister_id: &Principal, chunk: &[u8], - ) -> impl 'agent + AsyncCall<(UploadChunkResult,)> { + ) -> impl 'agent + AsyncCall { #[derive(CandidType, Deserialize)] struct Argument<'a> { canister_id: Principal, @@ -462,7 +462,10 @@ impl<'agent> ManagementCanister<'agent> { } /// Clear a canister's chunked WASM storage. - pub fn clear_chunk_store(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<()> { + pub fn clear_chunk_store( + &self, + canister_id: &Principal, + ) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument<'a> { canister_id: &'a Principal, @@ -477,7 +480,7 @@ impl<'agent> ManagementCanister<'agent> { pub fn stored_chunks( &self, canister_id: &Principal, - ) -> impl 'agent + AsyncCall<(StoreChunksResult,)> { + ) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct Argument<'a> { canister_id: &'a Principal, @@ -514,7 +517,7 @@ impl<'agent> ManagementCanister<'agent> { pub fn fetch_canister_logs( &self, canister_id: &Principal, - ) -> impl 'agent + SyncCall<(FetchCanisterLogsResponse,)> { + ) -> impl 'agent + SyncCall { #[derive(CandidType)] struct In { canister_id: Principal, @@ -536,7 +539,7 @@ impl<'agent> ManagementCanister<'agent> { address: &str, network: BitcoinNetwork, min_confirmations: Option, - ) -> impl 'agent + SyncCall<(u64,)> { + ) -> impl 'agent + SyncCall { #[derive(CandidType)] struct In<'a> { address: &'a str, @@ -563,7 +566,7 @@ impl<'agent> ManagementCanister<'agent> { address: &str, network: BitcoinNetwork, filter: Option, - ) -> impl 'agent + SyncCall<(GetUtxosResponse,)> { + ) -> impl 'agent + SyncCall { #[derive(CandidType)] struct In<'a> { address: &'a str, diff --git a/ic-utils/src/interfaces/management_canister/builders.rs b/ic-utils/src/interfaces/management_canister/builders.rs index eb96c15e..e86f8727 100644 --- a/ic-utils/src/interfaces/management_canister/builders.rs +++ b/ic-utils/src/interfaces/management_canister/builders.rs @@ -5,6 +5,7 @@ pub use super::attributes::{ ComputeAllocation, FreezingThreshold, MemoryAllocation, ReservedCyclesLimit, WasmMemoryLimit, }; use super::{ChunkHash, ManagementCanister}; +use crate::call::CallFuture; use crate::{ call::AsyncCall, canister::Argument, interfaces::management_canister::MgmtMethod, Canister, }; @@ -20,6 +21,7 @@ use ic_agent::{export::Principal, AgentError, RequestId}; use sha2::{Digest, Sha256}; use std::collections::BTreeSet; use std::convert::{From, TryInto}; +use std::future::IntoFuture; use std::pin::Pin; use std::str::FromStr; @@ -318,7 +320,7 @@ impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> { /// Create an [AsyncCall] implementation that, when called, will create a /// canister. - pub fn build(self) -> Result, AgentError> { + pub fn build(self) -> Result, AgentError> { let controllers = match self.controllers { Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))), Some(Ok(x)) => Some(x), @@ -410,9 +412,9 @@ impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> { #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl<'agent, 'canister: 'agent> AsyncCall<(Principal,)> - for CreateCanisterBuilder<'agent, 'canister> -{ +impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> { + type Value = (Principal,); + async fn call(self) -> Result { self.build()?.call().await } @@ -422,6 +424,15 @@ impl<'agent, 'canister: 'agent> AsyncCall<(Principal,)> } } +impl<'agent, 'canister: 'agent> IntoFuture for CreateCanisterBuilder<'agent, 'canister> { + type IntoFuture = CallFuture<'agent, (Principal,)>; + type Output = Result<(Principal,), AgentError>; + + fn into_future(self) -> Self::IntoFuture { + AsyncCall::call_and_wait(self) + } +} + /// The install mode of the canister to install. If a canister is already installed, /// using [InstallMode::Install] will be an error. [InstallMode::Reinstall] overwrites /// the module, and [InstallMode::Upgrade] performs an Upgrade step. @@ -529,7 +540,7 @@ impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> { /// Create an [AsyncCall] implementation that, when called, will install the /// canister. - pub fn build(self) -> Result, AgentError> { + pub fn build(self) -> Result, AgentError> { Ok(self .canister .update(MgmtMethod::InstallCode.as_ref()) @@ -556,7 +567,9 @@ 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> { +impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> { + type Value = (); + async fn call(self) -> Result { self.build()?.call().await } @@ -566,6 +579,15 @@ impl<'agent, 'canister: 'agent> AsyncCall<()> for InstallCodeBuilder<'agent, 'ca } } +impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> { + type IntoFuture = CallFuture<'agent, ()>; + type Output = Result<(), AgentError>; + + fn into_future(self) -> Self::IntoFuture { + AsyncCall::call_and_wait(self) + } +} + /// A builder for an `install_chunked_code` call. #[derive(Debug)] pub struct InstallChunkedCodeBuilder<'agent, 'canister> { @@ -636,7 +658,7 @@ impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> } /// Create an [`AsyncCall`] implementation that, when called, will install the canister. - pub fn build(self) -> Result, AgentError> { + pub fn build(self) -> Result, AgentError> { #[derive(CandidType)] struct In { mode: InstallMode, @@ -685,15 +707,27 @@ impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl<'agent, 'canister: 'agent> AsyncCall<()> for InstallChunkedCodeBuilder<'agent, 'canister> { +impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> { + type Value = (); + async fn call(self) -> Result { self.call().await } + async fn call_and_wait(self) -> Result<(), AgentError> { self.call_and_wait().await } } +impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> { + type IntoFuture = CallFuture<'agent, ()>; + type Output = Result<(), AgentError>; + + fn into_future(self) -> Self::IntoFuture { + AsyncCall::call_and_wait(self) + } +} + /// A builder for a [`ManagementCanister::install`] call. This automatically selects one-shot installation or chunked installation depending on module size. /// /// # Warnings @@ -843,6 +877,17 @@ impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'c } } +impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture + for InstallBuilder<'agent, 'canister, 'builder> +{ + type IntoFuture = CallFuture<'builder, ()>; + type Output = Result<(), AgentError>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(self.call_and_wait()) + } +} + /// A builder for an `update_settings` call. #[derive(Debug)] pub struct UpdateCanisterBuilder<'agent, 'canister: 'agent> { @@ -1038,7 +1083,7 @@ impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> { /// Create an [AsyncCall] implementation that, when called, will update a /// canisters settings. - pub fn build(self) -> Result, AgentError> { + pub fn build(self) -> Result, AgentError> { #[derive(CandidType)] struct In { canister_id: Principal, @@ -1107,7 +1152,8 @@ impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> { #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl<'agent, 'canister: 'agent> AsyncCall<()> for UpdateCanisterBuilder<'agent, 'canister> { +impl<'agent, 'canister: 'agent> AsyncCall for UpdateCanisterBuilder<'agent, 'canister> { + type Value = (); async fn call(self) -> Result { self.build()?.call().await } @@ -1117,6 +1163,14 @@ impl<'agent, 'canister: 'agent> AsyncCall<()> for UpdateCanisterBuilder<'agent, } } +impl<'agent, 'canister: 'agent> IntoFuture for UpdateCanisterBuilder<'agent, 'canister> { + type IntoFuture = CallFuture<'agent, ()>; + type Output = Result<(), AgentError>; + fn into_future(self) -> Self::IntoFuture { + AsyncCall::call_and_wait(self) + } +} + #[cfg(not(target_family = "wasm"))] type BoxStream<'a, T> = Pin + Send + 'a>>; #[cfg(target_family = "wasm")] diff --git a/ic-utils/src/interfaces/wallet.rs b/ic-utils/src/interfaces/wallet.rs index 1722c510..514b2637 100644 --- a/ic-utils/src/interfaces/wallet.rs +++ b/ic-utils/src/interfaces/wallet.rs @@ -2,10 +2,13 @@ //! //! [cycles wallet]: https://github.com/dfinity/cycles-wallet -use std::ops::Deref; +use std::{ + future::{Future, IntoFuture}, + ops::Deref, +}; use crate::{ - call::{AsyncCall, SyncCall}, + call::{AsyncCall, CallFuture, SyncCall}, canister::Argument, interfaces::management_canister::{ attributes::{ComputeAllocation, FreezingThreshold, MemoryAllocation}, @@ -53,7 +56,7 @@ pub struct CanisterSettingsV1 { impl<'agent: 'canister, 'canister, Out> CallForwarder<'agent, 'canister, Out> where - Out: for<'de> ArgumentDecoder<'de> + Send + Sync, + Out: for<'de> ArgumentDecoder<'de> + Send + Sync + 'agent, { /// Set the argument with candid argument. Can be called at most once. pub fn with_arg(mut self, arg: Argument) -> Self @@ -79,11 +82,7 @@ where } /// Creates an [`AsyncCall`] implementation that, when called, will forward the specified canister call. - pub fn build<'out>(self) -> Result, AgentError> - where - Out: 'out, - 'agent: 'out, - { + pub fn build(self) -> Result, AgentError> { #[derive(CandidType, Deserialize)] struct In { canister: Principal, @@ -120,22 +119,26 @@ where } /// Calls the forwarded canister call on the wallet canister. Equivalent to `.build().call()`. - pub async fn call(self) -> Result { - self.build()?.call().await + pub fn call(self) -> impl Future> + 'agent { + let call = self.build(); + async { call?.call().await } } /// Calls the forwarded canister call on the wallet canister, and waits for the result. Equivalent to `.build().call_and_wait()`. - pub async fn call_and_wait(self) -> Result { - self.build()?.call_and_wait().await + pub fn call_and_wait(self) -> impl Future> + 'agent { + let call = self.build(); + async { call?.call_and_wait().await } } } #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] -impl<'agent: 'canister, 'canister, Out> AsyncCall for CallForwarder<'agent, 'canister, Out> +impl<'agent: 'canister, 'canister, Out> AsyncCall for CallForwarder<'agent, 'canister, Out> where - Out: for<'de> ArgumentDecoder<'de> + Send + Sync, + Out: for<'de> ArgumentDecoder<'de> + Send + Sync + 'agent, { + type Value = Out; + async fn call(self) -> Result { self.call().await } @@ -145,6 +148,17 @@ where } } +impl<'agent: 'canister, 'canister, Out> IntoFuture for CallForwarder<'agent, 'canister, Out> +where + Out: for<'de> ArgumentDecoder<'de> + Send + Sync + 'agent, +{ + type IntoFuture = CallFuture<'agent, Out>; + type Output = Result; + fn into_future(self) -> Self::IntoFuture { + Box::pin(self.call_and_wait()) + } +} + /// A wallet canister interface, for the standard wallet provided by DFINITY. /// This interface implements most methods conveniently for the user. #[derive(Debug, Clone)] @@ -461,7 +475,7 @@ impl<'agent> WalletCanister<'agent> { impl<'agent> WalletCanister<'agent> { /// Re-fetch the API version string of the wallet. - pub fn fetch_wallet_api_version(&self) -> impl 'agent + SyncCall<(Option,)> { + pub fn fetch_wallet_api_version(&self) -> impl 'agent + SyncCall,)> { self.query("wallet_api_version").build() } @@ -471,52 +485,52 @@ impl<'agent> WalletCanister<'agent> { } /// Get the friendly name of the wallet (if one exists). - pub fn name(&self) -> impl 'agent + SyncCall<(Option,)> { + pub fn name(&self) -> impl 'agent + SyncCall,)> { self.query("name").build() } /// Set the friendly name of the wallet. - pub fn set_name(&self, name: String) -> impl 'agent + AsyncCall<()> { + pub fn set_name(&self, name: String) -> impl 'agent + AsyncCall { self.update("set_name").with_arg(name).build() } /// Get the current controller's principal ID. - pub fn get_controllers(&self) -> impl 'agent + SyncCall<(Vec,)> { + pub fn get_controllers(&self) -> impl 'agent + SyncCall,)> { self.query("get_controllers").build() } /// Transfer controller to another principal ID. - pub fn add_controller(&self, principal: Principal) -> impl 'agent + AsyncCall<()> { + pub fn add_controller(&self, principal: Principal) -> impl 'agent + AsyncCall { self.update("add_controller").with_arg(principal).build() } /// Remove a user as a wallet controller. - pub fn remove_controller(&self, principal: Principal) -> impl 'agent + AsyncCall<()> { + pub fn remove_controller(&self, principal: Principal) -> impl 'agent + AsyncCall { self.update("remove_controller").with_arg(principal).build() } /// Get the list of custodians. - pub fn get_custodians(&self) -> impl 'agent + SyncCall<(Vec,)> { + pub fn get_custodians(&self) -> impl 'agent + SyncCall,)> { self.query("get_custodians").build() } /// Authorize a new custodian. - pub fn authorize(&self, custodian: Principal) -> impl 'agent + AsyncCall<()> { + pub fn authorize(&self, custodian: Principal) -> impl 'agent + AsyncCall { self.update("authorize").with_arg(custodian).build() } /// Deauthorize a custodian. - pub fn deauthorize(&self, custodian: Principal) -> impl 'agent + AsyncCall<()> { + pub fn deauthorize(&self, custodian: Principal) -> impl 'agent + AsyncCall { self.update("deauthorize").with_arg(custodian).build() } /// Get the balance with the 64-bit API. - pub fn wallet_balance64(&self) -> impl 'agent + SyncCall<(BalanceResult,)> { + pub fn wallet_balance64(&self) -> impl 'agent + SyncCall,)> { self.query("wallet_balance").build() } /// Get the balance with the 128-bit API. - pub fn wallet_balance128(&self) -> impl 'agent + SyncCall<(BalanceResult,)> { + pub fn wallet_balance128(&self) -> impl 'agent + SyncCall { self.query("wallet_balance128").build() } @@ -539,7 +553,7 @@ impl<'agent> WalletCanister<'agent> { &self, destination: Principal, amount: u64, - ) -> impl 'agent + AsyncCall<(Result<(), String>,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { canister: Principal, @@ -559,7 +573,7 @@ impl<'agent> WalletCanister<'agent> { &'canister self, destination: Principal, amount: u128, - ) -> impl 'agent + AsyncCall<(Result<(), String>,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { canister: Principal, @@ -599,7 +613,7 @@ impl<'agent> WalletCanister<'agent> { } /// A function for sending cycles to, so that a memo can be passed along with them. - pub fn wallet_receive(&self, memo: Option) -> impl 'agent + AsyncCall<((),)> { + pub fn wallet_receive(&self, memo: Option) -> impl 'agent + AsyncCall { #[derive(CandidType)] struct In { memo: Option, @@ -617,7 +631,7 @@ impl<'agent> WalletCanister<'agent> { compute_allocation: Option, memory_allocation: Option, freezing_threshold: Option, - ) -> impl 'agent + AsyncCall<(Result,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { cycles: u64, @@ -645,7 +659,7 @@ impl<'agent> WalletCanister<'agent> { compute_allocation: Option, memory_allocation: Option, freezing_threshold: Option, - ) -> impl 'agent + AsyncCall<(Result,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { cycles: u64, @@ -675,7 +689,7 @@ impl<'agent> WalletCanister<'agent> { compute_allocation: Option, memory_allocation: Option, freezing_threshold: Option, - ) -> impl 'agent + AsyncCall<(Result,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { cycles: u128, @@ -775,7 +789,7 @@ impl<'agent> WalletCanister<'agent> { compute_allocation: Option, memory_allocation: Option, freezing_threshold: Option, - ) -> impl 'agent + AsyncCall<(Result,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { cycles: u64, @@ -803,7 +817,7 @@ impl<'agent> WalletCanister<'agent> { compute_allocation: Option, memory_allocation: Option, freezing_threshold: Option, - ) -> impl 'agent + AsyncCall<(Result,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { cycles: u64, @@ -833,7 +847,7 @@ impl<'agent> WalletCanister<'agent> { compute_allocation: Option, memory_allocation: Option, freezing_threshold: Option, - ) -> impl 'agent + AsyncCall<(Result,)> { + ) -> impl 'agent + AsyncCall,)> { #[derive(CandidType)] struct In { cycles: u128, @@ -915,7 +929,10 @@ impl<'agent> WalletCanister<'agent> { /// Store the wallet WASM inside the wallet canister. /// This is needed to enable wallet_create_wallet - pub fn wallet_store_wallet_wasm(&self, wasm_module: Vec) -> impl 'agent + AsyncCall<()> { + pub fn wallet_store_wallet_wasm( + &self, + wasm_module: Vec, + ) -> impl 'agent + AsyncCall { #[derive(CandidType, Deserialize)] struct In { #[serde(with = "serde_bytes")] @@ -927,17 +944,17 @@ impl<'agent> WalletCanister<'agent> { } /// Add a principal to the address book. - pub fn add_address(&self, address: AddressEntry) -> impl 'agent + AsyncCall<()> { + pub fn add_address(&self, address: AddressEntry) -> impl 'agent + AsyncCall { self.update("add_address").with_arg(address).build() } /// List the entries in the address book. - pub fn list_addresses(&self) -> impl 'agent + SyncCall<(Vec,)> { + pub fn list_addresses(&self) -> impl 'agent + SyncCall,)> { self.query("list_addresses").build() } /// Remove a principal from the address book. - pub fn remove_address(&self, principal: Principal) -> impl 'agent + AsyncCall<()> { + pub fn remove_address(&self, principal: Principal) -> impl 'agent + AsyncCall { self.update("remove_address").with_arg(principal).build() } @@ -946,7 +963,7 @@ impl<'agent> WalletCanister<'agent> { &self, from: Option, to: Option, - ) -> impl 'agent + SyncCall<(Vec>,)> { + ) -> impl 'agent + SyncCall>,)> { #[derive(CandidType)] struct In { from: Option, @@ -967,7 +984,7 @@ impl<'agent> WalletCanister<'agent> { &self, from: Option, to: Option, - ) -> impl 'agent + SyncCall<(Vec,)> { + ) -> impl 'agent + SyncCall,)> { #[derive(CandidType)] struct In { from: Option, @@ -1074,7 +1091,7 @@ impl<'agent> WalletCanister<'agent> { &self, from: Option, to: Option, - ) -> impl 'agent + SyncCall<(Vec, u32)> { + ) -> impl 'agent + SyncCall, u32)> { #[derive(CandidType)] struct In { from: Option, @@ -1091,7 +1108,7 @@ impl<'agent> WalletCanister<'agent> { canister: Principal, from: Option, to: Option, - ) -> impl 'agent + SyncCall<(Option>>,)> { + ) -> impl 'agent + SyncCall>>,)> { #[derive(CandidType)] struct In { canister: Principal, @@ -1109,7 +1126,7 @@ impl<'agent> WalletCanister<'agent> { canister: Principal, from: Option, to: Option, - ) -> impl 'agent + SyncCall<(Option>,)> { + ) -> impl 'agent + SyncCall>,)> { #[derive(CandidType)] struct In { canister: Principal, diff --git a/ref-tests/tests/ic-ref.rs b/ref-tests/tests/ic-ref.rs index a809231f..0a4e7cf1 100644 --- a/ref-tests/tests/ic-ref.rs +++ b/ref-tests/tests/ic-ref.rs @@ -507,7 +507,7 @@ mod management_canister { reject_code: RejectCode::CanisterError, reject_message, error_code: None, - })) if *reject_message == format!("Canister {canister_id} has no update method 'update'") + })) if reject_message.contains(&format!("Canister {canister_id}: Canister has no update method 'update'")) ), "wrong error: {result:?}" ); @@ -521,7 +521,7 @@ mod management_canister { reject_code: RejectCode::CanisterError, reject_message, error_code: Some(error_code), - })) if *reject_message == format!("IC0536: Canister {} has no query method 'query'", canister_id) + })) if reject_message.contains(&format!("Canister {}: Canister has no query method 'query'", canister_id)) && error_code == "IC0536", ), "wrong error: {result:?}" @@ -1223,7 +1223,7 @@ mod extras { reject_code: RejectCode::CanisterError, reject_message, error_code: None, - })) if reject_message == "Canister iimsn-6yaaa-aaaaa-afiaa-cai is already installed" + })) if reject_message.contains("Canister iimsn-6yaaa-aaaaa-afiaa-cai is already installed") ), "wrong error: {result:?}" ); diff --git a/ref-tests/tests/integration.rs b/ref-tests/tests/integration.rs index d058aea6..eb825755 100644 --- a/ref-tests/tests/integration.rs +++ b/ref-tests/tests/integration.rs @@ -193,10 +193,10 @@ fn canister_reject_call() { reject_message, error_code: None, .. - })) if *reject_message == format!( - "Canister {} has no update method 'wallet_send'", + })) if reject_message.contains(&format!( + "Canister {}: Canister has no update method 'wallet_send'", alice.canister_id() - ) + )) ), "wrong error: {result:?}" );