#![cfg_attr(not(test), no_std)]
use core::{
convert::TryInto,
fmt::{Display, Formatter},
ops::Range,
str::{self, FromStr},
};
pub const LENGTH: usize = 11;
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub struct TurkishId {
id: [u8; LENGTH],
}
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
InvalidLength,
InvalidCharacter(char),
InvalidFinalChecksum,
InvalidInitialChecksum,
FirstDigitIsZero,
}
#[must_use = "validity check must not be ignored"]
pub fn is_valid(value: &str) -> bool {
validate(value).is_ok()
}
fn validate(str: &str) -> Result<(), Error> {
fn next_digit(t: &mut impl Iterator<Item = char>) -> Result<i32, Error> {
let c = t.next().ok_or(Error::InvalidLength)?;
c.to_digit(10)
.and_then(|d| i32::try_from(d).ok()) .ok_or(Error::InvalidCharacter(c))
}
if str.len() != LENGTH {
return Err(Error::InvalidLength);
}
let mut digits = str.chars();
let mut odd_sum = next_digit(&mut digits)?;
if odd_sum == 0 {
return Err(Error::FirstDigitIsZero);
}
let mut even_sum = 0;
for _ in 0..4 {
even_sum += next_digit(&mut digits)?;
odd_sum += next_digit(&mut digits)?;
}
let first_checksum = next_digit(&mut digits)?;
let final_checksum = next_digit(&mut digits)?;
let final_checksum_computed = (odd_sum + even_sum + first_checksum) % 10;
if final_checksum_computed != final_checksum {
return Err(Error::InvalidFinalChecksum);
}
let first_checksum_computed = ((odd_sum * 7) - even_sum).rem_euclid(10);
if first_checksum_computed != first_checksum {
return Err(Error::InvalidInitialChecksum);
}
Ok(())
}
impl Display for TurkishId {
fn fmt(&self, f: &mut Formatter) -> Result<(), core::fmt::Error> {
write!(
f,
"{}",
str::from_utf8(&self.id).map_err(|_| core::fmt::Error)?
)
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum FromSeqError {
OutOfRange,
}
impl TurkishId {
pub const SEQ_RANGE: Range<u32> = 100_000_000..1_000_000_000;
pub fn from_seq(seq: u32) -> Result<TurkishId, FromSeqError> {
fn to_ascii(digit: i32) -> u8 {
digit as u8 + b'0'
}
if !TurkishId::SEQ_RANGE.contains(&seq) {
return Err(FromSeqError::OutOfRange);
}
let mut d = [0; LENGTH];
let mut odd_sum: i32 = 0;
let mut even_sum: i32 = 0;
let mut divisor = Self::SEQ_RANGE.start;
for (i, item) in d.iter_mut().enumerate().take(9) {
let digit = (seq / divisor % 10) as i32;
if i % 2 == 0 {
odd_sum += digit;
} else {
even_sum += digit;
}
*item = to_ascii(digit);
divisor /= 10;
}
let first_checksum = ((odd_sum * 7) - even_sum).rem_euclid(10);
let second_checksum = (odd_sum + even_sum + first_checksum) % 10;
d[9] = to_ascii(first_checksum);
d[10] = to_ascii(second_checksum);
Ok(TurkishId { id: d })
}
}
impl FromStr for TurkishId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
validate(s)?;
let bytes = s.as_bytes().try_into().map_err(|_| Error::InvalidLength)?;
let result = Self { id: bytes };
Ok(result)
}
}
#[cfg(test)]
mod tests;