#[doc(inline)]
pub use super::attributes::{
ComputeAllocation, FreezingThreshold, MemoryAllocation, ReservedCyclesLimit, WasmMemoryLimit,
};
use super::{ChunkHash, LogVisibility, ManagementCanister};
use crate::call::CallFuture;
use crate::{
call::AsyncCall, canister::Argument, interfaces::management_canister::MgmtMethod, Canister,
};
use async_trait::async_trait;
use candid::{utils::ArgumentEncoder, CandidType, Deserialize, Nat};
use futures_util::{
future::ready,
stream::{self, FuturesUnordered},
FutureExt, Stream, StreamExt, TryStreamExt,
};
use ic_agent::{agent::CallResponse, export::Principal, AgentError};
use sha2::{Digest, Sha256};
use std::{
collections::BTreeSet,
convert::{From, TryInto},
future::IntoFuture,
pin::Pin,
str::FromStr,
};
#[derive(Debug, Clone, CandidType, Deserialize)]
pub struct CanisterSettings {
pub controllers: Option<Vec<Principal>>,
pub compute_allocation: Option<Nat>,
pub memory_allocation: Option<Nat>,
pub freezing_threshold: Option<Nat>,
pub reserved_cycles_limit: Option<Nat>,
pub wasm_memory_limit: Option<Nat>,
pub log_visibility: Option<LogVisibility>,
}
#[derive(Debug)]
pub struct CreateCanisterBuilder<'agent, 'canister: 'agent> {
canister: &'canister Canister<'agent>,
effective_canister_id: Principal,
controllers: Option<Result<Vec<Principal>, AgentError>>,
compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
log_visibility: Option<Result<LogVisibility, AgentError>>,
is_provisional_create: bool,
amount: Option<u128>,
specified_id: Option<Principal>,
}
impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> {
pub fn builder(canister: &'canister Canister<'agent>) -> Self {
Self {
canister,
effective_canister_id: Principal::management_canister(),
controllers: None,
compute_allocation: None,
memory_allocation: None,
freezing_threshold: None,
reserved_cycles_limit: None,
wasm_memory_limit: None,
log_visibility: None,
is_provisional_create: false,
amount: None,
specified_id: None,
}
}
#[allow(clippy::wrong_self_convention)]
pub fn as_provisional_create_with_amount(self, amount: Option<u128>) -> Self {
Self {
is_provisional_create: true,
amount,
..self
}
}
pub fn as_provisional_create_with_specified_id(self, specified_id: Principal) -> Self {
Self {
is_provisional_create: true,
specified_id: Some(specified_id),
effective_canister_id: specified_id,
..self
}
}
pub fn with_effective_canister_id<C, E>(self, effective_canister_id: C) -> Self
where
E: std::fmt::Display,
C: TryInto<Principal, Error = E>,
{
match effective_canister_id.try_into() {
Ok(effective_canister_id) => Self {
effective_canister_id,
..self
},
Err(_) => self,
}
}
pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<Principal, Error = E>,
{
let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
ca.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
});
let controllers: Option<Result<Vec<Principal>, _>> =
match (controller_to_add, self.controllers) {
(_, Some(Err(sticky))) => Some(Err(sticky)),
(Some(Err(e)), _) => Some(Err(e)),
(None, _) => None,
(Some(Ok(controller)), Some(Ok(controllers))) => {
let mut controllers = controllers;
controllers.push(controller);
Some(Ok(controllers))
}
(Some(Ok(controller)), None) => Some(Ok(vec![controller])),
};
Self {
controllers,
..self
}
}
pub fn with_controller<C, E>(self, controller: C) -> Self
where
E: std::fmt::Display,
C: TryInto<Principal, Error = E>,
{
self.with_optional_controller(Some(controller))
}
pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<ComputeAllocation, Error = E>,
{
Self {
compute_allocation: compute_allocation.map(|ca| {
ca.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
where
E: std::fmt::Display,
C: TryInto<ComputeAllocation, Error = E>,
{
self.with_optional_compute_allocation(Some(compute_allocation))
}
pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<MemoryAllocation, Error = E>,
{
Self {
memory_allocation: memory_allocation.map(|ma| {
ma.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
where
E: std::fmt::Display,
C: TryInto<MemoryAllocation, Error = E>,
{
self.with_optional_memory_allocation(Some(memory_allocation))
}
pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<FreezingThreshold, Error = E>,
{
Self {
freezing_threshold: freezing_threshold.map(|ma| {
ma.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
where
E: std::fmt::Display,
C: TryInto<FreezingThreshold, Error = E>,
{
self.with_optional_freezing_threshold(Some(freezing_threshold))
}
pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
where
E: std::fmt::Display,
C: TryInto<ReservedCyclesLimit, Error = E>,
{
self.with_optional_reserved_cycles_limit(Some(limit))
}
pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<ReservedCyclesLimit, Error = E>,
{
Self {
reserved_cycles_limit: limit.map(|limit| {
limit
.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
where
E: std::fmt::Display,
C: TryInto<WasmMemoryLimit, Error = E>,
{
self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
}
pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<WasmMemoryLimit, Error = E>,
{
Self {
wasm_memory_limit: wasm_memory_limit.map(|limit| {
limit
.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
where
E: std::fmt::Display,
C: TryInto<LogVisibility, Error = E>,
{
self.with_optional_log_visibility(Some(log_visibility))
}
pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<LogVisibility, Error = E>,
{
Self {
log_visibility: log_visibility.map(|visibility| {
visibility
.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = (Principal,)>, AgentError> {
let controllers = match self.controllers {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(x),
None => None,
};
let compute_allocation = match self.compute_allocation {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u8::from(x))),
None => None,
};
let memory_allocation = match self.memory_allocation {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u64::from(x))),
None => None,
};
let freezing_threshold = match self.freezing_threshold {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u64::from(x))),
None => None,
};
let reserved_cycles_limit = match self.reserved_cycles_limit {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u128::from(x))),
None => None,
};
let wasm_memory_limit = match self.wasm_memory_limit {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u64::from(x))),
None => None,
};
let log_visibility = match self.log_visibility {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(x),
None => None,
};
#[derive(Deserialize, CandidType)]
struct Out {
canister_id: Principal,
}
let async_builder = if self.is_provisional_create {
#[derive(CandidType)]
struct In {
amount: Option<Nat>,
settings: CanisterSettings,
specified_id: Option<Principal>,
}
let in_arg = In {
amount: self.amount.map(Nat::from),
settings: CanisterSettings {
controllers,
compute_allocation,
memory_allocation,
freezing_threshold,
reserved_cycles_limit,
wasm_memory_limit,
log_visibility,
},
specified_id: self.specified_id,
};
self.canister
.update(MgmtMethod::ProvisionalCreateCanisterWithCycles.as_ref())
.with_arg(in_arg)
.with_effective_canister_id(self.effective_canister_id)
} else {
self.canister
.update(MgmtMethod::CreateCanister.as_ref())
.with_arg(CanisterSettings {
controllers,
compute_allocation,
memory_allocation,
freezing_threshold,
reserved_cycles_limit,
wasm_memory_limit,
log_visibility,
})
.with_effective_canister_id(self.effective_canister_id)
};
Ok(async_builder
.build()
.map(|result: (Out,)| (result.0.canister_id,)))
}
pub async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
self.build()?.call().await
}
pub async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
self.build()?.call_and_wait().await
}
}
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> {
type Value = (Principal,);
async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
self.build()?.call().await
}
async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
self.build()?.call_and_wait().await
}
}
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)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Hash, CandidType, Copy)]
pub enum WasmMemoryPersistence {
Keep,
Replace,
}
#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
pub struct CanisterUpgradeOptions {
pub skip_pre_upgrade: Option<bool>,
pub wasm_memory_persistence: Option<WasmMemoryPersistence>,
}
#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
pub enum InstallMode {
#[serde(rename = "install")]
Install,
#[serde(rename = "reinstall")]
Reinstall,
#[serde(rename = "upgrade")]
Upgrade(Option<CanisterUpgradeOptions>),
}
#[derive(Debug, Clone, CandidType, Deserialize)]
pub struct CanisterInstall {
pub mode: InstallMode,
pub canister_id: Principal,
#[serde(with = "serde_bytes")]
pub wasm_module: Vec<u8>,
#[serde(with = "serde_bytes")]
pub arg: Vec<u8>,
}
impl FromStr for InstallMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"install" => Ok(InstallMode::Install),
"reinstall" => Ok(InstallMode::Reinstall),
"upgrade" => Ok(InstallMode::Upgrade(None)),
&_ => Err(format!("Invalid install mode: {}", s)),
}
}
}
#[derive(Debug)]
pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
canister: &'canister Canister<'agent>,
canister_id: Principal,
wasm: &'canister [u8],
arg: Argument,
mode: Option<InstallMode>,
}
impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
pub fn builder(
canister: &'canister Canister<'agent>,
canister_id: &Principal,
wasm: &'canister [u8],
) -> Self {
Self {
canister,
canister_id: *canister_id,
wasm,
arg: Default::default(),
mode: None,
}
}
pub fn with_arg<Argument: CandidType>(
mut self,
arg: Argument,
) -> InstallCodeBuilder<'agent, 'canister> {
self.arg.set_idl_arg(arg);
self
}
pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
assert!(self.arg.0.is_none(), "argument is being set more than once");
self.arg = Argument::from_candid(tuple);
self
}
pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
self.arg.set_raw_arg(arg);
self
}
pub fn with_mode(self, mode: InstallMode) -> Self {
Self {
mode: Some(mode),
..self
}
}
pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
Ok(self
.canister
.update(MgmtMethod::InstallCode.as_ref())
.with_arg(CanisterInstall {
mode: self.mode.unwrap_or(InstallMode::Install),
canister_id: self.canister_id,
wasm_module: self.wasm.to_owned(),
arg: self.arg.serialize()?,
})
.with_effective_canister_id(self.canister_id)
.build())
}
pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
self.build()?.call().await
}
pub async fn call_and_wait(self) -> Result<(), AgentError> {
self.build()?.call_and_wait().await
}
}
#[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> {
type Value = ();
async fn call(self) -> Result<CallResponse<()>, AgentError> {
self.build()?.call().await
}
async fn call_and_wait(self) -> Result<(), AgentError> {
self.build()?.call_and_wait().await
}
}
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)
}
}
#[derive(Debug)]
pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
canister: &'canister Canister<'agent>,
target_canister: Principal,
store_canister: Option<Principal>,
chunk_hashes_list: Vec<ChunkHash>,
wasm_module_hash: Vec<u8>,
arg: Argument,
mode: InstallMode,
}
impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
pub fn builder(
canister: &'canister Canister<'agent>,
target_canister: Principal,
wasm_module_hash: &[u8],
) -> Self {
Self {
canister,
target_canister,
wasm_module_hash: wasm_module_hash.to_vec(),
store_canister: None,
chunk_hashes_list: vec![],
arg: Argument::new(),
mode: InstallMode::Install,
}
}
pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
self.chunk_hashes_list = chunk_hashes;
self
}
pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
self.store_canister = Some(store_canister);
self
}
pub fn with_arg(mut self, argument: impl CandidType) -> Self {
self.arg.set_idl_arg(argument);
self
}
pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
assert!(self.arg.0.is_none(), "argument is being set more than once");
self.arg = Argument::from_candid(argument);
self
}
pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
self.arg.set_raw_arg(argument);
self
}
pub fn with_install_mode(mut self, mode: InstallMode) -> Self {
self.mode = mode;
self
}
pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
#[derive(CandidType)]
struct In {
mode: InstallMode,
target_canister: Principal,
store_canister: Option<Principal>,
chunk_hashes_list: Vec<ChunkHash>,
wasm_module_hash: Vec<u8>,
arg: Vec<u8>,
sender_canister_version: Option<u64>,
}
let Self {
mode,
target_canister,
store_canister,
chunk_hashes_list,
wasm_module_hash,
arg,
..
} = self;
Ok(self
.canister
.update(MgmtMethod::InstallChunkedCode.as_ref())
.with_arg(In {
mode,
target_canister,
store_canister,
chunk_hashes_list,
wasm_module_hash,
arg: arg.serialize()?,
sender_canister_version: None,
})
.with_effective_canister_id(target_canister)
.build())
}
pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
self.build()?.call().await
}
pub async fn call_and_wait(self) -> Result<(), AgentError> {
self.build()?.call_and_wait().await
}
}
#[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> {
type Value = ();
async fn call(self) -> Result<CallResponse<()>, AgentError> {
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)
}
}
#[derive(Debug)]
pub struct InstallBuilder<'agent, 'canister, 'builder> {
canister: &'canister ManagementCanister<'agent>,
canister_id: Principal,
wasm: &'builder [u8],
arg: Argument,
mode: InstallMode,
}
impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
pub fn builder(
canister: &'canister ManagementCanister<'agent>,
canister_id: &Principal,
wasm: &'builder [u8],
) -> Self {
Self {
canister,
canister_id: *canister_id,
wasm,
arg: Default::default(),
mode: InstallMode::Install,
}
}
pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
self.arg.set_idl_arg(arg);
self
}
pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
assert!(self.arg.0.is_none(), "argument is being set more than once");
self.arg = Argument::from_candid(tuple);
self
}
pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
self.arg.set_raw_arg(arg);
self
}
pub fn with_mode(self, mode: InstallMode) -> Self {
Self { mode, ..self }
}
pub async fn call_and_wait(self) -> Result<(), AgentError> {
self.call_and_wait_with_progress()
.await
.try_for_each(|_| ready(Ok(())))
.await
}
pub async fn call_and_wait_with_progress(
self,
) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
let stream_res = async move {
let arg = self.arg.serialize()?;
let stream: BoxStream<'_, _> =
if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
Box::pin(
async move {
self.canister
.install_code(&self.canister_id, self.wasm)
.with_raw_arg(arg)
.with_mode(self.mode)
.call_and_wait()
.await
}
.into_stream(),
)
} else {
let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
let mut to_upload_chunks = vec![];
for (hash, chunk) in &all_chunks {
if !existing_chunks.contains(hash) {
to_upload_chunks.push(*chunk);
}
}
let upload_chunks_stream = FuturesUnordered::new();
for chunk in to_upload_chunks {
upload_chunks_stream.push(async move {
let (_res,) = self
.canister
.upload_chunk(&self.canister_id, chunk)
.call_and_wait()
.await?;
Ok(())
})
}
let install_chunked_code_stream = async move {
let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
self.canister
.install_chunked_code(
&self.canister_id,
&Sha256::digest(self.wasm),
)
.with_chunk_hashes(results)
.with_raw_arg(arg)
.with_install_mode(self.mode)
.call_and_wait()
.await
}
.into_stream();
let clear_store_stream = async move {
self.canister
.clear_chunk_store(&self.canister_id)
.call_and_wait()
.await
}
.into_stream();
Box::pin(
upload_chunks_stream
.chain(install_chunked_code_stream)
.chain(clear_store_stream ),
)
};
Ok(stream)
}.await;
match stream_res {
Ok(stream) => stream,
Err(err) => Box::pin(stream::once(async { Err(err) })),
}
}
}
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())
}
}
#[derive(Debug)]
pub struct UpdateCanisterBuilder<'agent, 'canister: 'agent> {
canister: &'canister Canister<'agent>,
canister_id: Principal,
controllers: Option<Result<Vec<Principal>, AgentError>>,
compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
log_visibility: Option<Result<LogVisibility, AgentError>>,
}
impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> {
pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
Self {
canister,
canister_id: *canister_id,
controllers: None,
compute_allocation: None,
memory_allocation: None,
freezing_threshold: None,
reserved_cycles_limit: None,
wasm_memory_limit: None,
log_visibility: None,
}
}
pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<Principal, Error = E>,
{
let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
ca.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
});
let controllers: Option<Result<Vec<Principal>, _>> =
match (controller_to_add, self.controllers) {
(_, Some(Err(sticky))) => Some(Err(sticky)),
(Some(Err(e)), _) => Some(Err(e)),
(None, _) => None,
(Some(Ok(controller)), Some(Ok(controllers))) => {
let mut controllers = controllers;
controllers.push(controller);
Some(Ok(controllers))
}
(Some(Ok(controller)), None) => Some(Ok(vec![controller])),
};
Self {
controllers,
..self
}
}
pub fn with_controller<C, E>(self, controller: C) -> Self
where
E: std::fmt::Display,
C: TryInto<Principal, Error = E>,
{
self.with_optional_controller(Some(controller))
}
pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<ComputeAllocation, Error = E>,
{
Self {
compute_allocation: compute_allocation.map(|ca| {
ca.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
where
E: std::fmt::Display,
C: TryInto<ComputeAllocation, Error = E>,
{
self.with_optional_compute_allocation(Some(compute_allocation))
}
pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<MemoryAllocation, Error = E>,
{
Self {
memory_allocation: memory_allocation.map(|ma| {
ma.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
where
E: std::fmt::Display,
C: TryInto<MemoryAllocation, Error = E>,
{
self.with_optional_memory_allocation(Some(memory_allocation))
}
pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<FreezingThreshold, Error = E>,
{
Self {
freezing_threshold: freezing_threshold.map(|ma| {
ma.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
where
E: std::fmt::Display,
C: TryInto<FreezingThreshold, Error = E>,
{
self.with_optional_freezing_threshold(Some(freezing_threshold))
}
pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
where
E: std::fmt::Display,
C: TryInto<ReservedCyclesLimit, Error = E>,
{
self.with_optional_reserved_cycles_limit(Some(limit))
}
pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<ReservedCyclesLimit, Error = E>,
{
Self {
reserved_cycles_limit: limit.map(|ma| {
ma.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
where
E: std::fmt::Display,
C: TryInto<WasmMemoryLimit, Error = E>,
{
self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
}
pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<WasmMemoryLimit, Error = E>,
{
Self {
wasm_memory_limit: wasm_memory_limit.map(|limit| {
limit
.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
where
E: std::fmt::Display,
C: TryInto<LogVisibility, Error = E>,
{
self.with_optional_log_visibility(Some(log_visibility))
}
pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
where
E: std::fmt::Display,
C: TryInto<LogVisibility, Error = E>,
{
Self {
log_visibility: log_visibility.map(|limit| {
limit
.try_into()
.map_err(|e| AgentError::MessageError(format!("{}", e)))
}),
..self
}
}
pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
#[derive(CandidType)]
struct In {
canister_id: Principal,
settings: CanisterSettings,
}
let controllers = match self.controllers {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(x),
None => None,
};
let compute_allocation = match self.compute_allocation {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u8::from(x))),
None => None,
};
let memory_allocation = match self.memory_allocation {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u64::from(x))),
None => None,
};
let freezing_threshold = match self.freezing_threshold {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u64::from(x))),
None => None,
};
let reserved_cycles_limit = match self.reserved_cycles_limit {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u128::from(x))),
None => None,
};
let wasm_memory_limit = match self.wasm_memory_limit {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(Nat::from(u64::from(x))),
None => None,
};
let log_visibility = match self.log_visibility {
Some(Err(x)) => return Err(AgentError::MessageError(format!("{}", x))),
Some(Ok(x)) => Some(x),
None => None,
};
Ok(self
.canister
.update(MgmtMethod::UpdateSettings.as_ref())
.with_arg(In {
canister_id: self.canister_id,
settings: CanisterSettings {
controllers,
compute_allocation,
memory_allocation,
freezing_threshold,
reserved_cycles_limit,
wasm_memory_limit,
log_visibility,
},
})
.with_effective_canister_id(self.canister_id)
.build())
}
pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
self.build()?.call().await
}
pub async fn call_and_wait(self) -> Result<(), AgentError> {
self.build()?.call_and_wait().await
}
}
#[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> {
type Value = ();
async fn call(self) -> Result<CallResponse<()>, AgentError> {
self.build()?.call().await
}
async fn call_and_wait(self) -> Result<(), AgentError> {
self.build()?.call_and_wait().await
}
}
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<Box<dyn Stream<Item = T> + Send + 'a>>;
#[cfg(target_family = "wasm")]
type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;