use stilltypes::prelude::*;
use stillwater::effect::prelude::*;
use stillwater::validation::{ValidateAll, Validation};
#[derive(Clone)]
struct AppEnv {
db_connected: bool,
}
#[derive(Debug)]
struct UserId(#[allow(dead_code)] u64);
#[derive(Debug)]
enum AppError {
Validation(Vec<DomainError>),
Database(String),
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AppError::Validation(errors) => {
write!(f, "Validation errors: ")?;
for (i, e) in errors.iter().enumerate() {
if i > 0 {
write!(f, "; ")?;
}
write!(f, "{}", e)?;
}
Ok(())
}
AppError::Database(msg) => write!(f, "Database error: {}", msg),
}
}
}
fn create_user_in_db(
email: Email,
phone: PhoneNumber,
) -> impl Effect<Output = UserId, Error = AppError, Env = AppEnv> {
asks(move |env: &AppEnv| {
if env.db_connected {
println!(" [DB] Creating user with email: {}", email.get());
println!(" [DB] Normalized phone: {}", phone.to_e164());
Ok::<UserId, AppError>(UserId(12345))
} else {
Err(AppError::Database("Not connected".to_string()))
}
})
.and_then(from_result)
}
fn register_user(
email_input: String,
phone_input: String,
) -> impl Effect<Output = UserId, Error = AppError, Env = AppEnv> {
let email_v: Validation<Email, Vec<DomainError>> =
Validation::from_result(Email::new(email_input).map_err(|e| vec![e]));
let phone_v: Validation<PhoneNumber, Vec<DomainError>> =
Validation::from_result(PhoneNumber::new(phone_input).map_err(|e| vec![e]));
let validated = (email_v, phone_v).validate_all();
from_validation::<_, _, AppEnv>(validated)
.map_err(AppError::Validation)
.and_then(|(email, phone)| create_user_in_db(email, phone))
}
#[tokio::main]
async fn main() {
println!("Stilltypes API Handler Example");
println!("=============================\n");
let env = AppEnv { db_connected: true };
println!("=== Valid Registration ===");
let effect = register_user("user@example.com".into(), "+14155551234".into());
match effect.execute(&env).await {
Ok(user_id) => println!("Created user: {:?}\n", user_id),
Err(e) => println!("Failed: {}\n", e),
}
println!("=== Invalid Registration (multiple errors) ===");
let effect = register_user("bad-email".into(), "bad-phone".into());
match effect.execute(&env).await {
Ok(user_id) => println!("Created user: {:?}\n", user_id),
Err(e) => println!("Failed: {}\n", e),
}
println!("=== Database Error ===");
let disconnected_env = AppEnv {
db_connected: false,
};
let effect = register_user("user@example.com".into(), "+14155551234".into());
match effect.execute(&disconnected_env).await {
Ok(user_id) => println!("Created user: {:?}\n", user_id),
Err(e) => println!("Failed: {}\n", e),
}
println!("=== Effect Composition Demo ===");
println!("Effects are lazy - nothing happens until execute() is called.");
println!("This allows building complex pipelines before running them.");
let _complex_effect = register_user("test@example.com".into(), "+442071234567".into());
println!("Effect built but not executed yet!");
println!("\nKey concepts demonstrated:");
println!(" 1. Validation::from_result() converts Result to Validation");
println!(" 2. validate_all() accumulates all errors");
println!(" 3. from_validation() bridges to Effect");
println!(" 4. and_then() chains I/O operations");
println!(" 5. Environment (AppEnv) provides dependencies");
println!(" 6. Effects are lazy and composable");
}