1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*!
The type [CheckDigitAlgorithm] provides an implementation of the check
digit calculation as part of the SEDOL specification.

# Example

```rust
use codes_check_digits::{sedol, Calculator};

let calculator = sedol::get_algorithm_instance();
assert!(calculator.is_valid("0540528"));
assert!(calculator.validate("0540528").is_ok());
assert_eq!(calculator.calculate("054052"), Ok(8));
 ```

*/

use crate::{
    common::{ascii_alphanum_to_u8, is_ascii_alphanumeric_upper_no_vowels, is_length_eq},
    error::CheckDigitError,
    Calculator,
};

// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------

///
/// Validate the SEDOL numbers defined by the London Stock Exchange
///
#[derive(Debug, Default)]
pub struct CheckDigitAlgorithm {}

// ------------------------------------------------------------------------------------------------
// Public Functions
// ------------------------------------------------------------------------------------------------

const SHARED_INSTANCE: CheckDigitAlgorithm = CheckDigitAlgorithm {};

pub const fn get_algorithm_instance() -> &'static CheckDigitAlgorithm {
    &SHARED_INSTANCE
}

// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------

const WEIGHTS: [u16; 6] = [1, 3, 1, 7, 3, 9];

impl Calculator<u8> for CheckDigitAlgorithm {
    fn name(&self) -> &'static str {
        "Stock Exchange Daily Official List (SEDOL)"
    }

    fn calculate(&self, s: &str) -> Result<u8, CheckDigitError> {
        is_length_eq(s, 6)?;
        is_ascii_alphanumeric_upper_no_vowels(s)?;
        let sum: u16 = s
            .chars()
            .enumerate()
            .map(|(i, c)| (ascii_alphanum_to_u8(c) as u16) * WEIGHTS[i])
            .sum();

        Ok(((10 - (sum % 10)) % 10) as u8)
    }
}

// ------------------------------------------------------------------------------------------------
// Unit Tests
// ------------------------------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use crate::sedol::CheckDigitAlgorithm;
    use crate::Calculator;

    #[test]
    fn test_validate_hsbc() {
        let sedol = CheckDigitAlgorithm::default();
        assert!(sedol.is_valid("0540528"));
        assert_eq!(sedol.calculate("054052"), Ok(8));
    }
}