Frnic contact create (#16)

This commit is contained in:
kmkaplan 2023-04-14 10:10:06 +02:00 committed by GitHub
parent 1b6a774232
commit 7024bcd7a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 490 additions and 0 deletions

View File

@ -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<Ext<Create<ContactCreate<'a>>>> for crate::contact::create::ContactCreate<'a> {}
impl<'a> Extension for Ext<Create<ContactCreate<'a>>> {
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 `<contact:name>` element
/// will be the family name.
first_name: Cow<'a, str>,
},
/// This contact is a legal entity.
LegalEntity(Box<LegalEntityInfos<'a>>),
}
impl<'a> From<ContactCreate<'a>> for Ext<Create<ContactCreate<'a>>> {
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<Publication<'a>>,
) -> 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<W: core::fmt::Write + ?Sized>(
&self,
_: Option<Id<'_>>,
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<Cow<'a, str>>,
pub vat: Option<Cow<'a, str>>,
pub trademark: Option<Cow<'a, str>>,
pub asso: Option<Association<'a>>,
pub duns: Option<Cow<'a, str>>,
pub local: Option<Cow<'a, str>>,
}
#[derive(Debug)]
pub enum LegalStatus<'a> {
Company,
Association,
Other(Cow<'a, str>),
}
impl<'a> ToXml for LegalStatus<'a> {
fn serialize<W: core::fmt::Write + ?Sized>(
&self,
_field: Option<Id<'_>>,
serializer: &mut instant_xml::Serializer<W>,
) -> 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<Cow<'a, str>>,
/// Date of declaration to the prefecture
#[xml(rename = "decl")]
pub declaration: Option<Cow<'a, str>>,
/// Information of publication in the official gazette
#[xml(rename = "publ")]
pub publication: Option<Publication<'a>>,
}
/// 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>,
}

158
src/extensions/frnic/mod.rs Normal file
View File

@ -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<T> {
pub data: T,
}
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "create", ns(XMLNS))]
pub struct Create<T> {
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),
);
}
}

View File

@ -61,6 +61,8 @@ pub mod extensions {
pub const XMLNS: &str = "urn:ietf:params:xml:ns:rgp-1.0"; pub const XMLNS: &str = "urn:ietf:params:xml:ns:rgp-1.0";
} }
pub mod frnic;
} }
pub use client::EppClient; pub use client::EppClient;

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<create xmlns="urn:ietf:params:xml:ns:contact-1.0">
<id>XXXXXXX</id>
<postalInfo type="loc">
<name>SARL DUPONT</name>
<addr>
<street>1 Rue des coquelicots</street>
<city>Paris</city>
<pc>75000</pc>
<cc>FR</cc>
</addr>
</postalInfo>
<voice>+33.1234567890</voice>
<email>test@test.fr</email>
<authInfo>
<pw>Afn-123456</pw>
</authInfo>
</create>
</create>
<extension>
<ext xmlns="http://www.afnic.fr/xml/epp/frnic-2.0">
<create>
<contact>
<legalEntityInfos>
<legalStatus s="company" />
</legalEntityInfos>
</contact>
</create>
</ext>
</extension>
<clTRID>cltrid:1626454866</clTRID>
</command>
</epp>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<create xmlns="urn:ietf:params:xml:ns:contact-1.0">
<id>XXX000</id>
<postalInfo type="loc">
<name>Dupont</name>
<addr>
<street>1 Rue des fleurs</street>
<city>Paris</city>
<pc>75000</pc>
<cc>FR</cc>
</addr>
</postalInfo>
<voice>+33.1234567890</voice>
<email>test@test.fr</email>
<authInfo>
<pw>Afn-12345678</pw>
</authInfo>
</create>
</create>
<extension>
<ext xmlns="http://www.afnic.fr/xml/epp/frnic-2.0">
<create>
<contact>
<firstName>Michel</firstName>
</contact>
</create>
</ext>
</extension>
<clTRID>cltrid:1626454866</clTRID>
</command>
</epp>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<create xmlns="urn:ietf:params:xml:ns:contact-1.0">
<id>XXXX0000</id>
<postalInfo type="loc">
<name>Dupont JO</name>
<addr>
<street>1 Rue des Fleurs</street>
<city>Paris</city>
<pc>75000</pc>
<cc>FR</cc>
</addr>
</postalInfo>
<voice>+33.1234567890</voice>
<email>test@test.fr</email>
<authInfo>
<pw>Afn-123456</pw>
</authInfo>
</create>
</create>
<extension>
<ext xmlns="http://www.afnic.fr/xml/epp/frnic-2.0">
<create>
<contact>
<legalEntityInfos>
<legalStatus s="association" />
<asso>
<decl>2011-05-02</decl>
<publ page="15" announce="123456">2011-05-07</publ>
</asso>
</legalEntityInfos>
</contact>
</create>
</ext>
</extension>
<clTRID>cltrid:1626454866</clTRID>
</command>
</epp>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<create xmlns="urn:ietf:params:xml:ns:contact-1.0">
<id>XXXX0000</id>
<postalInfo type="loc">
<name>SARL DUPONT SIREN</name>
<addr>
<street>1 Rue des Sirenes</street>
<city>Paris</city>
<pc>75000</pc>
<cc>FR</cc>
</addr>
</postalInfo>
<voice>+33.1234567890</voice>
<email>test@test.fr</email>
<authInfo>
<pw>Afn-123456</pw>
</authInfo>
</create>
</create>
<extension>
<ext xmlns="http://www.afnic.fr/xml/epp/frnic-2.0">
<create>
<contact>
<legalEntityInfos>
<legalStatus s="company" />
<siren>123456789</siren>
</legalEntityInfos>
</contact>
</create>
</ext>
</extension>
<clTRID>cltrid:1626454866</clTRID>
</command>
</epp>