use anyhow::{Context as AnyhowContext, Result};
use std::fmt::Display;
pub trait ResultExt<T> {
fn and_then_async<F, U>(self, f: F) -> Result<U>
where
F: FnOnce(T) -> Result<U>;
fn or_else_with<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> Result<T>;
fn map_err_context<C>(self, context: C) -> Result<T>
where
C: Display + Send + Sync + 'static;
fn map_ok<F, U>(self, f: F) -> Result<U>
where
F: FnOnce(T) -> U;
fn tap<F>(self, f: F) -> Result<T>
where
F: FnOnce(&T);
fn tap_err<F>(self, f: F) -> Result<T>
where
F: FnOnce(&anyhow::Error);
}
impl<T> ResultExt<T> for Result<T> {
fn and_then_async<F, U>(self, f: F) -> Result<U>
where
F: FnOnce(T) -> Result<U>,
{
self.and_then(f)
}
fn or_else_with<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> Result<T>,
{
self.or_else(|_| f())
}
fn map_err_context<C>(self, context: C) -> Result<T>
where
C: Display + Send + Sync + 'static,
{
self.context(context)
}
fn map_ok<F, U>(self, f: F) -> Result<U>
where
F: FnOnce(T) -> U,
{
self.map(f)
}
fn tap<F>(self, f: F) -> Result<T>
where
F: FnOnce(&T),
{
if let Ok(ref value) = self {
f(value);
}
self
}
fn tap_err<F>(self, f: F) -> Result<T>
where
F: FnOnce(&anyhow::Error),
{
if let Err(ref e) = self {
f(e);
}
self
}
}
pub trait OptionExt<T> {
fn ok_or_error<E>(self, error: E) -> Result<T>
where
E: Into<anyhow::Error>;
fn and_then_some<F, U>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> Option<U>;
fn or_else_some<F>(self, f: F) -> Option<T>
where
F: FnOnce() -> Option<T>;
fn filter_some<F>(self, predicate: F) -> Option<T>
where
F: FnOnce(&T) -> bool;
}
impl<T> OptionExt<T> for Option<T> {
fn ok_or_error<E>(self, error: E) -> Result<T>
where
E: Into<anyhow::Error>,
{
self.ok_or_else(|| error.into())
}
fn and_then_some<F, U>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> Option<U>,
{
self.and_then(f)
}
fn or_else_some<F>(self, f: F) -> Option<T>
where
F: FnOnce() -> Option<T>,
{
self.or_else(f)
}
fn filter_some<F>(self, predicate: F) -> Option<T>
where
F: FnOnce(&T) -> bool,
{
self.filter(predicate)
}
}
pub struct Applicative<T> {
values: Vec<T>,
}
impl<T> Applicative<T> {
pub fn new(values: Vec<T>) -> Self {
Self { values }
}
pub fn apply<F, U>(self, f: F) -> Applicative<U>
where
F: Fn(T) -> U,
{
Applicative::new(self.values.into_iter().map(f).collect())
}
pub fn apply_result<F, U>(self, f: F) -> Result<Applicative<U>>
where
F: Fn(T) -> Result<U>,
{
let results: Result<Vec<U>> = self.values.into_iter().map(f).collect();
results.map(Applicative::new)
}
pub fn unwrap(self) -> Vec<T> {
self.values
}
}
pub fn compose_results<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> Result<C>
where
F: Fn(A) -> Result<B>,
G: Fn(B) -> Result<C>,
{
move |a| f(a).and_then(&g)
}
pub fn lift_result<T, U, F>(f: F) -> impl Fn(T) -> Result<U>
where
F: Fn(T) -> U,
{
move |t| Ok(f(t))
}
pub fn sequence_results<T>(results: Vec<Result<T>>) -> Result<Vec<T>> {
results.into_iter().collect()
}
pub fn traverse_results<T, U, F>(values: Vec<T>, f: F) -> Result<Vec<U>>
where
F: Fn(T) -> Result<U>,
{
values.into_iter().map(f).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
#[test]
fn test_result_ext() {
let result: Result<i32> = Ok(42);
let chained = result
.map_ok(|x| x * 2)
.and_then_async(|x| Ok(x + 1))
.tap(|x| println!("Value: {x}"));
assert_eq!(chained.unwrap(), 85);
}
#[test]
fn test_option_ext() {
let value = Some(42);
let result = value
.and_then_some(|x| Some(x * 2))
.filter_some(|&x| x > 50)
.ok_or_error(anyhow!("Value too small"));
assert!(result.is_ok());
assert_eq!(result.unwrap(), 84);
}
#[test]
fn test_kleisli_composition() {
let add_one = |x: i32| -> Result<i32> { Ok(x + 1) };
let double = |x: i32| -> Result<i32> { Ok(x * 2) };
let composed = compose_results(add_one, double);
assert_eq!(composed(5).unwrap(), 12); }
#[test]
fn test_sequence() {
let results = vec![Ok(1), Ok(2), Ok(3)];
let sequenced = sequence_results(results);
assert_eq!(sequenced.unwrap(), vec![1, 2, 3]);
let with_error = vec![Ok(1), Err(anyhow!("error")), Ok(3)];
let sequenced_err = sequence_results(with_error);
assert!(sequenced_err.is_err());
}
}