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