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 pub fn new(config: Config) -> Result<Self> {
83 debug!("Configuration: {:?}", config);
84 let mut s = Self::default();
85 s.set_config(config)?;
86 Ok(s)
87 }
88
89 pub async fn set_network(&mut self, network_key: String) -> Result<()> {
91 debug!("Selected network_key: {:?}", network_key.clone());
92
93 let Some(network) = self.networks.iter().find(|network| network.key == network_key) else {
94 return Err(crate::Error::NetworkUnavailable(network_key));
95 };
96
97 debug!("Selected Network: {:?}", network);
98 self.active_network = Some(network.clone());
99
100 Ok(())
101 }
102
103 pub fn set_networks(&mut self, networks: Vec<ApiNetwork>) {
105 self.networks = networks;
106 }
107
108 pub async fn get_networks(&mut self) -> Result<Vec<ApiNetwork>> {
110 if self.networks.is_empty() {
111 if self.access_token.is_none() {
112 return Err(crate::Error::MissingNetwork);
113 }
114
115 let result = self.get_networks_backend().await;
116 match result {
117 Ok(n) => {
118 self.networks = n.clone();
119 }
120 Err(e) => Err(e)?,
121 }
122 }
123
124 Ok(self.networks.clone())
125 }
126
127 async fn get_networks_backend(&self) -> Result<Vec<ApiNetwork>> {
129 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
130 let access_token = self
131 .access_token
132 .as_ref()
133 .ok_or(crate::error::Error::MissingAccessToken)?;
134 let backend_networks = get_networks(config, access_token).await?;
135
136 Ok(backend_networks)
137 }
138
139 async fn try_get_active_user_wallet(&mut self, pin: &EncryptionPin) -> Result<WalletBorrow<'_>> {
146 let Some(repo) = &mut self.repo else {
147 return Err(crate::Error::UserRepoNotInitialized);
148 };
149 let Some(active_user) = &mut self.active_user else {
150 return Err(crate::Error::UserNotInitialized);
151 };
152 let network = self.active_network.as_ref().ok_or(crate::Error::MissingNetwork)?;
153 let config = self.config.as_mut().ok_or(crate::Error::MissingConfig)?;
154 let wallet = active_user
155 .wallet_manager
156 .try_get(config, &self.access_token, repo, network, pin)
157 .await?;
158 Ok(wallet)
159 }
160
161 pub fn get_build_info() -> String {
168 build::CLAP_LONG_VERSION.to_string()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use crate::core::core_testing_utils::handle_error_test_cases;
175 use crate::testing_utils::IOTA_NETWORK_KEY;
176 use crate::{
177 core::Sdk,
178 error::Result,
179 testing_utils::{PIN, USERNAME, example_wallet_borrow, set_config},
180 user::MockUserRepo,
181 wallet::wallet::MockWalletUser,
182 wallet_manager::WalletBorrow,
183 };
184 use api_types::api::dlt::ApiGetNetworksResponse;
185 use api_types::api::networks::ApiNetwork;
186 use rstest::rstest;
187
188 use crate::{
189 testing_utils::{AUTH_PROVIDER, HEADER_X_APP_NAME, TOKEN, example_api_networks},
190 wallet_manager::MockWalletManager,
191 };
192
193 #[rstest]
194 #[case::success(Ok(WalletBorrow::from(MockWalletUser::new())))]
195 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
196 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
197 #[case::missing_network(Err(crate::Error::MissingNetwork))]
198 #[case::missing_config(Err(crate::Error::MissingConfig))]
199 #[tokio::test]
200 async fn test_try_get_active_user_wallet(#[case] expected: Result<WalletBorrow<'_>>) {
201 let (_srv, config, _cleanup) = set_config().await;
203 let mut sdk = Sdk::new(config).unwrap();
204
205 match &expected {
206 Ok(_) => {
207 sdk.repo = Some(Box::new(MockUserRepo::new()));
208 let mock_wallet_manager = example_wallet_borrow();
209 sdk.active_user = Some(crate::types::users::ActiveUser {
210 username: USERNAME.into(),
211 wallet_manager: Box::new(mock_wallet_manager),
212 });
213 sdk.set_networks(example_api_networks());
214 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
215 }
216 Err(error) => {
217 handle_error_test_cases(error, &mut sdk, 0, 0).await;
218 }
219 }
220
221 let response = Sdk::try_get_active_user_wallet(&mut sdk, &PIN).await;
223
224 match expected {
226 Ok(_wallet_borrow) => {
227 response.unwrap();
228 }
229 Err(ref expected_err) => {
230 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
231 }
232 }
233 }
234
235 #[test]
236 fn test_get_build_info() {
237 let build_info = Sdk::get_build_info();
238 assert!(!build_info.is_empty());
239 println!("{build_info}");
240 }
241
242 #[rstest]
243 #[case::success(Ok(example_api_networks()))]
244 #[case::missing_config(Err(crate::Error::MissingConfig))]
245 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
246 #[tokio::test]
247 async fn test_get_networks_backend(#[case] expected: Result<Vec<ApiNetwork>>) {
248 let (mut srv, config, _cleanup) = set_config().await;
250 let mut sdk = Sdk::new(config).unwrap();
251 let mut mock_server = None;
252
253 match &expected {
254 Ok(_) => {
255 sdk.active_user = Some(crate::types::users::ActiveUser {
256 username: USERNAME.into(),
257 wallet_manager: Box::new(MockWalletManager::new()),
258 });
259 sdk.access_token = Some(TOKEN.clone());
260
261 let resp_body = ApiGetNetworksResponse {
262 networks: example_api_networks(),
263 };
264 let mock_body_response = serde_json::to_string(&resp_body).unwrap();
265
266 mock_server = Some(
267 srv.mock("GET", "/api/config/networks")
268 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
269 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
270 .with_status(200)
271 .with_header("content-type", "application/json")
272 .with_body(&mock_body_response)
273 .expect(1)
274 .create(),
275 );
276 }
277 Err(error) => {
278 handle_error_test_cases(error, &mut sdk, 0, 0).await;
279 }
280 }
281
282 let response = Sdk::get_networks_backend(&sdk).await;
284
285 match expected {
287 Ok(resp) => {
288 assert_eq!(response.unwrap(), resp);
289 }
290 Err(ref expected_err) => {
291 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
292 }
293 }
294 if let Some(m) = mock_server {
295 m.assert();
296 }
297 }
298}