diff --git a/Cargo.toml b/Cargo.toml index c00f46f..f21a91a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ futures-core = { version = "0.3.30" } reqwest = { version = "0.12.4", features = ["json"]} serde = { version = "1.0.195", features = ["derive"] } serde_json = { version = "1.0.111" } -thiserror = { version = "2.0.6" } +thiserror = { version = "1.0.56" } time = { version = "0.3.36", features = ["macros", "parsing", "formatting"] } tracing = { version = "0.1.40" } url = { version = "2.5.0" } diff --git a/src/api.rs b/src/api.rs index 7a8823c..9078a49 100644 --- a/src/api.rs +++ b/src/api.rs @@ -42,7 +42,7 @@ trait PaginatedErr<'a, T> { fn once_err(self) -> PaginatedStream<'a, T>; } -impl<'a, T: 'a + Send + Sync> PaginatedErr<'a, T> for Error { +impl<'a, T: 'a + Send> PaginatedErr<'a, T> for Error { fn once_err(self) -> PaginatedStream<'a, T> { Box::pin(async_stream::stream! { yield Err(self); }) } @@ -70,7 +70,7 @@ pub trait Container: Deref + Value { /// A stream of paginated results from freedom. /// /// Each item in the stream is a result, since one or more items may fail to be serialized -pub type PaginatedStream<'a, T> = Pin> + 'a + Send + Sync>>; +pub type PaginatedStream<'a, T> = Pin> + 'a + Send>>; /// The primary trait for interfacing with the Freedom API pub trait Api: Send + Sync { @@ -85,7 +85,7 @@ pub trait Api: Send + Sync { /// /// The JSON response is then deserialized into the required type, erroring if the /// deserialization fails, and providing the object if it succeeds. - fn get_json_map(&self, url: Url) -> impl Future> + Send + Sync + fn get_json_map(&self, url: Url) -> impl Future> + Send where T: Value, { @@ -103,10 +103,7 @@ pub trait Api: Send + Sync { /// authentication. /// /// Returns the raw binary body, and the status code. - fn get( - &self, - url: Url, - ) -> impl Future> + Send + Sync; + fn get(&self, url: Url) -> impl Future> + Send; /// Creates a stream of items from a paginated endpoint. /// @@ -124,7 +121,7 @@ pub trait Api: Send + Sync { /// of the async book. fn get_paginated(&self, head_url: Url) -> PaginatedStream<'_, Self::Container> where - T: 'static + Value + Send + Sync, + T: 'static + Value, { let base = self.config().environment().freedom_entrypoint(); let mut current_url = head_url; // Not necessary but makes control flow more obvious @@ -297,7 +294,7 @@ pub trait Api: Send + Sync { &self, url: Url, msg: S, - ) -> impl Future> + Send + Sync + ) -> impl Future> + Send where S: serde::Serialize + Send + Sync, T: Value, @@ -310,11 +307,7 @@ pub trait Api: Send + Sync { } /// Lower level method, not intended for direct use - fn post( - &self, - url: Url, - msg: S, - ) -> impl Future> + Send + Sync + fn post(&self, url: Url, msg: S) -> impl Future> + Send where S: serde::Serialize + Send + Sync; @@ -337,7 +330,7 @@ pub trait Api: Send + Sync { fn get_account_by_name( &self, account_name: &str, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let mut uri = self.path_to_url("accounts/search/findOneByName"); uri.set_query(Some(&format!("name={account_name}"))); @@ -364,7 +357,7 @@ pub trait Api: Send + Sync { &self, task_id: i32, file_name: &str, - ) -> impl Future> + Send + Sync { + ) -> impl Future> + Send { async move { let path = format!("downloads/{}/{}", task_id, file_name); let uri = self.path_to_url(path); @@ -382,7 +375,7 @@ pub trait Api: Send + Sync { fn get_account_by_id( &self, account_id: i32, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let uri = self.path_to_url(format!("accounts/{account_id}")); self.get_json_map(uri).await @@ -393,7 +386,9 @@ pub trait Api: Send + Sync { /// /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process /// and return type - fn get_accounts(&self) -> PaginatedStream<'_, Self::Container> { + fn get_accounts( + &self, + ) -> Pin, Error>> + '_>> { let uri = self.path_to_url("accounts"); self.get_paginated(uri) } @@ -402,7 +397,9 @@ pub trait Api: Send + Sync { /// /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process /// and return type - fn get_satellite_bands(&self) -> PaginatedStream<'_, Self::Container> { + fn get_satellite_bands( + &self, + ) -> Pin, Error>> + '_>> { let uri = self.path_to_url("satellite_bands"); self.get_paginated(uri) } @@ -413,7 +410,7 @@ pub trait Api: Send + Sync { fn get_satellite_band_by_id( &self, satellite_band_id: i32, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let uri = self.path_to_url(format!("satellite_bands/{satellite_band_id}")); self.get_json_map(uri).await @@ -426,7 +423,7 @@ pub trait Api: Send + Sync { fn get_satellite_band_by_name( &self, satellite_band_name: &str, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let mut uri = self.path_to_url("satellite_bands/search/findOneByName"); uri.set_query(Some(&format!("name={satellite_band_name}"))); @@ -441,7 +438,7 @@ pub trait Api: Send + Sync { fn get_satellite_bands_by_account_name( &self, account_name: &str, - ) -> PaginatedStream<'_, Self::Container> { + ) -> Pin, Error>> + '_>> { let mut uri = self.path_to_url("satellite_bands/search/findAllByAccountName"); uri.set_query(Some(&format!("accountName={account_name}"))); @@ -456,7 +453,8 @@ pub trait Api: Send + Sync { fn get_satellite_configurations_by_account_name( &self, account_name: &str, - ) -> PaginatedStream<'_, Self::Container> { + ) -> Pin, Error>> + '_>> + { let mut uri = self.path_to_url("satellite_configurations/search/findAllByAccountName"); uri.set_query(Some(&format!("accountName={account_name}"))); @@ -469,7 +467,8 @@ pub trait Api: Send + Sync { /// and return type fn get_satellite_configurations( &self, - ) -> PaginatedStream<'_, Self::Container> { + ) -> Pin, Error>> + '_>> + { let uri = self.path_to_url("satellite_configurations"); self.get_paginated(uri) @@ -479,8 +478,7 @@ pub trait Api: Send + Sync { fn get_satellite_configuration_by_id( &self, satellite_configuration_id: i32, - ) -> impl Future, Error>> + Send + Sync - { + ) -> impl Future, Error>> + Send { async move { let uri = self.path_to_url(format!( "satellite_configurations/{satellite_configuration_id}" @@ -494,8 +492,7 @@ pub trait Api: Send + Sync { fn get_satellite_configuration_by_name( &self, satellite_configuration_name: &str, - ) -> impl Future, Error>> + Send + Sync - { + ) -> impl Future, Error>> + Send { async move { let mut uri = self.path_to_url("satellite_configurations/search/findOneByName"); uri.set_query(Some(&format!("name={satellite_configuration_name}"))); @@ -508,7 +505,7 @@ pub trait Api: Send + Sync { /// /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process /// and return type - fn get_sites(&self) -> PaginatedStream<'_, Self::Container> { + fn get_sites(&self) -> Pin, Error>> + '_>> { let uri = self.path_to_url("sites"); self.get_paginated(uri) } @@ -519,7 +516,7 @@ pub trait Api: Send + Sync { fn get_site_by_id( &self, id: i32, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let uri = self.path_to_url(format!("sites/{id}")); self.get_json_map(uri).await @@ -531,8 +528,8 @@ pub trait Api: Send + Sync { /// See [`get`](Self::get) documentation for more details about the process and return type fn get_site_by_name( &self, - name: impl AsRef + Send + Sync, - ) -> impl Future, Error>> + Send + Sync { + name: impl AsRef + Send, + ) -> impl Future, Error>> + Send { async move { let mut uri = self.path_to_url("sites/search/findOneByName"); let query = format!("name={}", name.as_ref()); @@ -548,7 +545,7 @@ pub trait Api: Send + Sync { fn get_request_by_id( &self, task_request_id: i32, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let uri = self.path_to_url(format!("requests/{task_request_id}")); @@ -573,7 +570,7 @@ pub trait Api: Send + Sync { &self, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync { + ) -> impl Future>, Error>> + Send { async move { let mut uri = self.path_to_url("requests/search/findAllByTargetDateBetween"); @@ -600,7 +597,7 @@ pub trait Api: Send + Sync { end: OffsetDateTime, ) -> PaginatedStream<'_, Self::Container> where - T: AsRef + Send + Sync, + T: AsRef + Send, { let mut uri = self.path_to_url("requests/search/findAllByAccountAndTargetDateBetween"); @@ -641,7 +638,7 @@ pub trait Api: Send + Sync { configuration_uri: T, ) -> PaginatedStream<'_, Self::Container> where - T: AsRef + Send + Sync, + T: AsRef + Send, { let mut uri = self.path_to_url("requests/search/findAllByConfigurationOrderByCreatedAsc"); @@ -664,11 +661,11 @@ pub trait Api: Send + Sync { satellites: I, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - T: AsRef + Send + Sync, - I: IntoIterator + Send + Sync, - S: AsRef + Send + Sync, + T: AsRef + Send, + I: IntoIterator + Send, + S: AsRef + Send, { async move { let satellites_string = crate::utils::list_to_string(satellites); @@ -702,9 +699,9 @@ pub trait Api: Send + Sync { configuration_uri: T, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - T: AsRef + Send + Sync, + T: AsRef + Send, { async move { let mut uri = @@ -731,10 +728,10 @@ pub trait Api: Send + Sync { fn get_requests_by_ids( &self, ids: I, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - I: IntoIterator + Send + Sync, - S: AsRef + Send + Sync, + I: IntoIterator + Send, + S: AsRef + Send, { async move { let ids_string = crate::utils::list_to_string(ids); @@ -780,7 +777,7 @@ pub trait Api: Send + Sync { satellite_name: T, ) -> PaginatedStream<'_, Self::Container> where - T: AsRef + Send + Sync, + T: AsRef + Send, { let mut uri = self.path_to_url("requests/search/findBySatelliteName"); @@ -799,9 +796,9 @@ pub trait Api: Send + Sync { satellite_name: T, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - T: AsRef + Send + Sync, + T: AsRef + Send, { async move { let mut uri = @@ -831,7 +828,7 @@ pub trait Api: Send + Sync { status: T, ) -> Result>, Error> where - T: TryInto + Send + Sync, + T: TryInto + Send, Error: From<>::Error>, { let status: TaskStatusType = status.try_into()?; @@ -855,8 +852,8 @@ pub trait Api: Send + Sync { end: OffsetDateTime, ) -> PaginatedStream<'_, Self::Container> where - T: AsRef + Send + Sync, - U: AsRef + Send + Sync, + T: AsRef + Send, + U: AsRef + Send, { let mut uri = self.path_to_url("requests/search/findAllByStatusAndAccountAndTargetDateBetween"); @@ -881,9 +878,9 @@ pub trait Api: Send + Sync { typ: T, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - T: TryInto + Send + Sync, + T: TryInto + Send, Error: From<>::Error>, { async move { @@ -910,7 +907,7 @@ pub trait Api: Send + Sync { /// See [`get`](Self::get) documentation for more details about the process and return type fn get_requests_passed_today( &self, - ) -> impl Future>, Error>> + Send + Sync { + ) -> impl Future>, Error>> + Send { async move { let uri = self.path_to_url("requests/search/findAllPassedToday"); @@ -927,7 +924,7 @@ pub trait Api: Send + Sync { /// See [`get`](Self::get) documentation for more details about the process and return type fn get_requests_upcoming_today( &self, - ) -> impl Future>, Error>> + Send + Sync { + ) -> impl Future>, Error>> + Send { async move { let uri = self.path_to_url("requests/search/findAllUpcomingToday"); @@ -952,7 +949,7 @@ pub trait Api: Send + Sync { fn get_satellite_by_id( &self, satellite_id: i32, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let uri = self.path_to_url(format!("satellites/{}", satellite_id)); @@ -964,7 +961,7 @@ pub trait Api: Send + Sync { fn get_satellite_by_name( &self, satellite_name: &str, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let mut uri = self.path_to_url("satellites/findOneByName"); uri.set_query(Some(&format!("name={satellite_name}"))); @@ -979,7 +976,7 @@ pub trait Api: Send + Sync { fn get_task_by_id( &self, task_id: i32, - ) -> impl Future, Error>> + Send + Sync { + ) -> impl Future, Error>> + Send { async move { let uri = self.path_to_url(format!("tasks/{}", task_id)); @@ -996,9 +993,9 @@ pub trait Api: Send + Sync { account_uri: T, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - T: AsRef + Send + Sync, + T: AsRef + Send, { async move { let mut uri = self.path_to_url("tasks/search/findByAccountAndPassOverlapping"); @@ -1028,11 +1025,11 @@ pub trait Api: Send + Sync { band: V, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - T: AsRef + Send + Sync, - U: AsRef + Send + Sync, - V: AsRef + Send + Sync, + T: AsRef + Send, + U: AsRef + Send, + V: AsRef + Send, { async move { let mut uri = self.path_to_url( @@ -1066,11 +1063,11 @@ pub trait Api: Send + Sync { band: V, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync + ) -> impl Future>, Error>> + Send where - T: AsRef + Send + Sync, - U: AsRef + Send + Sync, - V: AsRef + Send + Sync, + T: AsRef + Send, + U: AsRef + Send, + V: AsRef + Send, { async move { let mut uri = self.path_to_url( @@ -1106,7 +1103,7 @@ pub trait Api: Send + Sync { &self, start: OffsetDateTime, end: OffsetDateTime, - ) -> impl Future>, Error>> + Send + Sync { + ) -> impl Future>, Error>> + Send { async move { let mut uri = self.path_to_url("tasks/search/findByStartBetweenOrderByStartAsc"); @@ -1160,7 +1157,7 @@ pub trait Api: Send + Sync { /// See [`get`](Self::get) documentation for more details about the process and return type fn get_tasks_passed_today( &self, - ) -> impl Future>, Error>> + Send + Sync { + ) -> impl Future>, Error>> + Send { async move { let uri = self.path_to_url("tasks/search/findAllPassedToday"); @@ -1177,7 +1174,7 @@ pub trait Api: Send + Sync { /// See [`get`](Self::get) documentation for more details about the process and return type fn get_tasks_upcoming_today( &self, - ) -> impl Future>, Error>> + Send + Sync { + ) -> impl Future>, Error>> + Send { async move { let uri = self.path_to_url("tasks/search/findAllUpcomingToday"); @@ -1192,7 +1189,7 @@ pub trait Api: Send + Sync { /// /// See [`get_paginated`](Self::get_paginated) documentation for more details about the process /// and return type - fn get_users(&self) -> PaginatedStream<'_, Self::Container> { + fn get_users(&self) -> Pin, Error>> + '_>> { let uri = self.path_to_url("users"); self.get_paginated(uri) } @@ -1388,7 +1385,7 @@ pub trait Api: Send + Sync { &self, band_id: u32, site_configuration_id: u32, - ) -> impl Future> + Send + Sync { + ) -> impl Future> + Send { async move { let url = self.path_to_url("fps"); let payload = serde_json::json!({ @@ -1430,7 +1427,7 @@ pub trait Api: Send + Sync { &self, band_id: u32, satellite_id: u32, - ) -> impl Future> + Send + Sync { + ) -> impl Future> + Send { async move { let url = self.path_to_url("fps"); let payload = serde_json::json!({ diff --git a/src/caching_client.rs b/src/caching_client.rs index 5beb3a4..5b56c41 100644 --- a/src/caching_client.rs +++ b/src/caching_client.rs @@ -45,22 +45,28 @@ impl Api for CachingClient { self.inner.delete(url).await } + #[tracing::instrument] async fn get(&self, url: Url) -> Result<(Bytes, StatusCode), Error> { + // This is a rather cheap clone. Something like 50 bytes. This is necessary since we will + // be passing this to the tokio executor which has lifetime requirements of `'static` let client = &self.inner; - let url_clone = url.clone(); + let value = self + .cache + .try_get_with(url.clone(), async { + let (body, status) = client.get(url).await?; - let fut = async { - let (body, status) = client.get(url_clone).await?; + if !status.is_success() { + return Err(Error::Response(status.to_string())); + } - Ok::<_, Error>((body, status)) - }; + Ok((body, status)) + }) + .await; - let (body, status) = match self.cache.get(&url).await { - Some(out) => out, - None => fut.await?, - }; - - Ok((body, status)) + match value { + Ok(val) => Ok(val), + Err(e) => Err((*e).clone()), + } } async fn post(&self, url: Url, msg: S) -> Result @@ -78,3 +84,20 @@ impl Api for CachingClient { self.inner.config_mut() } } + +#[tracing::instrument] +fn deserialize_from_value(value: serde_json::Value) -> Result +where + T: serde::de::DeserializeOwned + std::fmt::Debug, +{ + match serde_json::from_value::(value).map_err(From::from) { + Ok(item) => { + tracing::debug!(object = ?item, "Received valid object"); + Ok(item) + } + e => { + tracing::warn!(error= ?e, "Object failed deserialization"); + e + } + } +} diff --git a/src/client.rs b/src/client.rs index 56ee40f..31495a0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -105,7 +105,6 @@ impl Api for Client { let status = resp.status(); let body = resp.bytes().await?; - Ok((body, status)) }