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}