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