1pub fn digits(s: &str) -> Option<String> {
8 let s = s.trim();
9 let s = match s.get(..4) {
10 Some(p) if p.eq_ignore_ascii_case("isbn") => &s[4..],
11 _ => s,
12 };
13 let s = s.trim_start_matches([':', ' ']);
14 let mut digits = String::new();
15 for c in s.chars() {
16 match c {
17 '0'..='9' => digits.push(c),
18 'X' | 'x' => digits.push('X'),
19 '-' | ' ' => {}
20 _ => return None,
21 }
22 }
23 let value = |c: char| c.to_digit(10);
24 let ok = match digits.len() {
25 13 => {
26 let mut sum = 0;
27 for (i, c) in digits.chars().enumerate() {
28 sum += value(c)? * if i % 2 == 0 { 1 } else { 3 };
29 }
30 sum % 10 == 0
31 }
32 10 => {
33 let mut sum = 0;
34 for (i, c) in digits.chars().enumerate() {
35 let v = match c {
37 'X' if i == 9 => 10,
38 c => value(c)?,
39 };
40 sum += v * (10 - i as u32);
41 }
42 sum % 11 == 0
43 }
44 _ => false,
45 };
46 ok.then_some(digits)
47}
48
49pub fn is_valid(s: &str) -> bool {
51 digits(s).is_some()
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn digits_of_valid_isbns() {
60 assert_eq!(
61 digits("978-3-16-148410-0").as_deref(),
62 Some("9783161484100")
63 );
64 assert_eq!(
65 digits("ISBN: 978 3 16 148410 0").as_deref(),
66 Some("9783161484100")
67 );
68 assert_eq!(digits("isbn 0-306-40615-2").as_deref(), Some("0306406152"));
69 assert_eq!(digits("0-8044-2957-X").as_deref(), Some("080442957X"));
70 assert_eq!(digits("0-8044-2957-x").as_deref(), Some("080442957X"));
71 assert_eq!(digits("978-3-16-148410-1"), None, "check digit");
72 assert_eq!(digits("X-306-40615-2"), None);
73 assert_eq!(digits("978316148410"), None);
74 assert_eq!(digits("tbd"), None);
75 assert_eq!(digits(""), None);
76 }
77
78 #[test]
79 fn validity() {
80 for isbn in [
81 "978-3-16-148410-0",
82 "9783161484100",
83 "ISBN 0-306-40615-2",
84 "080442957X",
85 ] {
86 assert!(is_valid(isbn), "{isbn}");
87 }
88 for isbn in [
89 "978-3-16-148410-1",
90 "0-306-40615-3",
91 "97831614841",
92 "X783161484100",
93 "978-3-16-l48410-0",
94 "978316148410X",
95 ] {
96 assert!(!is_valid(isbn), "{isbn}");
97 }
98 }
99}