use std::error::Error;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Analysis {
pub valid: bool,
pub next_check_digit: char,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DigitSequenceError {
Empty,
NonAsciiDigit,
}
impl fmt::Display for DigitSequenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "digit sequence cannot be empty"),
Self::NonAsciiDigit => write!(f, "digit sequence must contain only ASCII digits"),
}
}
}
impl Error for DigitSequenceError {}
pub fn validate(sequence: &str) -> Result<bool, DigitSequenceError> {
validate_digit_sequence(sequence)?;
Ok(luhn::valid(sequence))
}
pub fn calculate_check_digit(sequence: &str) -> Result<char, DigitSequenceError> {
validate_digit_sequence(sequence)?;
Ok(luhn::checksum(sequence.as_bytes()) as char)
}
pub fn analyze(sequence: &str) -> Result<Analysis, DigitSequenceError> {
validate_digit_sequence(sequence)?;
Ok(Analysis {
valid: luhn::valid(sequence),
next_check_digit: luhn::checksum(sequence.as_bytes()) as char,
})
}
fn validate_digit_sequence(sequence: &str) -> Result<(), DigitSequenceError> {
if sequence.is_empty() {
return Err(DigitSequenceError::Empty);
}
if !sequence.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(DigitSequenceError::NonAsciiDigit);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_invalid_sequence() {
assert_eq!(validate("1234567890"), Ok(false));
}
#[test]
fn validates_valid_sequence() {
assert_eq!(validate("12345678903"), Ok(true));
}
#[test]
fn calculates_next_check_digit() {
assert_eq!(calculate_check_digit("1234567890"), Ok('3'));
}
#[test]
fn returns_full_analysis() {
assert_eq!(
analyze("12345678903"),
Ok(Analysis {
valid: true,
next_check_digit: '1',
})
);
}
#[test]
fn rejects_empty_sequences() {
assert_eq!(analyze(""), Err(DigitSequenceError::Empty));
}
#[test]
fn rejects_non_ascii_digits() {
assert_eq!(analyze("12A3"), Err(DigitSequenceError::NonAsciiDigit));
assert_eq!(analyze("123"), Err(DigitSequenceError::NonAsciiDigit));
}
}