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}