idkollen_client/models/
org_number.rs1use fmt::Display;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt;
4use thiserror::Error;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct OrgNumber(String);
12
13#[derive(Debug, Error)]
14#[error("invalid organisation number: {0}")]
15pub struct OrgNumberError(String);
16
17impl OrgNumber {
18 pub fn parse(s: &str) -> Result<Self, OrgNumberError> {
19 let digits = s.chars().filter(|c| c.is_ascii_digit()).collect::<String>();
20
21 if digits.len() != 10 {
22 return Err(OrgNumberError(format!(
23 "must contain exactly 10 digits, got {}",
24 digits.len()
25 )));
26 }
27
28 if digits.as_bytes()[2] < b'2' {
30 return Err(OrgNumberError(
31 "third digit must be 2 or greater".to_owned(),
32 ));
33 }
34
35 if !luhn10(&digits) {
36 return Err(OrgNumberError("Luhn check failed".to_owned()));
37 }
38
39 Ok(Self(format!("{}-{}", &digits[..6], &digits[6..])))
40 }
41
42 #[inline]
43 #[must_use]
44 pub fn as_str(&self) -> &str {
45 &self.0
46 }
47}
48
49fn luhn10(s: &str) -> bool {
51 let sum: u32 = s
52 .chars()
53 .enumerate()
54 .map(|(i, c)| {
55 let d = c.to_digit(10).unwrap();
56 let v = if i % 2 == 0 { d * 2 } else { d };
57 if v >= 10 { v - 9 } else { v }
58 })
59 .sum();
60
61 sum.is_multiple_of(10)
62}
63
64impl From<OrgNumber> for String {
65 #[inline]
66 fn from(o: OrgNumber) -> String {
67 o.0
68 }
69}
70
71impl AsRef<str> for OrgNumber {
72 #[inline]
73 fn as_ref(&self) -> &str {
74 &self.0
75 }
76}
77
78impl Display for OrgNumber {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 f.write_str(&self.0)
81 }
82}
83
84impl Serialize for OrgNumber {
85 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
86 self.0.serialize(s)
87 }
88}
89
90impl<'de> Deserialize<'de> for OrgNumber {
91 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
92 let s = String::deserialize(d)?;
93
94 OrgNumber::parse(&s).map_err(serde::de::Error::custom)
95 }
96}