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)
    }
}