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