From 80c3cba2662f2410517eeded628e53c874fc7df5 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sun, 5 Jan 2025 03:52:59 -0500 Subject: [PATCH] feat: add protection for users If get is used in a loop it's possible to not know that it's not doing anything useful anymore --- examples/loop_yield_data_state.rs | 27 ++++++++++++++---------- src/data_state.rs | 35 +++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/examples/loop_yield_data_state.rs b/examples/loop_yield_data_state.rs index 59e0588..57b5b05 100644 --- a/examples/loop_yield_data_state.rs +++ b/examples/loop_yield_data_state.rs @@ -35,17 +35,22 @@ async fn common_code() -> Result<(), Box> { assert_eq!(status_code, &200); break; } else { - state.get(|| { - let req = client.get("http://httpbin.org/get"); - let response_handler = |resp: reqwest::Result| async { - resp.map(|resp| resp.status()) - .context("Request failed, got an error back") - }; - let ui_notify = || { - println!("Request Completed, this is where you would wake up your UI thread"); - }; - Awaiting(fetch_plus(req, response_handler, ui_notify)) - }); + let is_able_to_make_progress = state + .get(|| { + let req = client.get("http://httpbin.org/get"); + let response_handler = |resp: reqwest::Result| async { + resp.map(|resp| resp.status()) + .context("Request failed, got an error back") + }; + let ui_notify = || { + println!( + "Request Completed, this is where you would wake up your UI thread" + ); + }; + Awaiting(fetch_plus(req, response_handler, ui_notify)) + }) + .is_able_to_make_progress(); + assert!(is_able_to_make_progress); reqwest_cross::yield_now().await; } } diff --git a/src/data_state.rs b/src/data_state.rs index b517120..0bef86a 100644 --- a/src/data_state.rs +++ b/src/data_state.rs @@ -27,6 +27,14 @@ pub enum DataStateError { FromE(E), } +#[derive(Debug)] +/// Provides a way to ensure the calling code knows if it is calling a function +/// that cannot do anything useful anymore +pub enum CanMakeProgress { + AbleToMakeProgress, + UnableToMakeProgress, +} + /// Used to represent data that is pending being available #[derive(Debug)] pub struct Awaiting(pub oneshot::Receiver>); @@ -84,11 +92,12 @@ impl DataState { } } - /// Attempts to load the data. + /// Attempts to load the data and returns if it is able to make progress. /// /// Note: F needs to return `AwaitingType` and not T because it needs to /// be able to be pending if T is not ready. - pub fn get(&mut self, fetch_fn: F) + #[must_use] + pub fn get(&mut self, fetch_fn: F) -> CanMakeProgress where F: FnOnce() -> Awaiting, { @@ -96,18 +105,22 @@ impl DataState { DataState::None => { let rx = fetch_fn(); *self = DataState::AwaitingResponse(rx); + CanMakeProgress::AbleToMakeProgress } DataState::AwaitingResponse(rx) => { if let Some(new_state) = Self::await_data(rx) { *self = new_state; } + CanMakeProgress::AbleToMakeProgress } DataState::Present(_data) => { // Does nothing data is already present + CanMakeProgress::UnableToMakeProgress } DataState::Failed(_e) => { // Have no way to let the user know there is an error nothing we // can do here + CanMakeProgress::UnableToMakeProgress } } } @@ -184,3 +197,21 @@ impl From for DataStateError { anyhow!(value).into() } } + +impl CanMakeProgress { + /// Returns `true` if the can make progress is [`AbleToMakeProgress`]. + /// + /// [`AbleToMakeProgress`]: CanMakeProgress::AbleToMakeProgress + #[must_use] + pub fn is_able_to_make_progress(&self) -> bool { + matches!(self, Self::AbleToMakeProgress) + } + + /// Returns `true` if the can make progress is [`UnableToMakeProgress`]. + /// + /// [`UnableToMakeProgress`]: CanMakeProgress::UnableToMakeProgress + #[must_use] + pub fn is_unable_to_make_progress(&self) -> bool { + matches!(self, Self::UnableToMakeProgress) + } +}