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