ic_test/icp/
deployer.rs

1use candid::Principal;
2use ic_cdk::api::management_canister::main::{CanisterInstallMode, CanisterSettings};
3use thiserror::Error;
4
5use super::{
6    caller::Caller,
7    provider::{Provider, RejectResponse},
8};
9
10#[derive(Debug, Error)]
11pub enum DeployError {
12    #[error("failed to candid encode arguments: {}", .0)]
13    ArgumentEncoding(candid::error::Error),
14    #[error("canister rejected: {}, error_code: {}", .0.reject_message, .0.error_code)]
15    Reject(RejectResponse),
16    #[error("failed to candid decode result: {}", .0)]
17    ResultDecoding(candid::error::Error),
18    #[error("canister creation failed: {}", .0)]
19    CreateCanister(String),
20    #[error("canister id is missing")]
21    UnspecifiedCanister,
22}
23
24pub enum DeployMode {
25    Create,
26    Install,
27    Reinstall,
28    Upgrade,
29}
30
31pub trait Deployer {
32    type Caller: Caller;
33
34    fn deploy<Canister>(
35        &self,
36        args: Result<Vec<u8>, candid::error::Error>,
37        new: fn(&Self::Caller, Principal) -> Canister,
38    ) -> DeployBuilder<Canister, Self::Caller>;
39}
40
41pub struct DeployBuilder<Canister, C: Caller> {
42    pub provider: C::Provider,
43    pub caller: C,
44    pub canister_id: Option<Principal>,
45    pub mode: DeployMode,
46    pub settings: CanisterSettings,
47    pub cycles: u128,
48    pub wasm: Vec<u8>,
49    pub args: Result<Vec<u8>, candid::error::Error>,
50    pub new: fn(&C, Principal) -> Canister,
51}
52
53impl<Canister, C: Caller> DeployBuilder<Canister, C> {
54    pub fn with_canister_id(self, canister_id: Principal) -> Self {
55        Self {
56            canister_id: Some(canister_id),
57            ..self
58        }
59    }
60
61    pub fn with_controllers(self, controllers: Vec<Principal>) -> Self {
62        Self {
63            settings: CanisterSettings {
64                controllers: Some(controllers.clone()),
65                ..self.settings
66            },
67            ..self
68        }
69    }
70
71    pub fn with_cycles(self, cycles: u128) -> Self {
72        Self { cycles, ..self }
73    }
74
75    pub fn with_settings(self, settings: CanisterSettings) -> Self {
76        Self { settings, ..self }
77    }
78
79    pub fn with_wasm(self, wasm: Vec<u8>) -> Self {
80        Self { wasm, ..self }
81    }
82
83    pub fn with_install(self) -> Self {
84        Self {
85            mode: DeployMode::Install,
86            ..self
87        }
88    }
89
90    pub fn with_upgrade(self) -> Self {
91        Self {
92            mode: DeployMode::Upgrade,
93            ..self
94        }
95    }
96
97    pub fn with_reinstall(self) -> Self {
98        Self {
99            mode: DeployMode::Reinstall,
100            ..self
101        }
102    }
103
104    pub async fn maybe_call(self) -> Result<Canister, DeployError> {
105        let args = self.args.map_err(DeployError::ArgumentEncoding)?;
106
107        let canister_id = if let DeployMode::Create = self.mode {
108            self.provider
109                .create_canister(self.settings, self.canister_id)
110                .await
111                .map_err(DeployError::Reject)?
112        } else {
113            match self.canister_id {
114                Some(canister_id) => canister_id,
115                None => {
116                    return Err(DeployError::UnspecifiedCanister);
117                }
118            }
119        };
120
121        self.provider
122            .add_cycles(canister_id, self.cycles)
123            .await
124            .map_err(DeployError::Reject)?;
125
126        let mode = match self.mode {
127            DeployMode::Create | DeployMode::Install => CanisterInstallMode::Install,
128            DeployMode::Reinstall => CanisterInstallMode::Reinstall,
129            DeployMode::Upgrade => CanisterInstallMode::Upgrade(None),
130        };
131
132        self.provider
133            .install_code(mode, canister_id, self.wasm, args)
134            .await
135            .map_err(DeployError::Reject)?;
136
137        Ok((self.new)(&self.caller, canister_id))
138    }
139
140    pub async fn call(self) -> Canister {
141        self.maybe_call().await.unwrap()
142    }
143}