reqwest_cross/data_state.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
//! Helpers for handling pending data.
use anyhow::anyhow;
use futures::channel::oneshot;
use std::fmt::{Debug, Display};
use thiserror::Error;
use tracing::{error, warn};
/// Provides a common way to specify the bounds errors are expected to meet
pub trait ErrorBounds: Display + Send + Sync + 'static + Debug {}
impl<T: Display + Send + Sync + 'static + Debug> ErrorBounds for T {}
#[derive(Error, Debug)]
/// Represents the types of errors that can occur while using [DataState]
pub enum DataStateError<E: ErrorBounds> {
/// Sender was dropped, request cancelled
#[error("Request sender was dropped")]
SenderDropped(oneshot::Canceled),
/// The response received from the request was an error
#[error("Response received was an error: {0}")]
ErrorResponse(E),
/// This variant is supplied for use by application code
#[error(transparent)]
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<T, E: ErrorBounds>(pub oneshot::Receiver<Result<T, E>>);
/// Used to store a type that is not always available and we need to keep
/// polling it to get it ready
#[derive(Debug, Default)]
pub enum DataState<T, E: ErrorBounds = anyhow::Error> {
/// Represent no data present and not pending
#[default]
None,
/// Represents data has been requested and awaiting it being available
AwaitingResponse(Awaiting<T, E>), // TODO 4: Add support for a timeout on waiting
/// Represents data that is available for use
Present(T),
/// Represents an error that Occurred
Failed(DataStateError<E>),
}
impl<T, E: ErrorBounds> DataState<T, E> {
#[cfg(feature = "egui")]
/// Attempts to load the data and displays appropriate UI if applicable.
/// Some branches lead to no UI being displayed, in particular when the data
/// or an error is received (On the expectation it will show next frame).
/// When in an error state the error messages will show as applicable.
/// If called an already has data present this function does nothing and
/// returns [CanMakeProgress::UnableToMakeProgress]
///
/// If a `retry_msg` is provided then it overrides the default
///
/// Note see [`Self::get`] for more info.
#[must_use]
pub fn egui_get<F>(
&mut self,
ui: &mut egui::Ui,
retry_msg: Option<&str>,
fetch_fn: F,
) -> CanMakeProgress
where
F: FnOnce() -> Awaiting<T, E>,
{
match self {
DataState::None => {
ui.spinner();
self.get(fetch_fn)
}
DataState::AwaitingResponse(_) => {
ui.spinner();
self.get(fetch_fn)
}
DataState::Present(_data) => {
// Does nothing as data is already present
CanMakeProgress::UnableToMakeProgress
}
DataState::Failed(e) => {
ui.colored_label(ui.visuals().error_fg_color, e.to_string());
if ui.button(retry_msg.unwrap_or("Retry Request")).clicked() {
*self = DataState::default();
}
CanMakeProgress::AbleToMakeProgress
}
}
}
/// Attempts to load the data and returns if it is able to make progress.
///
/// Note: F needs to return `AwaitingType<T>` and not T because it needs to
/// be able to be pending if T is not ready.
#[must_use]
pub fn get<F>(&mut self, fetch_fn: F) -> CanMakeProgress
where
F: FnOnce() -> Awaiting<T, E>,
{
match self {
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
}
}
}
/// Checks to see if the data is ready and if it is returns a new [`Self`]
/// otherwise None.
pub fn await_data(rx: &mut Awaiting<T, E>) -> Option<Self> {
Some(match rx.0.try_recv() {
Ok(recv_opt) => match recv_opt {
Some(outcome_result) => match outcome_result {
Ok(data) => DataState::Present(data),
Err(err_msg) => {
warn!(?err_msg, "Error response received instead of the data");
DataState::Failed(DataStateError::ErrorResponse(err_msg))
}
},
None => {
return None;
}
},
Err(e) => {
error!("Error receiving on channel. Sender dropped.");
DataState::Failed(DataStateError::SenderDropped(e))
}
})
}
/// Returns `true` if the data state is [`Present`].
///
/// [`Present`]: DataState::Present
#[must_use]
pub fn is_present(&self) -> bool {
matches!(self, Self::Present(..))
}
/// Returns `true` if the data state is [`None`].
///
/// [`None`]: DataState::None
#[must_use]
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
impl<T, E: ErrorBounds> AsRef<DataState<T, E>> for DataState<T, E> {
fn as_ref(&self) -> &DataState<T, E> {
self
}
}
impl<T, E: ErrorBounds> AsMut<DataState<T, E>> for DataState<T, E> {
fn as_mut(&mut self) -> &mut DataState<T, E> {
self
}
}
impl<E: ErrorBounds> From<E> for DataStateError<E> {
fn from(value: E) -> Self {
Self::FromE(value)
}
}
impl From<&str> for DataStateError<anyhow::Error> {
fn from(value: &str) -> Self {
value.to_string().into()
}
}
impl From<String> for DataStateError<anyhow::Error> {
fn from(value: String) -> Self {
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)
}
}