freedom_api/
api.rs

1//! # ATLAS Freedom API
2//!
3//! This module exists to define the Freedom API trait, which can be implemented for multiple client
4//! types.
5//!
6//! The API trait
7#![allow(clippy::type_complexity)]
8use std::{future::Future, ops::Deref, pin::Pin};
9
10use async_stream::stream;
11use bytes::Bytes;
12use freedom_config::Config;
13use freedom_models::{
14    account::Account,
15    band::Band,
16    pagination::Paginated,
17    satellite::Satellite,
18    satellite_configuration::SatelliteConfiguration,
19    site::{Site, SiteConfiguration},
20    task::{Task, TaskRequest, TaskStatusType, TaskType},
21    user::User,
22    utils::Embedded,
23};
24use reqwest::StatusCode;
25use serde::de::DeserializeOwned;
26use serde_json::Value as JsonValue;
27use time::{OffsetDateTime, format_description::well_known::Iso8601};
28use url::Url;
29
30use futures_core::Stream;
31
32use crate::error::Error;
33
34pub(crate) mod post;
35
36/// A super trait containing all the requirements for Freedom API Values
37pub trait Value: std::fmt::Debug + DeserializeOwned + Clone + Send + Sync {}
38
39impl<T> Value for T where T: std::fmt::Debug + DeserializeOwned + Clone + Send + Sync {}
40
41trait PaginatedErr<'a, T> {
42    fn once_err(self) -> PaginatedStream<'a, T>;
43}
44
45impl<'a, T: 'a + Send + Sync> PaginatedErr<'a, T> for Error {
46    fn once_err(self) -> PaginatedStream<'a, T> {
47        Box::pin(async_stream::stream! { yield Err(self); })
48    }
49}
50
51/// The trait defining the required functionality of container types
52///
53/// The Freedom API is generic over "containers". Each implementer of the [`Api`] trait must
54/// also define a container. This is useful since certain clients will return Arc'd values, i.e. the
55/// caching client, while others return the values wrapped in a simple [`Inner`] type which is just
56/// a stack value.
57///
58/// However, for most cases this complexity can be ignored, since containers are required to
59/// implement [`Deref`](std::ops::Deref) of `T`. So for read-only operations the container can be
60/// used as if it were `T`. For mutable access see [`Self::into_inner`].
61///
62/// # Example
63///
64/// ```no_run
65/// # use freedom_api::prelude::*;
66/// # #[tokio::main]
67/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
68/// # let config = Config::from_env()?;
69/// # let client = Client::from_config(config);
70/// let request = client
71///     .get_request_by_id(42)
72///     .await?;
73///
74/// println!("Created on {}", request.created); // Direct access to created field
75///                                             // through the Container
76/// # Ok(())
77/// # }
78/// ```
79pub trait Container<T>: Deref<Target = T> + Value {
80    /// All containers are capable of returning the value they wrap
81    ///
82    /// However, the runtime performance of this varies by client type. For [`crate::Client`], this
83    /// operation is essentially free, however for the caching client, this often results in a clone
84    /// of the value.
85    fn into_inner(self) -> T;
86}
87
88impl<T: Deref<Target = T> + Value> Container<T> for Box<T> {
89    fn into_inner(self) -> T {
90        *self
91    }
92}
93
94/// A simple container which stores a `T`.
95///
96/// This container exists to allow us to store items on the stack, without needing to allocate with
97/// something like `Box<T>`. For all other intents and purposes, it acts as the `T` which it
98/// contains.
99#[derive(
100    Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash,
101)]
102#[repr(transparent)]
103#[serde(transparent)]
104pub struct Inner<T>(T);
105
106impl<T> std::ops::Deref for Inner<T> {
107    type Target = T;
108
109    fn deref(&self) -> &Self::Target {
110        &self.0
111    }
112}
113
114impl<T> std::ops::DerefMut for Inner<T> {
115    fn deref_mut(&mut self) -> &mut Self::Target {
116        &mut self.0
117    }
118}
119
120impl<T> Container<T> for Inner<T>
121where
122    T: Value,
123{
124    fn into_inner(self) -> T {
125        self.0
126    }
127}
128
129impl<T> Inner<T> {
130    pub fn new(inner: T) -> Self {
131        Self(inner)
132    }
133}
134
135/// A stream of paginated results from freedom.
136///
137/// Each item in the stream is a result, since one or more items may fail to be serialized
138pub type PaginatedStream<'a, T> = Pin<Box<dyn Stream<Item = Result<T, Error>> + 'a + Send + Sync>>;
139
140/// The primary trait for interfacing with the Freedom API
141pub trait Api: Send + Sync {
142    /// The [`Api`] supports implementors with different so-called "container" types.
143    ///
144    /// For a more detailed description, see the [`Container`] trait.
145    type Container<T: Value>: Container<T>;
146
147    /// Returns the freedom configuration for the API
148    fn config(&self) -> &Config;
149
150    /// Returns a mutable reference to the freedom configuration for the API
151    fn config_mut(&mut self) -> &mut Config;
152
153    /// Creates a get request at the provided absolute URI for the client's environment, using basic
154    /// authentication.
155    ///
156    /// Returns the raw binary body, and the status code.
157    fn get(
158        &self,
159        url: Url,
160    ) -> impl Future<Output = Result<(Bytes, StatusCode), Error>> + Send + Sync;
161
162    fn delete(
163        &self,
164        url: Url,
165    ) -> impl Future<Output = Result<(Bytes, StatusCode), Error>> + Send + Sync;
166
167    /// Lower level method, not intended for direct use
168    fn post<S>(
169        &self,
170        url: Url,
171        msg: S,
172    ) -> impl Future<Output = Result<(Bytes, StatusCode), Error>> + Send + Sync
173    where
174        S: serde::Serialize + Send + Sync;
175
176    /// Creates a get request at the provided absolute URI for the client's environment, using basic
177    /// authentication.
178    ///
179    /// The JSON response is then deserialized into the required type, erroring if the
180    /// deserialization fails, and providing the object if it succeeds.
181    fn get_json_map<T>(&self, url: Url) -> impl Future<Output = Result<T, Error>> + Send + Sync
182    where
183        T: Value,
184    {
185        async move {
186            let (body, status) = self.get(url).await?;
187
188            error_on_non_success(&status, &body)?;
189
190            let utf8_str = String::from_utf8_lossy(&body);
191            serde_json::from_str(&utf8_str).map_err(From::from)
192        }
193    }
194
195    /// Creates a stream of items from a paginated endpoint.
196    ///
197    /// The stream is produced as a collection of `Result<T>`. This is so that if any one item fails
198    /// deserialization, it is added to the stream of items as an error rather than causing the
199    /// entire stream to result in an Error.
200    ///
201    /// # Pinning
202    ///
203    /// For convenience the stream is pinned on the heap via [`Box::pin`](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.pin).
204    /// This allows us to treat the returned stream more like any other object, without requiring
205    /// the end user to manually  pin the result on the stack. This comes with a slight performance
206    /// penalty (it requires an allocation), however this will be negligible given the latency of
207    /// the responses. For more information on pinning in rust refer to the [pinning chapter](https://rust-lang.github.io/async-book/04_pinning/01_chapter.html)
208    /// of the async book.
209    fn get_paginated<T>(&self, head_url: Url) -> PaginatedStream<'_, Self::Container<T>>
210    where
211        T: 'static + Value + Send + Sync,
212    {
213        let base = self.config().environment().freedom_entrypoint();
214        let mut current_url = head_url; // Not necessary but makes control flow more obvious
215        Box::pin(stream! {
216            loop {
217                // Get the results for the current page.
218                let pag = self.get_json_map::<Paginated<JsonValue>>(current_url).await?;
219                for item in pag.items {
220                    let i = serde_json::from_value::<Self::Container<T>>(item).map_err(From::from);
221                    yield i;
222                }
223                if let Some(link) = pag.links.get("next") {
224                    // Update the URL to the next page.
225                    current_url = match link.has_host() {
226                        true => link.to_owned(),
227                        false => {
228                            base.clone()
229                                .join(link.as_str())
230                                .map_err(|e| crate::error::Error::pag_item(e.to_string()))?
231                        }
232                    };
233                } else {
234                    break;
235                }
236            }
237        })
238    }
239
240    /// Fetch the URL from the given path
241    fn path_to_url(&self, path: impl AsRef<str>) -> Result<Url, Error> {
242        let url = self.config().environment().freedom_entrypoint();
243        url.join(path.as_ref())
244            .map_err(|error| Error::InvalidUri(error.to_string()))
245    }
246
247    /// Request to delete the band details object matching the provided id
248    ///
249    /// # Example
250    ///
251    /// ```no_run
252    /// # use freedom_api::prelude::*;
253    /// # tokio_test::block_on(async {
254    /// let client = Client::from_env()?;
255    ///
256    /// client.delete_task_request(42).await?;
257    /// # Ok::<_, Box<dyn std::error::Error>>(())
258    /// # });
259    /// ```
260    fn delete_band_details(
261        &self,
262        id: i32,
263    ) -> impl Future<Output = Result<(), Error>> + Send + Sync {
264        async move {
265            let uri = self.path_to_url(format!("satellite_bands/{id}"))?;
266            let (body, status) = self.delete(uri).await?;
267            error_on_non_success(&status, &body)?;
268            Ok(())
269        }
270    }
271
272    /// Request to delete the satellite configuration matching the provided `id`
273    ///
274    /// # Example
275    ///
276    /// ```no_run
277    /// # use freedom_api::prelude::*;
278    /// # tokio_test::block_on(async {
279    /// let client = Client::from_env()?;
280    ///
281    /// client.delete_satellite_configuration(42).await?;
282    /// # Ok::<_, Box<dyn std::error::Error>>(())
283    /// # });
284    /// ```
285    fn delete_satellite_configuration(
286        &self,
287        id: i32,
288    ) -> impl Future<Output = Result<(), Error>> + Send + Sync {
289        async move {
290            let uri = self.path_to_url(format!("satellite_configurations/{id}"))?;
291            let (body, status) = self.delete(uri).await?;
292            error_on_non_success(&status, &body)?;
293            Ok(())
294        }
295    }
296
297    /// Request to delete the satellite object matching the provided `id`
298    ///
299    /// # Example
300    ///
301    /// ```no_run
302    /// # use freedom_api::prelude::*;
303    /// # tokio_test::block_on(async {
304    /// let client = Client::from_env()?;
305    ///
306    /// client.delete_satellite(42).await?;
307    /// # Ok::<_, Box<dyn std::error::Error>>(())
308    /// # });
309    /// ```
310    fn delete_satellite(&self, id: i32) -> impl Future<Output = Result<(), Error>> + Send + Sync {
311        async move {
312            let uri = self.path_to_url(format!("satellites/{id}"))?;
313            let (body, status) = self.delete(uri).await?;
314            error_on_non_success(&status, &body)?;
315            Ok(())
316        }
317    }
318
319    /// Request to delete the override matching the provided `id`
320    ///
321    /// # Example
322    ///
323    /// ```no_run
324    /// # use freedom_api::prelude::*;
325    /// # tokio_test::block_on(async {
326    /// let client = Client::from_env()?;
327    ///
328    /// client.delete_override(42).await?;
329    /// # Ok::<_, Box<dyn std::error::Error>>(())
330    /// # });
331    /// ```
332    fn delete_override(&self, id: i32) -> impl Future<Output = Result<(), Error>> + Send + Sync {
333        async move {
334            let uri = self.path_to_url(format!("overrides/{id}"))?;
335            let (body, status) = self.delete(uri).await?;
336            error_on_non_success(&status, &body)?;
337            Ok(())
338        }
339    }
340
341    /// Request to delete the user matching the provided `id`
342    ///
343    /// # Example
344    ///
345    /// ```no_run
346    /// # use freedom_api::prelude::*;
347    /// # tokio_test::block_on(async {
348    /// let client = Client::from_env()?;
349    ///
350    /// client.delete_user(42).await?;
351    /// # Ok::<_, Box<dyn std::error::Error>>(())
352    /// # });
353    /// ```
354    fn delete_user(&self, id: i32) -> impl Future<Output = Result<(), Error>> + Send + Sync {
355        async move {
356            let uri = self.path_to_url(format!("users/{id}"))?;
357            let (body, status) = self.delete(uri).await?;
358            error_on_non_success(&status, &body)?;
359            Ok(())
360        }
361    }
362
363    /// Request to delete the user matching the provided `id`
364    ///
365    /// # Example
366    ///
367    /// ```no_run
368    /// # use freedom_api::prelude::*;
369    /// # tokio_test::block_on(async {
370    /// let client = Client::from_env()?;
371    ///
372    /// client.delete_task_request(42).await?;
373    /// # Ok::<_, Box<dyn std::error::Error>>(())
374    /// # });
375    /// ```
376    fn delete_task_request(
377        &self,
378        id: i32,
379    ) -> impl Future<Output = Result<(), Error>> + Send + Sync {
380        async move {
381            let uri = self.path_to_url(format!("requests/{id}"))?;
382            let (body, status) = self.delete(uri).await?;
383            error_on_non_success(&status, &body)?;
384            Ok(())
385        }
386    }
387
388    /// Lower level method, not intended for direct use
389    fn post_json_map<S, T>(
390        &self,
391        url: Url,
392        msg: S,
393    ) -> impl Future<Output = Result<T, Error>> + Send + Sync
394    where
395        S: serde::Serialize + Send + Sync,
396        T: Value,
397    {
398        async move {
399            let (body, status) = self.post(url, msg).await?;
400
401            error_on_non_success(&status, &body)?;
402
403            let utf8_str = str::from_utf8(&body).map_err(|error| {
404                Error::Deserialization(format!("Failed to decode response as UTF-8: {error}"))
405            })?;
406            serde_json::from_str(utf8_str).map_err(From::from)
407        }
408    }
409
410    /// Produces a single [`Account`](freedom_models::account::Account) matching the provided ID.
411    ///
412    /// See [`get`](Self::get) documentation for more details about the process and return type
413    ///
414    /// # Example
415    ///
416    /// ```no_run
417    /// # use freedom_api::prelude::*;
418    /// # tokio_test::block_on(async {
419    /// let client = Client::from_env()?;
420    ///
421    /// let account = client.get_account_by_name("ATLAS").await?;
422    /// println!("{}", account.name);
423    /// # Ok::<_, Box<dyn std::error::Error>>(())
424    /// # });
425    /// ```
426    fn get_account_by_name(
427        &self,
428        account_name: &str,
429    ) -> impl Future<Output = Result<Self::Container<Account>, Error>> + Send + Sync {
430        async move {
431            let mut uri = self.path_to_url("accounts/search/findOneByName")?;
432            uri.set_query(Some(&format!("name={account_name}")));
433            self.get_json_map(uri).await
434        }
435    }
436
437    /// Produces a single [`Account`](freedom_models::account::Account) matching the provided ID.
438    ///
439    /// See [`get`](Self::get) documentation for more details about the process and return type
440    ///
441    /// # Example
442    ///
443    /// ```no_run
444    /// # use freedom_api::prelude::*;
445    /// # tokio_test::block_on(async {
446    /// let client = Client::from_env()?;
447    ///
448    /// let data = client.get_file_by_task_id_and_name(42, "data.bin").await?;
449    /// # Ok::<_, Box<dyn std::error::Error>>(())
450    /// # });
451    /// ```
452    fn get_file_by_task_id_and_name(
453        &self,
454        task_id: i32,
455        file_name: &str,
456    ) -> impl Future<Output = Result<Bytes, Error>> + Send + Sync {
457        async move {
458            let path = format!("downloads/{}/{}", task_id, file_name);
459            let uri = self.path_to_url(path)?;
460
461            let (data, status) = self.get(uri).await?;
462            error_on_non_success(&status, b"Failed to fetch file")?;
463
464            Ok(data)
465        }
466    }
467
468    /// Produces a single [`Account`](freedom_models::account::Account) matching the provided ID.
469    ///
470    /// See [`get`](Self::get) documentation for more details about the process and return type
471    fn get_account_by_id(
472        &self,
473        account_id: i32,
474    ) -> impl Future<Output = Result<Self::Container<Account>, Error>> + Send + Sync {
475        async move {
476            let uri = self.path_to_url(format!("accounts/{account_id}"))?;
477            self.get_json_map(uri).await
478        }
479    }
480
481    /// Produces a paginated stream of [`Account`](freedom_models::account::Account) objects.
482    ///
483    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
484    /// and return type
485    fn get_accounts(&self) -> PaginatedStream<'_, Self::Container<Account>> {
486        let uri = match self.path_to_url("accounts") {
487            Ok(uri) => uri,
488            Err(err) => return err.once_err(),
489        };
490        self.get_paginated(uri)
491    }
492
493    /// Produces a paginated stream of [`Band`] objects.
494    ///
495    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
496    /// and return type
497    fn get_satellite_bands(&self) -> PaginatedStream<'_, Self::Container<Band>> {
498        let uri = match self.path_to_url("satellite_bands") {
499            Ok(uri) => uri,
500            Err(err) => return err.once_err(),
501        };
502        self.get_paginated(uri)
503    }
504
505    /// Produces a single [`Band`] matching the provided ID.
506    ///
507    /// See [`get`](Self::get) documentation for more details about the process and return type
508    fn get_satellite_band_by_id(
509        &self,
510        satellite_band_id: i32,
511    ) -> impl Future<Output = Result<Self::Container<Band>, Error>> + Send + Sync {
512        async move {
513            let uri = self.path_to_url(format!("satellite_bands/{satellite_band_id}"))?;
514            self.get_json_map(uri).await
515        }
516    }
517
518    /// Produces a single [`Band`] matching the provided name.
519    ///
520    /// See [`get`](Self::get) documentation for more details about the process and return type
521    fn get_satellite_band_by_name(
522        &self,
523        satellite_band_name: &str,
524    ) -> impl Future<Output = Result<Self::Container<Band>, Error>> + Send + Sync {
525        async move {
526            let mut uri = self.path_to_url("satellite_bands/search/findOneByName")?;
527            uri.set_query(Some(&format!("name={satellite_band_name}")));
528            self.get_json_map(uri).await
529        }
530    }
531
532    /// Produces a paginated stream of [`Band`] objects matching the provided account name.
533    ///
534    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
535    /// and return type
536    fn get_satellite_bands_by_account_name(
537        &self,
538        account_name: &str,
539    ) -> PaginatedStream<'_, Self::Container<Band>> {
540        let mut uri = match self.path_to_url("satellite_bands/search/findAllByAccountName") {
541            Ok(uri) => uri,
542            Err(err) => return err.once_err(),
543        };
544        uri.set_query(Some(&format!("accountName={account_name}")));
545
546        self.get_paginated(uri)
547    }
548
549    /// Produces a paginated stream of [`SatelliteConfiguration`] objects matching the provided
550    /// account name.
551    ///
552    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
553    /// and return type
554    fn get_satellite_configurations_by_account_name(
555        &self,
556        account_name: &str,
557    ) -> PaginatedStream<'_, Self::Container<SatelliteConfiguration>> {
558        let mut uri = match self.path_to_url("satellite_configurations/search/findAllByAccountName")
559        {
560            Ok(uri) => uri,
561            Err(err) => return err.once_err(),
562        };
563        uri.set_query(Some(&format!("accountName={account_name}")));
564
565        self.get_paginated(uri)
566    }
567
568    /// Produces a paginated stream of [`SatelliteConfiguration`] objects.
569    ///
570    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
571    /// and return type
572    fn get_satellite_configurations(
573        &self,
574    ) -> PaginatedStream<'_, Self::Container<SatelliteConfiguration>> {
575        let uri = match self.path_to_url("satellite_configurations") {
576            Ok(uri) => uri,
577            Err(err) => return err.once_err(),
578        };
579
580        self.get_paginated(uri)
581    }
582
583    /// Produces a single satellite configuration matching the provided satellite configuration ID
584    fn get_satellite_configuration_by_id(
585        &self,
586        satellite_configuration_id: i32,
587    ) -> impl Future<Output = Result<Self::Container<SatelliteConfiguration>, Error>> + Send + Sync
588    {
589        async move {
590            let uri = self.path_to_url(format!(
591                "satellite_configurations/{satellite_configuration_id}"
592            ))?;
593
594            self.get_json_map(uri).await
595        }
596    }
597
598    /// Produces a single satellite configuration matching the provided satellite configuration name
599    fn get_satellite_configuration_by_name(
600        &self,
601        satellite_configuration_name: &str,
602    ) -> impl Future<Output = Result<Self::Container<SatelliteConfiguration>, Error>> + Send + Sync
603    {
604        async move {
605            let mut uri = self.path_to_url("satellite_configurations/search/findOneByName")?;
606            uri.set_query(Some(&format!("name={satellite_configuration_name}")));
607
608            self.get_json_map(uri).await
609        }
610    }
611
612    /// Produces a paginated stream of [`Site`] objects.
613    ///
614    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
615    /// and return type
616    fn get_sites(&self) -> PaginatedStream<'_, Self::Container<Site>> {
617        let uri = match self.path_to_url("sites") {
618            Ok(uri) => uri,
619            Err(err) => return err.once_err(),
620        };
621        self.get_paginated(uri)
622    }
623
624    /// Produces a single [`Site`] object matching the provided ID.
625    ///
626    /// See [`get`](Self::get) documentation for more details about the process and return type
627    fn get_site_by_id(
628        &self,
629        id: i32,
630    ) -> impl Future<Output = Result<Self::Container<Site>, Error>> + Send + Sync {
631        async move {
632            let uri = self.path_to_url(format!("sites/{id}"))?;
633            self.get_json_map(uri).await
634        }
635    }
636
637    /// Produces a single [`Site`] object matching the provided name.
638    ///
639    /// See [`get`](Self::get) documentation for more details about the process and return type
640    fn get_site_by_name(
641        &self,
642        name: impl AsRef<str> + Send + Sync,
643    ) -> impl Future<Output = Result<Self::Container<Site>, Error>> + Send + Sync {
644        async move {
645            let mut uri = self.path_to_url("sites/search/findOneByName")?;
646            let query = format!("name={}", name.as_ref());
647            uri.set_query(Some(&query));
648
649            self.get_json_map(uri).await
650        }
651    }
652
653    /// Produces a paginated stream of [`SiteConfiguration`] objects.
654    ///
655    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
656    /// and return type
657    fn get_site_configurations(&self) -> PaginatedStream<'_, Self::Container<SiteConfiguration>> {
658        let uri = match self.path_to_url("configurations") {
659            Ok(uri) => uri,
660            Err(err) => return err.once_err(),
661        };
662        self.get_paginated(uri)
663    }
664
665    /// Produces a single [`SiteConfiguration`] object matching the provided ID.
666    ///
667    /// See [`get`](Self::get) documentation for more details about the process and return type
668    fn get_site_configuration_by_id(
669        &self,
670        id: i32,
671    ) -> impl Future<Output = Result<Self::Container<SiteConfiguration>, Error>> + Send + Sync {
672        async move {
673            let uri = self.path_to_url(format!("configurations/{id}"))?;
674            self.get_json_map(uri).await
675        }
676    }
677
678    /// Produces a single [`SiteConfiguration`] object matching the provided name.
679    ///
680    /// See [`get`](Self::get) documentation for more details about the process and return type
681    fn get_site_configuration_by_name(
682        &self,
683        name: impl AsRef<str> + Send + Sync,
684    ) -> impl Future<Output = Result<Self::Container<SiteConfiguration>, Error>> + Send + Sync {
685        async move {
686            let mut uri = self.path_to_url("configurations/search/findOneByName")?;
687            let query = format!("name={}", name.as_ref());
688            uri.set_query(Some(&query));
689
690            self.get_json_map(uri).await
691        }
692    }
693
694    /// Produces a single [`TaskRequest`] matching the provided ID.
695    ///
696    /// See [`get`](Self::get) documentation for more details about the process and return type
697    fn get_request_by_id(
698        &self,
699        task_request_id: i32,
700    ) -> impl Future<Output = Result<Self::Container<TaskRequest>, Error>> + Send + Sync {
701        async move {
702            let uri = self.path_to_url(format!("requests/{task_request_id}"))?;
703
704            self.get_json_map(uri).await
705        }
706    }
707
708    /// Produces a paginated stream of [`TaskRequest`] objects.
709    ///
710    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
711    /// and return type
712    fn get_requests(&self) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
713        {
714            let uri = match self.path_to_url("requests/search/findAll") {
715                Ok(uri) => uri,
716                Err(err) => return err.once_err(),
717            };
718            self.get_paginated(uri)
719        }
720    }
721
722    /// Produces a vector of [`TaskRequest`] items, representing all the task requests matching the
723    /// target time overlapping with the provided time range.
724    fn get_requests_by_target_date_between(
725        &self,
726        start: OffsetDateTime,
727        end: OffsetDateTime,
728    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
729        async move {
730            let mut uri = self.path_to_url("requests/search/findAllByTargetDateBetween")?;
731
732            uri.set_query(Some(&format!(
733                "start={}&end={}",
734                start.format(&Iso8601::DEFAULT)?,
735                end.format(&Iso8601::DEFAULT)?,
736            )));
737
738            self.get_json_map(uri).await
739        }
740    }
741
742    /// Produces a vector of [`TaskRequest`] items,
743    /// representing all the task requests matching the account at the provided URI and whose
744    /// target time overlaps with the provided time range.
745    ///
746    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
747    /// and return type
748    fn get_requests_by_account_and_target_date_between<T>(
749        &self,
750        account_uri: T,
751        start: OffsetDateTime,
752        end: OffsetDateTime,
753    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
754    where
755        T: AsRef<str> + Send + Sync,
756    {
757        let start = match start.format(&Iso8601::DEFAULT).map_err(Error::from) {
758            Ok(start) => start,
759            Err(error) => return error.once_err(),
760        };
761
762        let end = match end.format(&Iso8601::DEFAULT).map_err(Error::from) {
763            Ok(end) => end,
764            Err(error) => return error.once_err(),
765        };
766
767        let mut uri = match self.path_to_url("requests/search/findAllByAccountAndTargetDateBetween")
768        {
769            Ok(uri) => uri,
770            Err(err) => return err.once_err(),
771        };
772
773        uri.set_query(Some(&format!(
774            "account={}&start={}&end={}",
775            account_uri.as_ref(),
776            start,
777            end
778        )));
779
780        self.get_paginated(uri)
781    }
782
783    /// Produces a paginated stream of [`TaskRequest`]
784    /// objects whose account name matches the provided name, and whose pass will occur today.
785    ///
786    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
787    /// and return type
788    fn get_requests_by_account_and_upcoming_today(
789        &self,
790    ) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
791        let uri = match self.path_to_url("requests/search/findByAccountUpcomingToday") {
792            Ok(uri) => uri,
793            Err(err) => return err.once_err(),
794        };
795
796        self.get_paginated(uri)
797    }
798
799    /// Produces a paginated stream of [`TaskRequest`]
800    /// objects whose satellite configuration matches that of the configuration at the
801    /// `configuration_uri` endpoint.
802    ///
803    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
804    /// and return type
805    ///
806    /// # Note
807    /// The results are ordered by the creation time of the task request
808    fn get_requests_by_configuration<T>(
809        &self,
810        configuration_uri: T,
811    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
812    where
813        T: AsRef<str> + Send + Sync,
814    {
815        let mut uri =
816            match self.path_to_url("requests/search/findAllByConfigurationOrderByCreatedAsc") {
817                Ok(uri) => uri,
818                Err(err) => return err.once_err(),
819            };
820
821        uri.set_query(Some(&format!(
822            "configuration={}",
823            configuration_uri.as_ref()
824        )));
825
826        self.get_paginated::<TaskRequest>(uri)
827    }
828
829    /// Produces a vector of [`TaskRequest`] items, representing all the task requests which match
830    /// the provided configuration, whose satellite name matches one of the names provided as part
831    /// of `satellite_name`, and which overlaps the provided time range.
832    ///
833    /// See [`get`](Self::get) documentation for more details about the process and return type
834    fn get_requests_by_configuration_and_satellite_names_and_target_date_between<T, I, S>(
835        &self,
836        configuration_uri: T,
837        satellites: I,
838        start: OffsetDateTime,
839        end: OffsetDateTime,
840    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
841    where
842        T: AsRef<str> + Send + Sync,
843        I: IntoIterator<Item = S> + Send + Sync,
844        S: AsRef<str> + Send + Sync,
845    {
846        async move {
847            let satellites_string = crate::utils::list_to_string(satellites);
848            let mut uri = self.path_to_url(
849                "requests/search/findAllByConfigurationAndSatelliteNamesAndTargetDateBetween",
850            )?;
851
852            uri.set_query(Some(&format!(
853                "configuration={}&satelliteNames={}&start={}&end={}",
854                configuration_uri.as_ref(),
855                satellites_string,
856                start.format(&Iso8601::DEFAULT)?,
857                end.format(&Iso8601::DEFAULT)?,
858            )));
859
860            Ok(self
861                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
862                .await?
863                .items)
864        }
865    }
866
867    /// Produces a vector of [`TaskRequest`] items, representing all the task requests matching the
868    /// configuration at the provided URI and whose target time overlaps with the provided time
869    /// range.
870    ///
871    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
872    /// and return type
873    fn get_requests_by_configuration_and_target_date_between<T>(
874        &self,
875        configuration_uri: T,
876        start: OffsetDateTime,
877        end: OffsetDateTime,
878    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
879    where
880        T: AsRef<str> + Send + Sync,
881    {
882        async move {
883            let mut uri =
884                self.path_to_url("requests/search/findAllByConfigurationAndTargetDateBetween")?;
885            uri.set_query(Some(&format!(
886                "configuration={}&start={}&end={}",
887                configuration_uri.as_ref(),
888                start.format(&Iso8601::DEFAULT)?,
889                end.format(&Iso8601::DEFAULT)?,
890            )));
891
892            Ok(self
893                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
894                .await?
895                .items)
896        }
897    }
898
899    /// Produces a vector of [`TaskRequest`] items,
900    /// representing all the task requests whose ID matches one of the IDs provided as part of
901    /// `ids`.
902    ///
903    /// See [`get`](Self::get) documentation for more details about the process and return type
904    fn get_requests_by_ids<I, S>(
905        &self,
906        ids: I,
907    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
908    where
909        I: IntoIterator<Item = S> + Send + Sync,
910        S: AsRef<str> + Send + Sync,
911    {
912        async move {
913            let ids_string = crate::utils::list_to_string(ids);
914            let mut uri = self.path_to_url("requests/search/findAllByIds")?;
915
916            uri.set_query(Some(&format!("ids={}", ids_string)));
917
918            Ok(self
919                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
920                .await?
921                .items)
922        }
923    }
924
925    /// Produces a paginated stream of [`TaskRequest`] objects which are public, and which overlap
926    /// with the provided time range.
927    ///
928    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
929    /// and return type
930    fn get_requests_by_overlapping_public(
931        &self,
932        start: OffsetDateTime,
933        end: OffsetDateTime,
934    ) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
935        let mut uri = match self.path_to_url("requests/search/findAllByOverlappingPublic") {
936            Ok(uri) => uri,
937            Err(err) => return err.once_err(),
938        };
939
940        uri.set_query(Some(&format!(
941            "start={}&end={}",
942            start.format(&Iso8601::DEFAULT).unwrap(),
943            end.format(&Iso8601::DEFAULT).unwrap(),
944        )));
945
946        self.get_paginated(uri)
947    }
948
949    /// Produces a paginated stream of [`TaskRequest`] objects whose satellite name matches one of
950    /// the names provided as part of `satellite_name`.
951    ///
952    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
953    /// and return type
954    fn get_requests_by_satellite_name<T>(
955        &self,
956        satellite_name: T,
957    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
958    where
959        T: AsRef<str> + Send + Sync,
960    {
961        let mut uri = match self.path_to_url("requests/search/findBySatelliteName") {
962            Ok(uri) => uri,
963            Err(err) => return err.once_err(),
964        };
965
966        uri.set_query(Some(&format!("name={}", satellite_name.as_ref())));
967
968        self.get_paginated(uri)
969    }
970
971    /// Produces a vector of [`TaskRequest`] items, representing all the task requests whose
972    /// satellite name matches the provided name and whose target time overlaps with the provided
973    /// time range.
974    ///
975    /// See [`get`](Self::get) documentation for more details about the process and return type
976    fn get_requests_by_satellite_name_and_target_date_between<T>(
977        &self,
978        satellite_name: T,
979        start: OffsetDateTime,
980        end: OffsetDateTime,
981    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
982    where
983        T: AsRef<str> + Send + Sync,
984    {
985        async move {
986            let mut uri =
987                self.path_to_url("requests/search/findAllBySatelliteNameAndTargetDateBetween")?;
988
989            uri.set_query(Some(&format!(
990                "name={}&start={}&end={}",
991                satellite_name.as_ref(),
992                start.format(&Iso8601::DEFAULT)?,
993                end.format(&Iso8601::DEFAULT)?
994            )));
995
996            Ok(self
997                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
998                .await?
999                .items)
1000        }
1001    }
1002
1003    /// Produces a paginated stream of [`TaskRequest`] objects whose status matches the provided
1004    /// status.
1005    ///
1006    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1007    /// and return type
1008    fn get_requests_by_status<T>(
1009        &self,
1010        status: T,
1011    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
1012    where
1013        T: TryInto<TaskStatusType> + Send + Sync,
1014        Error: From<<T as TryInto<TaskStatusType>>::Error>,
1015    {
1016        let status: TaskStatusType = match status.try_into() {
1017            Ok(val) => val,
1018            Err(err) => return Error::from(err).once_err(),
1019        };
1020        let mut uri = match self.path_to_url("requests/search/findByStatus") {
1021            Ok(uri) => uri,
1022            Err(err) => return err.once_err(),
1023        };
1024
1025        uri.set_query(Some(&format!("status={}", status.as_ref())));
1026
1027        self.get_paginated(uri)
1028    }
1029
1030    /// Produces a paginated stream of [`TaskRequest`], representing all the task requests which
1031    /// match the provided status, account, and overlap the provided time range.
1032    ///
1033    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1034    /// and return type
1035    fn get_requests_by_status_and_account_and_target_date_between<T, U>(
1036        &self,
1037        status: T,
1038        account_uri: U,
1039        start: OffsetDateTime,
1040        end: OffsetDateTime,
1041    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
1042    where
1043        T: AsRef<str> + Send + Sync,
1044        U: AsRef<str> + Send + Sync,
1045    {
1046        let mut uri = match self
1047            .path_to_url("requests/search/findAllByStatusAndAccountAndTargetDateBetween")
1048        {
1049            Ok(uri) => uri,
1050            Err(err) => return err.once_err(),
1051        };
1052
1053        uri.set_query(Some(&format!(
1054            "status={}&satelliteNames={}&start={}&end={}",
1055            status.as_ref(),
1056            account_uri.as_ref(),
1057            start.format(&Iso8601::DEFAULT).unwrap(),
1058            end.format(&Iso8601::DEFAULT).unwrap()
1059        )));
1060
1061        self.get_paginated(uri)
1062    }
1063
1064    /// Produces a vector of [`TaskRequest`] items, representing all the tasks which match the
1065    /// provided type, overlap with the provided time range.
1066    ///
1067    /// See [`get`](Self::get) documentation for more details about the process and return type
1068    fn get_requests_by_type_and_target_date_between<T>(
1069        &self,
1070        typ: T,
1071        start: OffsetDateTime,
1072        end: OffsetDateTime,
1073    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
1074    where
1075        T: TryInto<TaskType> + Send + Sync,
1076        Error: From<<T as TryInto<TaskType>>::Error>,
1077    {
1078        async move {
1079            let typ: TaskType = typ.try_into()?;
1080            let mut uri = self.path_to_url("requests/search/findAllByTypeAndTargetDateBetween")?;
1081
1082            uri.set_query(Some(&format!(
1083                "type={}&start={}&end={}",
1084                typ.as_ref(),
1085                start.format(&Iso8601::DEFAULT)?,
1086                end.format(&Iso8601::DEFAULT)?
1087            )));
1088
1089            Ok(self
1090                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1091                .await?
1092                .items)
1093        }
1094    }
1095
1096    /// Produces a vector of [`TaskRequest`] items, representing the list of tasks which have
1097    /// already occurred today.
1098    ///
1099    /// See [`get`](Self::get) documentation for more details about the process and return type
1100    fn get_requests_passed_today(
1101        &self,
1102    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
1103        async move {
1104            let uri = self.path_to_url("requests/search/findAllPassedToday")?;
1105
1106            Ok(self
1107                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1108                .await?
1109                .items)
1110        }
1111    }
1112
1113    /// Produces a vector of [`TaskRequest`] items, representing the list of tasks which will occur
1114    /// later today.
1115    ///
1116    /// See [`get`](Self::get) documentation for more details about the process and return type
1117    fn get_requests_upcoming_today(
1118        &self,
1119    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
1120        async move {
1121            let uri = self.path_to_url("requests/search/findAllUpcomingToday")?;
1122
1123            Ok(self
1124                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1125                .await?
1126                .items)
1127        }
1128    }
1129
1130    /// Produces a paginated stream of [`Satellite`] objects.
1131    ///
1132    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1133    /// and return type
1134    fn get_satellites(&self) -> PaginatedStream<'_, Self::Container<Satellite>> {
1135        let uri = match self.path_to_url("satellites") {
1136            Ok(uri) => uri,
1137            Err(err) => return err.once_err(),
1138        };
1139
1140        self.get_paginated(uri)
1141    }
1142
1143    /// Produces single satellite object matching the provided satellite ID
1144    fn get_satellite_by_id(
1145        &self,
1146        satellite_id: i32,
1147    ) -> impl Future<Output = Result<Self::Container<Satellite>, Error>> + Send + Sync {
1148        async move {
1149            let uri = self.path_to_url(format!("satellites/{}", satellite_id))?;
1150
1151            self.get_json_map(uri).await
1152        }
1153    }
1154
1155    /// Produces single satellite object matching the provided satellite name
1156    fn get_satellite_by_name(
1157        &self,
1158        satellite_name: &str,
1159    ) -> impl Future<Output = Result<Self::Container<Satellite>, Error>> + Send + Sync {
1160        async move {
1161            let mut uri = self.path_to_url("satellites/findOneByName")?;
1162            uri.set_query(Some(&format!("name={satellite_name}")));
1163
1164            self.get_json_map(uri).await
1165        }
1166    }
1167
1168    /// Produces a single [`Task`] matching the provided ID.
1169    ///
1170    /// See [`get`](Self::get) documentation for more details about the process and return type
1171    fn get_task_by_id(
1172        &self,
1173        task_id: i32,
1174    ) -> impl Future<Output = Result<Self::Container<Task>, Error>> + Send + Sync {
1175        async move {
1176            let uri = self.path_to_url(format!("tasks/{}", task_id))?;
1177
1178            self.get_json_map(uri).await
1179        }
1180    }
1181
1182    /// Produces a vector of [`Task`] items, representing all the tasks which match the provided
1183    /// account, and intersect with the provided time frame.
1184    ///
1185    /// See [`get`](Self::get) documentation for more details about the process and return type
1186    fn get_tasks_by_account_and_pass_overlapping<T>(
1187        &self,
1188        account_uri: T,
1189        start: OffsetDateTime,
1190        end: OffsetDateTime,
1191    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1192    where
1193        T: AsRef<str> + Send + Sync,
1194    {
1195        async move {
1196            let mut uri = self.path_to_url("tasks/search/findByAccountAndPassOverlapping")?;
1197
1198            uri.set_query(Some(&format!(
1199                "account={}&start={}&end={}",
1200                account_uri.as_ref(),
1201                start.format(&Iso8601::DEFAULT)?,
1202                end.format(&Iso8601::DEFAULT)?
1203            )));
1204
1205            Ok(self
1206                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1207                .await?
1208                .items)
1209        }
1210    }
1211
1212    /// Produces a vector of [`Task`] items, representing all the tasks which match the provided
1213    /// account, satellite, band, and intersect with the provided time frame.
1214    ///
1215    /// See [`get`](Self::get) documentation for more details about the process and return type
1216    fn get_tasks_by_account_and_satellite_and_band_and_pass_overlapping<T, U, V>(
1217        &self,
1218        account_uri: T,
1219        satellite_config_uri: U,
1220        band: V,
1221        start: OffsetDateTime,
1222        end: OffsetDateTime,
1223    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1224    where
1225        T: AsRef<str> + Send + Sync,
1226        U: AsRef<str> + Send + Sync,
1227        V: AsRef<str> + Send + Sync,
1228    {
1229        async move {
1230            let mut uri = self.path_to_url(
1231                "tasks/search/findByAccountAndSiteConfigurationAndBandAndPassOverlapping",
1232            )?;
1233
1234            uri.set_query(Some(&format!(
1235                "account={}&satellite={}&band={}&start={}&end={}",
1236                account_uri.as_ref(),
1237                satellite_config_uri.as_ref(),
1238                band.as_ref(),
1239                start.format(&Iso8601::DEFAULT)?,
1240                end.format(&Iso8601::DEFAULT)?,
1241            )));
1242
1243            Ok(self
1244                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1245                .await?
1246                .items)
1247        }
1248    }
1249
1250    /// Produces a vector of [`Task`] representing all the tasks which match the provided account,
1251    /// site configuration, band, and intersect with the provided time frame.
1252    ///
1253    /// See [`get`](Self::get) documentation for more details about the process and return type
1254    fn get_tasks_by_account_and_site_configuration_and_band_and_pass_overlapping<T, U, V>(
1255        &self,
1256        account_uri: T,
1257        site_config_uri: U,
1258        band: V,
1259        start: OffsetDateTime,
1260        end: OffsetDateTime,
1261    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1262    where
1263        T: AsRef<str> + Send + Sync,
1264        U: AsRef<str> + Send + Sync,
1265        V: AsRef<str> + Send + Sync,
1266    {
1267        async move {
1268            let mut uri = self.path_to_url(
1269                "tasks/search/findByAccountAndSiteConfigurationAndBandAndPassOverlapping",
1270            )?;
1271
1272            uri.set_query(Some(&format!(
1273                "account={}&siteConfig={}&band={}&start={}&end={}",
1274                account_uri.as_ref(),
1275                site_config_uri.as_ref(),
1276                band.as_ref(),
1277                start.format(&Iso8601::DEFAULT)?,
1278                end.format(&Iso8601::DEFAULT)?
1279            )));
1280
1281            Ok(self
1282                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1283                .await?
1284                .items)
1285        }
1286    }
1287
1288    /// Produces a vector of [`Task`] items, representing all the tasks contained within the
1289    /// provided time frame.
1290    ///
1291    /// See [`get`](Self::get) documentation for more details about the process and return type
1292    ///
1293    /// # Note
1294    ///
1295    /// This differs from [`Self::get_tasks_by_pass_overlapping`] in that it only produces tasks
1296    /// which are wholly contained within the window.
1297    fn get_tasks_by_pass_window(
1298        &self,
1299        start: OffsetDateTime,
1300        end: OffsetDateTime,
1301    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1302        async move {
1303            let mut uri = self.path_to_url("tasks/search/findByStartBetweenOrderByStartAsc")?;
1304
1305            uri.set_query(Some(&format!(
1306                "start={}&end={}",
1307                start.format(&Iso8601::DEFAULT)?,
1308                end.format(&Iso8601::DEFAULT)?
1309            )));
1310
1311            Ok(self
1312                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1313                .await?
1314                .items)
1315        }
1316    }
1317
1318    /// Produces a paginated stream of [`Task`] items, representing all the tasks which overlap the
1319    /// provided time frame.
1320    ///
1321    /// See [`get`](Self::get) documentation for more details about the process and return type
1322    ///
1323    /// # Note
1324    ///
1325    /// This differs from [`Self::get_tasks_by_pass_window`] in that it also includes tasks which
1326    /// only partially fall within the provided time frame.
1327    fn get_tasks_by_pass_overlapping(
1328        &self,
1329        start: OffsetDateTime,
1330        end: OffsetDateTime,
1331    ) -> PaginatedStream<'_, Self::Container<Task>> {
1332        let start = match start.format(&Iso8601::DEFAULT).map_err(Error::from) {
1333            Ok(start) => start,
1334            Err(error) => return error.once_err(),
1335        };
1336
1337        let end = match end.format(&Iso8601::DEFAULT).map_err(Error::from) {
1338            Ok(end) => end,
1339            Err(error) => return error.once_err(),
1340        };
1341
1342        let mut uri = match self.path_to_url("tasks/search/findByOverlapping") {
1343            Ok(uri) => uri,
1344            Err(err) => return err.once_err(),
1345        };
1346
1347        uri.set_query(Some(&format!("start={}&end={}", start, end)));
1348
1349        self.get_paginated(uri)
1350    }
1351
1352    /// Produces a vector of [`Task`] items, representing the list of tasks which have already
1353    /// occurred today.
1354    ///
1355    /// See [`get`](Self::get) documentation for more details about the process and return type
1356    fn get_tasks_passed_today(
1357        &self,
1358    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1359        async move {
1360            let uri = self.path_to_url("tasks/search/findAllPassedToday")?;
1361
1362            Ok(self
1363                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1364                .await?
1365                .items)
1366        }
1367    }
1368
1369    /// Produces a vector of [`Task`] items, representing the list of tasks which will occur later
1370    /// today.
1371    ///
1372    /// See [`get`](Self::get) documentation for more details about the process and return type
1373    fn get_tasks_upcoming_today(
1374        &self,
1375    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1376        async move {
1377            let uri = self.path_to_url("tasks/search/findAllUpcomingToday")?;
1378
1379            Ok(self
1380                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1381                .await?
1382                .items)
1383        }
1384    }
1385
1386    /// Produces a paginated stream of [`User`] objects.
1387    ///
1388    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1389    /// and return type
1390    fn get_users(&self) -> PaginatedStream<'_, Self::Container<User>> {
1391        let uri = match self.path_to_url("users") {
1392            Ok(uri) => uri,
1393            Err(err) => return err.once_err(),
1394        };
1395        self.get_paginated(uri)
1396    }
1397
1398    /// Create a new satellite band object
1399    ///
1400    /// # Example
1401    ///
1402    /// ```no_run
1403    /// # use freedom_api::prelude::*;
1404    /// # tokio_test::block_on(async {
1405    /// let client = Client::from_env()?;
1406    ///
1407    /// client
1408    ///     .new_band_details()
1409    ///     .name("My Satellite Band")
1410    ///     .band_type(BandType::Receive)
1411    ///     .frequency(8096.0)
1412    ///     .default_band_width(1.45)
1413    ///     .io_hardware(IoHardware::Modem)
1414    ///     .send()
1415    ///     .await?;
1416    /// # Ok::<_, Box<dyn std::error::Error>>(())
1417    /// # });
1418    /// ```
1419    fn new_band_details(&self) -> post::band::BandDetailsBuilder<'_, Self, post::band::NoName>
1420    where
1421        Self: Sized,
1422    {
1423        post::band::new(self)
1424    }
1425
1426    /// Create a new satellite configuration
1427    ///
1428    /// # Example
1429    ///
1430    /// ```no_run
1431    /// # use freedom_api::prelude::*;
1432    /// # tokio_test::block_on(async {
1433    /// let client = Client::from_env()?;
1434    ///
1435    /// client
1436    ///     .new_satellite_configuration()
1437    ///     .name("My Satellite Configuration")
1438    ///     .band_ids([1, 2, 3]) // List of band IDs to associate with config
1439    ///     .send()
1440    ///     .await?;
1441    /// # Ok::<_, Box<dyn std::error::Error>>(())
1442    /// # });
1443    /// ```
1444    fn new_satellite_configuration(
1445        &self,
1446    ) -> post::sat_config::SatelliteConfigurationBuilder<'_, Self, post::sat_config::NoName>
1447    where
1448        Self: Sized,
1449    {
1450        post::sat_config::new(self)
1451    }
1452
1453    /// Create a new satellite
1454    ///
1455    /// # Example
1456    ///
1457    /// ```no_run
1458    /// # use freedom_api::prelude::*;
1459    /// # tokio_test::block_on(async {
1460    /// let client = Client::from_env()?;
1461    ///
1462    /// client
1463    ///     .new_satellite()
1464    ///     .name("My Satellite")
1465    ///     .satellite_configuration_id(42)
1466    ///     .norad_id(3600)
1467    ///     .description("A test satellite")
1468    ///     .send()
1469    ///     .await?;
1470    /// # Ok::<_, Box<dyn std::error::Error>>(())
1471    /// # });
1472    /// ```
1473    fn new_satellite(&self) -> post::satellite::SatelliteBuilder<'_, Self, post::satellite::NoName>
1474    where
1475        Self: Sized,
1476    {
1477        post::satellite::new(self)
1478    }
1479
1480    /// Create a new override
1481    ///
1482    /// # Example
1483    ///
1484    /// ```no_run
1485    /// # use freedom_api::prelude::*;
1486    /// # tokio_test::block_on(async {
1487    /// let client = Client::from_env()?;
1488    ///
1489    /// client
1490    ///     .new_override()
1491    ///     .name("downconverter.gain override for sat 1 on config 2")
1492    ///     .satellite_id(1)
1493    ///     .satellite_configuration_id(2)
1494    ///     .with_property("site.hardware.modem.ttc.rx.demodulator.bitrate", 8096_u32)
1495    ///     .with_property("site.hardware.modem.ttc.tx.modulator.bitrate", 8096_u32)
1496    ///     .send()
1497    ///     .await?;
1498    /// # Ok::<_, Box<dyn std::error::Error>>(())
1499    /// # });
1500    /// ```
1501    fn new_override(&self) -> post::overrides::OverrideBuilder<'_, Self, post::overrides::NoName>
1502    where
1503        Self: Sized,
1504    {
1505        post::overrides::new(self)
1506    }
1507
1508    /// Create a new user
1509    ///
1510    /// # Example
1511    ///
1512    /// ```no_run
1513    /// # use freedom_api::prelude::*;
1514    /// # tokio_test::block_on(async {
1515    /// let client = Client::from_env()?;
1516    ///
1517    /// client
1518    ///     .new_user()
1519    ///     .account_id(1)
1520    ///     .first_name("Han")
1521    ///     .last_name("Solo")
1522    ///     .email("flyingsolo@gmail.com")
1523    ///     .send()
1524    ///     .await?;
1525    /// # Ok::<_, Box<dyn std::error::Error>>(())
1526    /// # });
1527    /// ```
1528    fn new_user(&self) -> post::user::UserBuilder<'_, Self, post::user::NoAccount>
1529    where
1530        Self: Sized,
1531    {
1532        post::user::new(self)
1533    }
1534
1535    /// Create a new task request
1536    ///
1537    /// # Example
1538    ///
1539    /// ```no_run
1540    /// # use freedom_api::prelude::*;
1541    /// # use time::OffsetDateTime;
1542    /// # use std::time::Duration;
1543    /// # tokio_test::block_on(async {
1544    /// let client = Client::from_env()?;
1545    ///
1546    /// client
1547    ///     .new_task_request()
1548    ///     .test_task("my_test_file.bin")
1549    ///     .target_time_utc(OffsetDateTime::now_utc() + Duration::from_secs(15 * 60))
1550    ///     .task_duration(120)
1551    ///     .satellite_id(1016)
1552    ///     .site_id(27)
1553    ///     .site_configuration_id(47)
1554    ///     .band_ids([2017, 2019])
1555    ///     .send()
1556    ///     .await?;
1557    /// # Ok::<_, Box<dyn std::error::Error>>(())
1558    /// # });
1559    /// ```
1560    fn new_task_request(&self) -> post::TaskRequestBuilder<'_, Self, post::request::NoType>
1561    where
1562        Self: Sized,
1563    {
1564        post::request::new(self)
1565    }
1566
1567    /// Fetch an FPS token for the provided band ID and site configuration ID
1568    ///
1569    /// # Example
1570    ///
1571    /// ```no_run
1572    /// # use freedom_api::prelude::*;
1573    /// # tokio_test::block_on(async {
1574    /// const BAND_ID: u32 = 42;
1575    /// const SITE_CONFIG_ID: u32 = 201;
1576    ///
1577    /// let client = Client::from_env()?;
1578    ///
1579    /// let token = client.new_token_by_site_configuration_id(BAND_ID, SITE_CONFIG_ID).await?;
1580    /// // Submit token to FPS ...
1581    /// println!("{:?}", token);
1582    /// # Ok::<_, Box<dyn std::error::Error>>(())
1583    /// # });
1584    /// ```
1585    fn new_token_by_site_configuration_id(
1586        &self,
1587        band_id: u32,
1588        site_configuration_id: u32,
1589    ) -> impl Future<Output = Result<String, Error>> + Send + Sync {
1590        async move {
1591            let url = self.path_to_url("fps")?;
1592            let payload = serde_json::json!({
1593                "band": format!("/api/satellite_bands/{}", band_id),
1594                "configuration": format!("/api/configurations/{}", site_configuration_id),
1595            });
1596
1597            let value: JsonValue = self.post_json_map(url, &payload).await?;
1598
1599            value
1600                .get("token")
1601                .ok_or(Error::Response(String::from("Missing token field")))?
1602                .as_str()
1603                .ok_or(Error::Response(String::from("Invalid type for token")))
1604                .map(|s| s.to_owned())
1605        }
1606    }
1607
1608    /// Fetch an FPS token for the provided band ID and satellite ID
1609    ///
1610    /// # Example
1611    ///
1612    /// ```no_run
1613    /// # use freedom_api::prelude::*;
1614    /// # tokio_test::block_on(async {
1615    /// const BAND_ID: u32 = 42;
1616    /// const SATELLITE_ID: u32 = 101;
1617    ///
1618    /// let client = Client::from_env()?;
1619    ///
1620    /// let token = client.new_token_by_satellite_id(BAND_ID, SATELLITE_ID).await?;
1621    /// // Submit token to FPS ...
1622    /// println!("{:?}", token);
1623    /// # Ok::<_, Box<dyn std::error::Error>>(())
1624    /// # });
1625    /// ```
1626    fn new_token_by_satellite_id(
1627        &self,
1628        band_id: u32,
1629        satellite_id: u32,
1630    ) -> impl Future<Output = Result<String, Error>> + Send + Sync {
1631        async move {
1632            let url = self.path_to_url("fps")?;
1633            let payload = serde_json::json!({
1634                "band": format!("/api/satellite_bands/{}", band_id),
1635                "satellite": format!("/api/satellites/{}", satellite_id),
1636            });
1637
1638            let value: JsonValue = self.post_json_map(url, &payload).await?;
1639
1640            value
1641                .get("token")
1642                .ok_or(Error::Response(String::from("Missing token field")))?
1643                .as_str()
1644                .ok_or(Error::Response(String::from("Invalid type for token")))
1645                .map(|s| s.to_owned())
1646        }
1647    }
1648}
1649
1650pub(crate) fn error_on_non_success(status: &StatusCode, body: &[u8]) -> Result<(), Error> {
1651    if !status.is_success() {
1652        return Err(Error::ResponseStatus {
1653            status: *status,
1654            error: String::from_utf8_lossy(body).to_string(),
1655        });
1656    }
1657
1658    Ok(())
1659}