iban/lib.rs
1#![doc = include_str!("../README.md")]
2#![doc(html_root_url = "https://docs.rs/iban_validate/5.0.1")]
3#![forbid(unsafe_code)]
4#![deny(missing_docs)]
5#![deny(bare_trait_objects)]
6#![deny(elided_lifetimes_in_paths)]
7#![deny(missing_debug_implementations)]
8#![no_std]
9
10use core::convert::TryFrom;
11use core::error::Error;
12use core::fmt::{self, Debug, Display};
13use core::str;
14
15mod base_iban;
16mod countries;
17mod generated;
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Deserializer, Serialize, Serializer};
20
21pub use base_iban::{BaseIban, ParseBaseIbanError};
22
23/// A trait that provide basic functions on an IBAN. It is implemented by both [`Iban`],
24/// which represents a fully validated IBAN, and [`BaseIban`], which might not have a correct BBAN.
25pub trait IbanLike {
26 /// Get the IBAN in the electronic format, without whitespace. This method
27 /// is simply a view into the inner string.
28 ///
29 /// # Example
30 /// ```rust
31 /// use iban::*;
32 /// let iban: Iban = "DE44 5001 0517 5407 3249 31".parse()?;
33 /// assert_eq!(iban.electronic_str(), "DE44500105175407324931");
34 /// # Ok::<(), ParseIbanError>(())
35 /// ```
36 #[must_use]
37 fn electronic_str(&self) -> &str;
38
39 /// Get the country code of the IBAN. This method simply returns a slice of
40 /// the inner representation.
41 ///
42 /// # Example
43 /// ```rust
44 /// use iban::*;
45 /// let iban: Iban = "DE44 5001 0517 5407 3249 31".parse()?;
46 /// assert_eq!(iban.country_code(), "DE");
47 /// # Ok::<(), ParseIbanError>(())
48 /// ```
49 #[inline]
50 #[must_use]
51 fn country_code(&self) -> &str {
52 &self.electronic_str()[0..2]
53 }
54
55 /// Get the check digits of the IBAN, as a string slice. This method simply returns
56 /// a slice of the inner representation. To obtain an integer instead,
57 /// use [`check_digits`](IbanLike::check_digits).
58 ///
59 /// # Example
60 /// ```rust
61 /// use iban::*;
62 /// let iban: Iban = "DE44 5001 0517 5407 3249 31".parse()?;
63 /// assert_eq!(iban.check_digits_str(), "44");
64 /// # Ok::<(), ParseIbanError>(())
65 /// ```
66 #[inline]
67 #[must_use]
68 fn check_digits_str(&self) -> &str {
69 &self.electronic_str()[2..4]
70 }
71
72 /// Get the check digits of the IBAN. This method parses the digits to an
73 /// integer, performing slightly more work than [`check_digits_str`](IbanLike::check_digits_str).
74 ///
75 /// # Example
76 /// ```rust
77 /// use iban::*;
78 /// let iban: Iban = "DE44 5001 0517 5407 3249 31".parse()?;
79 /// assert_eq!(iban.check_digits(), 44);
80 /// # Ok::<(), ParseIbanError>(())
81 /// ```
82 #[inline]
83 #[must_use]
84 fn check_digits(&self) -> u8 {
85 self.check_digits_str().parse().expect(
86 "Could not parse check digits. Please create an issue at \
87 https://github.com/ThomasdenH/iban_validate.",
88 )
89 }
90
91 /// Get the BBAN part of the IBAN, as a `&str`. Note that the BBAN is not
92 /// necessarily valid if this is not guaranteed by the implementing type.
93 /// Use [`Iban::bban`] to guarantee a correct BBAN.
94 ///
95 /// # Example
96 /// ```rust
97 /// use iban::*;
98 /// let iban: Iban = "DE44 5001 0517 5407 3249 31".parse()?;
99 /// assert_eq!(iban.bban_unchecked(), "500105175407324931");
100 /// # Ok::<(), ParseIbanError>(())
101 /// ```
102 #[inline]
103 #[must_use]
104 fn bban_unchecked(&self) -> &str {
105 &self.electronic_str()[4..]
106 }
107}
108
109impl IbanLike for Iban {
110 #[inline]
111 #[must_use]
112 fn electronic_str(&self) -> &str {
113 self.base_iban.electronic_str()
114 }
115}
116
117impl Iban {
118 /// Get the BBAN part of the IBAN, as a `&str`. This method, in contrast to [`IbanLike::bban_unchecked`],
119 /// is only available on the [`Iban`] structure, which means the returned BBAN string is always correct.
120 ///
121 /// # Example
122 /// ```rust
123 /// use iban::*;
124 /// let iban: Iban = "DE44 5001 0517 5407 3249 31".parse()?;
125 /// assert_eq!(iban.bban(), "500105175407324931");
126 /// # Ok::<(), ParseIbanError>(())
127 /// ```
128 #[inline]
129 #[must_use]
130 pub fn bban(&self) -> &str {
131 self.bban_unchecked()
132 }
133
134 /// Get the bank identifier of the IBAN. The bank identifier might not be
135 /// defined, in which case this method returns `None`.
136 ///
137 /// # Example
138 /// ```
139 /// use iban::*;
140 /// let iban: Iban = "AD12 0001 2030 2003 5910 0100".parse()?;
141 /// assert_eq!(iban.bank_identifier(), Some("0001"));
142 /// # Ok::<(), ParseIbanError>(())
143 /// ```
144 #[inline]
145 #[must_use]
146 pub fn bank_identifier(&self) -> Option<&str> {
147 generated::bank_identifier(self.country_code())
148 .map(|range| &self.electronic_str()[4..][range])
149 }
150
151 /// Get the branch identifier of the IBAN. The branch identifier might not be
152 /// defined, in which case this method returns `None`.
153 ///
154 /// # Example
155 /// ```
156 /// use iban::*;
157 /// let iban: Iban = "AD12 0001 2030 2003 5910 0100".parse()?;
158 /// assert_eq!(iban.branch_identifier(), Some("2030"));
159 /// # Ok::<(), ParseIbanError>(())
160 /// ```
161 #[must_use]
162 #[inline]
163 pub fn branch_identifier(&self) -> Option<&str> {
164 generated::branch_identifier(self.country_code())
165 .map(|range| &self.electronic_str()[4..][range])
166 }
167}
168
169impl From<Iban> for BaseIban {
170 #[inline]
171 #[must_use]
172 fn from(value: Iban) -> BaseIban {
173 value.base_iban
174 }
175}
176
177impl Debug for Iban {
178 #[inline]
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 Debug::fmt(&self.base_iban, f)
181 }
182}
183
184impl Display for Iban {
185 #[inline]
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 Display::fmt(&self.base_iban, f)
188 }
189}
190
191/// Represents an IBAN. To obtain it, make use of the [`parse()`] function, which will make sure the
192/// string follows the ISO 13616 standard. Apart from its own methods, `Iban` implements [`IbanLike`],
193/// which provides more functionality.
194///
195/// The impementation of [`Display`] provides spaced formatting of the IBAN. Electronic
196/// formatting can be obtained via [`electronic_str`](IbanLike::electronic_str).
197///
198/// A valid IBAN...
199/// - must start with two uppercase ASCII letters, followed
200/// by two digits, followed by any number of digits and ASCII
201/// letters.
202/// - must have a valid checksum.
203/// - must contain no whitespace, or be in the paper format, where
204/// characters are in space-separated groups of four.
205/// - must adhere to the country-specific format.
206///
207/// Sometimes it may be desirable to accept IBANs that do not have their
208/// country registered in the IBAN registry, or it may simply be unimportant
209/// whether the country's BBAN format was followed. In that case, you can use
210/// a [`BaseIban`] instead.
211///
212/// # Examples
213/// ```rust
214/// use iban::*;
215/// let address = "KZ86125KZT5004100100".parse::<iban::Iban>()?;
216/// assert_eq!(address.to_string(), "KZ86 125K ZT50 0410 0100");
217/// # Ok::<(), iban::ParseIbanError>(())
218/// ```
219///
220/// ## Formatting
221/// The IBAN specification describes two formats: an electronic format without
222/// whitespace and a paper format which seperates the IBAN in groups of
223/// four characters. Both will be parsed correctly by this crate. When
224/// formatting, [`Debug`] can be used to output the former and [`Display`] for
225/// the latter. This is true for a [`BaseIban`] as well as an [`Iban`].
226/// Alternatively, you can use [`IbanLike::electronic_str`] to obtain the
227/// electronic format as a string slice.
228/// ```
229/// # use iban::ParseIbanError;
230/// let iban: iban::Iban = "RO66BACX0000001234567890".parse()?;
231/// // Use Debug for the electronic format.
232/// assert_eq!(&format!("{:?}", iban), "RO66BACX0000001234567890");
233/// // Use Display for the pretty print format.
234/// assert_eq!(&format!("{}", iban), "RO66 BACX 0000 0012 3456 7890");
235/// # Ok::<(), ParseIbanError>(())
236/// ```
237/// [`parse()`]: https://doc.rust-lang.org/std/primitive.str.html#method.parse
238#[derive(Clone, Copy, Eq, PartialEq, Hash)]
239pub struct Iban {
240 /// The inner IBAN, which has been checked.
241 base_iban: BaseIban,
242}
243
244/// An error indicating the IBAN could not be parsed.
245///
246/// # Example
247/// ```rust
248/// use iban::{BaseIban, Iban, ParseIbanError, ParseBaseIbanError};
249/// use core::convert::TryFrom;
250///
251/// // The following IBAN has an invalid checksum
252/// assert_eq!(
253/// "MR00 0002 0001 0100 0012 3456 754".parse::<Iban>(),
254/// Err(ParseIbanError::from(ParseBaseIbanError::InvalidChecksum))
255/// );
256///
257/// // The following IBAN doesn't follow the country format
258/// let base_iban: BaseIban = "AL84212110090000AB023569874".parse()?;
259/// assert_eq!(
260/// Iban::try_from(base_iban),
261/// Err(ParseIbanError::InvalidBban(base_iban))
262/// );
263/// # Ok::<(), ParseBaseIbanError>(())
264/// ```
265#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
266#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
267pub enum ParseIbanError {
268 /// This variant indicates that the basic IBAN structure was not followed.
269 InvalidBaseIban {
270 /// The error indicating what went wrong when parsing the Iban.
271 source: ParseBaseIbanError,
272 },
273 /// This variant indicates that the BBAN did not follow the correct format.
274 /// The `BaseIban` provides functionality on the IBAN part of the
275 /// address.
276 InvalidBban(BaseIban),
277 /// This variant indicated that the country code of the IBAN was not recognized.
278 /// The `BaseIban` provides functionality on the IBAN part of the
279 /// address.
280 UnknownCountry(BaseIban),
281}
282
283impl From<ParseBaseIbanError> for ParseIbanError {
284 #[inline]
285 #[must_use]
286 fn from(source: ParseBaseIbanError) -> ParseIbanError {
287 ParseIbanError::InvalidBaseIban { source }
288 }
289}
290
291impl fmt::Display for ParseIbanError {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 write!(
294 f,
295 "{}",
296 match self {
297 ParseIbanError::InvalidBaseIban { .. } =>
298 "the string does not follow the base IBAN rules",
299 ParseIbanError::InvalidBban(..) => "the IBAN doesn't have a correct BBAN",
300 ParseIbanError::UnknownCountry(..) => "the IBAN country code wasn't recognized",
301 }
302 )
303 }
304}
305
306impl Error for ParseIbanError {
307 #[inline]
308 #[must_use]
309 fn source(&self) -> Option<&(dyn Error + 'static)> {
310 match self {
311 ParseIbanError::InvalidBaseIban { source } => Some(source),
312 _ => None,
313 }
314 }
315}
316
317impl<'a> TryFrom<&'a str> for Iban {
318 type Error = ParseIbanError;
319 /// Parse an IBAN without taking the BBAN into consideration.
320 ///
321 /// # Errors
322 /// If the string does not match the IBAN format or the checksum is
323 /// invalid, [`ParseIbanError::InvalidBaseIban`] will be
324 /// returned. If the country format is invalid or unknown, the other
325 /// variants will be returned with the [`BaseIban`] giving
326 /// access to some basic functionality nonetheless.
327 #[inline]
328 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
329 value
330 .parse::<BaseIban>()
331 .map_err(|source| ParseIbanError::InvalidBaseIban { source })
332 .and_then(Iban::try_from)
333 }
334}
335
336impl TryFrom<BaseIban> for Iban {
337 type Error = ParseIbanError;
338 /// Parse an IBAN without taking the BBAN into consideration.
339 ///
340 /// # Errors
341 /// If the string does not match the IBAN format or the checksum is
342 /// invalid, [`ParseIbanError::InvalidBaseIban`] will be
343 /// returned. If the country format is invalid or unknown, the other
344 /// variants will be returned with the [`BaseIban`] giving
345 /// access to some basic functionality nonetheless.
346 fn try_from(base_iban: BaseIban) -> Result<Iban, ParseIbanError> {
347 use countries::Matchable;
348 generated::country_pattern(base_iban.country_code())
349 .ok_or(ParseIbanError::UnknownCountry(base_iban))
350 .and_then(|matcher: &[(usize, _)]| {
351 if matcher.match_str(base_iban.bban_unchecked()) {
352 Ok(Iban { base_iban })
353 } else {
354 Err(ParseIbanError::InvalidBban(base_iban))
355 }
356 })
357 }
358}
359
360impl str::FromStr for Iban {
361 type Err = ParseIbanError;
362 #[inline]
363 fn from_str(address: &str) -> Result<Self, Self::Err> {
364 Iban::try_from(address)
365 }
366}
367
368#[cfg(feature = "serde")]
369impl Serialize for Iban {
370 #[inline]
371 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
372 self.base_iban.serialize(serializer)
373 }
374}
375
376#[cfg(feature = "serde")]
377impl<'de> Deserialize<'de> for Iban {
378 #[must_use]
379 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
380 struct IbanStringVisitor;
381 use serde::de;
382
383 impl<'vi> de::Visitor<'vi> for IbanStringVisitor {
384 type Value = Iban;
385
386 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
387 write!(formatter, "an IBAN string")
388 }
389
390 fn visit_str<E: de::Error>(self, value: &str) -> Result<Iban, E> {
391 value.parse::<Iban>().map_err(E::custom)
392 }
393 }
394
395 deserializer.deserialize_str(IbanStringVisitor)
396 }
397}