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,
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 single [`TaskRequest`] matching the provided ID.
628    ///
629    /// See [`get`](Self::get) documentation for more details about the process and return type
630    fn get_request_by_id(
631        &self,
632        task_request_id: i32,
633    ) -> impl Future<Output = Result<Self::Container<TaskRequest>, Error>> + Send + Sync {
634        async move {
635            let uri = self.path_to_url(format!("requests/{task_request_id}"))?;
636
637            self.get_json_map(uri).await
638        }
639    }
640
641    /// Produces a paginated stream of [`TaskRequest`] objects.
642    ///
643    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
644    /// and return type
645    fn get_requests(&self) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
646        {
647            let uri = match self.path_to_url("requests/search/findAll") {
648                Ok(uri) => uri,
649                Err(err) => return err.once_err(),
650            };
651            self.get_paginated(uri)
652        }
653    }
654
655    /// Produces a vector of [`TaskRequest`] items, representing all the task requests matching the
656    /// target time overlapping with the provided time range.
657    fn get_requests_by_target_date_between(
658        &self,
659        start: OffsetDateTime,
660        end: OffsetDateTime,
661    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
662        async move {
663            let mut uri = self.path_to_url("requests/search/findAllByTargetDateBetween")?;
664
665            uri.set_query(Some(&format!(
666                "start={}&end={}",
667                start.format(&Iso8601::DEFAULT).unwrap(),
668                end.format(&Iso8601::DEFAULT).unwrap(),
669            )));
670
671            self.get_json_map(uri).await
672        }
673    }
674
675    /// Produces a vector of [`TaskRequest`] items,
676    /// representing all the task requests matching the account at the provided URI and whose
677    /// target time overlaps with the provided time range.
678    ///
679    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
680    /// and return type
681    fn get_requests_by_account_and_target_date_between<T>(
682        &self,
683        account_uri: T,
684        start: OffsetDateTime,
685        end: OffsetDateTime,
686    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
687    where
688        T: AsRef<str> + Send + Sync,
689    {
690        let start = match start.format(&Iso8601::DEFAULT).map_err(Error::from) {
691            Ok(start) => start,
692            Err(error) => return error.once_err(),
693        };
694
695        let end = match end.format(&Iso8601::DEFAULT).map_err(Error::from) {
696            Ok(end) => end,
697            Err(error) => return error.once_err(),
698        };
699
700        let mut uri = match self.path_to_url("requests/search/findAllByAccountAndTargetDateBetween")
701        {
702            Ok(uri) => uri,
703            Err(err) => return err.once_err(),
704        };
705
706        uri.set_query(Some(&format!(
707            "account={}&start={}&end={}",
708            account_uri.as_ref(),
709            start,
710            end
711        )));
712
713        self.get_paginated(uri)
714    }
715
716    /// Produces a paginated stream of [`TaskRequest`]
717    /// objects whose account name matches the provided name, and whose pass will occur today.
718    ///
719    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
720    /// and return type
721    fn get_requests_by_account_and_upcoming_today(
722        &self,
723    ) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
724        let uri = match self.path_to_url("requests/search/findByAccountUpcomingToday") {
725            Ok(uri) => uri,
726            Err(err) => return err.once_err(),
727        };
728
729        self.get_paginated(uri)
730    }
731
732    /// Produces a paginated stream of [`TaskRequest`]
733    /// objects whose satellite configuration matches that of the configuration at the
734    /// `configuration_uri` endpoint.
735    ///
736    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
737    /// and return type
738    ///
739    /// # Note
740    /// The results are ordered by the creation time of the task request
741    fn get_requests_by_configuration<T>(
742        &self,
743        configuration_uri: T,
744    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
745    where
746        T: AsRef<str> + Send + Sync,
747    {
748        let mut uri =
749            match self.path_to_url("requests/search/findAllByConfigurationOrderByCreatedAsc") {
750                Ok(uri) => uri,
751                Err(err) => return err.once_err(),
752            };
753
754        uri.set_query(Some(&format!(
755            "configuration={}",
756            configuration_uri.as_ref()
757        )));
758
759        self.get_paginated::<TaskRequest>(uri)
760    }
761
762    /// Produces a vector of [`TaskRequest`] items, representing all the task requests which match
763    /// the provided configuration, whose satellite name matches one of the names provided as part
764    /// of `satellite_name`, and which overlaps the provided time range.
765    ///
766    /// See [`get`](Self::get) documentation for more details about the process and return type
767    fn get_requests_by_configuration_and_satellite_names_and_target_date_between<T, I, S>(
768        &self,
769        configuration_uri: T,
770        satellites: I,
771        start: OffsetDateTime,
772        end: OffsetDateTime,
773    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
774    where
775        T: AsRef<str> + Send + Sync,
776        I: IntoIterator<Item = S> + Send + Sync,
777        S: AsRef<str> + Send + Sync,
778    {
779        async move {
780            let satellites_string = crate::utils::list_to_string(satellites);
781            let mut uri = self.path_to_url(
782                "requests/search/findAllByConfigurationAndSatelliteNamesAndTargetDateBetween",
783            )?;
784
785            uri.set_query(Some(&format!(
786                "configuration={}&satelliteNames={}&start={}&end={}",
787                configuration_uri.as_ref(),
788                satellites_string,
789                start.format(&Iso8601::DEFAULT)?,
790                end.format(&Iso8601::DEFAULT)?,
791            )));
792
793            Ok(self
794                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
795                .await?
796                .items)
797        }
798    }
799
800    /// Produces a vector of [`TaskRequest`] items, representing all the task requests matching the
801    /// configuration at the provided URI and whose target time overlaps with the provided time
802    /// range.
803    ///
804    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
805    /// and return type
806    fn get_requests_by_configuration_and_target_date_between<T>(
807        &self,
808        configuration_uri: T,
809        start: OffsetDateTime,
810        end: OffsetDateTime,
811    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
812    where
813        T: AsRef<str> + Send + Sync,
814    {
815        async move {
816            let mut uri =
817                self.path_to_url("requests/search/findAllByConfigurationAndTargetDateBetween")?;
818            uri.set_query(Some(&format!(
819                "configuration={}&start={}&end={}",
820                configuration_uri.as_ref(),
821                start.format(&Iso8601::DEFAULT)?,
822                end.format(&Iso8601::DEFAULT)?,
823            )));
824
825            Ok(self
826                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
827                .await?
828                .items)
829        }
830    }
831
832    /// Produces a vector of [`TaskRequest`] items,
833    /// representing all the task requests whose ID matches one of the IDs provided as part of
834    /// `ids`.
835    ///
836    /// See [`get`](Self::get) documentation for more details about the process and return type
837    fn get_requests_by_ids<I, S>(
838        &self,
839        ids: I,
840    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
841    where
842        I: IntoIterator<Item = S> + Send + Sync,
843        S: AsRef<str> + Send + Sync,
844    {
845        async move {
846            let ids_string = crate::utils::list_to_string(ids);
847            let mut uri = self.path_to_url("requests/search/findAllByIds")?;
848
849            uri.set_query(Some(&format!("ids={}", ids_string)));
850
851            Ok(self
852                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
853                .await?
854                .items)
855        }
856    }
857
858    /// Produces a paginated stream of [`TaskRequest`] objects which are public, and which overlap
859    /// with the provided time range.
860    ///
861    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
862    /// and return type
863    fn get_requests_by_overlapping_public(
864        &self,
865        start: OffsetDateTime,
866        end: OffsetDateTime,
867    ) -> PaginatedStream<'_, Self::Container<TaskRequest>> {
868        let mut uri = match self.path_to_url("requests/search/findAllByOverlappingPublic") {
869            Ok(uri) => uri,
870            Err(err) => return err.once_err(),
871        };
872
873        uri.set_query(Some(&format!(
874            "start={}&end={}",
875            start.format(&Iso8601::DEFAULT).unwrap(),
876            end.format(&Iso8601::DEFAULT).unwrap(),
877        )));
878
879        self.get_paginated(uri)
880    }
881
882    /// Produces a paginated stream of [`TaskRequest`] objects whose satellite name matches one of
883    /// the names provided as part of `satellite_name`.
884    ///
885    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
886    /// and return type
887    fn get_requests_by_satellite_name<T>(
888        &self,
889        satellite_name: T,
890    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
891    where
892        T: AsRef<str> + Send + Sync,
893    {
894        let mut uri = match self.path_to_url("requests/search/findBySatelliteName") {
895            Ok(uri) => uri,
896            Err(err) => return err.once_err(),
897        };
898
899        uri.set_query(Some(&format!("name={}", satellite_name.as_ref())));
900
901        self.get_paginated(uri)
902    }
903
904    /// Produces a vector of [`TaskRequest`] items, representing all the task requests whose
905    /// satellite name matches the provided name and whose target time overlaps with the provided
906    /// time range.
907    ///
908    /// See [`get`](Self::get) documentation for more details about the process and return type
909    fn get_requests_by_satellite_name_and_target_date_between<T>(
910        &self,
911        satellite_name: T,
912        start: OffsetDateTime,
913        end: OffsetDateTime,
914    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
915    where
916        T: AsRef<str> + Send + Sync,
917    {
918        async move {
919            let mut uri =
920                self.path_to_url("requests/search/findAllBySatelliteNameAndTargetDateBetween")?;
921
922            uri.set_query(Some(&format!(
923                "name={}&start={}&end={}",
924                satellite_name.as_ref(),
925                start.format(&Iso8601::DEFAULT)?,
926                end.format(&Iso8601::DEFAULT)?
927            )));
928
929            Ok(self
930                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
931                .await?
932                .items)
933        }
934    }
935
936    /// Produces a paginated stream of [`TaskRequest`] objects whose status matches the provided
937    /// status.
938    ///
939    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
940    /// and return type
941    fn get_requests_by_status<T>(
942        &self,
943        status: T,
944    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
945    where
946        T: TryInto<TaskStatusType> + Send + Sync,
947        Error: From<<T as TryInto<TaskStatusType>>::Error>,
948    {
949        let status: TaskStatusType = match status.try_into() {
950            Ok(val) => val,
951            Err(err) => return Error::from(err).once_err(),
952        };
953        let mut uri = match self.path_to_url("requests/search/findByStatus") {
954            Ok(uri) => uri,
955            Err(err) => return err.once_err(),
956        };
957
958        uri.set_query(Some(&format!("status={}", status.as_ref())));
959
960        self.get_paginated(uri)
961    }
962
963    /// Produces a paginated stream of [`TaskRequest`], representing all the task requests which
964    /// match the provided status, account, and overlap the provided time range.
965    ///
966    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
967    /// and return type
968    fn get_requests_by_status_and_account_and_target_date_between<T, U>(
969        &self,
970        status: T,
971        account_uri: U,
972        start: OffsetDateTime,
973        end: OffsetDateTime,
974    ) -> PaginatedStream<'_, Self::Container<TaskRequest>>
975    where
976        T: AsRef<str> + Send + Sync,
977        U: AsRef<str> + Send + Sync,
978    {
979        let mut uri = match self
980            .path_to_url("requests/search/findAllByStatusAndAccountAndTargetDateBetween")
981        {
982            Ok(uri) => uri,
983            Err(err) => return err.once_err(),
984        };
985
986        uri.set_query(Some(&format!(
987            "status={}&satelliteNames={}&start={}&end={}",
988            status.as_ref(),
989            account_uri.as_ref(),
990            start.format(&Iso8601::DEFAULT).unwrap(),
991            end.format(&Iso8601::DEFAULT).unwrap()
992        )));
993
994        self.get_paginated(uri)
995    }
996
997    /// Produces a vector of [`TaskRequest`] items, representing all the tasks which match the
998    /// provided type, overlap with the provided time range.
999    ///
1000    /// See [`get`](Self::get) documentation for more details about the process and return type
1001    fn get_requests_by_type_and_target_date_between<T>(
1002        &self,
1003        typ: T,
1004        start: OffsetDateTime,
1005        end: OffsetDateTime,
1006    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync
1007    where
1008        T: TryInto<TaskType> + Send + Sync,
1009        Error: From<<T as TryInto<TaskType>>::Error>,
1010    {
1011        async move {
1012            let typ: TaskType = typ.try_into()?;
1013            let mut uri = self.path_to_url("requests/search/findAllByTypeAndTargetDateBetween")?;
1014
1015            uri.set_query(Some(&format!(
1016                "type={}&start={}&end={}",
1017                typ.as_ref(),
1018                start.format(&Iso8601::DEFAULT)?,
1019                end.format(&Iso8601::DEFAULT)?
1020            )));
1021
1022            Ok(self
1023                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1024                .await?
1025                .items)
1026        }
1027    }
1028
1029    /// Produces a vector of [`TaskRequest`] items, representing the list of tasks which have
1030    /// already occurred today.
1031    ///
1032    /// See [`get`](Self::get) documentation for more details about the process and return type
1033    fn get_requests_passed_today(
1034        &self,
1035    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
1036        async move {
1037            let uri = self.path_to_url("requests/search/findAllPassedToday")?;
1038
1039            Ok(self
1040                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1041                .await?
1042                .items)
1043        }
1044    }
1045
1046    /// Produces a vector of [`TaskRequest`] items, representing the list of tasks which will occur
1047    /// later today.
1048    ///
1049    /// See [`get`](Self::get) documentation for more details about the process and return type
1050    fn get_requests_upcoming_today(
1051        &self,
1052    ) -> impl Future<Output = Result<Self::Container<Vec<TaskRequest>>, Error>> + Send + Sync {
1053        async move {
1054            let uri = self.path_to_url("requests/search/findAllUpcomingToday")?;
1055
1056            Ok(self
1057                .get_json_map::<Embedded<Self::Container<Vec<TaskRequest>>>>(uri)
1058                .await?
1059                .items)
1060        }
1061    }
1062
1063    /// Produces a paginated stream of [`Satellite`] objects.
1064    ///
1065    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1066    /// and return type
1067    fn get_satellites(&self) -> PaginatedStream<'_, Self::Container<Satellite>> {
1068        let uri = match self.path_to_url("satellites") {
1069            Ok(uri) => uri,
1070            Err(err) => return err.once_err(),
1071        };
1072
1073        self.get_paginated(uri)
1074    }
1075
1076    /// Produces single satellite object matching the provided satellite ID
1077    fn get_satellite_by_id(
1078        &self,
1079        satellite_id: i32,
1080    ) -> impl Future<Output = Result<Self::Container<Satellite>, Error>> + Send + Sync {
1081        async move {
1082            let uri = self.path_to_url(format!("satellites/{}", satellite_id))?;
1083
1084            self.get_json_map(uri).await
1085        }
1086    }
1087
1088    /// Produces single satellite object matching the provided satellite name
1089    fn get_satellite_by_name(
1090        &self,
1091        satellite_name: &str,
1092    ) -> impl Future<Output = Result<Self::Container<Satellite>, Error>> + Send + Sync {
1093        async move {
1094            let mut uri = self.path_to_url("satellites/findOneByName")?;
1095            uri.set_query(Some(&format!("name={satellite_name}")));
1096
1097            self.get_json_map(uri).await
1098        }
1099    }
1100
1101    /// Produces a single [`Task`] matching the provided ID.
1102    ///
1103    /// See [`get`](Self::get) documentation for more details about the process and return type
1104    fn get_task_by_id(
1105        &self,
1106        task_id: i32,
1107    ) -> impl Future<Output = Result<Self::Container<Task>, Error>> + Send + Sync {
1108        async move {
1109            let uri = self.path_to_url(format!("tasks/{}", task_id))?;
1110
1111            self.get_json_map(uri).await
1112        }
1113    }
1114
1115    /// Produces a vector of [`Task`] items, representing all the tasks which match the provided
1116    /// account, and intersect with the provided time frame.
1117    ///
1118    /// See [`get`](Self::get) documentation for more details about the process and return type
1119    fn get_tasks_by_account_and_pass_overlapping<T>(
1120        &self,
1121        account_uri: T,
1122        start: OffsetDateTime,
1123        end: OffsetDateTime,
1124    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1125    where
1126        T: AsRef<str> + Send + Sync,
1127    {
1128        async move {
1129            let mut uri = self.path_to_url("tasks/search/findByAccountAndPassOverlapping")?;
1130
1131            uri.set_query(Some(&format!(
1132                "account={}&start={}&end={}",
1133                account_uri.as_ref(),
1134                start.format(&Iso8601::DEFAULT)?,
1135                end.format(&Iso8601::DEFAULT)?
1136            )));
1137
1138            Ok(self
1139                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1140                .await?
1141                .items)
1142        }
1143    }
1144
1145    /// Produces a vector of [`Task`] items, representing all the tasks which match the provided
1146    /// account, satellite, band, and intersect with the provided time frame.
1147    ///
1148    /// See [`get`](Self::get) documentation for more details about the process and return type
1149    fn get_tasks_by_account_and_satellite_and_band_and_pass_overlapping<T, U, V>(
1150        &self,
1151        account_uri: T,
1152        satellite_config_uri: U,
1153        band: V,
1154        start: OffsetDateTime,
1155        end: OffsetDateTime,
1156    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1157    where
1158        T: AsRef<str> + Send + Sync,
1159        U: AsRef<str> + Send + Sync,
1160        V: AsRef<str> + Send + Sync,
1161    {
1162        async move {
1163            let mut uri = self.path_to_url(
1164                "tasks/search/findByAccountAndSiteConfigurationAndBandAndPassOverlapping",
1165            )?;
1166
1167            uri.set_query(Some(&format!(
1168                "account={}&satellite={}&band={}&start={}&end={}",
1169                account_uri.as_ref(),
1170                satellite_config_uri.as_ref(),
1171                band.as_ref(),
1172                start.format(&Iso8601::DEFAULT)?,
1173                end.format(&Iso8601::DEFAULT)?,
1174            )));
1175
1176            Ok(self
1177                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1178                .await?
1179                .items)
1180        }
1181    }
1182
1183    /// Produces a vector of [`Task`] representing all the tasks which match the provided account,
1184    /// site configuration, band, and intersect with the provided time frame.
1185    ///
1186    /// See [`get`](Self::get) documentation for more details about the process and return type
1187    fn get_tasks_by_account_and_site_configuration_and_band_and_pass_overlapping<T, U, V>(
1188        &self,
1189        account_uri: T,
1190        site_config_uri: U,
1191        band: V,
1192        start: OffsetDateTime,
1193        end: OffsetDateTime,
1194    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync
1195    where
1196        T: AsRef<str> + Send + Sync,
1197        U: AsRef<str> + Send + Sync,
1198        V: AsRef<str> + Send + Sync,
1199    {
1200        async move {
1201            let mut uri = self.path_to_url(
1202                "tasks/search/findByAccountAndSiteConfigurationAndBandAndPassOverlapping",
1203            )?;
1204
1205            uri.set_query(Some(&format!(
1206                "account={}&siteConfig={}&band={}&start={}&end={}",
1207                account_uri.as_ref(),
1208                site_config_uri.as_ref(),
1209                band.as_ref(),
1210                start.format(&Iso8601::DEFAULT)?,
1211                end.format(&Iso8601::DEFAULT)?
1212            )));
1213
1214            Ok(self
1215                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1216                .await?
1217                .items)
1218        }
1219    }
1220
1221    /// Produces a vector of [`Task`] items, representing all the tasks contained within the
1222    /// provided time frame.
1223    ///
1224    /// See [`get`](Self::get) documentation for more details about the process and return type
1225    ///
1226    /// # Note
1227    ///
1228    /// This differs from [`Self::get_tasks_by_pass_overlapping`] in that it only produces tasks
1229    /// which are wholly contained within the window.
1230    fn get_tasks_by_pass_window(
1231        &self,
1232        start: OffsetDateTime,
1233        end: OffsetDateTime,
1234    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1235        async move {
1236            let mut uri = self.path_to_url("tasks/search/findByStartBetweenOrderByStartAsc")?;
1237
1238            uri.set_query(Some(&format!(
1239                "start={}&end={}",
1240                start.format(&Iso8601::DEFAULT)?,
1241                end.format(&Iso8601::DEFAULT)?
1242            )));
1243
1244            Ok(self
1245                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1246                .await?
1247                .items)
1248        }
1249    }
1250
1251    /// Produces a paginated stream of [`Task`] items, representing all the tasks which overlap the
1252    /// provided time frame.
1253    ///
1254    /// See [`get`](Self::get) documentation for more details about the process and return type
1255    ///
1256    /// # Note
1257    ///
1258    /// This differs from [`Self::get_tasks_by_pass_window`] in that it also includes tasks which
1259    /// only partially fall within the provided time frame.
1260    fn get_tasks_by_pass_overlapping(
1261        &self,
1262        start: OffsetDateTime,
1263        end: OffsetDateTime,
1264    ) -> PaginatedStream<'_, Self::Container<Task>> {
1265        let start = match start.format(&Iso8601::DEFAULT).map_err(Error::from) {
1266            Ok(start) => start,
1267            Err(error) => return error.once_err(),
1268        };
1269
1270        let end = match end.format(&Iso8601::DEFAULT).map_err(Error::from) {
1271            Ok(end) => end,
1272            Err(error) => return error.once_err(),
1273        };
1274
1275        let mut uri = match self.path_to_url("tasks/search/findByOverlapping") {
1276            Ok(uri) => uri,
1277            Err(err) => return err.once_err(),
1278        };
1279
1280        uri.set_query(Some(&format!("start={}&end={}", start, end)));
1281
1282        self.get_paginated(uri)
1283    }
1284
1285    /// Produces a vector of [`Task`] items, representing the list of tasks which have already
1286    /// occurred today.
1287    ///
1288    /// See [`get`](Self::get) documentation for more details about the process and return type
1289    fn get_tasks_passed_today(
1290        &self,
1291    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1292        async move {
1293            let uri = self.path_to_url("tasks/search/findAllPassedToday")?;
1294
1295            Ok(self
1296                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1297                .await?
1298                .items)
1299        }
1300    }
1301
1302    /// Produces a vector of [`Task`] items, representing the list of tasks which will occur later
1303    /// today.
1304    ///
1305    /// See [`get`](Self::get) documentation for more details about the process and return type
1306    fn get_tasks_upcoming_today(
1307        &self,
1308    ) -> impl Future<Output = Result<Self::Container<Vec<Task>>, Error>> + Send + Sync {
1309        async move {
1310            let uri = self.path_to_url("tasks/search/findAllUpcomingToday")?;
1311
1312            Ok(self
1313                .get_json_map::<Embedded<Self::Container<Vec<Task>>>>(uri)
1314                .await?
1315                .items)
1316        }
1317    }
1318
1319    /// Produces a paginated stream of [`User`] objects.
1320    ///
1321    /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process
1322    /// and return type
1323    fn get_users(&self) -> PaginatedStream<'_, Self::Container<User>> {
1324        let uri = match self.path_to_url("users") {
1325            Ok(uri) => uri,
1326            Err(err) => return err.once_err(),
1327        };
1328        self.get_paginated(uri)
1329    }
1330
1331    /// Create a new satellite band object
1332    ///
1333    /// # Example
1334    ///
1335    /// ```no_run
1336    /// # use freedom_api::prelude::*;
1337    /// # tokio_test::block_on(async {
1338    /// let client = Client::from_env()?;
1339    ///
1340    /// client
1341    ///     .new_band_details()
1342    ///     .name("My Satellite Band")
1343    ///     .band_type(BandType::Receive)
1344    ///     .frequency(8096.0)
1345    ///     .default_band_width(1.45)
1346    ///     .io_hardware(IoHardware::Modem)
1347    ///     .send()
1348    ///     .await?;
1349    /// # Ok::<_, Box<dyn std::error::Error>>(())
1350    /// # });
1351    /// ```
1352    fn new_band_details(&self) -> post::band::BandDetailsBuilder<'_, Self, post::band::NoName>
1353    where
1354        Self: Sized,
1355    {
1356        post::band::new(self)
1357    }
1358
1359    /// Create a new satellite configuration
1360    ///
1361    /// # Example
1362    ///
1363    /// ```no_run
1364    /// # use freedom_api::prelude::*;
1365    /// # tokio_test::block_on(async {
1366    /// let client = Client::from_env()?;
1367    ///
1368    /// client
1369    ///     .new_satellite_configuration()
1370    ///     .name("My Satellite Configuration")
1371    ///     .band_ids([1, 2, 3]) // List of band IDs to associate with config
1372    ///     .send()
1373    ///     .await?;
1374    /// # Ok::<_, Box<dyn std::error::Error>>(())
1375    /// # });
1376    /// ```
1377    fn new_satellite_configuration(
1378        &self,
1379    ) -> post::sat_config::SatelliteConfigurationBuilder<'_, Self, post::sat_config::NoName>
1380    where
1381        Self: Sized,
1382    {
1383        post::sat_config::new(self)
1384    }
1385
1386    /// Create a new satellite
1387    ///
1388    /// # Example
1389    ///
1390    /// ```no_run
1391    /// # use freedom_api::prelude::*;
1392    /// # tokio_test::block_on(async {
1393    /// let client = Client::from_env()?;
1394    ///
1395    /// client
1396    ///     .new_satellite()
1397    ///     .name("My Satellite")
1398    ///     .satellite_configuration_id(42)
1399    ///     .norad_id(3600)
1400    ///     .description("A test satellite")
1401    ///     .send()
1402    ///     .await?;
1403    /// # Ok::<_, Box<dyn std::error::Error>>(())
1404    /// # });
1405    /// ```
1406    fn new_satellite(&self) -> post::satellite::SatelliteBuilder<'_, Self, post::satellite::NoName>
1407    where
1408        Self: Sized,
1409    {
1410        post::satellite::new(self)
1411    }
1412
1413    /// Create a new override
1414    ///
1415    /// # Example
1416    ///
1417    /// ```no_run
1418    /// # use freedom_api::prelude::*;
1419    /// # tokio_test::block_on(async {
1420    /// let client = Client::from_env()?;
1421    ///
1422    /// client
1423    ///     .new_override()
1424    ///     .name("downconverter.gain override for sat 1 on config 2")
1425    ///     .satellite_id(1)
1426    ///     .satellite_configuration_id(2)
1427    ///     .add_property("site.hardware.modem.ttc.rx.demodulator.bitrate", 8096_u32)
1428    ///     .add_property("site.hardware.modem.ttc.tx.modulator.bitrate", 8096_u32)
1429    ///     .send()
1430    ///     .await?;
1431    /// # Ok::<_, Box<dyn std::error::Error>>(())
1432    /// # });
1433    /// ```
1434    fn new_override(&self) -> post::overrides::OverrideBuilder<'_, Self, post::overrides::NoName>
1435    where
1436        Self: Sized,
1437    {
1438        post::overrides::new(self)
1439    }
1440
1441    /// Create a new user
1442    ///
1443    /// # Example
1444    ///
1445    /// ```no_run
1446    /// # use freedom_api::prelude::*;
1447    /// # tokio_test::block_on(async {
1448    /// let client = Client::from_env()?;
1449    ///
1450    /// client
1451    ///     .new_user()
1452    ///     .account_id(1)
1453    ///     .first_name("Han")
1454    ///     .last_name("Solo")
1455    ///     .email("flyingsolo@gmail.com")
1456    ///     .send()
1457    ///     .await?;
1458    /// # Ok::<_, Box<dyn std::error::Error>>(())
1459    /// # });
1460    /// ```
1461    fn new_user(&self) -> post::user::UserBuilder<'_, Self, post::user::NoAccount>
1462    where
1463        Self: Sized,
1464    {
1465        post::user::new(self)
1466    }
1467
1468    /// Create a new task request
1469    ///
1470    /// # Example
1471    ///
1472    /// ```no_run
1473    /// # use freedom_api::prelude::*;
1474    /// # use time::OffsetDateTime;
1475    /// # use std::time::Duration;
1476    /// # tokio_test::block_on(async {
1477    /// let client = Client::from_env()?;
1478    ///
1479    /// client
1480    ///     .new_task_request()
1481    ///     .test_task("my_test_file.bin")
1482    ///     .target_time_utc(OffsetDateTime::now_utc() + Duration::from_secs(15 * 60))
1483    ///     .task_duration(120)
1484    ///     .satellite_id(1016)
1485    ///     .site_id(27)
1486    ///     .site_configuration_id(47)
1487    ///     .band_ids([2017, 2019])
1488    ///     .send()
1489    ///     .await?;
1490    /// # Ok::<_, Box<dyn std::error::Error>>(())
1491    /// # });
1492    /// ```
1493    fn new_task_request(&self) -> post::TaskRequestBuilder<'_, Self, post::request::NoType>
1494    where
1495        Self: Sized,
1496    {
1497        post::request::new(self)
1498    }
1499
1500    /// Fetch an FPS token for the provided band ID and site configuration ID
1501    ///
1502    /// # Example
1503    ///
1504    /// ```no_run
1505    /// # use freedom_api::prelude::*;
1506    /// # tokio_test::block_on(async {
1507    /// const BAND_ID: u32 = 42;
1508    /// const SITE_CONFIG_ID: u32 = 201;
1509    ///
1510    /// let client = Client::from_env()?;
1511    ///
1512    /// let token = client.new_token_by_site_configuration_id(BAND_ID, SITE_CONFIG_ID).await?;
1513    /// // Submit token to FPS ...
1514    /// println!("{:?}", token);
1515    /// # Ok::<_, Box<dyn std::error::Error>>(())
1516    /// # });
1517    /// ```
1518    fn new_token_by_site_configuration_id(
1519        &self,
1520        band_id: u32,
1521        site_configuration_id: u32,
1522    ) -> impl Future<Output = Result<String, Error>> + Send + Sync {
1523        async move {
1524            let url = self.path_to_url("fps")?;
1525            let payload = serde_json::json!({
1526                "band": format!("/api/satellite_bands/{}", band_id),
1527                "configuration": format!("/api/configurations/{}", site_configuration_id),
1528            });
1529
1530            let value: JsonValue = self.post_deserialize(url, &payload).await?;
1531
1532            value
1533                .get("token")
1534                .ok_or(Error::Response(String::from("Missing token field")))?
1535                .as_str()
1536                .ok_or(Error::Response(String::from("Invalid type for token")))
1537                .map(|s| s.to_owned())
1538        }
1539    }
1540
1541    /// Fetch an FPS token for the provided band ID and satellite 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 SATELLITE_ID: u32 = 101;
1550    ///
1551    /// let client = Client::from_env()?;
1552    ///
1553    /// let token = client.new_token_by_satellite_id(BAND_ID, SATELLITE_ID).await?;
1554    /// // Submit token to FPS ...
1555    /// println!("{:?}", token);
1556    /// # Ok::<_, Box<dyn std::error::Error>>(())
1557    /// # });
1558    /// ```
1559    fn new_token_by_satellite_id(
1560        &self,
1561        band_id: u32,
1562        satellite_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                "satellite": format!("/api/satellites/{}", satellite_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
1583pub(crate) fn error_on_non_success(status: &StatusCode, body: &[u8]) -> Result<(), Error> {
1584    if !status.is_success() {
1585        return Err(Error::ResponseStatus {
1586            status: *status,
1587            error: String::from_utf8_lossy(body).to_string(),
1588        });
1589    }
1590
1591    Ok(())
1592}