reqwest_cross/
data_state.rs

1//! Helpers for handling pending data.
2
3use anyhow::anyhow;
4use futures::channel::oneshot;
5use std::fmt::{Debug, Display};
6use thiserror::Error;
7use tracing::{error, warn};
8
9/// Provides a common way to specify the bounds errors are expected to meet
10pub trait ErrorBounds: Display + Send + Sync + 'static + Debug {}
11impl<T: Display + Send + Sync + 'static + Debug> ErrorBounds for T {}
12
13#[derive(Error, Debug)]
14/// Represents the types of errors that can occur while using [DataState]
15pub enum DataStateError<E: ErrorBounds> {
16    /// Sender was dropped, request cancelled
17    #[error("Request sender was dropped")]
18    SenderDropped(oneshot::Canceled),
19
20    /// The response received from the request was an error
21    #[error("Response received was an error: {0}")]
22    ErrorResponse(E),
23
24    /// This variant is supplied for use by application code
25    #[error(transparent)]
26    FromE(E),
27}
28
29#[derive(Debug)]
30/// Provides a way to ensure the calling code knows if it is calling a function
31/// that cannot do anything useful anymore
32pub enum CanMakeProgress {
33    /// Used to indicate that it is still possible for progress to be made
34    AbleToMakeProgress,
35
36    /// Used to indicate that further calls are not useful as no progress can be
37    /// made in current state
38    UnableToMakeProgress,
39}
40
41/// Used to represent data that is pending being available
42#[derive(Debug)]
43pub struct Awaiting<T, E: ErrorBounds>(pub oneshot::Receiver<Result<T, E>>);
44impl<T, E: ErrorBounds> From<oneshot::Receiver<Result<T, E>>> for Awaiting<T, E> {
45    fn from(value: oneshot::Receiver<Result<T, E>>) -> Self {
46        Self(value)
47    }
48}
49
50/// Used to store a type that is not always available and we need to keep
51/// polling it to get it ready
52#[derive(Debug, Default)]
53pub enum DataState<T, E: ErrorBounds = anyhow::Error> {
54    /// Represent no data present and not pending
55    #[default]
56    None,
57    /// Represents data has been requested and awaiting it being available
58    AwaitingResponse(Awaiting<T, E>), // TODO 4: Add support for a timeout on waiting
59    /// Represents data that is available for use
60    Present(T),
61    /// Represents an error that Occurred
62    Failed(DataStateError<E>),
63}
64
65impl<T, E: ErrorBounds> DataState<T, E> {
66    #[cfg(feature = "egui")]
67    /// Calls [Self::start_request] and adds a spinner if progress can be made
68    pub fn egui_start_request<F, R>(&mut self, ui: &mut egui::Ui, fetch_fn: F) -> CanMakeProgress
69    where
70        F: FnOnce() -> R,
71        R: Into<Awaiting<T, E>>,
72    {
73        let result = self.start_request(fetch_fn);
74        if result.is_able_to_make_progress() {
75            ui.spinner();
76        }
77        result
78    }
79
80    /// Starts a new request. Only intended to be on [Self::None] and if state
81    /// is any other value it returns [CanMakeProgress::UnableToMakeProgress]
82    #[must_use]
83    pub fn start_request<F, R>(&mut self, fetch_fn: F) -> CanMakeProgress
84    where
85        F: FnOnce() -> R,
86        R: Into<Awaiting<T, E>>,
87    {
88        if self.is_none() {
89            *self = DataState::AwaitingResponse(fetch_fn().into());
90            CanMakeProgress::AbleToMakeProgress
91        } else {
92            debug_assert!(
93                false,
94                "No known good reason this path should be hit other than logic error"
95            );
96            CanMakeProgress::UnableToMakeProgress
97        }
98    }
99
100    /// Convenience method that will try to make progress if in
101    /// [Self::AwaitingResponse] and does nothing otherwise. Returns a reference
102    /// to self for chaining
103    pub fn poll(&mut self) -> &mut Self {
104        if let DataState::AwaitingResponse(rx) = self {
105            if let Some(new_state) = Self::await_data(rx) {
106                *self = new_state;
107            }
108        }
109        self
110    }
111
112    #[cfg(feature = "egui")]
113    /// Meant to be a simple method to just provide the data if it's ready or
114    /// help with UI and polling to get it ready if it's not.
115    ///
116    /// WARNING: Does nothing if `self` is [Self::None]
117    ///
118    /// If a `error_btn_text` is provided then it overrides the default
119    pub fn egui_poll_mut(
120        &mut self,
121        ui: &mut egui::Ui,
122        error_btn_text: Option<&str>,
123    ) -> Option<&mut T> {
124        match self {
125            DataState::None => {}
126            DataState::AwaitingResponse(_) => {
127                ui.spinner();
128                self.poll();
129            }
130            DataState::Present(data) => {
131                return Some(data);
132            }
133            DataState::Failed(e) => {
134                ui.colored_label(ui.visuals().error_fg_color, e.to_string());
135                if ui
136                    .button(error_btn_text.unwrap_or("Clear Error Status"))
137                    .clicked()
138                {
139                    *self = DataState::default();
140                }
141            }
142        }
143        None
144    }
145
146    #[cfg(feature = "egui")]
147    /// Wraps [Self::egui_poll_mut] and returns an immutable reference
148    pub fn egui_poll(&mut self, ui: &mut egui::Ui, error_btn_text: Option<&str>) -> Option<&T> {
149        self.egui_poll_mut(ui, error_btn_text).map(|x| &*x)
150    }
151
152    /// Checks to see if the data is ready and if it is returns a new [`Self`]
153    /// otherwise None.
154    pub fn await_data(rx: &mut Awaiting<T, E>) -> Option<Self> {
155        Some(match rx.0.try_recv() {
156            Ok(recv_opt) => match recv_opt {
157                Some(outcome_result) => match outcome_result {
158                    Ok(data) => DataState::Present(data),
159                    Err(err_msg) => {
160                        warn!(?err_msg, "Error response received instead of the data");
161                        DataState::Failed(DataStateError::ErrorResponse(err_msg))
162                    }
163                },
164                None => {
165                    return None;
166                }
167            },
168            Err(e) => {
169                error!("Error receiving on channel. Sender dropped.");
170                DataState::Failed(DataStateError::SenderDropped(e))
171            }
172        })
173    }
174
175    /// Returns a reference to the inner data if available otherwise None.
176    ///
177    /// NOTE: This function does not poll to get the data ready if the state is
178    /// still awaiting
179    pub fn present(&self) -> Option<&T> {
180        if let Self::Present(data) = self {
181            Some(data)
182        } else {
183            None
184        }
185    }
186
187    /// Returns a mutable reference to the inner data if available otherwise
188    /// None
189    ///
190    /// NOTE: This function does not poll to get the data ready if the state is
191    /// still awaiting
192    pub fn present_mut(&mut self) -> Option<&mut T> {
193        if let Self::Present(data) = self {
194            Some(data)
195        } else {
196            None
197        }
198    }
199
200    /// Returns `true` if the data state is [`Present`].
201    ///
202    /// [`Present`]: DataState::Present
203    #[must_use]
204    pub fn is_present(&self) -> bool {
205        matches!(self, Self::Present(..))
206    }
207
208    /// Returns `true` if the data state is [`None`].
209    ///
210    /// [`None`]: DataState::None
211    #[must_use]
212    pub fn is_none(&self) -> bool {
213        matches!(self, Self::None)
214    }
215}
216
217impl<T, E: ErrorBounds> AsRef<DataState<T, E>> for DataState<T, E> {
218    fn as_ref(&self) -> &DataState<T, E> {
219        self
220    }
221}
222
223impl<T, E: ErrorBounds> AsMut<DataState<T, E>> for DataState<T, E> {
224    fn as_mut(&mut self) -> &mut DataState<T, E> {
225        self
226    }
227}
228
229impl<E: ErrorBounds> From<E> for DataStateError<E> {
230    fn from(value: E) -> Self {
231        Self::FromE(value)
232    }
233}
234
235impl From<&str> for DataStateError<anyhow::Error> {
236    fn from(value: &str) -> Self {
237        value.to_string().into()
238    }
239}
240
241impl From<String> for DataStateError<anyhow::Error> {
242    fn from(value: String) -> Self {
243        anyhow!(value).into()
244    }
245}
246
247impl CanMakeProgress {
248    /// Returns `true` if the can make progress is [`AbleToMakeProgress`].
249    ///
250    /// [`AbleToMakeProgress`]: CanMakeProgress::AbleToMakeProgress
251    #[must_use]
252    pub fn is_able_to_make_progress(&self) -> bool {
253        matches!(self, Self::AbleToMakeProgress)
254    }
255
256    /// Returns `true` if the can make progress is [`UnableToMakeProgress`].
257    ///
258    /// [`UnableToMakeProgress`]: CanMakeProgress::UnableToMakeProgress
259    #[must_use]
260    pub fn is_unable_to_make_progress(&self) -> bool {
261        matches!(self, Self::UnableToMakeProgress)
262    }
263}