Added dynatos-loadable.

Added `dynatos_loadable::Loadable`.
This commit is contained in:
Filipe Rodrigues 2024-02-20 12:42:43 +00:00
parent 24088b11f3
commit 1e7085a4a1
Signed by: zenithsiz
SSH Key Fingerprint: SHA256:Mb5ppb3Sh7IarBO/sBTXLHbYEOz37hJAlslLQPPAPaU
5 changed files with 246 additions and 0 deletions

15
Cargo.lock generated
View File

@ -64,6 +64,21 @@ dependencies = [
"web-sys",
]
[[package]]
name = "dynatos-loadable"
version = "0.1.0"
dependencies = [
"anyhow",
"duplicate",
"dynatos-reactive",
"dynatos-util",
"extend",
"js-sys",
"tracing",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "dynatos-reactive"
version = "0.1.0"

View File

@ -4,6 +4,7 @@ members = [
"dynatos",
"dynatos-context",
"dynatos-html",
"dynatos-loadable",
"dynatos-reactive",
"dynatos-router",
"dynatos-title",
@ -17,6 +18,7 @@ resolver = "2"
dynatos = { path = "dynatos" }
dynatos-context = { path = "dynatos-context" }
dynatos-html = { path = "dynatos-html" }
dynatos-loadable = { path = "dynatos-loadable" }
dynatos-reactive = { path = "dynatos-reactive" }
dynatos-router = { path = "dynatos-router" }
dynatos-title = { path = "dynatos-title" }

View File

@ -0,0 +1,17 @@
[package]
name = "dynatos-loadable"
version = "0.1.0"
edition = "2021"
[dependencies]
dynatos-reactive = { workspace = true }
dynatos-util = { workspace = true }
anyhow = { workspace = true }
duplicate = { workspace = true }
extend = { workspace = true }
js-sys = { workspace = true }
tracing = { workspace = true }
wasm-bindgen = { workspace = true }
web-sys = { workspace = true }

View File

@ -0,0 +1,10 @@
//! Loadable values for [`dynatos`]
// Features
#![feature(try_trait_v2, lint_reasons, never_type)]
// Modules
mod loadable;
// Exports
pub use loadable::{IntoLoaded, Loadable};

View File

@ -0,0 +1,202 @@
//! Loadable value
// Imports
use std::{
convert::Infallible,
ops::{ControlFlow, FromResidual, Try},
};
/// Loadable value.
#[derive(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: Clone, E: Clone> Clone for Loadable<T, E> {
fn clone(&self) -> Self {
match self {
Self::Empty => Self::Empty,
Self::Err(err) => Self::Err(err.clone()),
Self::Loaded(value) => Self::Loaded(value.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()),
}
}
}
/// 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)
}
}