use monadify::mdo;
use monadify::transformers::except::{Except, ExceptT, ExceptTKind};
use monadify::{Identity, IdentityKind};
use std::num::ParseIntError;
#[derive(Clone, PartialEq, Debug)]
enum ConfigError {
BadPort(String),
BadRetries(String),
}
#[derive(Clone, PartialEq, Debug)]
struct Config {
port: u16,
retries: u32,
}
type Checked<A> = Except<ConfigError, A>;
type CKind = ExceptTKind<ConfigError, IdentityKind>;
fn load(port_s: &str, retries_s: &str) -> Checked<Config> {
let port_raw: ExceptT<ParseIntError, IdentityKind, u16> =
ExceptT::from_result(port_s.parse::<u16>());
let port_c: Checked<u16> = port_raw.with_except_t(|e| ConfigError::BadPort(e.to_string()));
let retries_raw: ExceptT<ParseIntError, IdentityKind, u32> =
ExceptT::from_result(retries_s.parse::<u32>());
let retries_c: Checked<u32> =
retries_raw.with_except_t(|e| ConfigError::BadRetries(e.to_string()));
mdo! {
CKind;
port <- port_c;
retries <- retries_c;
pure(Config { port, retries })
}
}
fn main() {
println!("=== ExceptT Config Loader: unifying parse errors via with_except_t ===\n");
let Identity(res1) = load("8080", "3").run_except_t;
assert_eq!(
res1,
Ok(Config {
port: 8080,
retries: 3
}),
"Test 1 failed"
);
println!("load(\"8080\", \"3\") => {:?} PASSED", res1);
let Identity(res2) = load("notaport", "3").run_except_t;
assert!(
matches!(res2, Err(ConfigError::BadPort(_))),
"Test 2 failed: expected Err(BadPort(_)), got {:?}",
res2
);
println!("load(\"notaport\", \"3\") => {:?} PASSED", res2);
let Identity(res3) = load("8080", "xx").run_except_t;
assert!(
matches!(res3, Err(ConfigError::BadRetries(_))),
"Test 3 failed: expected Err(BadRetries(_)), got {:?}",
res3
);
println!("load(\"8080\", \"xx\") => {:?} PASSED", res3);
let Identity(res4) = load("99999", "3").run_except_t;
assert!(
matches!(res4, Err(ConfigError::BadPort(_))),
"Test 4 failed: expected Err(BadPort(_)), got {:?}",
res4
);
println!("load(\"99999\", \"3\") => {:?} PASSED", res4);
println!("\n=== All assertions passed! ===");
println!("\nKey insight:");
println!(
" * ExceptT::from_result wraps a Result into ExceptT<ParseIntError, IdentityKind, _>."
);
println!(" * with_except_t remaps the error channel: ParseIntError -> ConfigError.");
println!(" * mdo! sequences Checked<_> steps, short-circuiting on Err.");
println!(" * run_except_t is a field exposing Identity<Result<Config, ConfigError>>.");
}