use monadify::identity::{Identity, IdentityKind};
use monadify::mdo;
use monadify::transformers::except::{Except, ExceptTKind};
#[derive(Clone, PartialEq, Debug)]
enum ValidationError {
EmptyUsername,
PasswordTooShort,
InvalidEmail,
}
#[derive(Debug, Clone, PartialEq)]
struct User {
username: String,
email: String,
}
type Checked<A> = Except<ValidationError, A>;
type CKind = ExceptTKind<ValidationError, IdentityKind>;
fn check_username(username: String) -> Checked<String> {
if username.is_empty() {
Checked::throw(ValidationError::EmptyUsername)
} else {
Checked::ok(username)
}
}
fn check_password(username: String, password: String, email: String) -> Checked<(String, String)> {
if password.len() < 8 {
Checked::throw(ValidationError::PasswordTooShort)
} else {
Checked::ok((username, email))
}
}
fn check_email(username: String, email: String) -> Checked<User> {
if !email.contains('@') {
Checked::throw(ValidationError::InvalidEmail)
} else {
Checked::ok(User { username, email })
}
}
fn validate_form(username: String, password: String, email: String) -> Checked<User> {
mdo! {
CKind;
un <- check_username(username);
(un2, em) <- check_password(un, password.clone(), email.clone());
user <- check_email(un2, em);
pure(user)
}
}
fn run(prog: Checked<User>) -> Result<User, ValidationError> {
let Identity(res) = prog.run_except_t;
res
}
fn main() {
println!("=== Except-monad: User-Registration Form Validation ===\n");
println!("Test 1: valid form — expects Ok(User)");
let result = run(validate_form(
"alice".to_string(),
"supersecret".to_string(),
"alice@example.com".to_string(),
));
assert_eq!(
result,
Ok(User {
username: "alice".to_string(),
email: "alice@example.com".to_string(),
}),
"valid form must produce Ok(User)"
);
println!(" Ok(User {{ username: \"alice\", email: \"alice@example.com\" }})");
println!(" PASSED\n");
println!("Test 2: empty username — expects Err(EmptyUsername)");
let result = run(validate_form(
"".to_string(),
"supersecret".to_string(),
"alice@example.com".to_string(),
));
assert_eq!(
result,
Err(ValidationError::EmptyUsername),
"empty username must short-circuit with EmptyUsername"
);
println!(" Err(EmptyUsername) -- password and email checks never ran");
println!(" PASSED\n");
println!("Test 3: valid username, short password — expects Err(PasswordTooShort)");
let result = run(validate_form(
"alice".to_string(),
"abc".to_string(),
"alice@example.com".to_string(),
));
assert_eq!(
result,
Err(ValidationError::PasswordTooShort),
"short password must short-circuit with PasswordTooShort"
);
println!(" Err(PasswordTooShort) -- email check never ran");
println!(" PASSED\n");
println!("Test 4: valid username and password, bad email — expects Err(InvalidEmail)");
let result = run(validate_form(
"alice".to_string(),
"supersecret".to_string(),
"notanemail".to_string(),
));
assert_eq!(
result,
Err(ValidationError::InvalidEmail),
"invalid email must short-circuit with InvalidEmail"
);
println!(" Err(InvalidEmail)");
println!(" PASSED\n");
println!("=== All assertions passed! ===\n");
println!("Key insight: `mdo!` with `Except<ValidationError, _>` sequences validation:");
println!(" * `Checked::throw(e)` injects an error; all later `bind` steps are skipped.");
println!(" * `Checked::ok(x)` lifts a success value into the Except monad.");
println!(" * `ExceptT::bind` propagates `Err` without running the continuation.");
println!(" * `run_except_t` is a VALUE field; unwrap via `let Identity(res) = prog.run_except_t`.");
println!(" * Each step carries forward the data the next step needs as its bind-result");
println!(" parameter, keeping all generated `move` closures `FnMut + Clone`.");
}