dji_log_parser/keychain/
api.rs

1use serde::{Deserialize, Serialize};
2#[cfg(target_arch = "wasm32")]
3use tsify_next::Tsify;
4
5use crate::Result;
6
7use super::{EncodedKeychainFeaturePoint, KeychainFeaturePoint};
8
9const DEFAULT_ENDPOINT: &str = "https://dev.dji.com/openapi/v1/flight-records/keychains";
10
11/// Request structure for keychain API.
12#[derive(Debug, Default, Serialize, Clone)]
13#[cfg_attr(target_arch = "wasm32", derive(Tsify))]
14pub struct KeychainsRequest {
15    pub version: u16,
16    pub department: u8,
17    #[serde(rename = "keychainsArray")]
18    pub keychains: Vec<Vec<EncodedKeychainFeaturePoint>>,
19}
20
21/// Response structure received from the keychain API.
22#[derive(Debug, Deserialize)]
23pub struct KeychainsResponse {
24    pub data: Option<Vec<Vec<KeychainFeaturePoint>>>,
25    pub result: KeychainResponseResult,
26}
27
28#[derive(Debug, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct KeychainResponseResult {
31    pub code: u8,
32    pub msg: String,
33}
34
35impl KeychainsRequest {
36    /// Sends a synchronous request to the keychain API.
37    ///
38    /// This method is only available for non-WASM targets.
39    ///
40    /// # Arguments
41    ///
42    /// * `api_key` - The API key for authentication.
43    /// * `endpoint` - The URL endpoint for the API.
44    ///
45    /// # Returns
46    ///
47    /// A Result containing a vector of vectors of KeychainFeaturePoint on success,
48    /// or an Error on failure.
49    #[cfg(not(target_arch = "wasm32"))]
50    pub fn fetch(
51        &self,
52        api_key: &str,
53        endpoint: Option<&str>,
54    ) -> Result<Vec<Vec<KeychainFeaturePoint>>> {
55        let endpoint = endpoint.unwrap_or(DEFAULT_ENDPOINT);
56        native::fetch(api_key, endpoint, self)
57    }
58
59    /// Sends an asynchronous request to the keychain API.
60    ///
61    /// This method is available for both WASM and non-WASM targets behind the `native-async` feature.
62    ///
63    /// # Arguments
64    ///
65    /// * `api_key` - The API key for authentication.
66    /// * `endpoint` - The URL endpoint for the API.
67    ///
68    /// # Returns
69    ///
70    /// A Future that resolves to a Result containing a vector of vectors of KeychainFeaturePoint on success,
71    /// or an Error on failure.
72    #[cfg(any(target_arch = "wasm32", feature = "native-async"))]
73    pub async fn fetch_async(
74        &self,
75        api_key: &str,
76        endpoint: Option<&str>,
77    ) -> Result<Vec<Vec<KeychainFeaturePoint>>> {
78        let endpoint = endpoint.unwrap_or(DEFAULT_ENDPOINT);
79
80        #[cfg(not(target_arch = "wasm32"))]
81        return native::fetch_async(api_key, endpoint, self).await;
82        #[cfg(target_arch = "wasm32")]
83        return wasm::fetch_async(api_key, endpoint, self).await;
84    }
85}
86
87#[cfg(not(target_arch = "wasm32"))]
88pub(crate) mod native {
89    use std::time::Duration;
90
91    use crate::keychain::KeychainFeaturePoint;
92    use crate::{Error, Result};
93
94    use super::{KeychainsRequest, KeychainsResponse};
95
96    pub fn fetch(
97        api_key: &str,
98        endpoint: &str,
99        request: &KeychainsRequest,
100    ) -> Result<Vec<Vec<KeychainFeaturePoint>>> {
101        let body = serde_json::to_string(&request)?;
102
103        let response = ureq::post(endpoint)
104            .set("Content-Type", "application/json")
105            .set("Api-Key", api_key)
106            .timeout(Duration::from_secs(30))
107            .send_string(&body)
108            .map_err(|e| match e {
109                ureq::Error::Status(403, _) => Error::ApiKeyError,
110                ureq::Error::Status(status, _) => Error::NetworkRequestStatus(status),
111                _ => Error::NetworkConnection,
112            })?;
113
114        let keychains_response: KeychainsResponse = response.into_json()?;
115
116        if keychains_response.result.code != 0 {
117            Err(Error::ApiError(keychains_response.result.msg))
118        } else {
119            match keychains_response.data {
120                Some(data) => Ok(data),
121                None => Err(Error::ApiError("Missing keychain data".to_owned())),
122            }
123        }
124    }
125
126    #[cfg(feature = "native-async")]
127    pub async fn fetch_async(
128        api_key: &str,
129        endpoint: &str,
130        request: &KeychainsRequest,
131    ) -> Result<Vec<Vec<KeychainFeaturePoint>>> {
132        let api_key = api_key.to_string();
133        let endpoint = endpoint.to_string();
134        let request = request.clone();
135
136        let (tx, rx) = async_channel::bounded(1);
137
138        std::thread::spawn(move || {
139            let response = fetch(&api_key, &endpoint, &request);
140            tx.send(response);
141        });
142
143        rx.recv().await.map_err(|_| Error::NetworkConnection)?
144    }
145}
146
147#[cfg(target_arch = "wasm32")]
148pub(crate) mod wasm {
149    use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
150    use wasm_bindgen_futures::js_sys::Promise;
151    use wasm_bindgen_futures::JsFuture;
152    use web_sys::{Request, RequestInit, Response};
153
154    use crate::keychain::KeychainFeaturePoint;
155    use crate::{Error, Result};
156
157    use super::{KeychainsRequest, KeychainsResponse};
158
159    #[wasm_bindgen]
160    extern "C" {
161        #[wasm_bindgen(js_name = fetch)]
162        fn js_fetch(input: &Request) -> Promise;
163    }
164
165    pub async fn fetch_async(
166        api_key: &str,
167        endpoint: &str,
168        request: &KeychainsRequest,
169    ) -> Result<Vec<Vec<KeychainFeaturePoint>>> {
170        let body = serde_json::to_string(&request)?;
171
172        let mut init = RequestInit::new();
173        init.method("POST");
174        init.body(Some(&JsValue::from_str(&body)));
175
176        let request = Request::new_with_str_and_init(endpoint, &init)
177            .map_err(|_| Error::ApiError("Unable to init request".to_owned()))?;
178
179        request
180            .headers()
181            .set("Content-Type", "application/json")
182            .map_err(|_| Error::ApiError("Unable to set Content-Type header".to_owned()))?;
183        request
184            .headers()
185            .set("Api-Key", &api_key)
186            .map_err(|_| Error::ApiError("Unable to set Api-Key header".to_owned()))?;
187
188        let response: Response = JsFuture::from(js_fetch(&request))
189            .await
190            .map_err(|_| Error::ApiError("Unable to fetch request".to_owned()))?
191            .unchecked_into();
192
193        if !response.ok() {
194            return Err(Error::ApiKeyError);
195        }
196
197        let json = JsFuture::from(
198            response
199                .json()
200                .map_err(|_| Error::ApiError("Unable get response".to_owned()))?,
201        )
202        .await
203        .map_err(|_| Error::ApiError("Unable to get response".to_owned()))?;
204
205        let keychains_response: KeychainsResponse = serde_wasm_bindgen::from_value(json)
206            .map_err(|_| Error::ApiError("Unable to parse response".to_owned()))?;
207
208        if keychains_response.result.code != 0 {
209            Err(Error::ApiError(keychains_response.result.msg))
210        } else {
211            match keychains_response.data {
212                Some(data) => Ok(data),
213                None => Err(Error::ApiError("Missing keychain data".to_owned())),
214            }
215        }
216    }
217}