#[macro_use]
extern crate lazy_static;
use personnummer::{Personnummer, PersonnummerError};
use regex::{Match, Regex};
use std::{convert::TryFrom, error::Error, fmt};
lazy_static! {
static ref ORG_REGEX: Regex =
Regex::new(r"(?x)^(\d{2}){0,1}(\d{2})(\d{2})(\d{2})([-+]?)?(\d{3})(\d)$").unwrap();
}
#[derive(Debug)]
pub enum OrganisationsnummerError {
InvalidInput,
}
impl fmt::Display for OrganisationsnummerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
OrganisationsnummerError::InvalidInput => write!(f, "Invalid format"),
}
}
}
impl Error for OrganisationsnummerError {}
#[allow(dead_code)]
pub struct Organisationsnummer {
personnummer: Option<Personnummer>,
number: String,
}
pub struct FormattedOrganisationsnummer {
long: String,
short: String,
}
impl FormattedOrganisationsnummer {
pub fn long(&self) -> String {
self.long.clone()
}
pub fn short(&self) -> String {
self.short.clone()
}
}
impl TryFrom<&str> for Organisationsnummer {
type Error = OrganisationsnummerError;
fn try_from(org: &str) -> Result<Self, OrganisationsnummerError> {
let caps = ORG_REGEX
.captures(org)
.ok_or(OrganisationsnummerError::InvalidInput)?;
let match_to_u32 =
|m: Option<Match<'_>>| -> u32 { m.unwrap().as_str().parse::<u32>().unwrap_or(0) };
let prefix = match caps.get(1) {
Some(m) => match_to_u32(Some(m)),
None => 0,
};
let mut number = org.to_string().replace("-", "");
if prefix != 0 {
if prefix != 16 {
return Err(OrganisationsnummerError::InvalidInput);
} else {
number = number[2..].to_string();
}
}
let third = match_to_u32(caps.get(3));
if third < 20 {
return Err(OrganisationsnummerError::InvalidInput);
}
let second = match_to_u32(caps.get(2));
if second < 10 {
return Err(OrganisationsnummerError::InvalidInput);
}
if !luhn(number.clone()) {
return Err(OrganisationsnummerError::InvalidInput);
}
return Ok(Organisationsnummer {
personnummer: None,
number: number.clone(),
});
}
}
impl Organisationsnummer {
pub fn new(org: &str) -> Result<Organisationsnummer, OrganisationsnummerError> {
match Personnummer::new(org) {
Ok(pnr) => Ok(Organisationsnummer {
personnummer: Some(pnr),
number: org.to_string().replace("-", ""),
}),
Err(_) => Organisationsnummer::try_from(org),
}
}
pub fn parse(org: &str) -> Result<Organisationsnummer, OrganisationsnummerError> {
Organisationsnummer::new(org)
}
pub fn valid(&self) -> bool {
true
}
pub fn format(&self) -> FormattedOrganisationsnummer {
let formatted = match &self.personnummer {
Some(pnr) => {
let f = pnr.format();
let s = f.short();
let mut l = f.long();
if pnr.get_age() >= 100 {
l = l.replace("-", "+");
}
FormattedOrganisationsnummer {
long: l[2..].to_string(),
short: s[0..6].to_string() + &s[7..].to_string(),
}
},
None => FormattedOrganisationsnummer {
long: format!("{}-{}", &self.number[..6], &self.number[6..]),
short: self.number.clone(),
},
};
formatted
}
pub fn r#type(&self) -> String {
let first = match &self.personnummer {
Some(_) => 0,
None => self
.number
.chars()
.next()
.unwrap()
.to_digit(10)
.unwrap_or(0),
};
let r#type = match first {
0 => "Enskild firma",
1 => "Dödsbon",
2 => "Stat, landsting, kommun eller församling",
3 => {
"Utländska företag som bedriver näringsverksamhet eller äger fastigheter i Sverige"
}
5 => "Aktiebolag",
6 => "Enkelt bolag",
7 => "Ekonomisk förening eller bostadsrättsförening",
8 => "'Ideella förening och stiftelse",
9 => "Handelsbolag, kommanditbolag och enkelt bolag",
_ => "Okänt",
};
r#type.to_string()
}
pub fn vat_number(&self) -> String {
let number = match &self.personnummer {
Some(pnr) => pnr.format().long()[2..13].to_string().replace("-", ""),
None => self.number.clone(),
};
format!("SE{}01", number)
}
pub fn personnummer(&self) -> Result<Personnummer, PersonnummerError> {
Personnummer::new(&self.number)
}
pub fn is_personnummer(&self) -> bool {
match &self.personnummer {
Some(_) => true,
None => false,
}
}
}
fn luhn(value: String) -> bool {
let checksum = value
.chars()
.map(|c| c.to_digit(10).unwrap_or(0))
.enumerate()
.fold(0, |acc, (idx, v)| {
let value = if idx % 2 == 0 { v * 2 } else { v };
acc + if value > 9 { value - 9 } else { value }
});
(10 - (checksum as u8 % 10)) % 10 == 0
}
#[cfg(test)]
mod tests {
use super::*;
use reqwest::blocking::get;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct TestItem {
input: String,
long_format: String,
short_format: String,
r#type: String,
vat_number: String,
valid: bool,
}
fn get_test_list() -> Vec<TestItem> {
let res = get(
"https://raw.githubusercontent.com/organisationsnummer/meta/main/testdata/list.json",
)
.unwrap();
let list = res.json::<Vec<TestItem>>().unwrap();
list
}
#[test]
fn test_invalid_input() {
let list = get_test_list();
for item in list {
if item.valid {
continue;
}
assert!(Organisationsnummer::parse(item.input.as_str()).is_err());
}
}
#[test]
fn test_valid_organization_numbers() {
let list = get_test_list();
for item in list {
if !item.valid {
continue;
}
assert!(Organisationsnummer::parse(item.input.as_str())
.unwrap()
.valid());
}
}
#[test]
fn test_valid_organization_types() {
let list = get_test_list();
for item in list {
if !item.valid {
continue;
}
assert_eq!(
Organisationsnummer::parse(item.input.as_str())
.unwrap()
.r#type(),
item.r#type,
);
}
}
#[test]
fn test_valid_format_with_separator() {
let list = get_test_list();
for item in list {
if !item.valid {
continue;
}
assert_eq!(
Organisationsnummer::parse(item.input.as_str())
.unwrap()
.format()
.long(),
item.long_format,
);
}
}
#[test]
fn test_valid_format_without_separator() {
let list = get_test_list();
for item in list {
if !item.valid {
continue;
}
assert_eq!(
Organisationsnummer::parse(item.input.as_str())
.unwrap()
.format()
.short(),
item.short_format,
);
}
}
#[test]
fn test_valid_vat_numbers() {
let list = get_test_list();
for item in list {
if !item.valid {
continue;
}
assert_eq!(
Organisationsnummer::parse(item.input.as_str())
.unwrap()
.vat_number(),
item.vat_number
);
}
}
#[test]
fn test_valid_personal_identity_numbers() {
let list = get_test_list();
for item in list {
if !item.valid {
continue;
}
if item.r#type != "Enskild firma" {
continue;
}
assert!(Organisationsnummer::parse(item.long_format.as_str())
.unwrap()
.valid());
let org = Organisationsnummer::parse(item.input.as_str()).unwrap();
assert!(org.valid());
assert_eq!(org.format().short(), item.short_format);
assert_eq!(org.format().long(), item.long_format);
assert!(org.is_personnummer());
assert!(org.valid());
assert_eq!(org.vat_number(), item.vat_number);
}
}
}