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