use crate::traits::{Normalize, Valid};
pub struct ISBN {
pub identifier: String,
}
impl ISBN {
pub fn new(identifier: impl Into<String>) -> ISBN {
ISBN {identifier: identifier.into()}
}
pub fn checkdigit(&self) -> Option<char> {
let basic_string = self.reduce_to_basic();
match basic_string.len() {
10 => Some(checkdigit_ten(&self.identifier)),
13 => Some(checkdigit_thirteen(&self.identifier)),
_ => None
}
}
pub fn convert_to_13(&self) -> Option<String> {
if !&self.valid() {return None};
let basic_string = &self.reduce_to_basic();
let prepended_string = format!("{}{}", "978", &basic_string[..9]);
match basic_string.len() {
10 => Some(format!("{}{}", prepended_string, checkdigit_thirteen(&prepended_string)).to_string()),
13 => Some(basic_string.to_string()),
_ => None,
}
}
pub fn convert_to_10(&self) -> Option<String> {
if !self.valid() {return None};
let basic_string = &self.reduce_to_basic();
if basic_string.starts_with("979") {
return None;
}
match basic_string.len() {
10 => Some(basic_string.to_string()),
13 => Some(format!("{}{}", &basic_string[3..12], checkdigit_ten(&basic_string[3..]))),
_ => None,
}
}
fn reduce_to_basic(&self) -> String {
let clean_string = &self.identifier.replace("-", "");
scrub_alpha_prefix(clean_string)
}
}
impl Valid for ISBN {
fn valid(&self) -> bool {
let basic_string = self.reduce_to_basic();
match basic_string.len() {
10 => checkdigit_ten(&basic_string) == basic_string.chars().next_back().unwrap(),
13 => checkdigit_thirteen(&basic_string) == basic_string.chars().next_back().unwrap(),
_ => false
}
}
}
impl Normalize for ISBN {
fn normalize(&self) -> Option<String> {
self.convert_to_13()
}
}
fn scrub_alpha_prefix(string_to_scrub: &str) -> String {
string_to_scrub.chars()
.skip_while(|c| !c.is_ascii_digit())
.take_while(|c| c.is_ascii_digit() || c == &'X')
.collect::<String>()
}
fn checkdigit_ten(isbn: &str) -> char {
let basic_string = ISBN::new(isbn).reduce_to_basic();
let first_nine = basic_string.chars().take(9);
let first_nine_digits = first_nine.filter_map(|x| x.to_digit(10));
let multiplied = first_nine_digits.enumerate().map(|(index, digit)| digit * (10 - index as u32));
let summed: u32 = multiplied.sum();
let modulus: u32 = summed % 11;
from_digit_to_checkdigit(modulus)
}
fn checkdigit_thirteen(isbn: &str) -> char {
let basic_string = ISBN::new(isbn).reduce_to_basic();
let first_twelve = basic_string.chars().take(12);
let first_twelve_digits = first_twelve.filter_map(|x| x.to_digit(10));
let multiplied = first_twelve_digits.enumerate().map(|(index, digit)| digit * (1 + (index as u32 % 2) * 2 ));
let summed: u32 = multiplied.sum();
let modulus = summed % 10;
let finished = (10 - modulus) % 10;
char::from_digit(finished, 10).unwrap()
}
fn from_digit_to_checkdigit(num: u32) -> char {
let orig_num = char::from_digit((11_u32 - num) % 11, 11).unwrap();
if orig_num == 'a' {
'X'
} else {
orig_num
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_can_create_an_isbn() {
let isbn = ISBN::new("0139381430");
assert_eq!(isbn.identifier, "0139381430");
}
#[test]
fn it_calculates_the_checkdigit() {
assert_eq!(ISBN::new("0139381430").checkdigit().unwrap(), '0');
assert_eq!(ISBN::new("0-8044-2957-X").checkdigit().unwrap(), 'X');
assert_eq!(ISBN::new("9781449373320").checkdigit().unwrap(), '0');
assert_eq!(ISBN::new("9780306406152").checkdigit().unwrap(), '7')
}
#[test]
fn it_checks_the_validity() {
assert!(ISBN::new("0139381430").valid());
assert!(ISBN::new("9781449373320").valid());
assert!(ISBN::new("0-8044-2957-X").valid());
assert!(ISBN::new("ABC0139381430").valid());
}
#[test]
fn it_catches_invalid() {
assert!(!ISBN::new("01393814300").valid());
assert!(!ISBN::new("0139381432").valid());
assert!(!ISBN::new("9781449373322").valid());
}
#[test]
fn it_reduces_to_basic() {
assert_eq!(ISBN::new("0-8044-2957-X").reduce_to_basic(), "080442957X");
assert_eq!(ISBN::new("ABC0139381430").reduce_to_basic(), "0139381430");
}
#[test]
fn it_scrubs_alpha_prefix() {
assert_eq!(scrub_alpha_prefix("A1"), "1");
assert_eq!(scrub_alpha_prefix("A123"), "123");
assert_eq!(scrub_alpha_prefix("ABC0139381430"), "0139381430");
assert_eq!(scrub_alpha_prefix("ABC080442957X"), "080442957X");
assert_eq!(scrub_alpha_prefix("ABC080442957Y"), "080442957");
}
#[test]
fn it_converts_isbn_10_to_13() {
assert_eq!(ISBN::new("9781449373320").convert_to_13().unwrap(), "9781449373320");
assert_eq!(ISBN::new("0-306-40615-2").convert_to_13().unwrap(), "9780306406157");
}
#[test]
fn it_converts_isbn_13_to_10() {
assert_eq!(ISBN::new("9780306406157").convert_to_10().unwrap(), "0306406152");
assert_eq!(ISBN::new("0306406152").convert_to_10().unwrap(), "0306406152");
assert_eq!(ISBN::new("9798531132178").convert_to_10(), None);
assert_eq!(ISBN::new("1").convert_to_10(), None);
assert_eq!(ISBN::new("9780306406157978030640615797803064061579780306406157").convert_to_10(), None);
}
#[test]
fn it_normalizes() {
assert_eq!(ISBN::new("0-306-40615-2").normalize().unwrap(), "9780306406157");
assert_eq!(ISBN::new("0-306-40615-X").normalize(), None);
assert_eq!(ISBN::new("ISBN: 978-0-306-40615-7").normalize().unwrap(), "9780306406157");
assert_eq!(ISBN::new("ISBN: 978-0-306-40615-3").normalize(), None);
}
}