From 7024bcd7a550bb28a03abcfc19f4f240119b28f2 Mon Sep 17 00:00:00 2001 From: kmkaplan Date: Fri, 14 Apr 2023 10:10:06 +0200 Subject: [PATCH] Frnic contact create (#16) --- src/extensions/frnic/contact.rs | 183 ++++++++++++++++++ src/extensions/frnic/mod.rs | 158 +++++++++++++++ src/lib.rs | 2 + .../frnic_create_contact_company.xml | 36 ++++ .../frnic_create_contact_natural_person.xml | 34 ++++ .../frnic_create_contact_non_profit.xml | 40 ++++ .../extensions/frnic_create_contact_siren.xml | 37 ++++ 7 files changed, 490 insertions(+) create mode 100644 src/extensions/frnic/contact.rs create mode 100644 src/extensions/frnic/mod.rs create mode 100644 tests/resources/request/extensions/frnic_create_contact_company.xml create mode 100644 tests/resources/request/extensions/frnic_create_contact_natural_person.xml create mode 100644 tests/resources/request/extensions/frnic_create_contact_non_profit.xml create mode 100644 tests/resources/request/extensions/frnic_create_contact_siren.xml diff --git a/src/extensions/frnic/contact.rs b/src/extensions/frnic/contact.rs new file mode 100644 index 0000000..a35f825 --- /dev/null +++ b/src/extensions/frnic/contact.rs @@ -0,0 +1,183 @@ +//! Types for EPP FRNIC contact requests +use instant_xml::{Id, ToXml}; +use std::borrow::Cow; + +use crate::request::{Extension, Transaction}; + +use super::{Create, Ext, XMLNS}; + +impl<'a> Transaction>>> for crate::contact::create::ContactCreate<'a> {} + +impl<'a> Extension for Ext>> { + type Response = (); +} + +/// For french TLDs, a contact is either an individual (PP) or a legal +/// entity (PM). We use the `ContactCreate` extension to differentiate +/// between the creation of a PP and a PM. +#[derive(Debug)] +pub enum ContactCreate<'a> { + /// This contact is an individual. + NaturalPerson { + /// First name of the contact. The `` element + /// will be the family name. + first_name: Cow<'a, str>, + }, + /// This contact is a legal entity. + LegalEntity(Box>), +} + +impl<'a> From> for Ext>> { + fn from(data: ContactCreate<'a>) -> Self { + Ext { + data: Create { data }, + } + } +} + +impl<'a> ContactCreate<'a> { + pub fn new_natural_person(first_name: &'a str) -> Self { + Self::NaturalPerson { + first_name: first_name.into(), + } + } + + pub fn new_company( + siren: Option<&'a str>, + vat: Option<&'a str>, + trademark: Option<&'a str>, + duns: Option<&'a str>, + local: Option<&'a str>, + ) -> Self { + Self::LegalEntity(Box::new(LegalEntityInfos { + legal_status: LegalStatus::Company, + siren: siren.map(|s| s.into()), + vat: vat.map(|v| v.into()), + trademark: trademark.map(|t| t.into()), + asso: None, + duns: duns.map(|d| d.into()), + local: local.map(|l| l.into()), + })) + } + + pub fn new_non_profit( + waldec: Option<&'a str>, + declaration: Option<&'a str>, + publication: Option>, + ) -> Self { + Self::LegalEntity(Box::new(LegalEntityInfos { + legal_status: LegalStatus::Association, + siren: None, + vat: None, + trademark: None, + asso: Some(Association { + waldec: waldec.map(|w| w.into()), + declaration: declaration.map(|d| d.into()), + publication, + }), + duns: None, + local: None, + })) + } +} + +impl<'a> ToXml for ContactCreate<'a> { + fn serialize( + &self, + _: Option>, + serializer: &mut instant_xml::Serializer<'_, W>, + ) -> Result<(), instant_xml::Error> { + let contact_nc_name = "contact"; + let prefix = serializer.write_start(contact_nc_name, XMLNS)?; + serializer.end_start()?; + match self { + Self::NaturalPerson { first_name } => { + let first_name_nc_name = "firstName"; + let prefix = serializer.write_start(first_name_nc_name, XMLNS)?; + serializer.end_start()?; + first_name.serialize(None, serializer)?; + serializer.write_close(prefix, first_name_nc_name)?; + } + Self::LegalEntity(infos) => infos.serialize(None, serializer)?, + } + serializer.write_close(prefix, contact_nc_name)?; + Ok(()) + } +} + +#[derive(Debug, ToXml)] +#[xml(rename = "legalEntityInfos", ns(XMLNS))] +pub struct LegalEntityInfos<'a> { + pub legal_status: LegalStatus<'a>, + pub siren: Option>, + pub vat: Option>, + pub trademark: Option>, + pub asso: Option>, + pub duns: Option>, + pub local: Option>, +} + +#[derive(Debug)] +pub enum LegalStatus<'a> { + Company, + Association, + Other(Cow<'a, str>), +} + +impl<'a> ToXml for LegalStatus<'a> { + fn serialize( + &self, + _field: Option>, + serializer: &mut instant_xml::Serializer, + ) -> Result<(), instant_xml::Error> { + let ncname = "legalStatus"; + let (s, data) = match self { + LegalStatus::Company => ("company", None), + LegalStatus::Association => ("association", None), + LegalStatus::Other(text) => ("other", Some(&text.as_ref()[2..])), + }; + let prefix = serializer.write_start(ncname, XMLNS)?; + debug_assert_eq!(prefix, None); + serializer.write_attr("s", XMLNS, s)?; + if let Some(text) = data { + serializer.end_start()?; + text.serialize(None, serializer)?; + serializer.write_close(prefix, ncname)?; + } else { + serializer.end_empty()?; + } + Ok(()) + } +} + +/// Contains information that permits the identification of associations. +#[derive(Debug, ToXml)] +#[xml(rename = "asso", ns(XMLNS))] +pub struct Association<'a> { + /// The Waldec registration number. "Waldec" is the acronym for + /// the french "[Web des associations librement + /// déclarées](https://www.associations.gouv.fr/le-rna-repertoire-national-des-associations.html)" + pub waldec: Option>, + /// Date of declaration to the prefecture + #[xml(rename = "decl")] + pub declaration: Option>, + /// Information of publication in the official gazette + #[xml(rename = "publ")] + pub publication: Option>, +} + +/// Holds information about the publication in the +/// official gazette for the association. +#[derive(Debug, ToXml)] +#[xml(rename = "publ", ns(XMLNS))] +pub struct Publication<'a> { + /// Page number of the announcement + #[xml(attribute)] + pub page: u32, + #[xml(attribute)] + /// Number of the announcement + pub announce: u32, + /// Date of publication + #[xml(direct)] + pub date: Cow<'a, str>, +} diff --git a/src/extensions/frnic/mod.rs b/src/extensions/frnic/mod.rs new file mode 100644 index 0000000..e3571ca --- /dev/null +++ b/src/extensions/frnic/mod.rs @@ -0,0 +1,158 @@ +//! Mapping for the [frnic-2.0 +//! extension](https://www.afnic.fr/wp-media/uploads/2022/10/guide_d_integration_technique_EN_FRandoverseasTLD_30_09_VF.pdf) +use instant_xml::{FromXml, ToXml}; + +pub mod contact; + +pub use contact::ContactCreate; + +pub const XMLNS: &str = "http://www.afnic.fr/xml/epp/frnic-2.0"; + +#[derive(Debug, FromXml, ToXml)] +#[xml(rename = "ext", ns(XMLNS))] +pub struct Ext { + pub data: T, +} + +#[derive(Debug, FromXml, ToXml)] +#[xml(rename = "create", ns(XMLNS))] +pub struct Create { + pub data: T, +} + +#[cfg(test)] +mod tests { + use crate::contact::ContactCreate; + use crate::contact::{Address, PostalInfo, Voice}; + use crate::extensions::frnic; + use crate::tests::assert_serialized; + use frnic::{contact, Ext}; + + #[test] + fn test_contact_create_natural_person() { + // Technical Integration Guide, page 23. + let frnic_contact = Ext::from(frnic::ContactCreate::new_natural_person("Michel")); + let object = ContactCreate::new( + "XXX000", + "test@test.fr", + PostalInfo::new( + "loc", + "Dupont", + None, + Address::new( + &["1 Rue des fleurs"], + "Paris", + None, + Some("75000"), + "FR".parse().unwrap(), + ), + ), + Some(Voice::new("+33.1234567890")), + "Afn-12345678", + ); + assert_serialized( + "request/extensions/frnic_create_contact_natural_person.xml", + (&object, &frnic_contact), + ); + } + + #[test] + fn test_contact_create_company() { + // Technical Integration Guide, page 27. + let frnic_contact = Ext::from(frnic::ContactCreate::new_company( + None, None, None, None, None, + )); + let object = ContactCreate::new( + "XXXXXXX", + "test@test.fr", + PostalInfo::new( + "loc", + "SARL DUPONT", + None, + Address::new( + &["1 Rue des coquelicots"], + "Paris", + None, + Some("75000"), + "FR".parse().unwrap(), + ), + ), + Some(Voice::new("+33.1234567890")), + "Afn-123456", + ); + assert_serialized( + "request/extensions/frnic_create_contact_company.xml", + (&object, &frnic_contact), + ); + } + + #[test] + fn test_contact_create_corporation_with_siren() { + // Technical Integration Guide, page 28. + let frnic_contact = Ext::from(frnic::ContactCreate::new_company( + Some("123456789"), + None, + None, + None, + None, + )); + let object = ContactCreate::new( + "XXXX0000", + "test@test.fr", + PostalInfo::new( + "loc", + "SARL DUPONT SIREN", + None, + Address::new( + &["1 Rue des Sirenes"], + "Paris", + None, + Some("75000"), + "FR".parse().unwrap(), + ), + ), + Some(Voice::new("+33.1234567890")), + "Afn-123456", + ); + assert_serialized( + "request/extensions/frnic_create_contact_siren.xml", + (&object, &frnic_contact), + ); + } + + #[test] + fn test_contact_create_non_profit() { + // Technical Integration Guide, page 38. + let frnic_contact = Ext::from(frnic::ContactCreate::new_non_profit( + None, + Some("2011-05-02"), + Some(contact::Publication { + announce: 123456, + page: 15, + date: "2011-05-07".into(), + }), + )); + let object = ContactCreate::new( + "XXXX0000", + "test@test.fr", + PostalInfo::new( + "loc", + "Dupont JO", + None, + Address::new( + &["1 Rue des Fleurs"], + "Paris", + None, + Some("75000"), + "FR".parse().unwrap(), + ), + ), + Some(Voice::new("+33.1234567890")), + "Afn-123456", + ); + assert_serialized( + "request/extensions/frnic_create_contact_non_profit.xml", + (&object, &frnic_contact), + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 085eee2..f3ca694 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,8 @@ pub mod extensions { pub const XMLNS: &str = "urn:ietf:params:xml:ns:rgp-1.0"; } + + pub mod frnic; } pub use client::EppClient; diff --git a/tests/resources/request/extensions/frnic_create_contact_company.xml b/tests/resources/request/extensions/frnic_create_contact_company.xml new file mode 100644 index 0000000..bdf4ba5 --- /dev/null +++ b/tests/resources/request/extensions/frnic_create_contact_company.xml @@ -0,0 +1,36 @@ + + + + + + XXXXXXX + + SARL DUPONT + + 1 Rue des coquelicots + Paris + 75000 + FR + + + +33.1234567890 + test@test.fr + + Afn-123456 + + + + + + + + + + + + + + + cltrid:1626454866 + + diff --git a/tests/resources/request/extensions/frnic_create_contact_natural_person.xml b/tests/resources/request/extensions/frnic_create_contact_natural_person.xml new file mode 100644 index 0000000..001129b --- /dev/null +++ b/tests/resources/request/extensions/frnic_create_contact_natural_person.xml @@ -0,0 +1,34 @@ + + + + + + XXX000 + + Dupont + + 1 Rue des fleurs + Paris + 75000 + FR + + + +33.1234567890 + test@test.fr + + Afn-12345678 + + + + + + + + Michel + + + + + cltrid:1626454866 + + diff --git a/tests/resources/request/extensions/frnic_create_contact_non_profit.xml b/tests/resources/request/extensions/frnic_create_contact_non_profit.xml new file mode 100644 index 0000000..0ea36d2 --- /dev/null +++ b/tests/resources/request/extensions/frnic_create_contact_non_profit.xml @@ -0,0 +1,40 @@ + + + + + + XXXX0000 + + Dupont JO + + 1 Rue des Fleurs + Paris + 75000 + FR + + + +33.1234567890 + test@test.fr + + Afn-123456 + + + + + + + + + + + 2011-05-02 + 2011-05-07 + + + + + + + cltrid:1626454866 + + diff --git a/tests/resources/request/extensions/frnic_create_contact_siren.xml b/tests/resources/request/extensions/frnic_create_contact_siren.xml new file mode 100644 index 0000000..7f209ed --- /dev/null +++ b/tests/resources/request/extensions/frnic_create_contact_siren.xml @@ -0,0 +1,37 @@ + + + + + + XXXX0000 + + SARL DUPONT SIREN + + 1 Rue des Sirenes + Paris + 75000 + FR + + + +33.1234567890 + test@test.fr + + Afn-123456 + + + + + + + + + + 123456789 + + + + + + cltrid:1626454866 + +