use codes_common::check_digits::Calculator;
use codes_common::check_digits::LuhnAlgorithm;
use codes_iso_3166::part_1::CountryCode;
use lazy_static::lazy_static;
use std::{collections::HashMap, fmt::Debug};
pub trait NsinScheme: Debug + Sync {
fn agency_name(&self) -> &'static str;
fn name(&self) -> &'static str;
fn is_valid(&self, s: &str) -> bool;
}
#[allow(clippy::borrowed_box)]
pub fn national_number_scheme_for(country: &CountryCode) -> Option<&Box<dyn NsinScheme>> {
KNOWN_NSID.get(country)
}
lazy_static! {
static ref KNOWN_NSID: HashMap<CountryCode, Box<dyn NsinScheme>> = known_nsids();
}
#[derive(Debug, Default)]
struct Cusip {
check_digits: LuhnAlgorithm,
}
#[derive(Debug, Default)]
struct Sedol {}
#[derive(Debug, Default)]
struct Valoren {}
impl NsinScheme for Cusip {
fn agency_name(&self) -> &'static str {
"CUSIP Services Bureau"
}
fn name(&self) -> &'static str {
"CUSIP"
}
fn is_valid(&self, s: &str) -> bool {
assert!(s.len() <= 10);
self.check_digits.is_valid(s)
}
}
impl Calculator for Sedol {
fn calculate(&self, s: &str) -> Result<u8, codes_common::CodeParseError> {
const WEIGHTS: [u16; 6] = [1, 3, 1, 7, 3, 9];
let sum: u16 = s
.chars()
.enumerate()
.map(|(i, c)| {
let n = if c.is_ascii_uppercase() {
(c as u16) - 55 } else if c.is_ascii_digit() {
(c as u16) - (b'0' as u16)
} else {
unreachable!("What is {:?} doing here?", c)
};
n * WEIGHTS[i]
})
.sum();
Ok(((10 - (sum % 10)) % 10) as u8)
}
}
impl NsinScheme for Sedol {
fn agency_name(&self) -> &'static str {
"London Stock Exchange"
}
fn name(&self) -> &'static str {
"SEDOL"
}
fn is_valid(&self, s: &str) -> bool {
assert!(s.len() == 7);
Calculator::is_valid(self, s)
}
}
impl NsinScheme for Valoren {
fn agency_name(&self) -> &'static str {
"SIX Financial"
}
fn name(&self) -> &'static str {
"VALOR"
}
fn is_valid(&self, s: &str) -> bool {
s.len() >= 5 && s.len() <= 9 && s.chars().all(|c| c.is_ascii_digit())
}
}
fn known_nsids() -> HashMap<CountryCode, Box<dyn NsinScheme>> {
let mut nsids: HashMap<CountryCode, Box<dyn NsinScheme>> = Default::default();
nsids.insert(CountryCode::CA, Box::new(Cusip::default()));
nsids.insert(CountryCode::CH, Box::new(Valoren::default()));
nsids.insert(CountryCode::UK, Box::new(Sedol::default()));
nsids.insert(CountryCode::US, Box::new(Cusip::default()));
nsids
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sedol_check_digit() {
let sedol = Sedol::default();
assert!(NsinScheme::is_valid(&sedol, "0263494"));
}
#[test]
fn test_valoren() {
let valor = Valoren::default();
assert!(NsinScheme::is_valid(&valor, "1213853"));
}
}