ivms101/
lib.rs

1//! # Intervasp Messaging Standard 101 Rust library
2//!
3//! This crate provides functionality for working with data payloads
4//! defined in the [Intervasp Messaging Standard 101](https://intervasp.org/).
5//!
6//! ```
7//! use ivms101::Validatable;
8//!
9//! let person = ivms101::NaturalPerson::new("John", "Doe", Some("id-273934"), None).unwrap();
10//! assert!(person.validate().is_ok());
11//! ```
12
13pub use country_codes::{country, CountryCode};
14pub use types::{one_to_n::OneToN, zero_to_n::ZeroToN};
15
16mod country_codes;
17mod types;
18
19use lei::registration_authority::RegistrationAuthority;
20
21/// The main IVMS101 data structure.
22#[derive(serde::Serialize, serde::Deserialize)]
23#[serde(rename_all = "camelCase")]
24#[serde(deny_unknown_fields)]
25pub struct IVMS101 {
26    /// The originator of the transaction.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub originator: Option<Originator>,
29    /// The beneficiary of the transaction.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub beneficiary: Option<Beneficiary>,
32    /// The originating VASP.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    #[serde(rename = "originatingVASP")]
35    pub originating_vasp: Option<OriginatingVASP>,
36    /// The beneficiary VASP.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    #[serde(rename = "beneficiaryVASP")]
39    pub beneficiary_vasp: Option<BeneficiaryVASP>,
40}
41
42impl Validatable for IVMS101 {
43    fn validate(&self) -> Result<(), Error> {
44        if let Some(o) = &self.originator {
45            o.validate()?;
46        }
47        if let Some(b) = &self.beneficiary {
48            b.validate()?;
49        }
50        if let Some(ov) = &self.originating_vasp {
51            ov.validate()?;
52        }
53        if let Some(bv) = &self.beneficiary_vasp {
54            bv.validate()?;
55        }
56        Ok(())
57    }
58}
59
60/// The transaction originator.
61#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
62#[serde(rename_all = "camelCase")]
63#[serde(deny_unknown_fields)]
64pub struct Originator {
65    /// The persons forming the originator.
66    pub originator_persons: OneToN<Person>,
67    /// The account number of the originator.
68    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
69    pub account_number: ZeroToN<types::StringMax100>,
70}
71
72impl Validatable for Originator {
73    fn validate(&self) -> Result<(), Error> {
74        for person in self.originator_persons.clone() {
75            if let Person::NaturalPerson(np) = &person {
76                if np.geographic_address.is_empty()
77                    && np.customer_identification.is_none()
78                    && np.national_identification.is_none()
79                    && np.date_and_place_of_birth.is_none()
80                {
81                    return Err(
82                        "Natural person: one of 1) geographic address 2) customer id 3) national id 4) date and place of birth is required (IVMS101 C1)".into());
83                }
84            };
85            person.validate()?;
86        }
87        Ok(())
88    }
89}
90
91impl Originator {
92    /// Constructs an `Originator` with the given person.
93    ///
94    /// # Errors
95    ///
96    /// Returns a [`Error`] if the validation fails.
97    pub fn new(person: Person) -> Result<Self, Error> {
98        Ok(Self {
99            originator_persons: person.into(),
100            account_number: None.into(),
101        })
102    }
103}
104
105/// The transaction beneficiary.
106#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
107#[serde(rename_all = "camelCase")]
108#[serde(deny_unknown_fields)]
109pub struct Beneficiary {
110    /// The persons forming the beneficiary.
111    pub beneficiary_persons: OneToN<Person>,
112    /// The account number of the beneficiary.
113    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
114    pub account_number: ZeroToN<types::StringMax100>,
115}
116
117impl Validatable for Beneficiary {
118    fn validate(&self) -> Result<(), Error> {
119        for person in self.beneficiary_persons.clone() {
120            person.validate()?;
121        }
122        Ok(())
123    }
124}
125
126impl Beneficiary {
127    /// Constructs a `Beneficiary` with the given person and account number.
128    ///
129    /// # Errors
130    ///
131    /// Returns a [`Error`] if the validation of the account number fails.
132    pub fn new(person: Person, account_number: Option<&str>) -> Result<Self, Error> {
133        Ok(Self {
134            beneficiary_persons: person.into(),
135            account_number: account_number.map(TryInto::try_into).transpose()?.into(),
136        })
137    }
138}
139
140/// The originating VASP wrapper.
141#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
142#[serde(deny_unknown_fields)]
143pub struct OriginatingVASP {
144    /// The originating VASP.
145    #[serde(rename = "originatingVASP")]
146    pub originating_vasp: Person,
147}
148
149impl OriginatingVASP {
150    /// Constructs an `OriginatingVASP` with the given name and LEI.
151    ///
152    /// # Errors
153    ///
154    /// Returns a `Error` if the validation of the name fails.
155    pub fn new(name: &str, lei: &lei::LEI) -> Result<Self, Error> {
156        Ok(Self {
157            originating_vasp: Person::LegalPerson(LegalPerson {
158                name: LegalPersonName {
159                    name_identifier: LegalPersonNameID {
160                        legal_person_name: name.try_into()?,
161                        legal_person_name_identifier_type: LegalPersonNameTypeCode::Legal,
162                    }
163                    .into(),
164                    local_name_identifier: None.into(),
165                    phonetic_name_identifier: None.into(),
166                },
167                geographic_address: ZeroToN::None,
168                customer_identification: None,
169                national_identification: Some(NationalIdentification {
170                    national_identifier: lei.to_string().as_str().try_into().unwrap(),
171                    national_identifier_type: NationalIdentifierTypeCode::LegalEntityIdentifier,
172                    country_of_issue: None,
173                    registration_authority: None,
174                }),
175                country_of_registration: None,
176            }),
177        })
178    }
179
180    /// Returns the LEI of the originating VASP
181    ///
182    /// # Errors
183    ///
184    /// Returns an error if the national identification
185    /// of the legal person is not a valid LEI.
186    pub fn lei(&self) -> Result<Option<lei::LEI>, lei::Error> {
187        self.originating_vasp.lei()
188    }
189}
190
191impl Validatable for OriginatingVASP {
192    fn validate(&self) -> Result<(), Error> {
193        self.originating_vasp.validate()
194    }
195}
196
197/// The beneficiary VASP wrapper.
198#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
199#[serde(deny_unknown_fields)]
200pub struct BeneficiaryVASP {
201    /// The beneficiary VASP.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    #[serde(rename = "beneficiaryVASP")]
204    pub beneficiary_vasp: Option<Person>,
205}
206
207impl Validatable for BeneficiaryVASP {
208    fn validate(&self) -> Result<(), Error> {
209        match &self.beneficiary_vasp {
210            None => Ok(()),
211            Some(p) => p.validate(),
212        }
213    }
214}
215
216/// Either a natural or a legal person.
217#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
218#[serde(rename_all = "camelCase")]
219#[serde(deny_unknown_fields)]
220pub enum Person {
221    NaturalPerson(NaturalPerson),
222    LegalPerson(LegalPerson),
223}
224
225impl Person {
226    /// The first name of the person.
227    #[must_use]
228    pub fn first_name(&self) -> Option<String> {
229        match self {
230            Self::NaturalPerson(p) => p.first_name(),
231            Self::LegalPerson(_p) => None,
232        }
233    }
234
235    /// The last name of the person.
236    #[must_use]
237    pub fn last_name(&self) -> String {
238        match self {
239            Self::NaturalPerson(p) => p.last_name(),
240            Self::LegalPerson(p) => p.name(),
241        }
242    }
243
244    /// The address of the person.
245    #[must_use]
246    pub fn address(&self) -> Option<&Address> {
247        match self {
248            Self::NaturalPerson(p) => p.address(),
249            Self::LegalPerson(p) => p.address(),
250        }
251    }
252
253    /// The customer identification of the person.
254    #[must_use]
255    pub fn customer_identification(&self) -> Option<String> {
256        match self {
257            Self::NaturalPerson(p) => p.customer_identification.clone().map(|s| s.to_string()),
258            Self::LegalPerson(p) => p.customer_identification.clone().map(|s| s.to_string()),
259        }
260    }
261
262    /// For legal persons, returns their LEI. Returns `None`
263    /// for natural persons.
264    pub fn lei(&self) -> Result<Option<lei::LEI>, lei::Error> {
265        match self {
266            Self::NaturalPerson(_) => Ok(None),
267            Self::LegalPerson(l) => l.lei(),
268        }
269    }
270}
271
272impl Validatable for Person {
273    fn validate(&self) -> Result<(), Error> {
274        match self {
275            Person::NaturalPerson(p) => p.validate(),
276            Person::LegalPerson(p) => p.validate(),
277        }
278    }
279}
280
281/// A natural person.
282#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
283#[serde(rename_all = "camelCase")]
284#[serde(deny_unknown_fields)]
285pub struct NaturalPerson {
286    /// The name.
287    pub name: OneToN<NaturalPersonName>,
288    /// The geographic address.
289    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
290    pub geographic_address: ZeroToN<Address>,
291    /// The national identification.
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub national_identification: Option<NationalIdentification>,
294    /// The customer identification.
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub customer_identification: Option<types::StringMax50>,
297    /// The date and place of birth.
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub date_and_place_of_birth: Option<DateAndPlaceOfBirth>,
300    /// The country of residence.
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub country_of_residence: Option<CountryCode>,
303}
304
305impl NaturalPerson {
306    /// Constructs a `NaturalPerson`.
307    ///
308    /// # Errors
309    ///
310    /// Returns an error if the validation of the first name, last name
311    /// or customer identification fails.
312    pub fn new(
313        first_name: &str,
314        last_name: &str,
315        customer_identification: Option<&str>,
316        address: Option<Address>,
317    ) -> Result<Self, Error> {
318        Ok(Self {
319            name: NaturalPersonName {
320                name_identifier: NaturalPersonNameID {
321                    primary_identifier: last_name.try_into()?,
322                    secondary_identifier: Some(first_name.try_into()?),
323                    name_identifier_type: NaturalPersonNameTypeCode::LegalName,
324                }
325                .into(),
326                local_name_identifier: None.into(),
327                phonetic_name_identifier: None.into(),
328            }
329            .into(),
330            geographic_address: address.into(),
331            national_identification: None,
332            customer_identification: customer_identification.map(TryInto::try_into).transpose()?,
333            date_and_place_of_birth: None,
334            country_of_residence: None,
335        })
336    }
337
338    #[must_use]
339    fn first_name(&self) -> Option<String> {
340        Some(
341            self.name
342                .first()
343                .name_identifier
344                .first()
345                .clone()
346                .secondary_identifier?
347                .into(),
348        )
349    }
350
351    #[must_use]
352    fn last_name(&self) -> String {
353        self.name
354            .first()
355            .name_identifier
356            .first()
357            .primary_identifier
358            .to_string()
359    }
360
361    #[must_use]
362    fn address(&self) -> Option<&Address> {
363        self.geographic_address.first()
364    }
365}
366
367impl Validatable for NaturalPerson {
368    fn validate(&self) -> Result<(), Error> {
369        self.name
370            .clone()
371            .into_iter()
372            .try_for_each(|name| name.validate())?;
373        self.geographic_address
374            .clone()
375            .into_iter()
376            .try_for_each(|addr| addr.validate())?;
377
378        Ok(())
379    }
380}
381
382/// The name of a natural person.
383#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
384#[serde(rename_all = "camelCase")]
385#[serde(deny_unknown_fields)]
386pub struct NaturalPersonName {
387    /// The name.
388    pub name_identifier: OneToN<NaturalPersonNameID>,
389    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
390    pub local_name_identifier: ZeroToN<NaturalPersonNameID>,
391    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
392    pub phonetic_name_identifier: ZeroToN<NaturalPersonNameID>,
393}
394
395impl Validatable for NaturalPersonName {
396    fn validate(&self) -> Result<(), Error> {
397        let has_legl = self
398            .name_identifier
399            .clone()
400            .into_iter()
401            .any(|ni| ni.name_identifier_type == NaturalPersonNameTypeCode::LegalName);
402        if !has_legl {
403            return Err("Natural person must have a legal name id (IVMS101 C6)".into());
404        }
405        Ok(())
406    }
407}
408
409/// The natural person name ID.
410#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
411#[serde(rename_all = "camelCase")]
412#[serde(deny_unknown_fields)]
413pub struct NaturalPersonNameID {
414    /// The primary name.
415    pub primary_identifier: types::StringMax100,
416    #[serde(skip_serializing_if = "Option::is_none")]
417    /// The secondary name.
418    pub secondary_identifier: Option<types::StringMax100>,
419    /// The type of name.
420    pub name_identifier_type: NaturalPersonNameTypeCode,
421}
422
423/// A localized natural person name.
424#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
425#[serde(rename_all = "camelCase")]
426#[serde(deny_unknown_fields)]
427pub struct Address {
428    /// The address type.
429    pub address_type: AddressTypeCode,
430    /// The department.
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub department: Option<types::StringMax50>,
433    /// The sub-department.
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub sub_department: Option<types::StringMax70>,
436    /// The street name.
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub street_name: Option<types::StringMax70>,
439    /// The building number.
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub building_number: Option<types::StringMax16>,
442    /// The building name.
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub building_name: Option<types::StringMax35>,
445    /// The floor.
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub floor: Option<types::StringMax70>,
448    /// The post box.
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub post_box: Option<types::StringMax16>,
451    /// The room.
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub room: Option<types::StringMax70>,
454    /// The postal code.
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub post_code: Option<types::StringMax16>,
457    /// The name of the town.
458    pub town_name: types::StringMax35,
459    /// The town location name.
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub town_location_name: Option<types::StringMax35>,
462    /// The district name.
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub district_name: Option<types::StringMax35>,
465    /// The country sub-division.
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub country_sub_division: Option<types::StringMax35>,
468    /// The address lines.
469    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
470    pub address_line: ZeroToN<types::StringMax70>,
471    /// The country.
472    pub country: CountryCode,
473}
474
475impl Address {
476    /// Constructs an `Address`.
477    ///
478    /// # Errors
479    ///
480    /// Returns an error if the validation of the passed arguments fails.
481    pub fn new(
482        street: Option<&str>,
483        number: Option<&str>,
484        address_line: Option<&str>,
485        postal_code: &str,
486        town: &str,
487        country: &str,
488    ) -> Result<Self, Error> {
489        Ok(Self {
490            address_type: AddressTypeCode::Residential,
491            department: None,
492            sub_department: None,
493            street_name: street.map(TryInto::try_into).transpose()?,
494            building_number: number.map(TryInto::try_into).transpose()?,
495            building_name: None,
496            floor: None,
497            post_box: None,
498            room: None,
499            post_code: Some(postal_code.try_into()?),
500            town_name: town.try_into()?,
501            town_location_name: None,
502            district_name: None,
503            country_sub_division: None,
504            address_line: address_line.map(TryInto::try_into).transpose()?.into(),
505            country: country.try_into()?,
506        })
507    }
508
509    /// Returns a string where all address lines have
510    /// been joined with a comma.
511    #[must_use]
512    pub fn address_lines(&self) -> Option<String> {
513        if self.address_line.is_empty() {
514            None
515        } else {
516            Some(
517                self.address_line
518                    .clone()
519                    .into_iter()
520                    .map(Into::into)
521                    .collect::<Vec<String>>()
522                    .join(", "),
523            )
524        }
525    }
526}
527
528impl std::fmt::Display for Address {
529    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
530        format_address(
531            f,
532            self.street_name.as_ref().map(types::StringMax70::as_str),
533            self.building_number
534                .as_ref()
535                .map(types::StringMax16::as_str),
536            self.address_lines().as_deref(),
537            self.post_code.as_ref().map(types::StringMax16::as_str),
538            self.town_name.as_str(),
539            self.country.as_str(),
540        )
541    }
542}
543
544/// Formats the address into a single formatter.
545///
546/// Will smartly handle absent parts to join everything
547/// into a comma-delimited string.
548pub fn format_address(
549    f: &mut std::fmt::Formatter,
550    street: Option<&str>,
551    number: Option<&str>,
552    address_line: Option<&str>,
553    postcode: Option<&str>,
554    town: &str,
555    country_code: &str,
556) -> std::fmt::Result {
557    if let Some(s) = street {
558        write!(f, "{s}")?;
559        if let Some(n) = number {
560            write!(f, " {n}")?;
561        }
562        write!(f, ", ")?;
563    }
564    if let Some(al) = address_line {
565        write!(f, "{al}, ")?;
566    }
567    if let Some(pc) = postcode {
568        write!(f, "{pc} ")?;
569    }
570    write!(
571        f,
572        "{town}, {}",
573        country(country_code.to_lowercase().as_str()).unwrap_or(country_code)
574    )
575}
576
577impl Validatable for Address {
578    fn validate(&self) -> Result<(), Error> {
579        if self.address_line.is_empty()
580            && (self.street_name.is_none()
581                || (self.building_name.is_none() && self.building_number.is_none()))
582        {
583            return Err("Either 1) address line or 2) street name and either building name or building number are required (IVMS101 C8)".into());
584        }
585        Ok(())
586    }
587}
588
589/// The date and place of birth.
590#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
591#[serde(rename_all = "camelCase")]
592#[serde(deny_unknown_fields)]
593pub struct DateAndPlaceOfBirth {
594    /// The date of birth.
595    pub date_of_birth: Date,
596    /// The place of birth.
597    pub place_of_birth: types::StringMax70,
598}
599
600impl Validatable for DateAndPlaceOfBirth {
601    fn validate(&self) -> Result<(), Error> {
602        if self.date_of_birth >= chrono::prelude::Utc::now().date_naive() {
603            return Err("Date of birth must be in the past (IVMS101 C2)".into());
604        }
605        Ok(())
606    }
607}
608
609/// National identification information.
610#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
611#[serde(rename_all = "camelCase")]
612#[serde(deny_unknown_fields)]
613pub struct NationalIdentification {
614    /// The national identifier.
615    pub national_identifier: types::StringMax35,
616    /// The national identifier type.
617    pub national_identifier_type: NationalIdentifierTypeCode,
618    /// The country of issuance.
619    #[serde(skip_serializing_if = "Option::is_none")]
620    pub country_of_issue: Option<CountryCode>,
621    /// The registration authority.
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub registration_authority: Option<RegistrationAuthority>,
624}
625
626/// A legal person.
627#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
628#[serde(rename_all = "camelCase")]
629#[serde(deny_unknown_fields)]
630pub struct LegalPerson {
631    /// The name of the legal person.
632    pub name: LegalPersonName,
633    /// The address.
634    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
635    pub geographic_address: ZeroToN<Address>,
636    /// The customer identification.
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub customer_identification: Option<types::StringMax50>,
639    /// The national identification.
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub national_identification: Option<NationalIdentification>,
642    /// The country of registration.
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub country_of_registration: Option<CountryCode>,
645}
646
647impl LegalPerson {
648    /// Constructs a `LegalPerson`.
649    ///
650    /// # Errors
651    ///
652    /// Returns an error if the validation of the name or customer identificaiton
653    /// fails.
654    pub fn new(
655        name: &str,
656        customer_identification: &str,
657        address: Address,
658        lei: &lei::LEI,
659    ) -> Result<Self, Error> {
660        Ok(Self {
661            name: LegalPersonName {
662                name_identifier: LegalPersonNameID {
663                    legal_person_name: name.try_into()?,
664                    legal_person_name_identifier_type: LegalPersonNameTypeCode::Legal,
665                }
666                .into(),
667                local_name_identifier: None.into(),
668                phonetic_name_identifier: None.into(),
669            },
670            geographic_address: Some(address).into(),
671            customer_identification: Some(customer_identification.try_into()?),
672            national_identification: Some(NationalIdentification {
673                national_identifier: lei.to_string().as_str().try_into().unwrap(),
674                national_identifier_type: NationalIdentifierTypeCode::LegalEntityIdentifier,
675                country_of_issue: None,
676                registration_authority: None,
677            }),
678            country_of_registration: None,
679        })
680    }
681
682    fn lei(&self) -> Result<Option<lei::LEI>, lei::Error> {
683        self.national_identification
684            .as_ref()
685            .map(|ni| lei::LEI::try_from(ni.national_identifier.to_string().as_str()))
686            .transpose()
687    }
688}
689
690impl LegalPerson {
691    #[must_use]
692    fn name(&self) -> String {
693        self.name
694            .name_identifier
695            .first()
696            .legal_person_name
697            .to_string()
698    }
699
700    #[must_use]
701    fn address(&self) -> Option<&Address> {
702        self.geographic_address.first()
703    }
704}
705
706impl Validatable for LegalPerson {
707    fn validate(&self) -> Result<(), Error> {
708        let has_geog = self
709            .geographic_address
710            .clone()
711            .into_iter()
712            .any(|addr| addr.address_type == AddressTypeCode::Residential);
713        if !has_geog
714            && self.national_identification.is_none()
715            && self.customer_identification.is_none()
716        {
717            return Err(
718                "Legal person needs either geographic address, customer number or national identification (IVMS101 C4)"
719                    .into(),
720            );
721        }
722        if let Some(ni) = &self.national_identification {
723            if !matches!(
724                ni.national_identifier_type,
725                NationalIdentifierTypeCode::RegistrationAuthorityIdentifier
726                    | NationalIdentifierTypeCode::Unspecified
727                    | NationalIdentifierTypeCode::LegalEntityIdentifier
728                    | NationalIdentifierTypeCode::TaxIdentificationNumber
729            ) {
730                return Err("Legal person must have a 'RAID', 'MISC', 'LEIX' or 'TXID' identification (IVMS101 C7)".into());
731            }
732        };
733        if let Some(ni) = &self.national_identification {
734            if ni.national_identifier_type == NationalIdentifierTypeCode::LegalEntityIdentifier {
735                if let Err(e) = lei::LEI::try_from(ni.national_identifier.as_str()) {
736                    return Err(format!("Invalid LEI: {e} (IVMS101 C11)").as_str().into());
737                }
738            }
739        };
740        self.name.validate()?;
741        self.geographic_address
742            .clone()
743            .into_iter()
744            .try_for_each(|addr| addr.validate())?;
745        match &self.national_identification {
746            Some(ni) => {
747                if ni.country_of_issue.is_some() {
748                    return Err("Legal person must not have a country of issue (IVMS101 C9)".into());
749                }
750                if ni.national_identifier_type != NationalIdentifierTypeCode::LegalEntityIdentifier
751                    && ni.registration_authority.is_none()
752                {
753                    return Err("Legal person must specify registration authority for non-'LEIX' identification (IVMS101 C9)".into());
754                }
755                if ni.national_identifier_type == NationalIdentifierTypeCode::LegalEntityIdentifier
756                    && ni.registration_authority.is_some()
757                {
758                    return Err("Legal person must not specify registration authority for 'LEIX' identification (IVMS101 C9)".into());
759                }
760            }
761            None => (),
762        }
763        Ok(())
764    }
765}
766
767/// The name of a legal person.
768#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
769#[serde(rename_all = "camelCase")]
770#[serde(deny_unknown_fields)]
771pub struct LegalPersonName {
772    /// The primary name identifier.
773    pub name_identifier: OneToN<LegalPersonNameID>,
774    /// The localized version of the name.
775    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
776    pub local_name_identifier: ZeroToN<LegalPersonNameID>,
777    /// The phonetic version of the name.
778    #[serde(default, skip_serializing_if = "ZeroToN::is_empty")]
779    pub phonetic_name_identifier: ZeroToN<LegalPersonNameID>,
780}
781
782impl Validatable for LegalPersonName {
783    fn validate(&self) -> Result<(), Error> {
784        let has_legl = self
785            .name_identifier
786            .clone()
787            .into_iter()
788            .any(|ni| ni.legal_person_name_identifier_type == LegalPersonNameTypeCode::Legal);
789        if !has_legl {
790            return Err("Legal person must have a legal name id (IVMS101 C5)".into());
791        }
792        Ok(())
793    }
794}
795
796/// A legal person name ID.
797#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
798#[serde(rename_all = "camelCase")]
799#[serde(deny_unknown_fields)]
800pub struct LegalPersonNameID {
801    /// The legal person name.
802    pub legal_person_name: types::StringMax100,
803    /// The type of name.
804    pub legal_person_name_identifier_type: LegalPersonNameTypeCode,
805}
806
807/// An intermediary VASP.
808#[derive(serde::Serialize, serde::Deserialize)]
809#[serde(rename_all = "camelCase")]
810#[serde(deny_unknown_fields)]
811pub struct IntermediaryVASP {
812    /// The intermediary VASP person.
813    pub intermediary_vasp: Person,
814    /// The sequence number.
815    pub sequence: u32,
816}
817
818// Validating C12 (sequentialIntegrity) requires surrounding context
819impl Validatable for IntermediaryVASP {
820    fn validate(&self) -> Result<(), Error> {
821        self.intermediary_vasp.validate()?;
822        Ok(())
823    }
824}
825
826/// The type of natural person name.
827#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
828pub enum NaturalPersonNameTypeCode {
829    #[serde(rename = "ALIA")]
830    Alias,
831    #[serde(rename = "BIRT")]
832    NameAtBirth,
833    #[serde(rename = "MAID")]
834    MaidenName,
835    #[serde(rename = "LEGL")]
836    LegalName,
837    #[serde(rename = "MISC")]
838    Unspecified,
839}
840
841/// The type of legal person name.
842#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
843pub enum LegalPersonNameTypeCode {
844    #[serde(rename = "LEGL")]
845    Legal,
846    #[serde(rename = "SHRT")]
847    Short,
848    #[serde(rename = "TRAD")]
849    Trading,
850}
851
852type Date = chrono::NaiveDate;
853
854/// The type of address.
855#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
856pub enum AddressTypeCode {
857    #[serde(rename = "HOME")]
858    Residential,
859    #[serde(rename = "BIZZ")]
860    Business,
861    #[serde(rename = "GEOG")]
862    Geographic,
863}
864
865/// The type of national identifier.
866#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
867pub enum NationalIdentifierTypeCode {
868    #[serde(rename = "ARNU")]
869    AlienRegistrationNumber,
870    #[serde(rename = "CCPT")]
871    PassportNumber,
872    #[serde(rename = "RAID")]
873    RegistrationAuthorityIdentifier,
874    #[serde(rename = "DRLC")]
875    DriverLicenseNumber,
876    #[serde(rename = "FIIN")]
877    ForeignInvestmentIdentityNumber,
878    #[serde(rename = "TXID")]
879    TaxIdentificationNumber,
880    #[serde(rename = "SOCS")]
881    SocialSecurityNumber,
882    #[serde(rename = "IDCD")]
883    IdentityCardNumber,
884    #[serde(rename = "LEIX")]
885    LegalEntityIdentifier,
886    #[serde(rename = "MISC")]
887    Unspecified,
888}
889
890/// Implements validation for a data structure according
891/// to the rules of the IVMS101 standard.
892pub trait Validatable {
893    fn validate(&self) -> Result<(), Error>;
894}
895
896/// An error while validating an IVMS data structure.
897#[derive(thiserror::Error, Debug, PartialEq, Eq)]
898pub enum Error {
899    #[error("Validation error: {0}")]
900    ValidationError(String),
901    #[error("invalid country code: {0}")]
902    InvalidCountryCode(String),
903}
904
905impl From<&str> for Error {
906    fn from(value: &str) -> Self {
907        Self::ValidationError(value.to_owned())
908    }
909}
910
911#[cfg(test)]
912mod tests {
913    use super::*;
914    use serde_test::{assert_tokens, Token};
915
916    impl NaturalPerson {
917        fn mock() -> Self {
918            Self {
919                name: NaturalPersonName::mock().into(),
920                geographic_address: None.into(),
921                national_identification: None,
922                customer_identification: None,
923                date_and_place_of_birth: None,
924                country_of_residence: None,
925            }
926        }
927    }
928
929    impl LegalPerson {
930        fn mock() -> Self {
931            Self {
932                name: LegalPersonName::mock(),
933                geographic_address: None.into(),
934                customer_identification: None,
935                national_identification: None,
936                country_of_registration: None,
937            }
938        }
939    }
940
941    impl LegalPersonName {
942        fn mock() -> Self {
943            Self {
944                name_identifier: LegalPersonNameID::mock().into(),
945                local_name_identifier: None.into(),
946                phonetic_name_identifier: None.into(),
947            }
948        }
949    }
950
951    impl LegalPersonNameID {
952        fn mock() -> Self {
953            Self {
954                legal_person_name: "Company A".try_into().unwrap(),
955                legal_person_name_identifier_type: LegalPersonNameTypeCode::Legal,
956            }
957        }
958    }
959
960    impl NationalIdentification {
961        fn mock() -> Self {
962            Self {
963                national_identifier: "id".try_into().unwrap(),
964                national_identifier_type: NationalIdentifierTypeCode::Unspecified,
965                country_of_issue: None,
966                registration_authority: Some("RA000001".try_into().unwrap()),
967            }
968        }
969    }
970
971    impl Address {
972        fn mock() -> Self {
973            Self {
974                address_type: AddressTypeCode::Residential,
975                department: None,
976                sub_department: None,
977                street_name: None,
978                building_number: None,
979                building_name: None,
980                floor: None,
981                post_box: None,
982                room: None,
983                post_code: None,
984                town_name: "Zurich".try_into().unwrap(),
985                town_location_name: None,
986                district_name: None,
987                country_sub_division: None,
988                address_line: Some("Main street".try_into().unwrap()).into(),
989                country: "CH".try_into().unwrap(),
990            }
991        }
992    }
993
994    impl NaturalPersonNameID {
995        fn mock() -> Self {
996            Self {
997                primary_identifier: "Engels".try_into().unwrap(),
998                secondary_identifier: Some("Friedrich".try_into().unwrap()),
999                name_identifier_type: NaturalPersonNameTypeCode::LegalName,
1000            }
1001        }
1002    }
1003
1004    impl NaturalPersonName {
1005        fn mock() -> Self {
1006            Self {
1007                name_identifier: NaturalPersonNameID::mock().into(),
1008                local_name_identifier: None.into(),
1009                phonetic_name_identifier: None.into(),
1010            }
1011        }
1012    }
1013
1014    impl DateAndPlaceOfBirth {
1015        fn mock() -> Self {
1016            Self {
1017                date_of_birth: chrono::NaiveDate::from_ymd_opt(1946, 11, 5).unwrap(),
1018                place_of_birth: "London".try_into().unwrap(),
1019            }
1020        }
1021    }
1022
1023    #[test]
1024    fn test_date() {
1025        assert_tokens(
1026            &Date::from_ymd_opt(2018, 11, 5).unwrap(),
1027            &[Token::String("2018-11-05")],
1028        );
1029    }
1030
1031    #[test]
1032    fn test_type_codes() {
1033        assert_tokens(
1034            &NaturalPersonNameTypeCode::Alias,
1035            &[Token::UnitVariant {
1036                name: "NaturalPersonNameTypeCode",
1037                variant: "ALIA",
1038            }],
1039        );
1040        assert_tokens(
1041            &LegalPersonNameTypeCode::Legal,
1042            &[Token::UnitVariant {
1043                name: "LegalPersonNameTypeCode",
1044                variant: "LEGL",
1045            }],
1046        );
1047        assert_tokens(
1048            &AddressTypeCode::Business,
1049            &[Token::UnitVariant {
1050                name: "AddressTypeCode",
1051                variant: "BIZZ",
1052            }],
1053        );
1054        assert_tokens(
1055            &NationalIdentifierTypeCode::AlienRegistrationNumber,
1056            &[Token::UnitVariant {
1057                name: "NationalIdentifierTypeCode",
1058                variant: "ARNU",
1059            }],
1060        );
1061    }
1062
1063    fn match_validation_error(val: &impl Validatable, code: u8) {
1064        let res = val.validate();
1065        assert!(res
1066            .unwrap_err()
1067            .to_string()
1068            .ends_with(format!("(IVMS101 C{code})").as_str()));
1069    }
1070
1071    #[test]
1072    fn test_person_serialization() {
1073        let person = Person::NaturalPerson(NaturalPerson::mock());
1074        let serialized = serde_json::to_string(&person).unwrap();
1075        assert_eq!(
1076            serialized,
1077            r#"{"naturalPerson":{"name":{"nameIdentifier":{"primaryIdentifier":"Engels","secondaryIdentifier":"Friedrich","nameIdentifierType":"LEGL"}}}}"#
1078        );
1079        let deserialized: Person = serde_json::from_str(&serialized).unwrap();
1080        assert_eq!(person, deserialized);
1081
1082        let person = Person::LegalPerson(LegalPerson::mock());
1083        let serialized = serde_json::to_string(&person).unwrap();
1084        assert_eq!(
1085            serialized,
1086            r#"{"legalPerson":{"name":{"nameIdentifier":{"legalPersonName":"Company A","legalPersonNameIdentifierType":"LEGL"}}}}"#
1087        );
1088        let deserialized: Person = serde_json::from_str(&serialized).unwrap();
1089        assert_eq!(person, deserialized);
1090    }
1091
1092    #[test]
1093    fn test_c1_validation_error() {
1094        let originator = Originator {
1095            originator_persons: Person::NaturalPerson(NaturalPerson::mock()).into(),
1096            account_number: None.into(),
1097        };
1098        match_validation_error(&originator, 1);
1099    }
1100
1101    #[test]
1102    fn test_c1_validation_pass() {
1103        let mut person = NaturalPerson::mock();
1104        person.geographic_address = Some(Address::mock()).into();
1105        let originator = Originator {
1106            originator_persons: Person::NaturalPerson(person.clone()).into(),
1107            account_number: None.into(),
1108        };
1109        originator.validate().unwrap();
1110
1111        person.geographic_address = None.into();
1112        person.customer_identification = Some("customer-id".try_into().unwrap());
1113        let originator = Originator {
1114            originator_persons: Person::NaturalPerson(person.clone()).into(),
1115            account_number: None.into(),
1116        };
1117        originator.validate().unwrap();
1118
1119        person.customer_identification = None;
1120        person.national_identification = Some(NationalIdentification::mock());
1121        let originator = Originator {
1122            originator_persons: Person::NaturalPerson(person.clone()).into(),
1123            account_number: None.into(),
1124        };
1125        originator.validate().unwrap();
1126
1127        person.national_identification = None;
1128        person.date_and_place_of_birth = Some(DateAndPlaceOfBirth::mock());
1129        let originator = Originator {
1130            originator_persons: Person::NaturalPerson(person).into(),
1131            account_number: None.into(),
1132        };
1133        originator.validate().unwrap();
1134
1135        let beneficiary = Beneficiary {
1136            beneficiary_persons: Person::NaturalPerson(NaturalPerson::mock()).into(),
1137            account_number: None.into(),
1138        };
1139        beneficiary.validate().unwrap();
1140    }
1141
1142    #[test]
1143    fn test_c2_validation_error() {
1144        let date = DateAndPlaceOfBirth {
1145            date_of_birth: chrono::NaiveDate::MAX,
1146            place_of_birth: "Bern".try_into().unwrap(),
1147        };
1148        match_validation_error(&date, 2);
1149    }
1150
1151    #[test]
1152    fn test_c2_validation_pass() {
1153        let date = DateAndPlaceOfBirth {
1154            date_of_birth: chrono::NaiveDate::MIN,
1155            place_of_birth: "Bern".try_into().unwrap(),
1156        };
1157
1158        date.validate().unwrap();
1159    }
1160
1161    // C3 is tested in test_invalid_country_code
1162
1163    #[test]
1164    fn test_c4_validation_error() {
1165        let legal = LegalPerson::mock();
1166        match_validation_error(&legal, 4);
1167    }
1168
1169    #[test]
1170    fn test_c4_validation_pass() {
1171        let mut legal = LegalPerson::mock();
1172
1173        legal.geographic_address = Some(Address::mock()).into();
1174        legal.validate().unwrap();
1175        legal.geographic_address = None.into();
1176
1177        legal.customer_identification = Some("id".try_into().unwrap());
1178        legal.validate().unwrap();
1179        legal.customer_identification = None;
1180
1181        legal.national_identification = Some(NationalIdentification::mock());
1182        legal.validate().unwrap();
1183    }
1184
1185    #[test]
1186    fn test_c5_validation_error() {
1187        let mut legal = LegalPersonName::mock();
1188        legal.name_identifier = LegalPersonNameID {
1189            legal_person_name: "Company A".try_into().unwrap(),
1190            legal_person_name_identifier_type: LegalPersonNameTypeCode::Short,
1191        }
1192        .into();
1193        match_validation_error(&legal, 5);
1194    }
1195
1196    #[test]
1197    fn test_c5_validation_pass() {
1198        let legal = LegalPersonName::mock();
1199        legal.validate().unwrap();
1200    }
1201
1202    #[test]
1203    fn test_c6_validation_error() {
1204        let mut name = NaturalPersonName::mock();
1205        name.name_identifier = NaturalPersonNameID {
1206            primary_identifier: "Karl".try_into().unwrap(),
1207            name_identifier_type: NaturalPersonNameTypeCode::Alias,
1208            secondary_identifier: None,
1209        }
1210        .into();
1211        match_validation_error(&name, 6);
1212    }
1213
1214    #[test]
1215    fn test_c6_validation_pass() {
1216        let mut name = NaturalPersonName::mock();
1217        name.name_identifier = NaturalPersonNameID {
1218            primary_identifier: "Emil Steinberger".try_into().unwrap(),
1219            secondary_identifier: None,
1220            name_identifier_type: NaturalPersonNameTypeCode::LegalName,
1221        }
1222        .into();
1223        name.validate().unwrap();
1224    }
1225
1226    #[test]
1227    fn test_c7_validation_error() {
1228        let mut person = LegalPerson::mock();
1229        let mut id = NationalIdentification::mock();
1230
1231        for code in [
1232            NationalIdentifierTypeCode::AlienRegistrationNumber,
1233            NationalIdentifierTypeCode::PassportNumber,
1234            NationalIdentifierTypeCode::DriverLicenseNumber,
1235            NationalIdentifierTypeCode::ForeignInvestmentIdentityNumber,
1236            NationalIdentifierTypeCode::IdentityCardNumber,
1237            NationalIdentifierTypeCode::SocialSecurityNumber,
1238        ] {
1239            id.national_identifier_type = code;
1240            person.national_identification = Some(id.clone());
1241            match_validation_error(&person, 7);
1242        }
1243    }
1244
1245    #[test]
1246    fn test_c7_validation_pass() {
1247        let mut person = LegalPerson::mock();
1248
1249        for code in [
1250            NationalIdentifierTypeCode::LegalEntityIdentifier,
1251            NationalIdentifierTypeCode::Unspecified,
1252            NationalIdentifierTypeCode::RegistrationAuthorityIdentifier,
1253            NationalIdentifierTypeCode::TaxIdentificationNumber,
1254        ] {
1255            let mut id = NationalIdentification::mock();
1256            id.national_identifier_type = code.clone();
1257            if code == NationalIdentifierTypeCode::LegalEntityIdentifier {
1258                // Use a valid LEI to make C11 pass
1259                id.national_identifier = "2594007XIACKNMUAW223".try_into().unwrap();
1260                // Make C9 pass
1261                id.registration_authority = None;
1262            }
1263            person.national_identification = Some(id.clone());
1264            person.validate().unwrap();
1265        }
1266    }
1267
1268    #[test]
1269    fn test_c8_validation_error() {
1270        let mut addr = Address::mock();
1271        addr.address_line = None.into();
1272        match_validation_error(&addr, 8);
1273
1274        addr.street_name = Some("main street".try_into().unwrap());
1275        match_validation_error(&addr, 8);
1276    }
1277
1278    #[test]
1279    fn test_c8_validation_pass() {
1280        let mut addr = Address::mock();
1281        addr.validate().unwrap();
1282
1283        addr.address_line = None.into();
1284        addr.street_name = Some("main street".try_into().unwrap());
1285        addr.building_name = Some("main building".try_into().unwrap());
1286        addr.validate().unwrap();
1287
1288        addr.building_name = None;
1289        addr.building_number = Some("12".try_into().unwrap());
1290        addr.validate().unwrap();
1291    }
1292
1293    #[test]
1294    fn test_c9_validation_error() {
1295        let mut ni = NationalIdentification::mock();
1296        ni.country_of_issue = Some("CH".try_into().unwrap());
1297        let mut person = LegalPerson::mock();
1298        person.national_identification = Some(ni.clone());
1299        match_validation_error(&person, 9);
1300
1301        ni.national_identifier_type = NationalIdentifierTypeCode::LegalEntityIdentifier;
1302        // Use a valid LEI to make C11 pass
1303        ni.national_identifier = "2594007XIACKNMUAW223".try_into().unwrap();
1304        person.national_identification = Some(ni.clone());
1305        match_validation_error(&person, 9);
1306
1307        ni.national_identifier_type = NationalIdentifierTypeCode::Unspecified;
1308        ni.registration_authority = None;
1309        person.national_identification = Some(ni);
1310        match_validation_error(&person, 9);
1311    }
1312
1313    #[test]
1314    fn test_c9_validation_pass() {
1315        let mut person = LegalPerson::mock();
1316        person.customer_identification = Some("id".try_into().unwrap());
1317        person.validate().unwrap();
1318
1319        let mut ni = NationalIdentification::mock();
1320        person.national_identification = Some(ni.clone());
1321        person.validate().unwrap();
1322
1323        ni.registration_authority = None;
1324        ni.national_identifier_type = NationalIdentifierTypeCode::LegalEntityIdentifier;
1325        // Use a valid LEI to make C11 pass
1326        ni.national_identifier = "2594007XIACKNMUAW223".try_into().unwrap();
1327        person.national_identification = Some(ni);
1328        person.validate().unwrap();
1329    }
1330
1331    // C10 is tested in test_registration_authority_invalid_value
1332
1333    #[test]
1334    fn test_c11_validation_error() {
1335        let mut person = LegalPerson::mock();
1336        let mut ni = NationalIdentification::mock();
1337        ni.registration_authority = None;
1338        ni.national_identifier_type = NationalIdentifierTypeCode::LegalEntityIdentifier;
1339        ni.national_identifier = "invalid-lei".try_into().unwrap();
1340        person.national_identification = Some(ni);
1341        match_validation_error(&person, 11);
1342    }
1343
1344    #[test]
1345    fn test_c11_validation_pass() {
1346        let mut person = LegalPerson::mock();
1347        let mut ni = NationalIdentification::mock();
1348        ni.registration_authority = None;
1349        ni.national_identifier_type = NationalIdentifierTypeCode::LegalEntityIdentifier;
1350        ni.national_identifier = "2594007XIACKNMUAW223".try_into().unwrap();
1351        person.national_identification = Some(ni);
1352        person.validate().unwrap();
1353    }
1354
1355    #[test]
1356    fn test_natural_person_name() {
1357        let mut person = NaturalPerson::mock();
1358        assert_eq!(person.first_name(), Some("Friedrich".into()));
1359        assert_eq!(person.last_name(), "Engels");
1360        let mut name = NaturalPersonNameID::mock();
1361        name.secondary_identifier = None;
1362        person.name = NaturalPersonName {
1363            name_identifier: name.into(),
1364            local_name_identifier: None.into(),
1365            phonetic_name_identifier: None.into(),
1366        }
1367        .into();
1368        assert_eq!(person.first_name(), None);
1369        assert_eq!(person.last_name(), "Engels".to_string());
1370    }
1371
1372    #[test]
1373    fn test_legal_person_name() {
1374        assert_eq!(LegalPerson::mock().name(), "Company A");
1375    }
1376
1377    #[test]
1378    fn test_address_display() {
1379        let person = NaturalPerson::mock();
1380        assert_eq!(person.address(), None);
1381        let mut address = Address::mock();
1382        assert_eq!(
1383            address.to_string(),
1384            "Main street, Zurich, Switzerland".to_string()
1385        );
1386        address.post_code = Some("8000".try_into().unwrap());
1387        assert_eq!(
1388            address.to_string(),
1389            "Main street, 8000 Zurich, Switzerland".to_string()
1390        );
1391        address.address_line =
1392            vec!["line 1".try_into().unwrap(), "line 2".try_into().unwrap()].into();
1393        assert_eq!(
1394            address.to_string(),
1395            "line 1, line 2, 8000 Zurich, Switzerland".to_string()
1396        );
1397        address.address_line = None.into();
1398        assert_eq!(address.to_string(), "8000 Zurich, Switzerland".to_string());
1399        address.street_name = Some("Main street".try_into().unwrap());
1400        address.building_number = Some("12".try_into().unwrap());
1401        assert_eq!(
1402            address.to_string(),
1403            "Main street 12, 8000 Zurich, Switzerland".to_string()
1404        );
1405    }
1406}