mod error;
mod range;
mod utils;
use core::fmt;
use error::Error;
use num_format::{Locale, ToFormattedString};
use range::{random_number, Range};
use regex::Regex;
use utils::{mod_eleven, sum_product, PATTERN};
#[derive(Debug)]
pub struct Rut {
number: u32,
dv: char,
}
#[derive(Copy, Clone)]
pub enum Format {
DOTS,
DASH,
NONE,
}
impl fmt::Display for Rut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_format(Format::DASH))
}
}
impl Rut {
pub fn from(input: &str) -> Result<Rut, Error> {
match Rut::extract_from(input) {
Ok(unverified_rut) => Rut::check_dv(unverified_rut),
Err(error) => Err(error),
}
}
pub fn from_number(number: u32) -> Result<Rut, Error> {
let min = Range::MIN.to_u32();
let max = Range::MAX.to_u32();
let range = min..max;
if range.contains(&number) {
let dv = Rut::generate_dv(number);
Ok(Rut { number, dv })
} else {
Err(Error::OutOfRange)
}
}
pub fn randomize() -> Rut {
let number = random_number();
let dv = Rut::generate_dv(number);
Rut { number, dv }
}
pub fn to_format(&self, format: Format) -> String {
match format {
Format::DOTS => format!(
"{}-{}",
self.number.to_formatted_string(&Locale::es_CL),
self.dv
),
Format::DASH => format!("{}-{}", self.number, self.dv),
Format::NONE => format!("{}{}", self.number, self.dv),
}
}
pub fn number(&self) -> &u32 {
&self.number
}
pub fn dv(&self) -> &char {
&self.dv
}
fn check_dv(unsigned_rut: Rut) -> Result<Rut, Error> {
let signed_rut = Rut::from_number(unsigned_rut.number).unwrap();
if unsigned_rut.dv != signed_rut.dv {
Err(Error::InvalidDV {
must_be: signed_rut.dv,
instead: unsigned_rut.dv,
})
} else {
Ok(signed_rut)
}
}
fn extract_from(input: &str) -> Result<Rut, Error> {
let regex = Regex::new(PATTERN).unwrap();
if regex.is_match(input) {
let captures = regex.captures(input).unwrap();
let number: u32 = captures["number"].replace(".", "").parse().unwrap();
let dv = captures["dv"].to_uppercase().chars().nth(0).unwrap();
Ok(Rut { number, dv })
} else {
Err(Error::InvalidFormat)
}
}
fn generate_dv(number: u32) -> char {
let total_number = sum_product(number);
let dv = mod_eleven(total_number);
match dv {
10 => 'K',
11 => '0',
_ => format!("{}", dv).chars().nth(0).unwrap(),
}
}
}
#[cfg(test)]
mod rut_test {
use super::*;
#[test]
fn from() {
let valid_rut = ["17951585-7", "5.665.328-7", "241367738"];
for rut in valid_rut.iter() {
assert!(Rut::from(rut).is_ok())
}
}
#[test]
fn from_number() {
let valid_rut = [(17951585, '7'), (12621806, '0'), (24136773, '8')];
for (number, dv) in valid_rut.iter() {
let rut = Rut::from_number(*number).unwrap();
assert_eq!(&rut.number, number);
assert_eq!(rut.dv(), dv);
}
}
#[test]
fn to_format() {
let rut_tuple = [
(
"17951585-7",
Rut::from_number(17951585).unwrap(),
Format::DASH,
),
(
"5.665.328-7",
Rut::from_number(5665328).unwrap(),
Format::DOTS,
),
(
"241367738",
Rut::from_number(24136773).unwrap(),
Format::NONE,
),
];
for (input, rut, format) in rut_tuple.iter() {
assert_eq!(rut.to_format(*format), *input)
}
}
#[test]
fn randomize() {
let rut = Rut::randomize();
assert_eq!(rut.to_string(), rut.to_string())
}
#[test]
fn wrong_dv() {
assert_eq!(
Rut::from("17951585-K").unwrap_err().to_string(),
Error::InvalidDV {
must_be: '7',
instead: 'K'
}
.to_string()
)
}
#[test]
fn invalid_format() {
assert_eq!(
Rut::from("17.951,585-7").unwrap_err().to_string(),
Error::InvalidFormat.to_string()
)
}
#[test]
fn out_of_range() {
assert_eq!(
Rut::from_number(999999).unwrap_err().to_string(),
Error::OutOfRange.to_string()
);
assert_eq!(
Rut::from_number(100000000).unwrap_err().to_string(),
Error::OutOfRange.to_string()
);
}
}