Files
dynatos/dynatos-loadable/src/loadable.rs

343 lines
7.7 KiB
Rust

//! Loadable value
// Imports
use std::{
convert::Infallible,
ops::{ControlFlow, FromResidual, Try},
};
/// Loadable value.
#[derive(Clone, Copy, Debug)]
pub enum Loadable<T, E> {
/// Empty
Empty,
/// Failed to load
Err(E),
/// Loaded
Loaded(T),
}
impl<T, E> Loadable<T, E> {
/// Creates a loadable from a result
pub fn from_res<E2>(res: Result<T, E2>) -> Self
where
E: From<E2>,
{
match res {
Ok(value) => Self::Loaded(value),
Err(err) => Self::Err(err.into()),
}
}
/// Returns if the loadable is empty.
#[must_use]
pub fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
/// Returns if the loadable is loaded.
///
/// This means it's either an error or a value
#[must_use]
pub fn is_loaded(&self) -> bool {
!self.is_empty()
}
/// Returns this loadable's value by reference.
pub fn as_ref(&self) -> Loadable<&T, E>
where
E: Clone,
{
match self {
Self::Empty => Loadable::Empty,
Self::Err(err) => Loadable::Err(err.clone()),
Self::Loaded(value) => Loadable::Loaded(value),
}
}
/// Returns this loadable's value by mutable reference
pub fn as_mut(&mut self) -> Loadable<&mut T, E>
where
E: Clone,
{
match self {
Self::Empty => Loadable::Empty,
Self::Err(err) => Loadable::Err(err.clone()),
Self::Loaded(value) => Loadable::Loaded(value),
}
}
/// Maps this loadable's value
pub fn map<U, F>(self, f: F) -> Loadable<U, E>
where
F: FnOnce(T) -> U,
{
match self {
Self::Empty => Loadable::Empty,
Self::Err(err) => Loadable::Err(err),
Self::Loaded(value) => Loadable::Loaded(f(value)),
}
}
/// Zips two loadable.
///
/// If is empty, the result will be empty.
/// If any is errored, the result will be an error.
pub fn zip<U>(self, rhs: Loadable<U, E>) -> Loadable<(T, U), E> {
match (self, rhs) {
// If there's an error, propagate
(Self::Err(err), _) | (_, Loadable::Err(err)) => Loadable::Err(err),
// Otherwise, if we have both values, return loaded
(Self::Loaded(lhs), Loadable::Loaded(rhs)) => Loadable::Loaded((lhs, rhs)),
// Otherwise, we're empty
_ => Loadable::Empty,
}
}
/// Chains this loadable with another if it's loaded
///
/// If any operation returns empty or error, it will be propagated
pub fn and_then<U, F>(self, f: F) -> Loadable<U, E>
where
F: FnOnce(T) -> Loadable<U, E>,
{
match self {
Self::Empty => Loadable::Empty,
Self::Err(err) => Loadable::Err(err),
Self::Loaded(value) => f(value),
}
}
/// Converts this to an option.
///
/// Maps `Loadable::Loaded` to `Some` and the rest to `None`.
pub fn loaded(self) -> Option<T> {
match self {
Self::Empty => None,
Self::Err(_err) => None,
Self::Loaded(value) => Some(value),
}
}
}
impl<T, E> Loadable<&T, E> {
/// Clones the inner value
pub fn cloned(self) -> Loadable<T, E>
where
T: Clone,
{
self.map(T::clone)
}
}
impl<T, E> From<T> for Loadable<T, E> {
fn from(value: T) -> Self {
Self::Loaded(value)
}
}
impl<T, E> Try for Loadable<T, E> {
type Output = T;
type Residual = Loadable<!, E>;
fn from_output(output: Self::Output) -> Self {
Self::Loaded(output)
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
Self::Empty => ControlFlow::Break(Loadable::Empty),
Self::Err(err) => ControlFlow::Break(Loadable::Err(err)),
Self::Loaded(value) => ControlFlow::Continue(value),
}
}
}
impl<T, E, E2> FromResidual<Loadable<!, E2>> for Loadable<T, E>
where
E: From<E2>,
{
fn from_residual(residual: Loadable<!, E2>) -> Self {
match residual {
Loadable::Empty => Self::Empty,
Loadable::Err(err) => Self::Err(err.into()),
Loadable::Loaded(never) => never,
}
}
}
impl<T, E, E2> FromResidual<Result<Infallible, E2>> for Loadable<T, E>
where
E: From<E2>,
{
fn from_residual(residual: Result<Infallible, E2>) -> Self {
match residual {
Ok(never) => match never {},
Err(err) => Self::Err(err.into()),
}
}
}
/// Collects an iterator of `Loadable<T, E>` into a `Loadable<C, E>`,
/// where `C` is a collection of `T`s.
///
/// If any empty, or error loadables are found, this immediately short-circuits
/// and returns them
impl<C, T, E> FromIterator<Loadable<T, E>> for Loadable<C, E>
where
C: Default + Extend<T>,
{
fn from_iter<I: IntoIterator<Item = Loadable<T, E>>>(iter: I) -> Self {
let mut collection = C::default();
for item in iter.into_iter() {
// If we find any empty, or errors, return them immediately
let item = match item {
Loadable::Empty => return Self::Empty,
Loadable::Err(err) => return Self::Err(err),
Loadable::Loaded(value) => value,
};
collection.extend_one(item);
}
Self::Loaded(collection)
}
}
/// Extension trait for iterators of `Loadable<T, E>`
#[extend::ext(name = IteratorLoadableExt)]
pub impl<I, T, E> I
where
I: Iterator<Item = Loadable<T, E>>,
{
/// Flattens an iterator of `Loadable<T, E>` to `Loadable<T::Item, E>`, where `T: IntoIterator`
fn flatten_loaded(self) -> FlattenLoaded<I, T, E>
where
T: IntoIterator,
{
FlattenLoaded {
inner: self,
value_it: None,
}
}
/// Finds the position of a value in an iterator of `Loadable<T, E>`.
fn position_loaded<F>(self, mut pred: F) -> Loadable<Option<usize>, E>
where
F: FnMut(T) -> bool,
{
for (item_idx, item) in self.enumerate() {
let item = item?;
if pred(item) {
return Loadable::Loaded(Some(item_idx));
}
}
Loadable::Loaded(None)
}
/// [`Iterator::scan`]-like adaptor
fn scan_loaded<St, B, F>(self, init: St, f: F) -> ScanLoaded<I, St, F>
where
F: FnMut(&mut St, T) -> Option<B>,
{
ScanLoaded {
inner: self,
f,
state: init,
}
}
}
/// Iterator returned by [`IteratorLoadableExt::flatten_loaded`]
// TODO: Impl `Clone, Copy, Debug` with the correct bounds.
#[derive(Clone, Copy, Debug)]
pub struct FlattenLoaded<I, T, E>
where
I: Iterator<Item = Loadable<T, E>>,
T: IntoIterator,
{
/// Inner iterator
inner: I,
/// Current value iterator
value_it: Option<T::IntoIter>,
}
impl<I, T, E> Iterator for FlattenLoaded<I, T, E>
where
I: Iterator<Item = Loadable<T, E>>,
T: IntoIterator,
{
type Item = Loadable<T::Item, E>;
fn next(&mut self) -> Option<Self::Item> {
// Loop until we find the next value
loop {
// If we have a value iterator, try to yield it first
if let Some(it) = &mut self.value_it {
match it.next() {
// If there was still a value, yield it
Some(value) => return Some(Loadable::Loaded(value)),
// Otherwise, get rid of the iterator
None => self.value_it = None,
}
}
// If the inner value didn't have anything, try to get the next value
match self.inner.next()? {
// If empty, or error, return them
Loadable::Empty => return Some(Loadable::Empty),
Loadable::Err(err) => return Some(Loadable::Err(err)),
// On loaded, set the value iterator and try to extract it again
Loadable::Loaded(iter) => self.value_it = Some(iter.into_iter()),
}
}
}
}
/// Iterator returned by [`IteratorLoadableExt::scan_loaded`]
#[derive(Clone, Copy, Debug)]
pub struct ScanLoaded<I, St, F> {
/// Inner iterator
inner: I,
/// Function
f: F,
/// State
state: St,
}
impl<I, T, E, St, B, F> Iterator for ScanLoaded<I, St, F>
where
I: Iterator<Item = Loadable<T, E>>,
F: FnMut(&mut St, T) -> Option<B>,
{
type Item = Loadable<B, E>;
fn next(&mut self) -> Option<Self::Item> {
let value = match self.inner.next()? {
Loadable::Empty => return Some(Loadable::Empty),
Loadable::Err(err) => return Some(Loadable::Err(err)),
Loadable::Loaded(value) => value,
};
(self.f)(&mut self.state, value).map(Loadable::Loaded)
}
}
/// Extension trait to create a [`Loadable::Loaded`] from a value.
#[extend::ext(name = IntoLoaded)]
pub impl<T> T {
/// Converts this `T` value into a loaded `Loadable<T>`
fn into_loaded<E>(self) -> Loadable<T, E> {
Loadable::Loaded(self)
}
}