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