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 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
//! 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>>);
impl<T, E: ErrorBounds> From<oneshot::Receiver<Result<T, E>>> for Awaiting<T, E> {
fn from(value: oneshot::Receiver<Result<T, E>>) -> Self {
Self(value)
}
}
/// 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")]
/// Calls [Self::start_request] and adds a spinner if progress can be made
pub fn egui_start_request<F, R>(&mut self, ui: &mut egui::Ui, fetch_fn: F) -> CanMakeProgress
where
F: FnOnce() -> R,
R: Into<Awaiting<T, E>>,
{
let result = self.start_request(fetch_fn);
if result.is_able_to_make_progress() {
ui.spinner();
}
result
}
/// Starts a new request. Only intended to be on [Self::None] and if state
/// is any other value it returns [CanMakeProgress::UnableToMakeProgress]
#[must_use]
pub fn start_request<F, R>(&mut self, fetch_fn: F) -> CanMakeProgress
where
F: FnOnce() -> R,
R: Into<Awaiting<T, E>>,
{
if self.is_none() {
*self = DataState::AwaitingResponse(fetch_fn().into());
CanMakeProgress::AbleToMakeProgress
} else {
debug_assert!(
false,
"No known good reason this path should be hit other than logic error"
);
CanMakeProgress::UnableToMakeProgress
}
}
/// Convenience method that will try to make progress if in
/// [Self::AwaitingResponse] and does nothing otherwise. Returns a reference
/// to self for chaining
pub fn poll(&mut self) -> &mut Self {
if let DataState::AwaitingResponse(rx) = self {
if let Some(new_state) = Self::await_data(rx) {
*self = new_state;
}
}
self
}
#[cfg(feature = "egui")]
/// Meant to be a simple method to just provide the data if it's ready or
/// help with UI and polling to get it ready if it's not.
///
/// WARNING: Does nothing if `self` is [Self::None]
///
/// If a `error_btn_text` is provided then it overrides the default
pub fn egui_poll_mut(
&mut self,
ui: &mut egui::Ui,
error_btn_text: Option<&str>,
) -> Option<&mut T> {
match self {
DataState::None => {}
DataState::AwaitingResponse(_) => {
ui.spinner();
self.poll();
}
DataState::Present(data) => {
return Some(data);
}
DataState::Failed(e) => {
ui.colored_label(ui.visuals().error_fg_color, e.to_string());
if ui
.button(error_btn_text.unwrap_or("Clear Error Status"))
.clicked()
{
*self = DataState::default();
}
}
}
None
}
#[cfg(feature = "egui")]
/// Wraps [Self::egui_poll_mut] and returns an immutable reference
pub fn egui_poll(&mut self, ui: &mut egui::Ui, error_btn_text: Option<&str>) -> Option<&T> {
self.egui_poll_mut(ui, error_btn_text).map(|x| &*x)
}
/// 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 a reference to the inner data if available otherwise None.
///
/// NOTE: This function does not poll to get the data ready if the state is
/// still awaiting
pub fn present(&self) -> Option<&T> {
if let Self::Present(data) = self {
Some(data)
} else {
None
}
}
/// Returns a mutable reference to the inner data if available otherwise
/// None
///
/// NOTE: This function does not poll to get the data ready if the state is
/// still awaiting
pub fn present_mut(&mut self) -> Option<&mut T> {
if let Self::Present(data) = self {
Some(data)
} else {
None
}
}
/// 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)
}
}