posemesh_domain_http/
lib.rs

1use futures::channel::mpsc::Receiver;
2
3use crate::domain_data::{
4    DomainData, DomainDataMetadata, DownloadQuery, delete_by_id, download_by_id,
5    download_metadata_v1, download_v1_stream,
6};
7#[cfg(target_family = "wasm")]
8use crate::domain_data::{UploadDomainData, upload_v1};
9
10mod auth;
11pub mod config;
12pub mod discovery;
13pub mod domain_data;
14#[cfg(target_family = "wasm")]
15pub mod wasm;
16
17use crate::auth::TokenCache;
18use crate::discovery::{DiscoveryService, DomainWithServer};
19#[derive(Debug, Clone)]
20pub struct DomainClient {
21    discovery_client: DiscoveryService,
22    pub client_id: String,
23}
24
25impl DomainClient {
26    pub fn new(api_url: &str, dds_url: &str, client_id: &str) -> Self {
27        if client_id.is_empty() {
28            panic!("client_id is empty");
29        }
30        Self {
31            discovery_client: DiscoveryService::new(api_url, dds_url, client_id),
32            client_id: client_id.to_string(),
33        }
34    }
35
36    pub async fn new_with_app_credential(
37        api_url: &str,
38        dds_url: &str,
39        client_id: &str,
40        app_key: &str,
41        app_secret: &str,
42    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
43        let mut dc = DomainClient::new(api_url, dds_url, client_id);
44        let _ = dc
45            .discovery_client
46            .sign_in_as_auki_app(app_key, app_secret)
47            .await?;
48        Ok(dc)
49    }
50
51    pub async fn new_with_user_credential(
52        api_url: &str,
53        dds_url: &str,
54        client_id: &str,
55        email: &str,
56        password: &str,
57        remember_password: bool,
58    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
59        let mut dc = DomainClient::new(api_url, dds_url, client_id);
60        let _ = dc
61            .discovery_client
62            .sign_in_with_auki_account(email, password, remember_password)
63            .await?;
64        Ok(dc)
65    }
66
67    pub fn with_oidc_access_token(&self, token: &str) -> Self {
68        Self {
69            discovery_client: self.discovery_client.with_oidc_access_token(token),
70            client_id: self.client_id.clone(),
71        }
72    }
73
74    pub async fn download_domain_data(
75        &self,
76        domain_id: &str,
77        query: &DownloadQuery,
78    ) -> Result<
79        Receiver<Result<DomainData, Box<dyn std::error::Error + Send + Sync>>>,
80        Box<dyn std::error::Error + Send + Sync>,
81    > {
82        let domain = self.discovery_client.auth_domain(domain_id).await?;
83        let rx = download_v1_stream(
84            &domain.domain.domain_server.url,
85            &self.client_id,
86            &domain.get_access_token(),
87            domain_id,
88            query,
89        )
90        .await?;
91        Ok(rx)
92    }
93
94    #[cfg(not(target_family = "wasm"))]
95    pub async fn upload_domain_data(
96        &self,
97        domain_id: &str,
98        data: Receiver<domain_data::UploadDomainData>,
99    ) -> Result<Vec<DomainDataMetadata>, Box<dyn std::error::Error + Send + Sync>> {
100        use crate::{auth::TokenCache, domain_data::upload_v1_stream};
101        let domain = self.discovery_client.auth_domain(domain_id).await?;
102        upload_v1_stream(
103            &domain.domain.domain_server.url,
104            &domain.get_access_token(),
105            domain_id,
106            data,
107        )
108        .await
109    }
110
111    #[cfg(target_family = "wasm")]
112    pub async fn upload_domain_data(
113        &self,
114        domain_id: &str,
115        data: Vec<UploadDomainData>,
116    ) -> Result<Vec<DomainDataMetadata>, Box<dyn std::error::Error + Send + Sync>> {
117        let domain = self.discovery_client.auth_domain(domain_id).await?;
118        upload_v1(
119            &domain.domain.domain_server.url,
120            &domain.get_access_token(),
121            domain_id,
122            data,
123        )
124        .await
125    }
126
127    pub async fn download_metadata(
128        &self,
129        domain_id: &str,
130        query: &DownloadQuery,
131    ) -> Result<Vec<DomainDataMetadata>, Box<dyn std::error::Error + Send + Sync>> {
132        let domain = self.discovery_client.auth_domain(domain_id).await?;
133        download_metadata_v1(
134            &domain.domain.domain_server.url,
135            &self.client_id,
136            &domain.get_access_token(),
137            domain_id,
138            query,
139        )
140        .await
141    }
142
143    pub async fn download_domain_data_by_id(
144        &self,
145        domain_id: &str,
146        id: &str,
147    ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
148        let domain = self.discovery_client.auth_domain(domain_id).await?;
149        download_by_id(
150            &domain.domain.domain_server.url,
151            &self.client_id,
152            &domain.get_access_token(),
153            domain_id,
154            id,
155        )
156        .await
157    }
158
159    pub async fn delete_domain_data_by_id(
160        &self,
161        domain_id: &str,
162        id: &str,
163    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
164        let domain = self.discovery_client.auth_domain(domain_id).await?;
165        delete_by_id(
166            &domain.domain.domain_server.url,
167            &domain.get_access_token(),
168            domain_id,
169            id,
170        )
171        .await
172    }
173
174    pub async fn list_domains(
175        &self,
176        org: &str,
177    ) -> Result<Vec<DomainWithServer>, Box<dyn std::error::Error + Send + Sync>> {
178        self.discovery_client.list_domains(org).await
179    }
180
181    
182}
183
184#[cfg(not(target_family = "wasm"))]
185#[cfg(test)]
186mod tests {
187    use std::time::Duration;
188
189    use crate::domain_data::{CreateDomainData, DomainAction, UpdateDomainData, UploadDomainData};
190
191    use super::*;
192    use futures::{StreamExt, channel::mpsc};
193    use tokio::{spawn, time::sleep};
194
195    fn get_config() -> (config::Config, String) {
196        if std::path::Path::new("../.env.local").exists() {
197            dotenvy::from_filename("../.env.local").ok();
198            dotenvy::dotenv().ok();
199        }
200        let config = config::Config::from_env().unwrap();
201        (config, std::env::var("DOMAIN_ID").unwrap())
202    }
203
204    #[tokio::test]
205    async fn test_download_domain_data_with_app_credential() {
206        // Create a test client
207        let config = get_config();
208        let client = DomainClient::new_with_app_credential(
209            &config.0.api_url,
210            &config.0.dds_url,
211            &config.0.client_id,
212            &config.0.app_key.unwrap(),
213            &config.0.app_secret.unwrap(),
214        )
215        .await
216        .expect("Failed to create client");
217
218        // Create a test query
219        let query = DownloadQuery {
220            ids: vec![],
221            name: None,
222            data_type: Some("dmt_accel_csv".to_string()),
223        };
224
225        // Test the download function
226        let result = client.download_domain_data(&config.1, &query).await;
227
228        assert!(result.is_ok(), "error message : {:?}", result.err());
229
230        let mut rx = result.unwrap();
231        let mut count = 0;
232        while let Some(Ok(data)) = rx.next().await {
233            count += 1;
234            assert_eq!(data.metadata.data_type, "dmt_accel_csv");
235        }
236        assert!(count > 0);
237    }
238
239    #[tokio::test]
240    async fn test_upload_domain_data_with_user_credential() {
241        use futures::SinkExt;
242        let config = get_config();
243        let client = DomainClient::new_with_user_credential(
244            &config.0.api_url,
245            &config.0.dds_url,
246            &config.0.client_id,
247            &config.0.email.unwrap(),
248            &config.0.password.unwrap(),
249            true,
250        )
251        .await
252        .expect("Failed to create client");
253
254        let data = vec![
255            UploadDomainData {
256                action: DomainAction::Create(CreateDomainData {
257                    name: "to be deleted".to_string(),
258                    data_type: "test".to_string(),
259                }),
260                data: "{\"test\": \"test\"}".as_bytes().to_vec(),
261            },
262            UploadDomainData {
263                action: DomainAction::Update(UpdateDomainData {
264                    id: "a84a36e5-312b-4f80-974a-06f5d19c1e16".to_string(),
265                }),
266                data: "{\"test\": \"test updated\"}".as_bytes().to_vec(),
267            },
268        ];
269        let (mut tx, rx) = mpsc::channel(10);
270        spawn(async move {
271            for d in data {
272                tx.send(d).await.unwrap();
273            }
274            tx.close().await.unwrap();
275        });
276        let result = client.upload_domain_data(&config.1, rx).await;
277
278        assert!(result.is_ok(), "error message : {:?}", result.err());
279
280        sleep(Duration::from_secs(5)).await;
281        let result = result.unwrap();
282        assert_eq!(result.len(), 2);
283
284        let ids = result.iter().map(|d| d.id.clone()).collect::<Vec<String>>();
285        assert_eq!(ids.len(), 2);
286        // Create a test query
287        let query = DownloadQuery {
288            ids: ids,
289            name: None,
290            data_type: None,
291        };
292
293        // Test the download function
294        let result = client.download_domain_data(&config.1, &query).await;
295
296        assert!(result.is_ok(), "error message : {:?}", result.err());
297
298        let mut to_delete = None;
299        let mut count = 0;
300        let mut rx = result.unwrap();
301        while let Some(Ok(data)) = rx.next().await {
302            count += 1;
303            if data.metadata.id == "a84a36e5-312b-4f80-974a-06f5d19c1e16" {
304                assert_eq!(data.data, b"{\"test\": \"test updated\"}");
305                continue;
306            } else {
307                assert_eq!(data.data, b"{\"test\": \"test\"}");
308            }
309            to_delete = Some(data.metadata.id.clone());
310        }
311        assert_eq!(count, 2);
312        assert_eq!(to_delete.is_some(), true);
313
314        // Delete the one whose id is not "a8"
315        let delete_result = client
316            .delete_domain_data_by_id(&config.1, &to_delete.unwrap())
317            .await;
318        assert!(
319            delete_result.is_ok(),
320            "Failed to delete data by id: {:?}",
321            delete_result.err()
322        );
323    }
324
325    #[tokio::test]
326    async fn test_download_domain_data_by_id() {
327        let config = get_config();
328        let client = DomainClient::new_with_app_credential(
329            &config.0.api_url,
330            &config.0.dds_url,
331            &config.0.client_id,
332            &config.0.app_key.unwrap(),
333            &config.0.app_secret.unwrap(),
334        )
335        .await
336        .expect("Failed to create client");
337
338        // Now test download by id
339        let download_result = client
340            .download_domain_data_by_id(&config.1, "a84a36e5-312b-4f80-974a-06f5d19c1e16")
341            .await;
342
343        assert!(
344            download_result.is_ok(),
345            "download by id failed: {:?}",
346            download_result.err()
347        );
348        let downloaded_bytes = download_result.unwrap();
349        assert_eq!(downloaded_bytes, b"{\"test\": \"test updated\"}".to_vec());
350    }
351
352    #[tokio::test]
353    async fn test_download_domain_metadata() {
354        let config = get_config();
355        let client = DomainClient::new_with_app_credential(
356            &config.0.api_url,
357            &config.0.dds_url,
358            &config.0.client_id,
359            &config.0.app_key.clone().unwrap(),
360            &config.0.app_secret.clone().unwrap(),
361        )
362        .await
363        .expect("Failed to create client");
364
365        // Download all metadata for the domain
366        let result = client
367            .download_metadata(
368                &config.1,
369                &DownloadQuery {
370                    ids: vec![],
371                    name: None,
372                    data_type: Some("test".to_string()),
373                },
374            )
375            .await;
376        assert!(
377            result.is_ok(),
378            "Failed to download domain metadata: {:?}",
379            result.err()
380        );
381        let result = result.unwrap();
382        assert!(result.len() > 0);
383        for meta in result {
384            assert!(!meta.id.is_empty());
385            assert_eq!(meta.domain_id, config.1);
386            assert!(!meta.name.is_empty());
387            assert_eq!(meta.data_type, "test");
388        }
389    }
390
391    #[tokio::test]
392    async fn test_load_domain_with_oidc_access_token() {
393        let config = get_config();
394        // Assume we have a function to get a valid oidc_access_token for testing
395        let oidc_access_token =
396            std::env::var("AUTH_TEST_TOKEN").expect("AUTH_TEST_TOKEN env var not set");
397        if oidc_access_token.is_empty() {
398            eprintln!("Missing AUTH_TEST_TOKEN, skipping test");
399            return;
400        }
401
402        let client =
403            DiscoveryService::new(&config.0.api_url, &config.0.dds_url, &config.0.client_id);
404
405        let domain = client
406            .with_oidc_access_token(&oidc_access_token)
407            .auth_domain(&config.1)
408            .await;
409        assert!(domain.is_ok(), "Failed to get domain: {:?}", domain.err());
410        assert_eq!(domain.unwrap().domain.id, config.1);
411    }
412
413    #[tokio::test]
414    async fn test_list_domains() {
415        let config = get_config();
416        let client = DomainClient::new_with_app_credential(
417            &config.0.api_url,
418            &config.0.dds_url,
419            &config.0.client_id,
420            &config.0.app_key.unwrap(),
421            &config.0.app_secret.unwrap(),
422        )
423        .await
424        .expect("Failed to create client");
425
426        let org = std::env::var("TEST_ORGANIZATION").unwrap_or("own".to_string());
427        let result = client.list_domains(&org).await.unwrap();
428        assert!(result.len() > 0, "No domains found");
429    }
430}