Skip to main content

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::{PayloadStatus, Task, TaskRequest, TaskStatusType, TaskType},
21    user::{User, WhoAmI},
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 data = client.get_file_by_task_id_and_name(42, "data.bin").await?;
422    /// # Ok::<_, Box<dyn std::error::Error>>(())
423    /// # });
424    /// ```
425    fn get_file_by_task_id_and_name(
426        &self,
427        task_id: i32,
428        file_name: &str,
429    ) -> impl Future<Output = Result<Bytes, Error>> + Send + Sync {
430        async move {
431            let path = format!("downloads/{}/{}", task_id, file_name);
432            let uri = self.path_to_url(path)?;
433
434            let (data, status) = self.get(uri).await?;
435            error_on_non_success(&status, b"Failed to fetch file")?;
436
437            Ok(data)
438        }
439    }
440
441    /// Produces a paginated stream of [`Account`](freedom_models::account::Account) objects.
442    ///
443    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
444    /// and return type
445    fn get_accounts(&self) -> PaginatedStream<'_, Self::Container<Account>> {
446        let uri = match self.path_to_url("accounts") {
447            Ok(uri) => uri,
448            Err(err) => return err.once_err(),
449        };
450        self.get_paginated(uri)
451    }
452
453    /// Produces a single [`Account`](freedom_models::account::Account) matching the provided ID.
454    ///
455    /// See [`get`](Self::get) documentation for more details about the process and return type
456    fn get_account_by_id(
457        &self,
458        account_id: i32,
459    ) -> impl Future<Output = Result<Self::Container<Account>, Error>> + Send + Sync {
460        async move {
461            let uri = self.path_to_url(format!("accounts/{account_id}"))?;
462            self.get_json_map(uri).await
463        }
464    }
465
466    /// Produces a single [`Account`](freedom_models::account::Account) matching the provided ID.
467    ///
468    /// See [`get`](Self::get) documentation for more details about the process and return type
469    ///
470    /// # Example
471    ///
472    /// ```no_run
473    /// # use freedom_api::prelude::*;
474    /// # tokio_test::block_on(async {
475    /// let client = Client::from_env()?;
476    ///
477    /// let account = client.get_account_by_name("ATLAS").await?;
478    /// println!("{}", account.name);
479    /// # Ok::<_, Box<dyn std::error::Error>>(())
480    /// # });
481    /// ```
482    fn get_account_by_name(
483        &self,
484        account_name: &str,
485    ) -> impl Future<Output = Result<Self::Container<Account>, Error>> + Send + Sync {
486        async move {
487            let mut uri = self.path_to_url("accounts/search/findOneByName")?;
488            uri.set_query(Some(&format!("name={account_name}")));
489            self.get_json_map(uri).await
490        }
491    }
492
493    /// Gets the [`PayloadStatus`] for a particular task ID and band name, optionally also filtering
494    /// by artifact name.
495    ///
496    /// See [`get`](Self::get) documentation for more details about the process and return type
497    ///
498    /// # Example
499    ///
500    /// ```no_run
501    /// # use freedom_api::prelude::*;
502    /// # tokio_test::block_on(async {
503    /// let client = Client::from_env()?;
504    ///
505    /// let status = client.get_payload_status(1, "My-Band", None).await?;
506    /// println!("{}", status.payload_enabled);
507    /// # Ok::<_, Box<dyn std::error::Error>>(())
508    /// # });
509    /// ```
510    fn get_payload_status(
511        &self,
512        task_id: i32,
513        band_name: &str,
514        artifact_name: Option<&str>,
515    ) -> impl Future<Output = Result<Self::Container<PayloadStatus>, Error>> + Send + Sync {
516        async move {
517            let mut uri = self.path_to_url(format!("tasks/{}/payloadStatus", task_id))?;
518
519            // rustc is getting all testy with holding pairs across awaits, so we need to prove to
520            // it that it is not
521            {
522                let mut pairs = uri.query_pairs_mut();
523                pairs.append_pair("band", band_name.as_ref());
524
525                if let Some(artifact_name) = artifact_name {
526                    pairs.append_pair("artifact", artifact_name.as_ref());
527                }
528
529                let _ = pairs.finish();
530            }
531
532            self.get_json_map(uri).await
533        }
534    }
535
536    /// Produces a paginated stream of [`Band`] objects.
537    ///
538    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
539    /// and return type
540    fn get_satellite_bands(&self) -> PaginatedStream<'_, Self::Container<Band>> {
541        let uri = match self.path_to_url("satellite_bands") {
542            Ok(uri) => uri,
543            Err(err) => return err.once_err(),
544        };
545        self.get_paginated(uri)
546    }
547
548    /// Produces a single [`Band`] matching the provided ID.
549    ///
550    /// See [`get`](Self::get) documentation for more details about the process and return type
551    fn get_satellite_band_by_id(
552        &self,
553        satellite_band_id: i32,
554    ) -> impl Future<Output = Result<Self::Container<Band>, Error>> + Send + Sync {
555        async move {
556            let uri = self.path_to_url(format!("satellite_bands/{satellite_band_id}"))?;
557            self.get_json_map(uri).await
558        }
559    }
560
561    /// Produces a single [`Band`] matching the provided name.
562    ///
563    /// See [`get`](Self::get) documentation for more details about the process and return type
564    fn get_satellite_band_by_name(
565        &self,
566        satellite_band_name: &str,
567    ) -> impl Future<Output = Result<Self::Container<Band>, Error>> + Send + Sync {
568        async move {
569            let mut uri = self.path_to_url("satellite_bands/search/findOneByName")?;
570            uri.set_query(Some(&format!("name={satellite_band_name}")));
571            self.get_json_map(uri).await
572        }
573    }
574
575    /// Produces a paginated stream of [`Band`] objects matching the provided account name.
576    ///
577    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
578    /// and return type
579    fn get_satellite_bands_by_account_name(
580        &self,
581        account_name: &str,
582    ) -> PaginatedStream<'_, Self::Container<Band>> {
583        let mut uri = match self.path_to_url("satellite_bands/search/findAllByAccountName") {
584            Ok(uri) => uri,
585            Err(err) => return err.once_err(),
586        };
587        uri.set_query(Some(&format!("accountName={account_name}")));
588
589        self.get_paginated(uri)
590    }
591
592    /// Produces a paginated stream of [`SatelliteConfiguration`] objects matching the provided
593    /// account name.
594    ///
595    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
596    /// and return type
597    fn get_satellite_configurations_by_account_name(
598        &self,
599        account_name: &str,
600    ) -> PaginatedStream<'_, Self::Container<SatelliteConfiguration>> {
601        let mut uri = match self.path_to_url("satellite_configurations/search/findAllByAccountName")
602        {
603            Ok(uri) => uri,
604            Err(err) => return err.once_err(),
605        };
606        uri.set_query(Some(&format!("accountName={account_name}")));
607
608        self.get_paginated(uri)
609    }
610
611    /// Produces a paginated stream of [`SatelliteConfiguration`] objects.
612    ///
613    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
614    /// and return type
615    fn get_satellite_configurations(
616        &self,
617    ) -> PaginatedStream<'_, Self::Container<SatelliteConfiguration>> {
618        let uri = match self.path_to_url("satellite_configurations") {
619            Ok(uri) => uri,
620            Err(err) => return err.once_err(),
621        };
622
623        self.get_paginated(uri)
624    }
625
626    /// Produces a single satellite configuration matching the provided satellite configuration ID
627    fn get_satellite_configuration_by_id(
628        &self,
629        satellite_configuration_id: i32,
630    ) -> impl Future<Output = Result<Self::Container<SatelliteConfiguration>, Error>> + Send + Sync
631    {
632        async move {
633            let uri = self.path_to_url(format!(
634                "satellite_configurations/{satellite_configuration_id}"
635            ))?;
636
637            self.get_json_map(uri).await
638        }
639    }
640
641    /// Produces a single satellite configuration matching the provided satellite configuration name
642    fn get_satellite_configuration_by_name(
643        &self,
644        satellite_configuration_name: &str,
645    ) -> impl Future<Output = Result<Self::Container<SatelliteConfiguration>, Error>> + Send + Sync
646    {
647        async move {
648            let mut uri = self.path_to_url("satellite_configurations/search/findOneByName")?;
649            uri.set_query(Some(&format!("name={satellite_configuration_name}")));
650
651            self.get_json_map(uri).await
652        }
653    }
654
655    /// Produces a paginated stream of [`Site`] objects.
656    ///
657    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
658    /// and return type
659    fn get_sites(&self) -> PaginatedStream<'_, Self::Container<Site>> {
660        let uri = match self.path_to_url("sites") {
661            Ok(uri) => uri,
662            Err(err) => return err.once_err(),
663        };
664        self.get_paginated(uri)
665    }
666
667    /// Produces a single [`Site`] object matching the provided ID.
668    ///
669    /// See [`get`](Self::get) documentation for more details about the process and return type
670    fn get_site_by_id(
671        &self,
672        id: i32,
673    ) -> impl Future<Output = Result<Self::Container<Site>, Error>> + Send + Sync {
674        async move {
675            let uri = self.path_to_url(format!("sites/{id}"))?;
676            self.get_json_map(uri).await
677        }
678    }
679
680    /// Produces a single [`Site`] object matching the provided name.
681    ///
682    /// See [`get`](Self::get) documentation for more details about the process and return type
683    fn get_site_by_name(
684        &self,
685        name: impl AsRef<str> + Send + Sync,
686    ) -> impl Future<Output = Result<Self::Container<Site>, Error>> + Send + Sync {
687        async move {
688            let mut uri = self.path_to_url("sites/search/findOneByName")?;
689            let query = format!("name={}", name.as_ref());
690            uri.set_query(Some(&query));
691
692            self.get_json_map(uri).await
693        }
694    }
695
696    /// Produces a paginated stream of [`SiteConfiguration`] objects.
697    ///
698    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
699    /// and return type
700    fn get_site_configurations(&self) -> PaginatedStream<'_, Self::Container<SiteConfiguration>> {
701        let uri = match self.path_to_url("configurations") {
702            Ok(uri) => uri,
703            Err(err) => return err.once_err(),
704        };
705        self.get_paginated(uri)
706    }
707
708    /// Produces a single [`SiteConfiguration`] object matching the provided ID.
709    ///
710    /// See [`get`](Self::get) documentation for more details about the process and return type
711    fn get_site_configuration_by_id(
712        &self,
713        id: i32,
714    ) -> impl Future<Output = Result<Self::Container<SiteConfiguration>, Error>> + Send + Sync {
715        async move {
716            let uri = self.path_to_url(format!("configurations/{id}"))?;
717            self.get_json_map(uri).await
718        }
719    }
720
721    /// Produces a single [`SiteConfiguration`] object matching the provided name.
722    ///
723    /// See [`get`](Self::get) documentation for more details about the process and return type
724    fn get_site_configuration_by_name(
725        &self,
726        name: impl AsRef<str> + Send + Sync,
727    ) -> impl Future<Output = Result<Self::Container<SiteConfiguration>, Error>> + Send + Sync {
728        async move {
729            let mut uri = self.path_to_url("configurations/search/findOneByName")?;
730            let query = format!("name={}", name.as_ref());
731            uri.set_query(Some(&query));
732
733            self.get_json_map(uri).await
734        }
735    }
736
737    /// Produces a single [`TaskRequest`] matching the provided ID.
738    ///
739    /// See [`get`](Self::get) documentation for more details about the process and return type
740    fn get_request_by_id(
741        &self,
742        task_request_id: i32,
743    ) -> impl Future<Output = Result<Self::Container<TaskRequest>, Error>> + Send + Sync {
744        async move {
745            let uri = self.path_to_url(format!("requests/{task_request_id}"))?;
746
747            self.get_json_map(uri).await
748        }
749    }
750
751    /// Produces a paginated stream of [`TaskRequest`] objects.
752    ///
753    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
754    /// and return type
755    fn get_requests(&self) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
756        {
757            let uri = match self.path_to_url("requests/search/findAll") {
758                Ok(uri) => uri,
759                Err(err) => return err.once_err(),
760            };
761            self.get_paginated(uri)
762        }
763    }
764
765    /// Produces a vector of [`TaskRequest`] items, representing all the task requests matching the
766    /// target time overlapping with the provided time range.
767    fn get_requests_by_target_date_between(
768        &self,
769        start: OffsetDateTime,
770        end: OffsetDateTime,
771    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
772        async move {
773            let mut uri = self.path_to_url("requests/search/findAllByTargetDateBetween")?;
774
775            uri.set_query(Some(&format!(
776                "start={}&end={}",
777                start.format(&Iso8601::DEFAULT)?,
778                end.format(&Iso8601::DEFAULT)?,
779            )));
780
781            self.get_json_map(uri).await
782        }
783    }
784
785    /// Produces a vector of [`TaskRequest`] items,
786    /// representing all the task requests matching the account at the provided URI and whose
787    /// target time overlaps with the provided time range.
788    ///
789    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
790    /// and return type
791    fn get_requests_by_account_and_target_date_between<T>(
792        &self,
793        account_uri: T,
794        start: OffsetDateTime,
795        end: OffsetDateTime,
796    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
797    where
798        T: AsRef<str> + Send + Sync,
799    {
800        let start = match start.format(&Iso8601::DEFAULT).map_err(Error::from) {
801            Ok(start) => start,
802            Err(error) => return error.once_err(),
803        };
804
805        let end = match end.format(&Iso8601::DEFAULT).map_err(Error::from) {
806            Ok(end) => end,
807            Err(error) => return error.once_err(),
808        };
809
810        let mut uri = match self.path_to_url("requests/search/findAllByAccountAndTargetDateBetween")
811        {
812            Ok(uri) => uri,
813            Err(err) => return err.once_err(),
814        };
815
816        uri.set_query(Some(&format!(
817            "account={}&start={}&end={}",
818            account_uri.as_ref(),
819            start,
820            end
821        )));
822
823        self.get_paginated(uri)
824    }
825
826    /// Produces a paginated stream of [`TaskRequest`]
827    /// objects whose account name matches the provided name, and whose pass will occur today.
828    ///
829    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
830    /// and return type
831    fn get_requests_by_account_and_upcoming_today(
832        &self,
833    ) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
834        let uri = match self.path_to_url("requests/search/findByAccountUpcomingToday") {
835            Ok(uri) => uri,
836            Err(err) => return err.once_err(),
837        };
838
839        self.get_paginated(uri)
840    }
841
842    /// Produces a paginated stream of [`TaskRequest`]
843    /// objects whose satellite configuration matches that of the configuration at the
844    /// `configuration_uri` endpoint.
845    ///
846    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
847    /// and return type
848    ///
849    /// # Note
850    /// The results are ordered by the creation time of the task request
851    fn get_requests_by_configuration<T>(
852        &self,
853        configuration_uri: T,
854    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
855    where
856        T: AsRef<str> + Send + Sync,
857    {
858        let mut uri =
859            match self.path_to_url("requests/search/findAllByConfigurationOrderByCreatedAsc") {
860                Ok(uri) => uri,
861                Err(err) => return err.once_err(),
862            };
863
864        uri.set_query(Some(&format!(
865            "configuration={}",
866            configuration_uri.as_ref()
867        )));
868
869        self.get_paginated::<TaskRequest>(uri)
870    }
871
872    /// Produces a vector of [`TaskRequest`] items, representing all the task requests which match
873    /// the provided configuration, whose satellite name matches one of the names provided as part
874    /// of `satellite_name`, and which overlaps the provided time range.
875    ///
876    /// See [`get`](Self::get) documentation for more details about the process and return type
877    fn get_requests_by_configuration_and_satellite_names_and_target_date_between<T, I, S>(
878        &self,
879        configuration_uri: T,
880        satellites: I,
881        start: OffsetDateTime,
882        end: OffsetDateTime,
883    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
884    where
885        T: AsRef<str> + Send + Sync,
886        I: IntoIterator<Item = S> + Send + Sync,
887        S: AsRef<str> + Send + Sync,
888    {
889        async move {
890            let satellites_string = crate::utils::list_to_string(satellites);
891            let mut uri = self.path_to_url(
892                "requests/search/findAllByConfigurationAndSatelliteNamesAndTargetDateBetween",
893            )?;
894
895            uri.set_query(Some(&format!(
896                "configuration={}&satelliteNames={}&start={}&end={}",
897                configuration_uri.as_ref(),
898                satellites_string,
899                start.format(&Iso8601::DEFAULT)?,
900                end.format(&Iso8601::DEFAULT)?,
901            )));
902
903            Ok(self
904                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
905                .await?
906                .items)
907        }
908    }
909
910    /// Produces a vector of [`TaskRequest`] items, representing all the task requests matching the
911    /// configuration at the provided URI and whose target time overlaps with the provided time
912    /// range.
913    ///
914    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
915    /// and return type
916    fn get_requests_by_configuration_and_target_date_between<T>(
917        &self,
918        configuration_uri: T,
919        start: OffsetDateTime,
920        end: OffsetDateTime,
921    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
922    where
923        T: AsRef<str> + Send + Sync,
924    {
925        async move {
926            let mut uri =
927                self.path_to_url("requests/search/findAllByConfigurationAndTargetDateBetween")?;
928            uri.set_query(Some(&format!(
929                "configuration={}&start={}&end={}",
930                configuration_uri.as_ref(),
931                start.format(&Iso8601::DEFAULT)?,
932                end.format(&Iso8601::DEFAULT)?,
933            )));
934
935            Ok(self
936                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
937                .await?
938                .items)
939        }
940    }
941
942    /// Produces a vector of [`TaskRequest`] items,
943    /// representing all the task requests whose ID matches one of the IDs provided as part of
944    /// `ids`.
945    ///
946    /// See [`get`](Self::get) documentation for more details about the process and return type
947    fn get_requests_by_ids<I, S>(
948        &self,
949        ids: I,
950    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
951    where
952        I: IntoIterator<Item = S> + Send + Sync,
953        S: AsRef<str> + Send + Sync,
954    {
955        async move {
956            let ids_string = crate::utils::list_to_string(ids);
957            let mut uri = self.path_to_url("requests/search/findAllByIds")?;
958
959            uri.set_query(Some(&format!("ids={}", ids_string)));
960
961            Ok(self
962                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
963                .await?
964                .items)
965        }
966    }
967
968    /// Produces a paginated stream of [`TaskRequest`] objects which are public, and which overlap
969    /// with the provided time range.
970    ///
971    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
972    /// and return type
973    fn get_requests_by_overlapping_public(
974        &self,
975        start: OffsetDateTime,
976        end: OffsetDateTime,
977    ) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
978        let mut uri = match self.path_to_url("requests/search/findAllByOverlappingPublic") {
979            Ok(uri) => uri,
980            Err(err) => return err.once_err(),
981        };
982
983        uri.set_query(Some(&format!(
984            "start={}&end={}",
985            start.format(&Iso8601::DEFAULT).unwrap(),
986            end.format(&Iso8601::DEFAULT).unwrap(),
987        )));
988
989        self.get_paginated(uri)
990    }
991
992    /// Produces a paginated stream of [`TaskRequest`] objects whose satellite name matches one of
993    /// the names provided as part of `satellite_name`.
994    ///
995    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
996    /// and return type
997    fn get_requests_by_satellite_name<T>(
998        &self,
999        satellite_name: T,
1000    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
1001    where
1002        T: AsRef<str> + Send + Sync,
1003    {
1004        let mut uri = match self.path_to_url("requests/search/findBySatelliteName") {
1005            Ok(uri) => uri,
1006            Err(err) => return err.once_err(),
1007        };
1008
1009        uri.set_query(Some(&format!("name={}", satellite_name.as_ref())));
1010
1011        self.get_paginated(uri)
1012    }
1013
1014    /// Produces a vector of [`TaskRequest`] items, representing all the task requests whose
1015    /// satellite name matches the provided name and whose target time overlaps with the provided
1016    /// time range.
1017    ///
1018    /// See [`get`](Self::get) documentation for more details about the process and return type
1019    fn get_requests_by_satellite_name_and_target_date_between<T>(
1020        &self,
1021        satellite_name: T,
1022        start: OffsetDateTime,
1023        end: OffsetDateTime,
1024    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
1025    where
1026        T: AsRef<str> + Send + Sync,
1027    {
1028        async move {
1029            let mut uri =
1030                self.path_to_url("requests/search/findAllBySatelliteNameAndTargetDateBetween")?;
1031
1032            uri.set_query(Some(&format!(
1033                "name={}&start={}&end={}",
1034                satellite_name.as_ref(),
1035                start.format(&Iso8601::DEFAULT)?,
1036                end.format(&Iso8601::DEFAULT)?
1037            )));
1038
1039            Ok(self
1040                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1041                .await?
1042                .items)
1043        }
1044    }
1045
1046    /// Produces a paginated stream of [`TaskRequest`] objects whose status matches the provided
1047    /// status.
1048    ///
1049    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1050    /// and return type
1051    fn get_requests_by_status<T>(
1052        &self,
1053        status: T,
1054    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
1055    where
1056        T: TryInto<TaskStatusType> + Send + Sync,
1057        Error: From<<T as TryInto<TaskStatusType>>::Error>,
1058    {
1059        let status: TaskStatusType = match status.try_into() {
1060            Ok(val) => val,
1061            Err(err) => return Error::from(err).once_err(),
1062        };
1063        let mut uri = match self.path_to_url("requests/search/findByStatus") {
1064            Ok(uri) => uri,
1065            Err(err) => return err.once_err(),
1066        };
1067
1068        uri.set_query(Some(&format!("status={}", status.as_ref())));
1069
1070        self.get_paginated(uri)
1071    }
1072
1073    /// Produces a paginated stream of [`TaskRequest`], representing all the task requests which
1074    /// match the provided status, account, and overlap the provided time range.
1075    ///
1076    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1077    /// and return type
1078    fn get_requests_by_status_and_account_and_target_date_between<T, U>(
1079        &self,
1080        status: T,
1081        account_uri: U,
1082        start: OffsetDateTime,
1083        end: OffsetDateTime,
1084    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
1085    where
1086        T: AsRef<str> + Send + Sync,
1087        U: AsRef<str> + Send + Sync,
1088    {
1089        let mut uri = match self
1090            .path_to_url("requests/search/findAllByStatusAndAccountAndTargetDateBetween")
1091        {
1092            Ok(uri) => uri,
1093            Err(err) => return err.once_err(),
1094        };
1095
1096        uri.set_query(Some(&format!(
1097            "status={}&satelliteNames={}&start={}&end={}",
1098            status.as_ref(),
1099            account_uri.as_ref(),
1100            start.format(&Iso8601::DEFAULT).unwrap(),
1101            end.format(&Iso8601::DEFAULT).unwrap()
1102        )));
1103
1104        self.get_paginated(uri)
1105    }
1106
1107    /// Produces a vector of [`TaskRequest`] items, representing all the tasks which match the
1108    /// provided type, overlap with the provided time range.
1109    ///
1110    /// See [`get`](Self::get) documentation for more details about the process and return type
1111    fn get_requests_by_type_and_target_date_between<T>(
1112        &self,
1113        typ: T,
1114        start: OffsetDateTime,
1115        end: OffsetDateTime,
1116    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
1117    where
1118        T: TryInto<TaskType> + Send + Sync,
1119        Error: From<<T as TryInto<TaskType>>::Error>,
1120    {
1121        async move {
1122            let typ: TaskType = typ.try_into()?;
1123            let mut uri = self.path_to_url("requests/search/findAllByTypeAndTargetDateBetween")?;
1124
1125            uri.set_query(Some(&format!(
1126                "type={}&start={}&end={}",
1127                typ.as_ref(),
1128                start.format(&Iso8601::DEFAULT)?,
1129                end.format(&Iso8601::DEFAULT)?
1130            )));
1131
1132            Ok(self
1133                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1134                .await?
1135                .items)
1136        }
1137    }
1138
1139    /// Produces a vector of [`TaskRequest`] items, representing the list of tasks which have
1140    /// already occurred today.
1141    ///
1142    /// See [`get`](Self::get) documentation for more details about the process and return type
1143    fn get_requests_passed_today(
1144        &self,
1145    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
1146        async move {
1147            let uri = self.path_to_url("requests/search/findAllPassedToday")?;
1148
1149            Ok(self
1150                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1151                .await?
1152                .items)
1153        }
1154    }
1155
1156    /// Produces a vector of [`TaskRequest`] items, representing the list of tasks which will occur
1157    /// later today.
1158    ///
1159    /// See [`get`](Self::get) documentation for more details about the process and return type
1160    fn get_requests_upcoming_today(
1161        &self,
1162    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
1163        async move {
1164            let uri = self.path_to_url("requests/search/findAllUpcomingToday")?;
1165
1166            Ok(self
1167                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1168                .await?
1169                .items)
1170        }
1171    }
1172
1173    /// Produces a paginated stream of [`Satellite`] objects.
1174    ///
1175    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1176    /// and return type
1177    fn get_satellites(&self) -> PaginatedStream<'_, Self::Container<Satellite>> {
1178        let uri = match self.path_to_url("satellites") {
1179            Ok(uri) => uri,
1180            Err(err) => return err.once_err(),
1181        };
1182
1183        self.get_paginated(uri)
1184    }
1185
1186    /// Produces single satellite object matching the provided satellite ID
1187    fn get_satellite_by_id(
1188        &self,
1189        satellite_id: i32,
1190    ) -> impl Future<Output = Result<Self::Container<Satellite>, Error>> + Send + Sync {
1191        async move {
1192            let uri = self.path_to_url(format!("satellites/{}", satellite_id))?;
1193
1194            self.get_json_map(uri).await
1195        }
1196    }
1197
1198    /// Produces single satellite object matching the provided satellite name
1199    fn get_satellite_by_name(
1200        &self,
1201        satellite_name: &str,
1202    ) -> impl Future<Output = Result<Self::Container<Satellite>, Error>> + Send + Sync {
1203        async move {
1204            let mut uri = self.path_to_url("satellites/findOneByName")?;
1205            uri.set_query(Some(&format!("name={satellite_name}")));
1206
1207            self.get_json_map(uri).await
1208        }
1209    }
1210
1211    /// Produces a single [`Task`] matching the provided ID.
1212    ///
1213    /// See [`get`](Self::get) documentation for more details about the process and return type
1214    fn get_task_by_id(
1215        &self,
1216        task_id: i32,
1217    ) -> impl Future<Output = Result<Self::Container<Task>, Error>> + Send + Sync {
1218        async move {
1219            let uri = self.path_to_url(format!("tasks/{}", task_id))?;
1220
1221            self.get_json_map(uri).await
1222        }
1223    }
1224
1225    /// Produces a vector of [`Task`] items, representing all the tasks which match the provided
1226    /// account, and intersect with the provided time frame.
1227    ///
1228    /// See [`get`](Self::get) documentation for more details about the process and return type
1229    fn get_tasks_by_account_and_pass_overlapping<T>(
1230        &self,
1231        account_uri: T,
1232        start: OffsetDateTime,
1233        end: OffsetDateTime,
1234    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1235    where
1236        T: AsRef<str> + Send + Sync,
1237    {
1238        async move {
1239            let mut uri = self.path_to_url("tasks/search/findByAccountAndPassOverlapping")?;
1240
1241            uri.set_query(Some(&format!(
1242                "account={}&start={}&end={}",
1243                account_uri.as_ref(),
1244                start.format(&Iso8601::DEFAULT)?,
1245                end.format(&Iso8601::DEFAULT)?
1246            )));
1247
1248            Ok(self
1249                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1250                .await?
1251                .items)
1252        }
1253    }
1254
1255    /// Produces a vector of [`Task`] items, representing all the tasks which match the provided
1256    /// account, satellite, band, and intersect with the provided time frame.
1257    ///
1258    /// See [`get`](Self::get) documentation for more details about the process and return type
1259    fn get_tasks_by_account_and_satellite_and_band_and_pass_overlapping<T, U, V>(
1260        &self,
1261        account_uri: T,
1262        satellite_config_uri: U,
1263        band: V,
1264        start: OffsetDateTime,
1265        end: OffsetDateTime,
1266    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1267    where
1268        T: AsRef<str> + Send + Sync,
1269        U: AsRef<str> + Send + Sync,
1270        V: AsRef<str> + Send + Sync,
1271    {
1272        async move {
1273            let mut uri = self.path_to_url(
1274                "tasks/search/findByAccountAndSiteConfigurationAndBandAndPassOverlapping",
1275            )?;
1276
1277            uri.set_query(Some(&format!(
1278                "account={}&satellite={}&band={}&start={}&end={}",
1279                account_uri.as_ref(),
1280                satellite_config_uri.as_ref(),
1281                band.as_ref(),
1282                start.format(&Iso8601::DEFAULT)?,
1283                end.format(&Iso8601::DEFAULT)?,
1284            )));
1285
1286            Ok(self
1287                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1288                .await?
1289                .items)
1290        }
1291    }
1292
1293    /// Produces a vector of [`Task`] representing all the tasks which match the provided account,
1294    /// site configuration, band, and intersect with the provided time frame.
1295    ///
1296    /// See [`get`](Self::get) documentation for more details about the process and return type
1297    fn get_tasks_by_account_and_site_configuration_and_band_and_pass_overlapping<T, U, V>(
1298        &self,
1299        account_uri: T,
1300        site_config_uri: U,
1301        band: V,
1302        start: OffsetDateTime,
1303        end: OffsetDateTime,
1304    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1305    where
1306        T: AsRef<str> + Send + Sync,
1307        U: AsRef<str> + Send + Sync,
1308        V: AsRef<str> + Send + Sync,
1309    {
1310        async move {
1311            let mut uri = self.path_to_url(
1312                "tasks/search/findByAccountAndSiteConfigurationAndBandAndPassOverlapping",
1313            )?;
1314
1315            uri.set_query(Some(&format!(
1316                "account={}&siteConfig={}&band={}&start={}&end={}",
1317                account_uri.as_ref(),
1318                site_config_uri.as_ref(),
1319                band.as_ref(),
1320                start.format(&Iso8601::DEFAULT)?,
1321                end.format(&Iso8601::DEFAULT)?
1322            )));
1323
1324            Ok(self
1325                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1326                .await?
1327                .items)
1328        }
1329    }
1330
1331    /// Produces a vector of [`Task`] items, representing all the tasks contained within the
1332    /// provided time frame.
1333    ///
1334    /// See [`get`](Self::get) documentation for more details about the process and return type
1335    ///
1336    /// # Note
1337    ///
1338    /// This differs from [`Self::get_tasks_by_pass_overlapping`] in that it only produces tasks
1339    /// which are wholly contained within the window.
1340    fn get_tasks_by_pass_window(
1341        &self,
1342        start: OffsetDateTime,
1343        end: OffsetDateTime,
1344    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1345        async move {
1346            let mut uri = self.path_to_url("tasks/search/findByStartBetweenOrderByStartAsc")?;
1347
1348            uri.set_query(Some(&format!(
1349                "start={}&end={}",
1350                start.format(&Iso8601::DEFAULT)?,
1351                end.format(&Iso8601::DEFAULT)?
1352            )));
1353
1354            Ok(self
1355                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1356                .await?
1357                .items)
1358        }
1359    }
1360
1361    /// Produces a paginated stream of [`Task`] items, representing all the tasks which overlap the
1362    /// provided time frame.
1363    ///
1364    /// See [`get`](Self::get) documentation for more details about the process and return type
1365    ///
1366    /// # Note
1367    ///
1368    /// This differs from [`Self::get_tasks_by_pass_window`] in that it also includes tasks which
1369    /// only partially fall within the provided time frame.
1370    fn get_tasks_by_pass_overlapping(
1371        &self,
1372        start: OffsetDateTime,
1373        end: OffsetDateTime,
1374    ) -> PaginatedStream<'_, Self::Container<Task>> {
1375        let start = match start.format(&Iso8601::DEFAULT).map_err(Error::from) {
1376            Ok(start) => start,
1377            Err(error) => return error.once_err(),
1378        };
1379
1380        let end = match end.format(&Iso8601::DEFAULT).map_err(Error::from) {
1381            Ok(end) => end,
1382            Err(error) => return error.once_err(),
1383        };
1384
1385        let mut uri = match self.path_to_url("tasks/search/findByOverlapping") {
1386            Ok(uri) => uri,
1387            Err(err) => return err.once_err(),
1388        };
1389
1390        uri.set_query(Some(&format!("start={}&end={}", start, end)));
1391
1392        self.get_paginated(uri)
1393    }
1394
1395    /// Produces a vector of [`Task`] items, representing the list of tasks which have already
1396    /// occurred today.
1397    ///
1398    /// See [`get`](Self::get) documentation for more details about the process and return type
1399    fn get_tasks_passed_today(
1400        &self,
1401    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1402        async move {
1403            let uri = self.path_to_url("tasks/search/findAllPassedToday")?;
1404
1405            Ok(self
1406                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1407                .await?
1408                .items)
1409        }
1410    }
1411
1412    /// Produces a vector of [`Task`] items, representing the list of tasks which will occur later
1413    /// today.
1414    ///
1415    /// See [`get`](Self::get) documentation for more details about the process and return type
1416    fn get_tasks_upcoming_today(
1417        &self,
1418    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1419        async move {
1420            let uri = self.path_to_url("tasks/search/findAllUpcomingToday")?;
1421
1422            Ok(self
1423                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1424                .await?
1425                .items)
1426        }
1427    }
1428
1429    /// Produces a single [`User`] matching the provided ID.
1430    ///
1431    /// See [`get`](Self::get) documentation for more details about the process and return type
1432    fn get_user_by_id(
1433        &self,
1434        user_id: i32,
1435    ) -> impl Future<Output = Result<Self::Container<User>, Error>> + Send + Sync {
1436        async move {
1437            let uri = self.path_to_url(format!("users/{}", user_id))?;
1438
1439            self.get_json_map(uri).await
1440        }
1441    }
1442
1443    /// Produces a paginated stream of [`User`] objects.
1444    ///
1445    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1446    /// and return type
1447    fn get_users(&self) -> PaginatedStream<'_, Self::Container<User>> {
1448        let uri = match self.path_to_url("users") {
1449            Ok(uri) => uri,
1450            Err(err) => return err.once_err(),
1451        };
1452        self.get_paginated(uri)
1453    }
1454
1455    /// Create a new satellite band object
1456    ///
1457    /// # Example
1458    ///
1459    /// ```no_run
1460    /// # use freedom_api::prelude::*;
1461    /// # tokio_test::block_on(async {
1462    /// let client = Client::from_env()?;
1463    ///
1464    /// client
1465    ///     .new_band_details()
1466    ///     .name("My Satellite Band")
1467    ///     .band_type(BandType::Receive)
1468    ///     .frequency(8096.0)
1469    ///     .default_band_width(1.45)
1470    ///     .io_hardware(IoHardware::Modem)
1471    ///     .send()
1472    ///     .await?;
1473    /// # Ok::<_, Box<dyn std::error::Error>>(())
1474    /// # });
1475    /// ```
1476    fn new_band_details(&self) -> post::band::BandDetailsBuilder<'_, Self, post::band::NoName>
1477    where
1478        Self: Sized,
1479    {
1480        post::band::new(self)
1481    }
1482
1483    /// Create a new satellite configuration
1484    ///
1485    /// # Example
1486    ///
1487    /// ```no_run
1488    /// # use freedom_api::prelude::*;
1489    /// # tokio_test::block_on(async {
1490    /// let client = Client::from_env()?;
1491    ///
1492    /// client
1493    ///     .new_satellite_configuration()
1494    ///     .name("My Satellite Configuration")
1495    ///     .band_ids([1, 2, 3]) // List of band IDs to associate with config
1496    ///     .send()
1497    ///     .await?;
1498    /// # Ok::<_, Box<dyn std::error::Error>>(())
1499    /// # });
1500    /// ```
1501    fn new_satellite_configuration(
1502        &self,
1503    ) -> post::sat_config::SatelliteConfigurationBuilder<'_, Self, post::sat_config::NoName>
1504    where
1505        Self: Sized,
1506    {
1507        post::sat_config::new(self)
1508    }
1509
1510    /// Create a new satellite
1511    ///
1512    /// # Example
1513    ///
1514    /// ```no_run
1515    /// # use freedom_api::prelude::*;
1516    /// # tokio_test::block_on(async {
1517    /// let client = Client::from_env()?;
1518    ///
1519    /// client
1520    ///     .new_satellite()
1521    ///     .name("My Satellite")
1522    ///     .satellite_configuration_id(42)
1523    ///     .norad_id(3600)
1524    ///     .description("A test satellite")
1525    ///     .send()
1526    ///     .await?;
1527    /// # Ok::<_, Box<dyn std::error::Error>>(())
1528    /// # });
1529    /// ```
1530    fn new_satellite(&self) -> post::satellite::SatelliteBuilder<'_, Self, post::satellite::NoName>
1531    where
1532        Self: Sized,
1533    {
1534        post::satellite::new(self)
1535    }
1536
1537    /// Create a new override
1538    ///
1539    /// # Example
1540    ///
1541    /// ```no_run
1542    /// # use freedom_api::prelude::*;
1543    /// # tokio_test::block_on(async {
1544    /// let client = Client::from_env()?;
1545    ///
1546    /// client
1547    ///     .new_override()
1548    ///     .name("downconverter.gain override for sat 1 on config 2")
1549    ///     .satellite_id(1)
1550    ///     .satellite_configuration_id(2)
1551    ///     .with_property("site.hardware.modem.ttc.rx.demodulator.bitrate", 8096_u32)
1552    ///     .with_property("site.hardware.modem.ttc.tx.modulator.bitrate", 8096_u32)
1553    ///     .send()
1554    ///     .await?;
1555    /// # Ok::<_, Box<dyn std::error::Error>>(())
1556    /// # });
1557    /// ```
1558    fn new_override(&self) -> post::overrides::OverrideBuilder<'_, Self, post::overrides::NoName>
1559    where
1560        Self: Sized,
1561    {
1562        post::overrides::new(self)
1563    }
1564
1565    /// Create a new user
1566    ///
1567    /// # Example
1568    ///
1569    /// ```no_run
1570    /// # use freedom_api::prelude::*;
1571    /// # tokio_test::block_on(async {
1572    /// let client = Client::from_env()?;
1573    ///
1574    /// client
1575    ///     .new_user()
1576    ///     .account_id(1)
1577    ///     .first_name("Han")
1578    ///     .last_name("Solo")
1579    ///     .email("flyingsolo@gmail.com")
1580    ///     .send()
1581    ///     .await?;
1582    /// # Ok::<_, Box<dyn std::error::Error>>(())
1583    /// # });
1584    /// ```
1585    fn new_user(&self) -> post::user::UserBuilder<'_, Self, post::user::NoAccount>
1586    where
1587        Self: Sized,
1588    {
1589        post::user::new(self)
1590    }
1591
1592    /// Create a new task request
1593    ///
1594    /// # Example
1595    ///
1596    /// ```no_run
1597    /// # use freedom_api::prelude::*;
1598    /// # use time::OffsetDateTime;
1599    /// # use std::time::Duration;
1600    /// # tokio_test::block_on(async {
1601    /// let client = Client::from_env()?;
1602    ///
1603    /// client
1604    ///     .new_task_request()
1605    ///     .test_task("my_test_file.bin")
1606    ///     .target_time_utc(OffsetDateTime::now_utc() + Duration::from_secs(15 * 60))
1607    ///     .task_duration(120)
1608    ///     .satellite_id(1016)
1609    ///     .site_id(27)
1610    ///     .site_configuration_id(47)
1611    ///     .band_ids([2017, 2019])
1612    ///     .send()
1613    ///     .await?;
1614    /// # Ok::<_, Box<dyn std::error::Error>>(())
1615    /// # });
1616    /// ```
1617    fn new_task_request(&self) -> post::TaskRequestBuilder<'_, Self, post::request::NoType>
1618    where
1619        Self: Sized,
1620    {
1621        post::request::new(self)
1622    }
1623
1624    /// Fetch an FPS token for the provided band ID and site configuration ID
1625    ///
1626    /// # Example
1627    ///
1628    /// ```no_run
1629    /// # use freedom_api::prelude::*;
1630    /// # tokio_test::block_on(async {
1631    /// const BAND_ID: u32 = 42;
1632    /// const SITE_CONFIG_ID: u32 = 201;
1633    ///
1634    /// let client = Client::from_env()?;
1635    ///
1636    /// let token = client.new_token_by_site_configuration_id(BAND_ID, SITE_CONFIG_ID).await?;
1637    /// // Submit token to FPS ...
1638    /// println!("{:?}", token);
1639    /// # Ok::<_, Box<dyn std::error::Error>>(())
1640    /// # });
1641    /// ```
1642    fn new_token_by_site_configuration_id(
1643        &self,
1644        band_id: u32,
1645        site_configuration_id: u32,
1646    ) -> impl Future<Output = Result<String, Error>> + Send + Sync {
1647        async move {
1648            let url = self.path_to_url("fps")?;
1649            let payload = serde_json::json!({
1650                "band": format!("/api/satellite_bands/{}", band_id),
1651                "configuration": format!("/api/configurations/{}", site_configuration_id),
1652            });
1653
1654            let value: JsonValue = self.post_json_map(url, &payload).await?;
1655
1656            value
1657                .get("token")
1658                .ok_or(Error::Response(String::from("Missing token field")))?
1659                .as_str()
1660                .ok_or(Error::Response(String::from("Invalid type for token")))
1661                .map(|s| s.to_owned())
1662        }
1663    }
1664
1665    /// Fetch an FPS token for the provided band ID and satellite ID
1666    ///
1667    /// # Example
1668    ///
1669    /// ```no_run
1670    /// # use freedom_api::prelude::*;
1671    /// # tokio_test::block_on(async {
1672    /// const BAND_ID: u32 = 42;
1673    /// const SATELLITE_ID: u32 = 101;
1674    ///
1675    /// let client = Client::from_env()?;
1676    ///
1677    /// let token = client.new_token_by_satellite_id(BAND_ID, SATELLITE_ID).await?;
1678    /// // Submit token to FPS ...
1679    /// println!("{:?}", token);
1680    /// # Ok::<_, Box<dyn std::error::Error>>(())
1681    /// # });
1682    /// ```
1683    fn new_token_by_satellite_id(
1684        &self,
1685        band_id: u32,
1686        satellite_id: u32,
1687    ) -> impl Future<Output = Result<String, Error>> + Send + Sync {
1688        async move {
1689            let url = self.path_to_url("fps")?;
1690            let payload = serde_json::json!({
1691                "band": format!("/api/satellite_bands/{}", band_id),
1692                "satellite": format!("/api/satellites/{}", satellite_id),
1693            });
1694
1695            let value: JsonValue = self.post_json_map(url, &payload).await?;
1696
1697            value
1698                .get("token")
1699                .ok_or(Error::Response(String::from("Missing token field")))?
1700                .as_str()
1701                .ok_or(Error::Response(String::from("Invalid type for token")))
1702                .map(|s| s.to_owned())
1703        }
1704    }
1705
1706    /// Returns details about the user associated with the client's API keys
1707    ///
1708    /// See [`get`](Self::get) documentation for more details about the process and return type
1709    fn whoami(&self) -> impl Future<Output = Result<Self::Container<WhoAmI>, Error>> + Send + Sync {
1710        async move {
1711            let uri = self.path_to_url("users/search/whoami")?;
1712
1713            self.get_json_map(uri).await
1714        }
1715    }
1716}
1717
1718pub(crate) fn error_on_non_success(status: &StatusCode, body: &[u8]) -> Result<(), Error> {
1719    if !status.is_success() {
1720        return Err(Error::ResponseStatus {
1721            status: *status,
1722            error: String::from_utf8_lossy(body).to_string(),
1723        });
1724    }
1725
1726    Ok(())
1727}