remoteit_api/
api_async.rs

1//! Enabled by the `async` feature. Contains blocking implementations of the pre-written queries.
2//!
3//! On the docs page of this module, you can only see the builder structs for the functions.
4//!
5//! Please see [`R3Client`] for the actual functions you can call.
6
7use crate::auth::{build_auth_header, get_date};
8use crate::operations::{
9    CancelJob, DeleteFile, DeleteFileVersion, DeviceState, GetApplicationTypes, GetDevices,
10    GetFiles, GetJobs, GetOwnedOrganization, StartJob, cancel_job, delete_file,
11    delete_file_version, get_application_types, get_devices, get_files, get_jobs,
12    get_owned_organization, start_job,
13};
14use crate::{BASE_URL, GRAPHQL_PATH, R3Client};
15use bon::bon;
16use graphql_client::{GraphQLQuery, QueryBody, Response};
17use reqwest::Client;
18use reqwest::Method;
19use serde::{Deserialize, Serialize};
20use std::error::Error;
21
22#[bon]
23impl R3Client {
24    /// Sends a signed GraphQL request to the remote.it API in a blocking way.
25    ///
26    /// You probably don't want to use this function directly, but rather use the other functions in this module like [`R3Client::get_files()`].
27    ///
28    /// # Errors
29    /// - Any error that occurs during the request.
30    /// - Any error that occurs during deserialization of the response.
31    pub async fn send_remoteit_graphql_request_async<V: Serialize, R: for<'a> Deserialize<'a>>(
32        &self,
33        query_body: &QueryBody<V>,
34    ) -> Result<Response<R>, Box<dyn Error>> {
35        let date = get_date();
36        let auth_header = build_auth_header()
37            .key_id(self.credentials.access_key_id())
38            .key(self.credentials.key())
39            .content_type("application/json")
40            .method(&Method::POST)
41            .path(GRAPHQL_PATH)
42            .date(&date)
43            .call();
44        let client = Client::new();
45        let response = client
46            .post(format!("{BASE_URL}{GRAPHQL_PATH}"))
47            .header("Date", date)
48            .header("Content-Type", "application/json")
49            .header("Authorization", auth_header)
50            .json(&query_body)
51            .send()
52            .await?;
53        let response: Response<R> = response.json().await?;
54        Ok(response)
55    }
56
57    // region Scripting
58
59    /// Get a list of files that were uploaded to remote.it.
60    #[builder]
61    pub async fn get_files_async(
62        &self,
63        /// Optional organization ID for org context.
64        org_id: Option<String>,
65    ) -> Result<Response<get_files::ResponseData>, Box<dyn Error>> {
66        let request_body = GetFiles::build_query(get_files::Variables { org_id });
67        self.send_remoteit_graphql_request_async(&request_body)
68            .await
69    }
70
71    /// Delete a file from remote.it. Deletes all versions of the file.
72    #[builder]
73    pub async fn delete_file_async(
74        &self,
75        /// The ID of the file to delete.
76        /// You can get this from the response of [`R3Client::get_files()`].
77        file_id: String,
78    ) -> Result<Response<delete_file::ResponseData>, Box<dyn Error>> {
79        let request_body = DeleteFile::build_query(delete_file::Variables { file_id });
80        self.send_remoteit_graphql_request_async(&request_body)
81            .await
82    }
83
84    /// Delete a version of a file from remote.it. (Not the whole file)
85    #[builder]
86    pub async fn delete_file_version_async(
87        &self,
88        /// The ID of the file version to delete.
89        /// You can get this from the response of [`R3Client::get_files()`].
90        file_version_id: String,
91    ) -> Result<Response<delete_file_version::ResponseData>, Box<dyn Error>> {
92        let request_body =
93            DeleteFileVersion::build_query(delete_file_version::Variables { file_version_id });
94        self.send_remoteit_graphql_request_async(&request_body)
95            .await
96    }
97
98    /// Start scripting jobs on one or more devices.
99    #[builder]
100    pub async fn start_job_async(
101        &self,
102        /// The ID of the script file to run.
103        /// Note that this needs to be an executable file.
104        /// Get a list of files using [`R3Client::get_files()`].
105        file_id: String,
106        /// The IDs of the devices to run the script on.
107        /// Get a list of devices using [`R3Client::get_devices()`].
108        device_ids: Vec<String>,
109        /// Arguments to pass to the script.
110        /// These are optional.
111        /// For more information on script arguments please consult the remote.it API documentation.
112        #[builder(default)]
113        arguments: Vec<start_job::ArgumentInput>,
114    ) -> Result<Response<start_job::ResponseData>, Box<dyn Error>> {
115        let request_body = StartJob::build_query(start_job::Variables {
116            file_id,
117            device_ids,
118            arguments,
119        });
120        self.send_remoteit_graphql_request_async(&request_body)
121            .await
122    }
123
124    /// Cancel a job. See remote.it docs on more information on when jobs can be cancelled.
125    #[builder]
126    pub async fn cancel_job_async(
127        &self,
128        /// The ID of the job to cancel.
129        /// You get this after starting a job using [`R3Client::start_job()`].
130        job_id: String,
131    ) -> Result<Response<cancel_job::ResponseData>, Box<dyn Error>> {
132        let request_body = CancelJob::build_query(cancel_job::Variables { job_id });
133        self.send_remoteit_graphql_request_async(&request_body)
134            .await
135    }
136
137    /// Get a list of jobs that were started on remote.it.
138    #[builder]
139    pub async fn get_jobs_async(
140        &self,
141        /// Optional organization ID for org context.
142        org_id: Option<String>,
143        /// Optional limit how many results are returned. It is highly recommended to set a limit, because this query can take quite a while otherwise.
144        limit: Option<i64>,
145        /// Optional list of job IDs to filter by.
146        job_id_filter: Option<Vec<String>>,
147        /// Optional list of job statuses to filter by.
148        status_filter: Option<Vec<get_jobs::JobStatusEnum>>,
149    ) -> Result<Response<get_jobs::ResponseData>, Box<dyn Error>> {
150        let request_body = GetJobs::build_query(get_jobs::Variables {
151            org_id,
152            limit,
153            job_ids: job_id_filter,
154            statuses: status_filter,
155        });
156        self.send_remoteit_graphql_request_async(&request_body)
157            .await
158    }
159
160    // endregion
161    // region Organizations
162    /// Get data on your own organization, which belongs to the current user.
163    /// This Organization may or may not exist. You can create and configure your organization through the remote.it Web UI.
164    ///
165    /// # Returns
166    /// Data on your organization, if you have one.
167    #[builder]
168    pub async fn get_owned_organization_async(
169        &self,
170    ) -> Result<Response<get_owned_organization::ResponseData>, Box<dyn Error>> {
171        let request_body = GetOwnedOrganization::build_query(get_owned_organization::Variables {});
172        self.send_remoteit_graphql_request_async(&request_body)
173            .await
174    }
175    // endregion
176    // region Devices and Services
177
178    /// Get a list of application types that are available on remote.it.
179    #[builder]
180    pub async fn get_application_types_async(
181        &self,
182    ) -> Result<Response<get_application_types::ResponseData>, Box<dyn Error>> {
183        let request_body = GetApplicationTypes::build_query(get_application_types::Variables {});
184        self.send_remoteit_graphql_request_async(&request_body)
185            .await
186    }
187
188    /// Get a list of devices.
189    #[builder]
190    pub async fn get_devices_async(
191        &self,
192        /// Optional organization ID for org context.
193        org_id: Option<String>,
194        /// Optional limit for the number of devices to return.
195        limit: Option<i64>,
196        /// Optional offset for the devices. Useful for pagination.
197        offset: Option<i64>,
198        /// Optional state to filter by.
199        state: Option<DeviceState>,
200    ) -> Result<Response<get_devices::ResponseData>, Box<dyn Error>> {
201        let request_body = GetDevices::build_query(get_devices::Variables {
202            org_id,
203            limit,
204            offset,
205            state: state.map(|s| s.to_string()),
206        });
207        self.send_remoteit_graphql_request_async(&request_body)
208            .await
209    }
210
211    // endregion
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::credentials::Credentials;
218    use std::path::PathBuf;
219
220    fn get_credentials() -> Credentials {
221        Credentials::load_from_disk()
222            .custom_credentials_path(PathBuf::from(".env.remoteit"))
223            .call()
224            .unwrap()
225            .take_profile("default")
226            .unwrap()
227            .unwrap()
228    }
229
230    fn get_client() -> R3Client {
231        R3Client::builder().credentials(get_credentials()).build()
232    }
233
234    #[tokio::test]
235    async fn test_get_files_async() {
236        let response = get_client().get_files_async().call().await.unwrap();
237        assert!(response.data.is_some());
238        assert!(response.errors.is_none());
239    }
240
241    #[tokio::test]
242    async fn test_get_jobs_async() {
243        let response = get_client().get_jobs_async().limit(1).call().await.unwrap();
244        assert!(response.data.is_some());
245        assert!(response.errors.is_none());
246    }
247
248    #[tokio::test]
249    async fn test_get_jobs_with_filters_async() {
250        let response = get_client()
251            .get_jobs_async()
252            .job_id_filter(vec!["foobar".to_string()])
253            .status_filter(vec![get_jobs::JobStatusEnum::SUCCESS])
254            .call()
255            .await
256            .unwrap();
257        assert!(response.data.is_some());
258        assert!(response.errors.is_none());
259    }
260
261    #[tokio::test]
262    async fn test_get_application_types_async() {
263        let response = get_client()
264            .get_application_types_async()
265            .call()
266            .await
267            .unwrap();
268        assert!(response.data.is_some());
269        assert!(response.errors.is_none());
270    }
271
272    #[tokio::test]
273    async fn test_get_devices_async() {
274        let response = get_client().get_devices_async().call().await.unwrap();
275        assert!(response.data.is_some());
276        assert!(response.errors.is_none());
277    }
278}