headless_lms_models/library/credit_registration/
student_number.rs1const CHECK_DIGIT_WEIGHTS: [u32; 8] = [3, 7, 1, 3, 7, 1, 3, 7];
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum InvalidStudentNumber {
13 Format,
15 CheckDigit,
17}
18
19impl InvalidStudentNumber {
20 pub fn message(self) -> &'static str {
22 match self {
23 Self::Format => "A student number is nine digits and starts with 0.",
24 Self::CheckDigit => "The check digit does not match. Check the number for typos.",
25 }
26 }
27}
28
29pub fn parse_student_number(raw: &str) -> Result<String, InvalidStudentNumber> {
32 let number: String = raw.chars().filter(|c| !c.is_whitespace()).collect();
33 if number.len() != 9 || !number.starts_with('0') || !number.bytes().all(|b| b.is_ascii_digit())
34 {
35 return Err(InvalidStudentNumber::Format);
36 }
37 let sum: u32 = number
38 .bytes()
39 .zip(CHECK_DIGIT_WEIGHTS)
40 .map(|(digit, weight)| u32::from(digit - b'0') * weight)
41 .sum();
42 if (10 - sum % 10) % 10 != u32::from(number.as_bytes()[8] - b'0') {
43 return Err(InvalidStudentNumber::CheckDigit);
44 }
45 Ok(number)
46}