Validate Typed Identifiers with Newtype Wrappers
Wrap numeric identifiers in a newtype that parses a prefixed string, rejects zero values, and prints a padded canonical form.
Code
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct UserId(u64);
#[derive(Debug, PartialEq, Eq)]
enum IdError {
Empty,
Prefix,
Digits,
Zero,
}
impl fmt::Display for IdError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
IdError::Empty => "identifier digits are empty",
IdError::Prefix => "identifier must start with usr_",
IdError::Digits => "identifier must contain only digits",
IdError::Zero => "identifier must be greater than zero",
};
formatter.write_str(message)
}
}
impl FromStr for UserId {
type Err = IdError;
fn from_str(raw: &str) -> Result<Self, Self::Err> {
let digits = raw.strip_prefix("usr_").ok_or(IdError::Prefix)?;
if digits.is_empty() {
return Err(IdError::Empty);
}
let value: u64 = digits.parse().map_err(|_| IdError::Digits)?;
if value == 0 {
return Err(IdError::Zero);
}
Ok(UserId(value))
}
}
impl fmt::Display for UserId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "usr_{:06}", self.0)
}
}
fn main() {
let id: UserId = "usr_004210".parse().expect("valid id");
println!("{id}");
assert_eq!(id.to_string(), "usr_004210");
assert_eq!("usr_000000".parse::<UserId>(), Err(IdError::Zero));
assert_eq!("4210".parse::<UserId>(), Err(IdError::Prefix));
assert_eq!("usr_12x".parse::<UserId>(), Err(IdError::Digits));
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.