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