1pub mod config;
5
6#[cfg(feature = "postident")]
8pub mod postident;
9
10pub mod transaction;
12
13pub mod user;
15pub mod viviswap;
17pub mod wallet;
19
20pub mod exchange;
22
23pub mod share;
25
26#[cfg(test)]
28pub(crate) mod core_testing_utils;
29
30use crate::backend::dlt::get_networks;
31use crate::build;
32use crate::error::Result;
33use crate::types::newtypes::{AccessToken, EncryptionPin};
34use crate::types::users::ActiveUser;
35use crate::user::UserRepo;
36use crate::wallet_manager::WalletBorrow;
37use api_types::api::networks::ApiNetwork;
38pub use config::Config;
39use log::debug;
40
41pub(crate) type UserRepoT = Box<dyn UserRepo + Send + Sync + 'static>;
42
43pub struct Sdk {
45 config: Option<Config>,
47 active_user: Option<ActiveUser>,
49 access_token: Option<AccessToken>,
51 repo: Option<UserRepoT>,
53 active_network: Option<ApiNetwork>,
55 networks: Vec<ApiNetwork>,
57}
58
59impl Drop for Sdk {
60 fn drop(&mut self) {
62 debug!("Dropping SDK");
63 }
64}
65
66impl Default for Sdk {
67 fn default() -> Self {
69 Self {
70 config: None,
71 active_user: None,
72 access_token: None,
73 repo: None,
74 active_network: None,
75 networks: vec![],
76 }
77 }
78}
79
80impl Sdk {
81 #[allow(clippy::result_large_err)]
83 pub fn new(config: Config) -> Result<Self> {
84 debug!("Configuration: {:?}", config);
85 let mut s = Self::default();
86 s.set_config(config)?;
87 Ok(s)
88 }
89
90 pub async fn set_network(&mut self, network_key: String) -> Result<()> {
92 debug!("Selected network_key: {:?}", network_key.clone());
93
94 let Some(network) = self.networks.iter().find(|network| network.key == network_key) else {
95 return Err(crate::Error::NetworkUnavailable(network_key));
96 };
97
98 debug!("Selected Network: {:?}", network);
99 self.active_network = Some(network.clone());
100
101 Ok(())
102 }
103
104 pub fn set_networks(&mut self, networks: Vec<ApiNetwork>) {
106 self.networks = networks;
107 }
108
109 pub async fn get_networks(&mut self) -> Result<Vec<ApiNetwork>> {
111 if self.networks.is_empty() {
112 if self.access_token.is_none() {
113 return Err(crate::Error::MissingAccessToken);
114 }
115
116 let result = self.get_networks_backend().await;
117 match result {
118 Ok(n) => {
119 self.networks = n.clone();
120 }
121 Err(e) => Err(e)?,
122 }
123 }
124
125 Ok(self.networks.clone())
126 }
127
128 async fn get_networks_backend(&self) -> Result<Vec<ApiNetwork>> {
130 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
131 let access_token = self
132 .access_token
133 .as_ref()
134 .ok_or(crate::error::Error::MissingAccessToken)?;
135 let backend_networks = get_networks(config, access_token).await?;
136
137 Ok(backend_networks)
138 }
139
140 async fn try_get_active_user_wallet(&mut self, pin: &EncryptionPin) -> Result<WalletBorrow<'_>> {
147 let Some(repo) = &mut self.repo else {
148 return Err(crate::Error::UserRepoNotInitialized);
149 };
150 let Some(active_user) = &mut self.active_user else {
151 return Err(crate::Error::UserNotInitialized);
152 };
153 let network = self.active_network.as_ref().ok_or(crate::Error::MissingNetwork)?;
154 let config = self.config.as_mut().ok_or(crate::Error::MissingConfig)?;
155 let wallet = active_user
156 .wallet_manager
157 .try_get(
158 config,
159 &self.access_token,
160 repo,
161 network,
162 pin,
163 &active_user.mnemonic_derivation_options,
164 )
165 .await?;
166 Ok(wallet)
167 }
168
169 pub fn get_build_info() -> String {
176 build::CLAP_LONG_VERSION.to_string()
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use crate::core::core_testing_utils::handle_error_test_cases;
183 use crate::testing_utils::IOTA_NETWORK_KEY;
184 use crate::{
185 core::Sdk,
186 error::Result,
187 testing_utils::{PIN, USERNAME, example_wallet_borrow, set_config},
188 user::MockUserRepo,
189 wallet_manager::WalletBorrow,
190 };
191 use api_types::api::dlt::ApiGetNetworksResponse;
192 use api_types::api::networks::ApiNetwork;
193 use etopay_wallet::MockWalletUser;
194 use rstest::rstest;
195
196 use crate::{
197 testing_utils::{AUTH_PROVIDER, HEADER_X_APP_NAME, TOKEN, example_api_networks},
198 wallet_manager::MockWalletManager,
199 };
200
201 #[rstest]
202 #[case::success(Ok(WalletBorrow::from(MockWalletUser::new())))]
203 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
204 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
205 #[case::missing_network(Err(crate::Error::MissingNetwork))]
206 #[case::missing_config(Err(crate::Error::MissingConfig))]
207 #[tokio::test]
208 async fn test_try_get_active_user_wallet(#[case] expected: Result<WalletBorrow<'_>>) {
209 let (_srv, config, _cleanup) = set_config().await;
211 let mut sdk = Sdk::new(config).unwrap();
212
213 match &expected {
214 Ok(_) => {
215 sdk.repo = Some(Box::new(MockUserRepo::new()));
216 let mock_wallet_manager = example_wallet_borrow();
217 sdk.active_user = Some(crate::types::users::ActiveUser {
218 username: USERNAME.into(),
219 wallet_manager: Box::new(mock_wallet_manager),
220 mnemonic_derivation_options: Default::default(),
221 });
222 sdk.set_networks(example_api_networks());
223 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
224 }
225 Err(error) => {
226 handle_error_test_cases(error, &mut sdk, 0, 0).await;
227 }
228 }
229
230 let response = Sdk::try_get_active_user_wallet(&mut sdk, &PIN).await;
232
233 match expected {
235 Ok(_wallet_borrow) => {
236 response.unwrap();
237 }
238 Err(ref expected_err) => {
239 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
240 }
241 }
242 }
243
244 #[test]
245 fn test_get_build_info() {
246 let build_info = Sdk::get_build_info();
247 assert!(!build_info.is_empty());
248 println!("{build_info}");
249 }
250
251 #[rstest]
252 #[case::success(Ok(example_api_networks()))]
253 #[case::missing_config(Err(crate::Error::MissingConfig))]
254 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
255 #[tokio::test]
256 async fn test_get_networks_backend(#[case] expected: Result<Vec<ApiNetwork>>) {
257 let (mut srv, config, _cleanup) = set_config().await;
259 let mut sdk = Sdk::new(config).unwrap();
260 let mut mock_server = None;
261
262 match &expected {
263 Ok(_) => {
264 sdk.active_user = Some(crate::types::users::ActiveUser {
265 username: USERNAME.into(),
266 wallet_manager: Box::new(MockWalletManager::new()),
267 mnemonic_derivation_options: Default::default(),
268 });
269 sdk.access_token = Some(TOKEN.clone());
270
271 let resp_body = ApiGetNetworksResponse {
272 networks: example_api_networks(),
273 };
274 let mock_body_response = serde_json::to_string(&resp_body).unwrap();
275
276 mock_server = Some(
277 srv.mock("GET", "/api/config/networks")
278 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
279 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
280 .with_status(200)
281 .with_header("content-type", "application/json")
282 .with_body(&mock_body_response)
283 .expect(1)
284 .create(),
285 );
286 }
287 Err(error) => {
288 handle_error_test_cases(error, &mut sdk, 0, 0).await;
289 }
290 }
291
292 let response = Sdk::get_networks_backend(&sdk).await;
294
295 match expected {
297 Ok(resp) => {
298 assert_eq!(response.unwrap(), resp);
299 }
300 Err(ref expected_err) => {
301 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
302 }
303 }
304 if let Some(m) = mock_server {
305 m.assert();
306 }
307 }
308}