diff --git a/examples/post_band_details.rs b/examples/post_band_details.rs index d10ca70..2d0552a 100644 --- a/examples/post_band_details.rs +++ b/examples/post_band_details.rs @@ -11,13 +11,13 @@ async fn main() -> Result<(), Box> { let response = client .new_task_request() - .task_type(TaskType::Test) - .target_date_utc(OffsetDateTime::now_utc() + Duration::from_secs(15 * 60)) - .duration(120) - .satellite(1016) - .target_bands([2017, 2019]) - .site(27) - .configuration(47) + .test_task("idk.bin") + .target_time_utc(OffsetDateTime::now_utc() + Duration::from_secs(60 * 15)) + .task_duration(120) + .satellite_id(1016) + .site_id(27) + .site_configuration_id(47) + .band_ids([2017, 2019]) .send() .await?; diff --git a/src/api.rs b/src/api.rs index f97c823..d98a587 100644 --- a/src/api.rs +++ b/src/api.rs @@ -269,7 +269,7 @@ pub trait FreedomApi: Send + Sync { /// /// ```no_run /// # use freedom_api::prelude::*; - /// # use freedom_models::band::BandType; + /// # use freedom_models::band::{BandType, IoHardware}; /// # tokio_test::block_on(async { /// let client = Client::from_env()?; /// @@ -277,17 +277,19 @@ pub trait FreedomApi: Send + Sync { /// .new_band_details() /// .name("My Satellite Band") /// .band_type(BandType::Receive) - /// .default_band_width_mghz(1.45) + /// .frequency(8096.0) + /// .default_band_width(1.45) + /// .io_hardware(IoHardware::Modem) /// .send() /// .await?; /// # Ok::<_, Box>(()) /// # }); /// ``` - fn new_band_details(&self) -> post::BandDetailsBuilder<'_, Self> + fn new_band_details(&self) -> post::band::BandDetailsBuilder<'_, Self, post::band::NoName> where Self: Sized, { - post::BandDetailsBuilder::client(self) + post::band::new(self) } /// Create a new satellite configuration @@ -302,17 +304,19 @@ pub trait FreedomApi: Send + Sync { /// client /// .new_satellite_configuration() /// .name("My Satellite Configuration") - /// .band_details([1, 2, 3]) // List of band IDs to associate with config + /// .band_ids([1, 2, 3]) // List of band IDs to associate with config /// .send() /// .await?; /// # Ok::<_, Box>(()) /// # }); /// ``` - fn new_satellite_configuration(&self) -> post::SatelliteConfigurationBuilder<'_, Self> + fn new_satellite_configuration( + &self, + ) -> post::sat_config::SatelliteConfigurationBuilder<'_, Self, post::sat_config::NoName> where Self: Sized, { - post::SatelliteConfigurationBuilder::client(self) + post::sat_config::new(self) } /// Create a new satellite @@ -327,19 +331,19 @@ pub trait FreedomApi: Send + Sync { /// client /// .new_satellite() /// .name("My Satellite") + /// .satellite_configuration_id(42) + /// .norad_id(3600) /// .description("A test satellite") - /// .configuration(42) - /// .norad_cat_id(3600) /// .send() /// .await?; /// # Ok::<_, Box>(()) /// # }); /// ``` - fn new_satellite(&self) -> post::SatelliteBuilder<'_, Self> + fn new_satellite(&self) -> post::satellite::SatelliteBuilder<'_, Self, post::satellite::NoName> where Self: Sized, { - post::SatelliteBuilder::client(self) + post::satellite::new(self) } /// Create a new override @@ -354,8 +358,8 @@ pub trait FreedomApi: Send + Sync { /// client /// .new_override() /// .name("downconverter.gain override for sat 1 on config 2") - /// .satellite(1) - /// .configuration(2) + /// .satellite_id(1) + /// .satellite_configuration_id(2) /// .add_property("site.hardware.modem.ttc.rx.demodulator.bitrate", 8096_u32) /// .add_property("site.hardware.modem.ttc.tx.modulator.bitrate", 8096_u32) /// .send() @@ -363,11 +367,11 @@ pub trait FreedomApi: Send + Sync { /// # Ok::<_, Box>(()) /// # }); /// ``` - fn new_override(&self) -> post::OverrideBuilder<'_, Self> + fn new_override(&self) -> post::overrides::OverrideBuilder<'_, Self, post::overrides::NoName> where Self: Sized, { - post::OverrideBuilder::client(self) + post::overrides::new(self) } /// Create a new user @@ -390,11 +394,11 @@ pub trait FreedomApi: Send + Sync { /// # Ok::<_, Box>(()) /// # }); /// ``` - fn new_user(&self) -> post::UserBuilder<'_, Self> + fn new_user(&self) -> post::user::UserBuilder<'_, Self, post::user::NoAccount> where Self: Sized, { - post::UserBuilder::client(self) + post::user::new(self) } /// Create a new task request @@ -410,23 +414,23 @@ pub trait FreedomApi: Send + Sync { /// /// client /// .new_task_request() - /// .task_type(TaskType::Test) - /// .target_date_utc(OffsetDateTime::now_utc() + Duration::from_secs(15 * 60)) - /// .duration(120) - /// .satellite(1016) - /// .target_bands([2017, 2019]) - /// .site(27) - /// .configuration(47) + /// .test_task("my_test_file.bin") + /// .target_time_utc(OffsetDateTime::now_utc() + Duration::from_secs(15 * 60)) + /// .task_duration(120) + /// .satellite_id(1016) + /// .site_id(27) + /// .site_configuration_id(47) + /// .band_ids([2017, 2019]) /// .send() /// .await?; /// # Ok::<_, Box>(()) /// # }); /// ``` - fn new_task_request(&self) -> post::TaskRequestBuilder<'_, Self> + fn new_task_request(&self) -> post::TaskRequestBuilder<'_, Self, post::request::NoType> where Self: Sized, { - post::TaskRequestBuilder::client(self) + post::request::new(self) } /// Produces a single [`Account`](freedom_models::account::Account) matching the provided ID. diff --git a/src/api/post.rs b/src/api/post.rs index a91c8f2..b3d09af 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -1,417 +1,11 @@ -use std::collections::HashMap; - -use derive_builder::Builder; -use freedom_models::{ - band::{BandType, IoConfiguration}, - task::{Polarization, TaskType}, +pub mod band; +pub mod overrides; +pub mod request; +pub mod sat_config; +pub mod satellite; +pub mod user; + +pub use self::{ + band::BandDetailsBuilder, overrides::OverrideBuilder, request::TaskRequestBuilder, + sat_config::SatelliteConfigurationBuilder, satellite::SatelliteBuilder, user::UserBuilder, }; -use reqwest::Response; -use serde::Serialize; -use time::OffsetDateTime; - -use crate::{api::FreedomApi, prelude::BuilderError, Error}; - -#[derive(Builder, Debug, Clone, PartialEq, Serialize)] -#[builder(setter(into), build_fn(skip), pattern = "owned")] -#[serde(rename_all = "camelCase")] -pub struct BandDetails<'a, C> { - #[serde(skip_serializing)] - #[builder(setter(custom))] - client: &'a C, - name: String, - #[serde(rename(serialize = "type"))] - #[builder(setter(name = "band_type"))] - typ: BandType, - frequency_mghz: f64, - default_band_width_mghz: f64, - modulation: String, - eirp: f64, - gain: f64, - io_configuration: IoConfiguration, - polarization: Polarization, - manual_transmit_control: bool, -} - -impl<'a, C> BandDetailsBuilder<'a, C> -where - C: FreedomApi, -{ - pub(crate) fn client(client: &'a C) -> Self { - Self { - client: Some(client), - ..Default::default() - } - } - - pub async fn send(self) -> Result { - let client = self.client.unwrap(); - - let details = BandDetails { - client, - name: self.name.unwrap_or_default(), - typ: self.typ.unwrap_or(BandType::Receive), - frequency_mghz: self.frequency_mghz.unwrap_or_default(), - default_band_width_mghz: self.default_band_width_mghz.unwrap_or(1.0), - modulation: self.modulation.unwrap_or_default(), - eirp: self.eirp.unwrap_or_default(), - gain: self.gain.unwrap_or_default(), - io_configuration: self.io_configuration.unwrap_or(IoConfiguration { - start_hex_pattern: None, - end_hex_pattern: None, - strip_pattern: false, - io_hardware: None, - }), - polarization: self.polarization.unwrap_or_default(), - manual_transmit_control: self.manual_transmit_control.unwrap_or_default(), - }; - - let url = client.path_to_url("satellite_bands"); - client.post(url, details).await - } -} - -#[derive(Builder, Debug, Clone, PartialEq, Serialize)] -#[builder(setter(into), build_fn(skip), pattern = "owned")] -#[serde(rename_all = "camelCase")] -pub struct SatelliteConfiguration<'a, C> { - #[serde(skip_serializing)] - #[builder(setter(custom))] - client: &'a C, - name: String, - doppler: bool, - notes: String, - #[builder(setter(custom), default)] - band_details: Vec, -} - -impl<'a, C> SatelliteConfigurationBuilder<'a, C> -where - C: FreedomApi, -{ - pub(crate) fn client(client: &'a C) -> Self { - Self { - client: Some(client), - ..Default::default() - } - } - - pub fn band_details(mut self, band_details: impl IntoIterator) -> Self { - let client = self.client.unwrap(); - let band_details: Vec<_> = band_details - .into_iter() - .map(|id| { - client - .path_to_url(format!("satellite_bands/{}", id)) - .to_string() - }) - .collect(); - self.band_details = Some(band_details); - self - } - - pub async fn send(self) -> Result { - let client = self.client.unwrap(); - - let details = SatelliteConfiguration { - client, - name: self.name.unwrap_or_default(), - doppler: self.doppler.unwrap_or_default(), - notes: self.notes.unwrap_or_default(), - band_details: self.band_details.unwrap_or_default(), - }; - - let url = client.path_to_url("satellite_configurations"); - client.post(url, details).await - } -} - -#[derive(Builder, Debug, Clone, PartialEq, Serialize)] -#[builder(setter(into), build_fn(skip), pattern = "owned")] -#[serde(rename_all = "camelCase")] -pub struct Satellite<'a, C> { - #[serde(skip_serializing)] - #[builder(setter(custom))] - client: &'a C, - name: String, - description: String, - norad_cat_id: i32, - #[builder(setter(custom), default)] - configuration: String, -} - -impl<'a, C> SatelliteBuilder<'a, C> -where - C: FreedomApi, -{ - pub(crate) fn client(client: &'a C) -> Self { - Self { - client: Some(client), - ..Default::default() - } - } - - pub fn configuration(mut self, satellite_configuration_id: u32) -> Self { - let client = self.client.unwrap(); - let configuration = client - .path_to_url(format!( - "satellite_configurations/{}", - satellite_configuration_id - )) - .to_string(); - self.configuration = Some(configuration); - self - } - - pub async fn send(self) -> Result { - let client = self.client.unwrap(); - - let details = Satellite { - client, - name: self.name.unwrap_or_default(), - description: self.description.unwrap_or_default(), - norad_cat_id: self.norad_cat_id.unwrap_or_default(), - configuration: self.configuration.unwrap_or_default(), - }; - - let url = client.path_to_url("satellites"); - client.post(url, details).await - } -} - -#[derive(Builder, Debug, Clone, PartialEq, Serialize)] -#[builder(setter(into), build_fn(skip), pattern = "owned")] -#[serde(rename_all = "camelCase")] -pub struct Override<'a, C> { - #[serde(skip_serializing)] - #[builder(setter(custom))] - client: &'a C, - #[builder(setter(custom))] - name: Option, - #[builder(setter(custom), default)] - satellite: String, - #[builder(setter(custom), default)] - configuration: String, - #[builder(setter(custom), field(ty = "HashMap"))] - properties: HashMap, -} - -impl<'a, C> OverrideBuilder<'a, C> -where - C: FreedomApi, -{ - pub(crate) fn client(client: &'a C) -> Self { - Self { - client: Some(client), - ..Default::default() - } - } - - pub fn add_property(mut self, key: impl Into, value: impl std::fmt::Display) -> Self { - let _ = self.properties.insert(key.into(), value.to_string()); - self - } - - pub fn name(mut self, name: impl Into) -> Self { - self.name = Some(Some(name.into())); - self - } - - pub fn satellite(mut self, satellite_id: u32) -> Self { - let client = self.client.unwrap(); - let satellite = client - .path_to_url(format!("satellites/{}", satellite_id)) - .to_string(); - self.satellite = Some(satellite); - self - } - - pub fn configuration(mut self, satellite_configuration_id: u32) -> Self { - let client = self.client.unwrap(); - let configuration = client - .path_to_url(format!( - "satellite_configurations/{}", - satellite_configuration_id - )) - .to_string(); - self.configuration = Some(configuration); - self - } - - pub async fn send(self) -> Result { - let client = self.client.unwrap(); - - let override_builder = Override { - client, - name: self.name.unwrap_or_default(), - configuration: self.configuration.unwrap_or_default(), - satellite: self.satellite.unwrap_or_default(), - properties: self.properties, - }; - - let url = client.path_to_url("overrides"); - client.post(url, override_builder).await - } -} - -#[derive(Builder, Debug, Clone, PartialEq, Serialize)] -#[builder(setter(into), build_fn(skip), pattern = "owned")] -#[serde(rename_all = "camelCase")] -pub struct User<'a, C> { - #[serde(skip_serializing)] - #[builder(setter(custom))] - client: &'a C, - #[serde(skip_serializing)] - account_id: i32, - first_name: String, - last_name: String, - email: String, - machine_service: bool, - #[builder(setter(custom), field(ty = "Vec"))] - roles: Vec, -} - -impl<'a, C> UserBuilder<'a, C> -where - C: FreedomApi, -{ - pub(crate) fn client(client: &'a C) -> Self { - Self { - client: Some(client), - ..Default::default() - } - } - - pub async fn send(self) -> Result { - let client = self.client.unwrap(); - - let account_id = self.account_id.ok_or(BuilderError::AccountId)?; - let user = User { - client, - account_id, - first_name: self.first_name.unwrap_or_default(), - last_name: self.last_name.unwrap_or_default(), - email: self.email.unwrap_or_default(), - machine_service: self.machine_service.unwrap_or_default(), - roles: self.roles, - }; - - let url = client.path_to_url(format!("accounts/{}/newuser", user.account_id)); - client.post(url, user).await - } -} - -#[derive(Builder, Debug, Clone, PartialEq, Serialize)] -#[builder(build_fn(skip), pattern = "owned")] -#[serde(rename_all = "camelCase")] -pub struct TaskRequest<'a, C> { - #[serde(skip_serializing)] - #[builder(setter(custom))] - client: &'a C, - #[serde(rename(serialize = "type"))] - #[builder(setter(name = "task_type"))] - typ: TaskType, - #[builder(setter(custom))] - site: String, - #[builder(setter(custom))] - satellite: String, - #[builder(setter(custom))] - configuration: String, - #[builder(setter(custom), field(ty = "Vec"))] - target_bands: Vec, - #[builder( - setter(name = "target_date_utc", strip_option), - field(ty = "Option") - )] - target_date: String, - duration: u64, - #[builder(field(ty = "Option"), setter(strip_option))] - minimum_duration: Option, - #[builder(field(ty = "Option"), setter(strip_option))] - hours_of_flex: Option, - #[builder(field(ty = "Option"), setter(strip_option))] - test_file: Option, - #[serde(rename(serialize = "override"))] - #[builder(field(ty = "Option"))] - with_override: Option, -} - -impl<'a, C> TaskRequestBuilder<'a, C> -where - C: FreedomApi, -{ - pub(crate) fn client(client: &'a C) -> Self { - Self { - client: Some(client), - ..Default::default() - } - } - - pub fn site(mut self, site_id: u32) -> Self { - let client = self.client.unwrap(); - let site = client.path_to_url(format!("sites/{}", site_id)).to_string(); - self.site = Some(site); - self - } - - pub fn target_bands(mut self, band_ids: impl IntoIterator) -> Self { - let client = self.client.unwrap(); - let target_bands: Vec<_> = band_ids - .into_iter() - .map(|id| { - client - .path_to_url(format!("satellite_bands/{}", id)) - .to_string() - }) - .collect(); - self.target_bands = target_bands; - self - } - - pub fn satellite(mut self, satellite_id: u32) -> Self { - let client = self.client.unwrap(); - let satellite = client - .path_to_url(format!("satellites/{}", satellite_id)) - .to_string(); - self.satellite = Some(satellite); - self - } - - pub fn configuration(mut self, configuration_id: u32) -> Self { - let client = self.client.unwrap(); - let configuration = client - .path_to_url(format!("configurations/{}", configuration_id)) - .to_string(); - self.configuration = Some(configuration); - self - } - - pub async fn send(self) -> Result { - use time::macros::format_description; - let item = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]Z"); - - let client = self.client.unwrap(); - - let target_date = self.target_date.ok_or(BuilderError::TargetDate)?; - let target_date = target_date.format(item)?; - let task_type = self.typ.ok_or(BuilderError::TaskType)?; - let request = TaskRequest { - client, - typ: task_type, - site: self.site.ok_or(BuilderError::SiteId)?, - satellite: self.satellite.ok_or(BuilderError::SatelliteId)?, - configuration: self.configuration.ok_or(BuilderError::ConfigurationId)?, - target_date, - minimum_duration: self.minimum_duration, - duration: self.duration.ok_or(BuilderError::Duration)?, - hours_of_flex: self.hours_of_flex, - test_file: self.test_file, - with_override: self.with_override, - target_bands: self.target_bands, - }; - - println!("{}", serde_json::to_string_pretty(&request).unwrap()); - - let url = client.path_to_url("requests"); - client.post(url, request).await - } -} diff --git a/src/api/post/band.rs b/src/api/post/band.rs new file mode 100644 index 0000000..fa55c0d --- /dev/null +++ b/src/api/post/band.rs @@ -0,0 +1,177 @@ +use freedom_models::{ + band::{BandType, IoConfiguration, IoHardware}, + task::Polarization, +}; +use reqwest::Response; +use serde::Serialize; + +use crate::{api::FreedomApi, Error}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BandDetails { + name: String, + #[serde(rename(serialize = "type"))] + typ: BandType, + frequency_mghz: f64, + default_band_width_mghz: f64, + modulation: Option, + eirp: Option, + gain: Option, + io_configuration: IoConfiguration, + polarization: Option, + manual_transmit_control: bool, +} + +pub struct BandDetailsBuilder<'a, C, S> { + pub(crate) client: &'a C, + state: S, +} + +pub struct NoName; + +pub fn new(client: &C) -> BandDetailsBuilder<'_, C, NoName> { + BandDetailsBuilder { + client, + state: NoName, + } +} + +impl<'a, C> BandDetailsBuilder<'a, C, NoName> { + pub fn name(self, name: impl Into) -> BandDetailsBuilder<'a, C, NoBandType> { + BandDetailsBuilder { + client: self.client, + state: NoBandType { name: name.into() }, + } + } +} + +pub struct NoBandType { + name: String, +} + +impl<'a, C> BandDetailsBuilder<'a, C, NoBandType> { + pub fn band_type(self, band_type: BandType) -> BandDetailsBuilder<'a, C, NoFrequency> { + BandDetailsBuilder { + client: self.client, + state: NoFrequency { + name: self.state.name, + band_type, + }, + } + } +} + +pub struct NoFrequency { + name: String, + band_type: BandType, +} + +impl<'a, C> BandDetailsBuilder<'a, C, NoFrequency> { + pub fn frequency(self, frequency: impl Into) -> BandDetailsBuilder<'a, C, NoBandWidth> { + BandDetailsBuilder { + client: self.client, + state: NoBandWidth { + name: self.state.name, + band_type: self.state.band_type, + frequency_mghz: frequency.into(), + }, + } + } +} + +pub struct NoBandWidth { + name: String, + band_type: BandType, + frequency_mghz: f64, +} + +impl<'a, C> BandDetailsBuilder<'a, C, NoBandWidth> { + pub fn default_band_width( + self, + bandwidth_mghz: impl Into, + ) -> BandDetailsBuilder<'a, C, NoIoConfig> { + BandDetailsBuilder { + client: self.client, + state: NoIoConfig { + name: self.state.name, + band_type: self.state.band_type, + frequency_mghz: self.state.frequency_mghz, + default_band_width_mghz: bandwidth_mghz.into(), + }, + } + } +} + +pub struct NoIoConfig { + name: String, + band_type: BandType, + frequency_mghz: f64, + default_band_width_mghz: f64, +} + +impl<'a, C> BandDetailsBuilder<'a, C, NoIoConfig> { + pub fn io_hardware(self, hardware: IoHardware) -> BandDetailsBuilder<'a, C, BandDetails> { + let state = BandDetails { + name: self.state.name, + typ: self.state.band_type, + frequency_mghz: self.state.frequency_mghz, + default_band_width_mghz: self.state.default_band_width_mghz, + io_configuration: IoConfiguration { + start_hex_pattern: None, + end_hex_pattern: None, + strip_pattern: false, + io_hardware: Some(hardware), + }, + modulation: None, + eirp: None, + gain: None, + polarization: None, + manual_transmit_control: false, + }; + + BandDetailsBuilder { + client: self.client, + state, + } + } +} + +impl<'a, C> BandDetailsBuilder<'a, C, BandDetails> { + pub fn polarization(mut self, polarization: Polarization) -> Self { + self.state.polarization = Some(polarization); + self + } + + pub fn modulation(mut self, modulation: impl Into) -> Self { + self.state.modulation = Some(modulation.into()); + self + } + + pub fn effective_isotropic_radiative_power(mut self, eirp: impl Into) -> Self { + self.state.eirp = Some(eirp.into()); + self + } + + pub fn gain(mut self, gain: impl Into) -> Self { + self.state.gain = Some(gain.into()); + self + } + + pub fn manual_transmit_control(mut self, control: bool) -> Self { + self.state.manual_transmit_control = control; + self + } +} + +impl<'a, C> BandDetailsBuilder<'a, C, BandDetails> +where + C: FreedomApi, +{ + pub async fn send(self) -> Result { + let client = self.client; + + let url = client.path_to_url("satellite_bands"); + client.post(url, self.state).await + } +} diff --git a/src/api/post/overrides.rs b/src/api/post/overrides.rs new file mode 100644 index 0000000..56601fa --- /dev/null +++ b/src/api/post/overrides.rs @@ -0,0 +1,128 @@ +use std::collections::HashMap; + +use reqwest::Response; +use serde::Serialize; + +use crate::{api::FreedomApi, Error}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Override { + name: String, + satellite: String, + configuration: String, + properties: HashMap, +} + +pub struct OverrideBuilder<'a, C, S> { + pub(crate) client: &'a C, + state: S, +} + +pub fn new(client: &C) -> OverrideBuilder<'_, C, NoName> { + OverrideBuilder { + client, + state: NoName, + } +} + +pub struct NoName; + +impl<'a, C> OverrideBuilder<'a, C, NoName> { + pub fn name(self, name: impl Into) -> OverrideBuilder<'a, C, NoSatellite> { + OverrideBuilder { + client: self.client, + state: NoSatellite { name: name.into() }, + } + } +} + +pub struct NoSatellite { + name: String, +} + +impl<'a, C> OverrideBuilder<'a, C, NoSatellite> +where + C: FreedomApi, +{ + pub fn satellite_id(self, id: impl Into) -> OverrideBuilder<'a, C, NoConfig> { + let satellite = self + .client + .path_to_url(format!("satellites/{}", id.into())) + .to_string(); + + self.satellite_url(satellite) + } +} + +impl<'a, C> OverrideBuilder<'a, C, NoSatellite> { + pub fn satellite_url(self, url: impl Into) -> OverrideBuilder<'a, C, NoConfig> { + OverrideBuilder { + client: self.client, + state: NoConfig { + name: self.state.name, + satellite: url.into(), + }, + } + } +} + +pub struct NoConfig { + name: String, + satellite: String, +} + +impl<'a, C> OverrideBuilder<'a, C, NoConfig> +where + C: FreedomApi, +{ + pub fn satellite_configuration_id( + self, + id: impl Into, + ) -> OverrideBuilder<'a, C, Override> { + let satellite = self + .client + .path_to_url(format!("satellites/{}", id.into())) + .to_string(); + + self.satellite_configuration_url(satellite) + } +} + +impl<'a, C> OverrideBuilder<'a, C, NoConfig> { + pub fn satellite_configuration_url( + self, + url: impl Into, + ) -> OverrideBuilder<'a, C, Override> { + let state = Override { + name: self.state.name, + satellite: self.state.satellite, + configuration: url.into(), + properties: HashMap::new(), + }; + + OverrideBuilder { + client: self.client, + state, + } + } +} + +impl<'a, C> OverrideBuilder<'a, C, Override> { + pub fn add_property(mut self, key: impl Into, value: impl ToString) -> Self { + self.state.properties.insert(key.into(), value.to_string()); + self + } +} + +impl<'a, C> OverrideBuilder<'a, C, Override> +where + C: FreedomApi, +{ + pub async fn send(self) -> Result { + let client = self.client; + + let url = client.path_to_url("overrides"); + client.post(url, self.state).await + } +} diff --git a/src/api/post/request.rs b/src/api/post/request.rs new file mode 100644 index 0000000..12d40d4 --- /dev/null +++ b/src/api/post/request.rs @@ -0,0 +1,383 @@ +use freedom_models::task::TaskType; +use reqwest::Response; +use serde::Serialize; +use time::OffsetDateTime; + +use crate::{api::FreedomApi, Error}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TaskRequest { + #[serde(rename(serialize = "type"))] + typ: TaskType, + site: String, + satellite: String, + configuration: String, + target_bands: Vec, + target_date: String, + duration: u64, + minimum_duration: Option, + hours_of_flex: Option, + test_file: Option, + #[serde(rename(serialize = "override"))] + with_override: Option, +} + +pub struct TaskRequestBuilder<'a, C, S> { + pub(crate) client: &'a C, + state: S, +} + +pub fn new(client: &C) -> TaskRequestBuilder<'_, C, NoType> { + TaskRequestBuilder { + client, + state: NoType, + } +} + +pub trait TaskInner { + fn adjust(&mut self, request: &mut TaskRequest); +} + +impl TaskInner for TestTask { + fn adjust(&mut self, request: &mut TaskRequest) { + request.typ = TaskType::Test; + request.test_file = Some(std::mem::take(&mut self.test_file)); + } +} + +impl TaskInner for FlexTask { + fn adjust(&mut self, request: &mut TaskRequest) { + let kind = match self.kind { + FlexTaskKind::Before => TaskType::Before, + FlexTaskKind::After => TaskType::After, + FlexTaskKind::Around => TaskType::Around, + }; + + request.typ = kind; + request.hours_of_flex = Some(self.hours_of_flex); + } +} + +pub struct NoType; + +pub struct TestTask { + test_file: String, +} + +pub enum FlexTaskKind { + Before, + After, + Around, +} + +pub struct FlexTask { + kind: FlexTaskKind, + hours_of_flex: u8, +} + +pub struct ExactTask; + +impl TaskInner for ExactTask { + fn adjust(&mut self, request: &mut TaskRequest) { + request.typ = TaskType::Exact; + } +} + +impl<'a, C> TaskRequestBuilder<'a, C, NoType> { + pub fn exact_task(self) -> TaskRequestBuilder<'a, C, NoTime> { + TaskRequestBuilder { + client: self.client, + state: NoTime { kind: ExactTask }, + } + } + + pub fn flex_task( + self, + kind: FlexTaskKind, + hours_of_flex: u8, + ) -> TaskRequestBuilder<'a, C, NoTime> { + TaskRequestBuilder { + client: self.client, + state: NoTime { + kind: FlexTask { + kind, + hours_of_flex, + }, + }, + } + } + + pub fn flex_task_after(self, hours_of_flex: u8) -> TaskRequestBuilder<'a, C, NoTime> { + self.flex_task(FlexTaskKind::After, hours_of_flex) + } + + pub fn flex_task_around( + self, + hours_of_flex: u8, + ) -> TaskRequestBuilder<'a, C, NoTime> { + self.flex_task(FlexTaskKind::Around, hours_of_flex) + } + + pub fn flex_task_before( + self, + hours_of_flex: u8, + ) -> TaskRequestBuilder<'a, C, NoTime> { + self.flex_task(FlexTaskKind::Before, hours_of_flex) + } + + pub fn test_task( + self, + test_file: impl Into, + ) -> TaskRequestBuilder<'a, C, NoTime> { + TaskRequestBuilder { + client: self.client, + state: NoTime { + kind: TestTask { + test_file: test_file.into(), + }, + }, + } + } +} + +pub struct NoTime { + kind: T, +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoTime> { + pub fn target_time_utc(self, time: OffsetDateTime) -> TaskRequestBuilder<'a, C, NoDuration> { + TaskRequestBuilder { + client: self.client, + state: NoDuration { + kind: self.state.kind, + time, + }, + } + } +} + +pub struct NoDuration { + kind: T, + time: OffsetDateTime, +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoDuration> { + pub fn task_duration(self, seconds: u64) -> TaskRequestBuilder<'a, C, NoSatellite> { + TaskRequestBuilder { + client: self.client, + state: NoSatellite { + kind: self.state.kind, + time: self.state.time, + duration: seconds, + }, + } + } +} + +pub struct NoSatellite { + kind: T, + time: OffsetDateTime, + duration: u64, +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoSatellite> +where + C: FreedomApi, +{ + pub fn satellite_id(self, id: impl Into) -> TaskRequestBuilder<'a, C, NoSite> { + let satellite = self + .client + .path_to_url(format!("satellites/{}", id.into())) + .to_string(); + + self.satellite_url(satellite) + } +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoSatellite> { + pub fn satellite_url(self, url: impl Into) -> TaskRequestBuilder<'a, C, NoSite> { + TaskRequestBuilder { + client: self.client, + state: NoSite { + kind: self.state.kind, + time: self.state.time, + duration: self.state.duration, + satellite: url.into(), + }, + } + } +} + +pub struct NoSite { + kind: T, + time: OffsetDateTime, + duration: u64, + satellite: String, +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoSite> +where + C: FreedomApi, +{ + pub fn site_id(self, id: impl Into) -> TaskRequestBuilder<'a, C, NoConfig> { + let site = self + .client + .path_to_url(format!("sites/{}", id.into())) + .to_string(); + + self.site_url(site) + } +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoSite> { + pub fn site_url(self, url: impl Into) -> TaskRequestBuilder<'a, C, NoConfig> { + TaskRequestBuilder { + client: self.client, + state: NoConfig { + kind: self.state.kind, + time: self.state.time, + duration: self.state.duration, + satellite: self.state.satellite, + site: url.into(), + }, + } + } +} + +pub struct NoConfig { + kind: T, + time: OffsetDateTime, + duration: u64, + satellite: String, + site: String, +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoConfig> +where + C: FreedomApi, +{ + pub fn site_configuration_id(self, id: impl Into) -> TaskRequestBuilder<'a, C, NoBand> { + let configuration = self + .client + .path_to_url(format!("configurations/{}", id.into())) + .to_string(); + + self.site_configuration_url(configuration) + } +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoConfig> { + pub fn site_configuration_url( + self, + url: impl Into, + ) -> TaskRequestBuilder<'a, C, NoBand> { + TaskRequestBuilder { + client: self.client, + state: NoBand { + kind: self.state.kind, + time: self.state.time, + duration: self.state.duration, + satellite: self.state.satellite, + site: self.state.site, + configuration: url.into(), + }, + } + } +} + +pub struct NoBand { + kind: T, + time: OffsetDateTime, + duration: u64, + satellite: String, + site: String, + configuration: String, +} + +impl<'a, C, T> TaskRequestBuilder<'a, C, NoBand> +where + T: TaskInner, +{ + pub fn band_ids( + self, + ids: impl IntoIterator, + ) -> TaskRequestBuilder<'a, C, TaskRequest> + where + C: FreedomApi, + { + let client = self.client; + let bands = ids.into_iter().map(|id| { + client + .path_to_url(format!("satellite_bands/{}", id)) + .to_string() + }); + + self.band_urls(bands) + } + + pub fn band_urls( + mut self, + urls: impl IntoIterator, + ) -> TaskRequestBuilder<'a, C, TaskRequest> { + use time::macros::format_description; + let item = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]Z"); + + let target_date = self.state.time.format(item).unwrap(); + let target_bands: Vec<_> = urls.into_iter().collect(); + + let mut state = TaskRequest { + typ: TaskType::After, // This is overwritten in `adjust` + site: self.state.site, + satellite: self.state.satellite, + configuration: self.state.configuration, + target_bands, + target_date, + duration: self.state.duration, + minimum_duration: Some(self.state.duration), + hours_of_flex: None, + test_file: None, + with_override: None, + }; + + self.state.kind.adjust(&mut state); + + TaskRequestBuilder { + client: self.client, + state, + } + } +} + +impl<'a, C> TaskRequestBuilder<'a, C, TaskRequest> { + pub fn task_minimum_duration(mut self, duration: u64) -> Self { + self.state.minimum_duration = Some(duration); + self + } + + pub fn override_url(mut self, url: impl Into) -> Self { + self.state.with_override = Some(url.into()); + self + } +} + +impl<'a, C> TaskRequestBuilder<'a, C, TaskRequest> +where + C: FreedomApi, +{ + pub fn override_id(self, id: impl Into) -> Self { + let override_url = self + .client + .path_to_url(format!("overrides/{}", id.into())) + .to_string(); + + self.override_url(override_url) + } + + pub async fn send(self) -> Result { + let client = self.client; + + let url = client.path_to_url("requests"); + client.post(url, self.state).await + } +} diff --git a/src/api/post/sat_config.rs b/src/api/post/sat_config.rs new file mode 100644 index 0000000..324a8e5 --- /dev/null +++ b/src/api/post/sat_config.rs @@ -0,0 +1,104 @@ +use reqwest::Response; +use serde::Serialize; + +use crate::{api::FreedomApi, Error}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SatelliteConfiguration { + name: String, + doppler: Option, + notes: Option, + band_details: Vec, +} + +pub struct NoName; + +pub struct SatelliteConfigurationBuilder<'a, C, S> { + pub(crate) client: &'a C, + state: S, +} + +pub(crate) fn new(client: &C) -> SatelliteConfigurationBuilder<'_, C, NoName> { + SatelliteConfigurationBuilder { + client, + state: NoName, + } +} + +impl<'a, C> SatelliteConfigurationBuilder<'a, C, NoName> { + pub fn name(self, name: impl Into) -> SatelliteConfigurationBuilder<'a, C, NoBand> { + SatelliteConfigurationBuilder { + client: self.client, + state: NoBand { name: name.into() }, + } + } +} + +pub struct NoBand { + name: String, +} + +impl<'a, C> SatelliteConfigurationBuilder<'a, C, NoBand> { + pub fn band_urls( + self, + urls: impl IntoIterator, + ) -> SatelliteConfigurationBuilder<'a, C, SatelliteConfiguration> { + let band_details: Vec<_> = urls.into_iter().collect(); + + let state = SatelliteConfiguration { + name: self.state.name, + doppler: None, + notes: None, + band_details, + }; + + SatelliteConfigurationBuilder { + client: self.client, + state, + } + } +} + +impl<'a, C> SatelliteConfigurationBuilder<'a, C, NoBand> +where + C: FreedomApi, +{ + pub fn band_ids( + self, + ids: impl IntoIterator, + ) -> SatelliteConfigurationBuilder<'a, C, SatelliteConfiguration> { + let client = self.client; + let bands = ids.into_iter().map(|id| { + client + .path_to_url(format!("satellite_bands/{}", id)) + .to_string() + }); + + self.band_urls(bands) + } +} + +impl<'a, C> SatelliteConfigurationBuilder<'a, C, SatelliteConfiguration> { + pub fn doppler(mut self, doppler: bool) -> Self { + self.state.doppler = Some(doppler); + self + } + + pub fn notes(mut self, notes: impl Into) -> Self { + self.state.notes = Some(notes.into()); + self + } +} + +impl<'a, C> SatelliteConfigurationBuilder<'a, C, SatelliteConfiguration> +where + C: FreedomApi, +{ + pub async fn send(self) -> Result { + let client = self.client; + + let url = client.path_to_url("satellite_configurations"); + client.post(url, self.state).await + } +} diff --git a/src/api/post/satellite.rs b/src/api/post/satellite.rs new file mode 100644 index 0000000..391e1e6 --- /dev/null +++ b/src/api/post/satellite.rs @@ -0,0 +1,112 @@ +use reqwest::Response; +use serde::Serialize; + +use crate::{api::FreedomApi, Error}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Satellite { + name: String, + description: Option, + norad_cat_id: u32, + configuration: String, +} + +pub fn new(client: &C) -> SatelliteBuilder<'_, C, NoName> { + SatelliteBuilder { + client, + state: NoName, + } +} + +pub struct SatelliteBuilder<'a, C, S> { + pub(crate) client: &'a C, + state: S, +} + +pub struct NoName; + +impl<'a, C> SatelliteBuilder<'a, C, NoName> { + pub fn name(self, name: impl Into) -> SatelliteBuilder<'a, C, NoConfig> { + SatelliteBuilder { + client: self.client, + state: NoConfig { name: name.into() }, + } + } +} + +pub struct NoConfig { + name: String, +} + +impl<'a, C> SatelliteBuilder<'a, C, NoConfig> { + pub fn satellite_configuration_url( + self, + url: impl Into, + ) -> SatelliteBuilder<'a, C, NoNorad> { + SatelliteBuilder { + client: self.client, + state: NoNorad { + name: self.state.name, + configuration: url.into(), + }, + } + } +} + +impl<'a, C> SatelliteBuilder<'a, C, NoConfig> +where + C: FreedomApi, +{ + pub fn satellite_configuration_id( + self, + id: impl Into, + ) -> SatelliteBuilder<'a, C, NoNorad> { + let configuration = self + .client + .path_to_url(format!("satellite_configurations/{}", id.into())) + .to_string(); + + self.satellite_configuration_url(configuration) + } +} + +pub struct NoNorad { + name: String, + configuration: String, +} + +impl<'a, C> SatelliteBuilder<'a, C, NoNorad> { + pub fn norad_id(self, norad_id: u32) -> SatelliteBuilder<'a, C, Satellite> { + let state = Satellite { + name: self.state.name, + description: None, + norad_cat_id: norad_id, + configuration: self.state.configuration, + }; + + SatelliteBuilder { + client: self.client, + state, + } + } +} + +impl<'a, C> SatelliteBuilder<'a, C, Satellite> { + pub fn description(mut self, description: impl Into) -> Self { + self.state.description = Some(description.into()); + self + } +} + +impl<'a, C> SatelliteBuilder<'a, C, Satellite> +where + C: FreedomApi, +{ + pub async fn send(self) -> Result { + let client = self.client; + + let url = client.path_to_url("satellites"); + client.post(url, self.state).await + } +} diff --git a/src/api/post/user.rs b/src/api/post/user.rs new file mode 100644 index 0000000..bce10eb --- /dev/null +++ b/src/api/post/user.rs @@ -0,0 +1,131 @@ +use reqwest::Response; +use serde::Serialize; + +use crate::{api::FreedomApi, Error}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct User { + #[serde(skip_serializing)] + account_id: i32, + first_name: String, + last_name: String, + email: String, + machine_service: bool, + roles: Vec, +} + +pub struct UserBuilder<'a, C, S> { + client: &'a C, + state: S, +} + +pub fn new(client: &C) -> UserBuilder<'_, C, NoAccount> { + UserBuilder { + client, + state: NoAccount, + } +} + +pub struct NoAccount; + +impl<'a, C> UserBuilder<'a, C, NoAccount> { + pub fn account_id(self, account_id: impl Into) -> UserBuilder<'a, C, NoFirstName> { + UserBuilder { + client: self.client, + state: NoFirstName { + account_id: account_id.into(), + }, + } + } +} + +pub struct NoFirstName { + account_id: i32, +} + +impl<'a, C> UserBuilder<'a, C, NoFirstName> { + pub fn first_name(self, first_name: impl Into) -> UserBuilder<'a, C, NoLastName> { + UserBuilder { + client: self.client, + state: NoLastName { + account_id: self.state.account_id, + first_name: first_name.into(), + }, + } + } +} + +pub struct NoLastName { + account_id: i32, + first_name: String, +} + +impl<'a, C> UserBuilder<'a, C, NoLastName> { + pub fn last_name(self, last_name: impl Into) -> UserBuilder<'a, C, NoEmail> { + UserBuilder { + client: self.client, + state: NoEmail { + account_id: self.state.account_id, + first_name: self.state.first_name, + last_name: last_name.into(), + }, + } + } +} + +pub struct NoEmail { + account_id: i32, + first_name: String, + last_name: String, +} + +impl<'a, C> UserBuilder<'a, C, NoEmail> { + pub fn email(self, email: impl Into) -> UserBuilder<'a, C, User> { + let state = User { + account_id: self.state.account_id, + first_name: self.state.first_name, + last_name: self.state.last_name, + email: email.into(), + machine_service: false, + roles: Vec::new(), + }; + + UserBuilder { + client: self.client, + state, + } + } +} + +impl<'a, C> UserBuilder<'a, C, User> { + pub fn add_role(mut self, role: impl Into) -> Self { + self.state.roles.push(role.into()); + + self + } + + pub fn add_roles(mut self, roles: I) -> Self + where + I: IntoIterator, + T: Into, + { + for role in roles { + self = self.add_role(role); + } + + self + } +} + +impl<'a, C> UserBuilder<'a, C, User> +where + C: FreedomApi, +{ + pub async fn send(self) -> Result { + let client = self.client; + + let url = client.path_to_url(format!("accounts/{}/newuser", self.state.account_id)); + client.post(url, self.state).await + } +} diff --git a/src/lib.rs b/src/lib.rs index 473555a..0da40e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub type Result = std::result::Result; pub mod prelude { pub use crate::api::post::{ BandDetailsBuilder, OverrideBuilder, SatelliteBuilder, SatelliteConfigurationBuilder, + UserBuilder, }; pub use crate::api::{FreedomApi, FreedomApiContainer, FreedomApiValue}; #[cfg(feature = "caching")]