use pollster::block_on;
use raskell::hdo;
#[derive(Debug, PartialEq)]
enum StoreError {
Missing,
}
#[derive(Debug, PartialEq)]
enum ApiError {
Store,
TooSmall,
}
impl From<StoreError> for ApiError {
fn from(_: StoreError) -> Self {
ApiError::Store
}
}
fn double(value: i32) -> Result<i32, ApiError> {
Ok(value * 2)
}
fn missing() -> Result<i32, ApiError> {
Err(ApiError::TooSmall)
}
async fn load(value: i32) -> Result<i32, StoreError> {
if value > 0 {
Ok(value)
} else {
Err(StoreError::Missing)
}
}
async fn double_async(value: i32) -> Result<i32, ApiError> {
double(value)
}
#[test]
fn option_tail() {
let result: Option<i32> = hdo! {
a <- Some(10);
Some(a + 5)
};
assert_eq!(result, Some(15));
}
#[test]
fn option_tail_can_fail() {
let result: Option<i32> = hdo! {
a <- Some(10);
u32::try_from(a - 100).ok().map(|value| value as i32)
};
assert_eq!(result, None);
}
#[test]
fn option_tail_short_circuits_before_running() {
let result: Option<i32> = hdo! {
a <- None::<i32>;
Some(a + 5)
};
assert_eq!(result, None);
}
#[test]
fn result_tail() {
let result: Result<i32, ApiError> = hdo! {
a <- double(3);
double(a)
};
assert_eq!(result, Ok(12));
}
#[test]
fn result_tail_propagates_its_error() {
let result: Result<i32, ApiError> = hdo! {
a <- double(3);
let _ = a;
missing()
};
assert_eq!(result, Err(ApiError::TooSmall));
}
#[test]
fn list_tail() {
let result: Vec<i32> = hdo! {
a <- vec![1, 2];
vec![a, a * 10]
};
assert_eq!(result, vec![1, 10, 2, 20]);
}
#[test]
fn list_tail_can_be_empty() {
let result: Vec<i32> = hdo! {
a <- vec![1, 2, 3];
if a % 2 == 0 { vec![a] } else { Vec::new() }
};
assert_eq!(result, vec![2]);
}
#[test]
fn tail_follows_every_other_statement() {
let result: Result<i32, ApiError> = hdo! {
a <- double(4);
let b = a + 1;
guard b > 5 throw ApiError::TooSmall;
double(3);
double(b)
};
assert_eq!(result, Ok(18));
}
#[test]
fn tail_after_a_refutable_bind() {
let result: Option<i32> = hdo! {
Some(a) <- Some(Some(10));
Some(a * 2)
};
assert_eq!(result, Some(20));
}
#[test]
fn tail_after_a_typed_bind() {
let result: Option<i32> = hdo! {
a: i32 <- Some(10);
Some(a * 2)
};
assert_eq!(result, Some(20));
}
#[test]
fn tail_after_a_try_bind() {
let result: Result<i32, ApiError> = hdo! {
a <-? Err::<i32, StoreError>(StoreError::Missing);
double(a)
};
assert_eq!(result, Err(ApiError::Store));
}
#[test]
fn pure_still_works() {
let result: Option<i32> = hdo! {
a <- Some(10);
b <- Some(20);
pure a + b
};
assert_eq!(result, Some(30));
}
#[test]
fn pure_with_a_trailing_semicolon_still_works() {
let result: Option<i32> = hdo! {
a <- Some(10);
pure a * 2;
};
assert_eq!(result, Some(20));
}
#[test]
fn async_tail() {
let result: Option<i32> = block_on(hdo!(async {
a <- Some(10);
Some(a + 5)
}));
assert_eq!(result, Some(15));
}
#[test]
fn async_tail_awaits() {
let result: Result<i32, ApiError> = block_on(hdo!(async {
id <-? load(21).await;
double_async(id).await
}));
assert_eq!(result, Ok(42));
}
#[test]
fn async_tail_propagates_its_error() {
let result: Result<i32, ApiError> = block_on(hdo!(async {
id <-? load(1).await;
double_async(id).await;
missing()
}));
assert_eq!(result, Err(ApiError::TooSmall));
}
#[test]
fn async_short_circuits_before_the_tail() {
let result: Result<i32, ApiError> = block_on(hdo!(async {
id <-? load(-1).await;
double_async(id).await
}));
assert_eq!(result, Err(ApiError::Store));
}
#[test]
fn async_pure_still_works() {
let result: Result<i32, ApiError> = block_on(hdo!(async {
id <-? load(20).await;
guard id > 5 throw ApiError::TooSmall;
pure id + 1
}));
assert_eq!(result, Ok(21));
}