1use candid::Principal;
14use ic_cdk::management_canister::{CanisterInstallMode, CanisterSettings};
15use thiserror::Error;
16
17use super::{
18 caller::Caller,
19 provider::{Provider, RejectResponse},
20};
21
22#[derive(Debug, Error)]
24pub enum DeployError {
25 #[error("failed to candid encode arguments: {}", .0)]
26 ArgumentEncoding(candid::error::Error),
27 #[error("canister rejected: {}, error_code: {}", .0.reject_message, .0.error_code)]
28 Reject(RejectResponse),
29 #[error("failed to candid decode result: {}", .0)]
30 ResultDecoding(candid::error::Error),
31 #[error("canister creation failed: {}", .0)]
32 CreateCanister(String),
33 #[error("canister id is missing")]
34 UnspecifiedCanister,
35}
36
37pub enum DeployMode {
39 Create,
41
42 Install,
44
45 Reinstall,
47
48 Upgrade,
50}
51
52pub trait Deployer {
56 type Caller: Caller;
57
58 fn deploy<Canister>(
61 &self,
62 args: Result<Vec<u8>, candid::error::Error>,
63 new: fn(&Self::Caller, Principal) -> Canister,
64 ) -> DeployBuilder<Canister, Self::Caller>;
65}
66
67pub struct DeployBuilder<Canister, C: Caller> {
76 pub provider: C::Provider,
78 pub caller: C,
80 pub canister_id: Option<Principal>,
82 pub mode: DeployMode,
84 pub settings: CanisterSettings,
86 pub cycles: u128,
88 pub wasm: Vec<u8>,
90 pub args: Result<Vec<u8>, candid::error::Error>,
92 pub new: fn(&C, Principal) -> Canister,
94}
95
96impl<Canister, C: Caller> DeployBuilder<Canister, C> {
97 pub fn with_canister_id(self, canister_id: Principal) -> Self {
98 Self {
99 canister_id: Some(canister_id),
100 ..self
101 }
102 }
103
104 pub fn with_controllers(self, controllers: Vec<Principal>) -> Self {
105 Self {
106 settings: CanisterSettings {
107 controllers: Some(controllers.clone()),
108 ..self.settings
109 },
110 ..self
111 }
112 }
113
114 pub fn with_cycles(self, cycles: u128) -> Self {
115 Self { cycles, ..self }
116 }
117
118 pub fn with_settings(self, settings: CanisterSettings) -> Self {
119 Self { settings, ..self }
120 }
121
122 pub fn with_wasm(self, wasm: Vec<u8>) -> Self {
123 Self { wasm, ..self }
124 }
125
126 pub fn with_install(self) -> Self {
127 Self {
128 mode: DeployMode::Install,
129 ..self
130 }
131 }
132
133 pub fn with_upgrade(self) -> Self {
134 Self {
135 mode: DeployMode::Upgrade,
136 ..self
137 }
138 }
139
140 pub fn with_reinstall(self) -> Self {
141 Self {
142 mode: DeployMode::Reinstall,
143 ..self
144 }
145 }
146
147 pub async fn maybe_call(self) -> Result<Canister, DeployError> {
149 let args = self.args.map_err(DeployError::ArgumentEncoding)?;
150
151 let canister_id = if let DeployMode::Create = self.mode {
152 self.provider
153 .create_canister(self.settings, self.canister_id)
154 .await
155 .map_err(DeployError::Reject)?
156 } else {
157 match self.canister_id {
158 Some(canister_id) => canister_id,
159 None => {
160 return Err(DeployError::UnspecifiedCanister);
161 }
162 }
163 };
164
165 self.provider
166 .add_cycles(canister_id, self.cycles)
167 .await
168 .map_err(DeployError::Reject)?;
169
170 let mode = match self.mode {
171 DeployMode::Create | DeployMode::Install => CanisterInstallMode::Install,
172 DeployMode::Reinstall => CanisterInstallMode::Reinstall,
173 DeployMode::Upgrade => CanisterInstallMode::Upgrade(None),
174 };
175
176 self.provider
177 .install_code(mode, canister_id, self.wasm, args)
178 .await
179 .map_err(DeployError::Reject)?;
180
181 Ok((self.new)(&self.caller, canister_id))
182 }
183
184 pub async fn call(self) -> Canister {
186 self.maybe_call().await.unwrap()
187 }
188}