#![doc(html_root_url = "https://docs.rs/iban_validate/4.0.0")]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(bare_trait_objects)]
#![deny(elided_lifetimes_in_paths)]
#![deny(missing_debug_implementations)]
#![cfg_attr(not(feature = "std"), no_std)]
use core::convert::TryFrom;
use core::fmt;
use core::str;
mod base_iban;
mod countries;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use base_iban::{BaseIban, ParseBaseIbanError};
pub trait IbanLike {
fn electronic_str(&self) -> &str;
fn country_code(&self) -> &str {
&self.electronic_str()[0..2]
}
fn check_digits_str(&self) -> &str {
&self.electronic_str()[2..4]
}
fn check_digits(&self) -> u8 {
self.check_digits_str().parse().expect(
"Could not parse check digits. Please create an issue at \
https://github.com/ThomasdenH/iban_validate.",
)
}
fn bban_unchecked(&self) -> &str {
&self.electronic_str()[4..]
}
}
impl IbanLike for Iban {
fn electronic_str(&self) -> &str {
self.base_iban.electronic_str()
}
}
impl Iban {
pub fn bban(&self) -> &str {
self.bban_unchecked()
}
pub fn bank_identifier(&self) -> Option<&str> {
match self.country_code() {
"AD" => Some(0..4),
"AE" => Some(0..3),
"AL" => Some(0..3),
"AT" => Some(0..5),
"AZ" => Some(0..4),
"BA" => Some(0..3),
"BE" => Some(0..3),
"BG" => Some(0..4),
"BH" => Some(0..4),
"BR" => Some(0..8),
"BY" => Some(0..4),
"CH" => Some(0..5),
"CR" => Some(0..4),
"CY" => Some(0..3),
"CZ" => Some(0..4),
"DE" => Some(0..8),
"DK" => Some(0..4),
"DO" => Some(0..3),
"EE" => Some(0..2),
"EG" => Some(0..3),
"ES" => Some(0..4),
"FI" => Some(0..3),
"FO" => Some(0..4),
"FR" => Some(0..5),
"GB" => Some(0..4),
"GE" => Some(0..2),
"GI" => Some(0..4),
"GL" => Some(0..4),
"GR" => Some(0..3),
"GT" => Some(0..4),
"HR" => Some(0..7),
"HU" => Some(0..3),
"IE" => Some(0..4),
"IL" => Some(0..3),
"IQ" => Some(0..4),
"IS" => Some(0..2),
"IT" => Some(1..6),
"JO" => Some(4..8),
"KW" => Some(0..4),
"KZ" => Some(0..3),
"LB" => Some(0..4),
"LC" => Some(0..4),
"LI" => Some(0..5),
"LT" => Some(0..5),
"LU" => Some(0..3),
"LV" => Some(0..4),
"MC" => Some(0..5),
"MD" => Some(0..2),
"ME" => Some(0..3),
"MK" => Some(0..3),
"MR" => Some(0..5),
"MT" => Some(0..4),
"MU" => Some(0..6),
"NL" => Some(0..4),
"NO" => Some(0..4),
"PK" => Some(0..4),
"PL" => None,
"PS" => Some(0..4),
"PT" => Some(0..4),
"QA" => Some(0..4),
"RO" => Some(0..4),
"RS" => Some(0..3),
"SA" => Some(0..2),
"SC" => Some(0..6),
"SE" => Some(0..3),
"SI" => Some(0..5),
"SK" => Some(0..4),
"SM" => Some(1..6),
"ST" => Some(0..4),
"SV" => Some(0..4),
"TL" => Some(0..3),
"TN" => Some(0..2),
"TR" => Some(0..5),
"UA" => Some(0..6),
"VA" => Some(0..3),
"VG" => Some(0..4),
"XK" => Some(0..2),
_ => panic!(
"Unknown country! Please file an issue at \
https://github.com/ThomasdenH/iban_validate."
),
}
.map(|range| &self.electronic_str()[4..][range])
}
pub fn branch_identifier(&self) -> Option<&str> {
match self.country_code() {
"AD" => Some(4..8),
"AE" => None,
"AL" => Some(3..7),
"AT" | "AZ" => None,
"BA" => Some(3..6),
"BE" => None,
"BG" => Some(4..8),
"BH" => None,
"BR" => Some(8..13),
"BY" | "CH" | "CR" => None,
"CY" => Some(3..8),
"CZ" | "DE" | "DK" | "DO" | "EE" => None,
"EG" => Some(3..6),
"ES" => Some(4..8),
"FI" | "FO" | "FR" => None,
"GB" => Some(4..10),
"GE" | "GI" | "GL" => None,
"GR" => Some(4..7),
"GT" | "HR" => None,
"HU" => Some(3..7),
"IE" => Some(4..10),
"IL" => Some(3..6),
"IQ" => Some(4..7),
"IS" => Some(2..4),
"IT" => Some(6..11),
"JO" | "KW" | "KZ" | "LB" | "LC" | "LI" | "LT" | "LU" | "LV" => None,
"MC" => Some(5..10),
"MD" | "ME" | "MK" => None,
"MR" => Some(5..10),
"MT" => Some(4..9),
"MU" => Some(6..8),
"NL" | "NO" | "PK" => None,
"PL" => Some(0..8),
"PS" | "PT" | "QA" | "RO" | "RS" | "SA" => None,
"SC" => Some(6..8),
"SE" | "SI" | "SK" => None,
"SM" => Some(6..11),
"ST" => Some(4..8),
"SV" | "TL" => None,
"TN" => Some(2..5),
"TR" | "UA" | "VA" | "VG" => None,
"XK" => Some(2..4),
_ => panic!(
"Unknown country! Please file an issue at \
https://github.com/ThomasdenH/iban_validate."
),
}
.map(|range| &self.electronic_str()[4..][range])
}
}
impl From<Iban> for BaseIban {
fn from(value: Iban) -> BaseIban {
value.base_iban
}
}
impl fmt::Debug for Iban {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.base_iban, f)
}
}
impl fmt::Display for Iban {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.base_iban, f)
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub struct Iban {
base_iban: BaseIban,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ParseIbanError {
InvalidBaseIban {
source: ParseBaseIbanError,
},
InvalidBban(BaseIban),
UnknownCountry(BaseIban),
}
impl From<ParseBaseIbanError> for ParseIbanError {
fn from(source: ParseBaseIbanError) -> ParseIbanError {
ParseIbanError::InvalidBaseIban { source }
}
}
impl fmt::Display for ParseIbanError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
ParseIbanError::InvalidBaseIban { .. } =>
"the string does not follow the base IBAN rules",
ParseIbanError::InvalidBban(..) => "the IBAN doesn't have a correct BBAN",
ParseIbanError::UnknownCountry(..) => "the IBAN country code wasn't recognized",
}
)
}
}
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
impl Error for ParseIbanError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ParseIbanError::InvalidBaseIban { source } => Some(source),
_ => None,
}
}
}
impl<'a> TryFrom<&'a str> for Iban {
type Error = ParseIbanError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
value
.parse::<BaseIban>()
.map_err(|source| ParseIbanError::InvalidBaseIban { source })
.and_then(Iban::try_from)
}
}
impl TryFrom<BaseIban> for Iban {
type Error = ParseIbanError;
fn try_from(base_iban: BaseIban) -> Result<Iban, ParseIbanError> {
use core::borrow::Borrow;
use countries::{CharacterType::*, Matchable};
if let Some(matcher) = match base_iban.country_code() {
"AD" => Some([(4usize, N), (4, N), (12, C)].borrow()),
"AE" => Some([(3usize, N), (16, N)].borrow()),
"AL" => Some([(8usize, N), (16, C)].borrow()),
"AT" => Some([(5usize, N), (11, N)].borrow()),
"AZ" => Some([(4usize, A), (20, C)].borrow()),
"BA" => Some([(3usize, N), (3, N), (8, N), (2, N)].borrow()),
"BE" => Some([(3usize, N), (7, N), (2, N)].borrow()),
"BG" => Some([(4usize, A), (4, N), (2, N), (8, C)].borrow()),
"BH" => Some([(4usize, A), (14, C)].borrow()),
"BR" => Some([(8usize, N), (5, N), (10, N), (1, A), (1, C)].borrow()),
"BY" => Some([(4usize, C), (4, N), (16, C)].borrow()),
"CH" => Some([(5usize, N), (12, C)].borrow()),
"CR" => Some([(4usize, N), (14, N)].borrow()),
"CY" => Some([(3usize, N), (5, N), (16, C)].borrow()),
"CZ" => Some([(4usize, N), (6, N), (10, N)].borrow()),
"DE" => Some([(8usize, N), (10usize, N)].borrow()),
"DK" => Some([(4usize, N), (9, N), (1, N)].borrow()),
"DO" => Some([(4usize, C), (20, N)].borrow()),
"EE" => Some([(2usize, N), (2, N), (11, N), (1, N)].borrow()),
"EG" => Some([(4usize, N), (4, N), (17, N)].borrow()),
"ES" => Some([(4usize, N), (4, N), (1, N), (1, N), (10, N)].borrow()),
"FI" => Some([(3usize, N), (11, N)].borrow()),
"FO" => Some([(4usize, N), (9, N), (1, N)].borrow()),
"FR" => Some([(5usize, N), (5, N), (11, C), (2, N)].borrow()),
"GB" => Some([(4usize, A), (6, N), (8, N)].borrow()),
"GE" => Some([(2usize, A), (16, N)].borrow()),
"GI" => Some([(4usize, A), (15, C)].borrow()),
"GL" => Some([(4usize, N), (9, N), (1, N)].borrow()),
"GR" => Some([(3usize, N), (4, N), (16, C)].borrow()),
"GT" => Some([(4usize, C), (20, C)].borrow()),
"HR" => Some([(7usize, N), (10, N)].borrow()),
"HU" => Some([(3usize, N), (4, N), (1, N), (15, N), (1, N)].borrow()),
"IE" => Some([(4usize, A), (6, N), (8, N)].borrow()),
"IL" => Some([(3usize, N), (3, N), (13, N)].borrow()),
"IQ" => Some([(4usize, A), (3, N), (12, N)].borrow()),
"IS" => Some([(4usize, N), (2, N), (6, N), (10, N)].borrow()),
"IT" => Some([(1usize, A), (5, N), (5, N), (12, C)].borrow()),
"JO" => Some([(4usize, A), (4, N), (18, C)].borrow()),
"KW" => Some([(4usize, A), (22, C)].borrow()),
"KZ" => Some([(3usize, N), (13, C)].borrow()),
"LB" => Some([(4usize, N), (20, C)].borrow()),
"LC" => Some([(4usize, A), (24, C)].borrow()),
"LI" => Some([(5usize, N), (12, C)].borrow()),
"LT" => Some([(5usize, N), (11, N)].borrow()),
"LU" => Some([(3usize, N), (13, C)].borrow()),
"LV" => Some([(4usize, A), (13, C)].borrow()),
"MC" => Some([(5usize, N), (5, N), (11, C), (2, N)].borrow()),
"MD" => Some([(2usize, C), (18, C)].borrow()),
"ME" => Some([(3usize, N), (13, N), (2, N)].borrow()),
"MK" => Some([(3usize, N), (10, C), (2, N)].borrow()),
"MR" => Some([(5usize, N), (5, N), (11, N), (2, N)].borrow()),
"MT" => Some([(4usize, A), (5, N), (18, C)].borrow()),
"MU" => Some([(4usize, A), (2, N), (2, N), (12, N), (3, N), (3, A)].borrow()),
"NL" => Some([(4usize, A), (10, N)].borrow()),
"NO" => Some([(4usize, N), (6, N), (1, N)].borrow()),
"PK" => Some([(4usize, A), (16, C)].borrow()),
"PL" => Some([(8usize, N), (16, N)].borrow()),
"PS" => Some([(4usize, A), (21, C)].borrow()),
"PT" => Some([(4usize, N), (4, N), (11, N), (2, N)].borrow()),
"QA" => Some([(4usize, A), (21, C)].borrow()),
"RO" => Some([(4usize, A), (16, C)].borrow()),
"RS" => Some([(3usize, N), (13, N), (2, N)].borrow()),
"SA" => Some([(2usize, N), (18, C)].borrow()),
"SC" => Some([(4usize, A), (2, N), (2, N), (16, N), (3, A)].borrow()),
"SE" => Some([(3usize, N), (16, N), (1, N)].borrow()),
"SI" => Some([(5usize, N), (8, N), (2, N)].borrow()),
"SK" => Some([(4usize, N), (6, N), (10, N)].borrow()),
"SM" => Some([(1usize, A), (5, N), (5, N), (12, C)].borrow()),
"ST" => Some([(4usize, N), (4, N), (11, N), (2, N)].borrow()),
"SV" => Some([(4usize, A), (20, N)].borrow()),
"TL" => Some([(3usize, N), (14, N), (2, N)].borrow()),
"TN" => Some([(2usize, N), (3, N), (13, N), (2, N)].borrow()),
"TR" => Some([(5usize, N), (1, N), (16, C)].borrow()),
"UA" => Some([(6usize, N), (19, C)].borrow()),
"VA" => Some([(3usize, N), (15, N)].borrow()),
"VG" => Some([(4usize, A), (16, N)].borrow()),
"XK" => Some([(4usize, N), (10, N), (2, N)].borrow()),
_ => None,
} {
if (matcher as &[(_, _)]).match_str(base_iban.bban_unchecked()) {
Ok(Iban { base_iban })
} else {
Err(ParseIbanError::InvalidBban(base_iban))
}
} else {
Err(ParseIbanError::UnknownCountry(base_iban))
}
}
}
impl str::FromStr for Iban {
type Err = ParseIbanError;
fn from_str(address: &str) -> Result<Self, Self::Err> {
Iban::try_from(address)
}
}
#[cfg(feature = "serde")]
impl Serialize for Iban {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.base_iban.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Iban {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct IbanStringVisitor;
use serde::de;
impl<'vi> de::Visitor<'vi> for IbanStringVisitor {
type Value = Iban;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "an IBAN string")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Iban, E> {
value.parse::<Iban>().map_err(E::custom)
}
}
deserializer.deserialize_str(IbanStringVisitor)
}
}