use domainstack::typestate::{Unvalidated, Validated};
use domainstack::{rules, validate, ValidationError};
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct Email<State = Unvalidated> {
value: String,
_state: PhantomData<State>,
}
impl Email<Unvalidated> {
pub fn new(value: String) -> Self {
Self {
value,
_state: PhantomData,
}
}
#[allow(clippy::result_large_err)]
pub fn validate(self) -> Result<Email<Validated>, ValidationError> {
validate("email", self.value.as_str(), &rules::email())?;
Ok(Email {
value: self.value,
_state: PhantomData,
})
}
}
impl<State> Email<State> {
pub fn as_str(&self) -> &str {
&self.value
}
pub fn len(&self) -> usize {
self.value.len()
}
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
}
#[derive(Debug)]
pub struct UserRegistration<State = Unvalidated> {
username: String,
email: String,
password: String,
age: u8,
_state: PhantomData<State>,
}
impl UserRegistration<Unvalidated> {
pub fn builder() -> UserRegistrationBuilder {
UserRegistrationBuilder::default()
}
#[allow(clippy::result_large_err)]
pub fn validate(self) -> Result<UserRegistration<Validated>, ValidationError> {
let mut errors = ValidationError::default();
if let Err(e) = validate(
"username",
self.username.as_str(),
&rules::min_len(3).and(rules::max_len(20)),
) {
errors.extend(e);
}
if let Err(e) = validate("email", self.email.as_str(), &rules::email()) {
errors.extend(e);
}
if let Err(e) = validate("password", self.password.as_str(), &rules::min_len(8)) {
errors.extend(e);
}
if let Err(e) = validate("age", &self.age, &rules::range(13, 120)) {
errors.extend(e);
}
if errors.is_empty() {
Ok(UserRegistration {
username: self.username,
email: self.email,
password: self.password,
age: self.age,
_state: PhantomData,
})
} else {
Err(errors)
}
}
}
impl<State> UserRegistration<State> {
pub fn username(&self) -> &str {
&self.username
}
pub fn email(&self) -> &str {
&self.email
}
pub fn age(&self) -> u8 {
self.age
}
}
#[derive(Default)]
pub struct UserRegistrationBuilder {
username: Option<String>,
email: Option<String>,
password: Option<String>,
age: Option<u8>,
}
impl UserRegistrationBuilder {
pub fn username(mut self, username: impl Into<String>) -> Self {
self.username = Some(username.into());
self
}
pub fn email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}
pub fn password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
pub fn age(mut self, age: u8) -> Self {
self.age = Some(age);
self
}
pub fn build(self) -> UserRegistration<Unvalidated> {
UserRegistration {
username: self.username.unwrap_or_default(),
email: self.email.unwrap_or_default(),
password: self.password.unwrap_or_default(),
age: self.age.unwrap_or(0),
_state: PhantomData,
}
}
}
fn save_to_database(user: &UserRegistration<Validated>) -> Result<i64, String> {
println!("💾 Saving to database:");
println!(" Username: {}", user.username());
println!(" Email: {}", user.email());
println!(" Age: {}", user.age());
let user_id = 12345; println!(" [ok] User saved with ID: {}\n", user_id);
Ok(user_id)
}
fn send_welcome_email(email: &Email<Validated>) {
println!("📧 Sending welcome email to: {}", email.as_str());
println!(" [ok] Email sent successfully\n");
}
fn complete_registration(user: UserRegistration<Validated>) -> Result<(), String> {
println!("🎉 Completing registration for: {}", user.username());
let user_id = save_to_database(&user)?;
let email = Email::new(user.email().to_string())
.validate()
.map_err(|e| format!("Email validation failed: {}", e))?;
send_welcome_email(&email);
println!("[ok] Registration complete! User ID: {}\n", user_id);
Ok(())
}
fn main() {
println!("=== Phantom Types Validation Example ===\n");
println!("Example 1: Simple Email Validation");
println!("-----------------------------------");
let email = Email::new("alice@example.com".to_string());
println!("Created unvalidated email: {}", email.as_str());
match email.validate() {
Ok(validated_email) => {
println!("[ok] Email validated successfully");
send_welcome_email(&validated_email); }
Err(e) => println!("[error] Email validation failed: {}\n", e),
}
println!("Example 2: Invalid Email Validation");
println!("------------------------------------");
let invalid_email = Email::new("not-an-email".to_string());
println!("Created unvalidated email: {}", invalid_email.as_str());
match invalid_email.validate() {
Ok(_) => println!("[ok] Email validated (unexpected)\n"),
Err(e) => {
println!("[error] Email validation failed as expected:");
for v in &e.violations {
println!(" [{}] {}: {}", v.path, v.code, v.message);
}
println!();
}
}
println!("Example 3: Builder Pattern with Validation");
println!("-------------------------------------------");
let user = UserRegistration::builder()
.username("alice")
.email("alice@example.com")
.password("secure_password_123")
.age(25)
.build();
println!("Built unvalidated user: {}", user.username());
match user.validate() {
Ok(validated_user) => {
println!("[ok] User validated successfully");
complete_registration(validated_user).unwrap();
}
Err(e) => {
println!("[error] User validation failed:");
for v in &e.violations {
println!(" [{}] {}: {}", v.path, v.code, v.message);
}
println!();
}
}
println!("Example 4: Multiple Validation Errors");
println!("--------------------------------------");
let invalid_user = UserRegistration::builder()
.username("ab") .email("not-an-email") .password("short") .age(200) .build();
println!("Built invalid user: {}", invalid_user.username());
match invalid_user.validate() {
Ok(_) => println!("[ok] User validated (unexpected)\n"),
Err(e) => {
println!(
"[error] User validation failed with {} errors:",
e.violations.len()
);
for v in &e.violations {
println!(" [{}] {}: {}", v.path, v.code, v.message);
}
println!();
}
}
println!("Example 5: Partial Validation Errors");
println!("-------------------------------------");
let partial_invalid = UserRegistration::builder()
.username("bob")
.email("bob@example.com")
.password("short") .age(30)
.build();
match partial_invalid.validate() {
Ok(_) => println!("[ok] User validated (unexpected)\n"),
Err(e) => {
println!("[error] User validation failed:");
for v in &e.violations {
println!(" [{}] {}: {}", v.path, v.code, v.message);
}
println!();
}
}
println!("Example 6: Zero-Cost Abstraction");
println!("---------------------------------");
println!(
"Size of Email<Unvalidated>: {} bytes",
std::mem::size_of::<Email<Unvalidated>>()
);
println!(
"Size of Email<Validated>: {} bytes",
std::mem::size_of::<Email<Validated>>()
);
println!(
"Size of String: {} bytes",
std::mem::size_of::<String>()
);
println!("[ok] PhantomData adds ZERO bytes of overhead!\n");
println!("=== Example Completed ===");
}