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