instant_epp/contact/
mod.rs

1//! Mapping for EPP contact objects
2//!
3//! As described in [RFC 5733](https://tools.ietf.org/html/rfc5733).
4
5use std::borrow::Cow;
6use std::fmt;
7use std::str::FromStr;
8
9use instant_xml::{display_to_xml, from_xml_str, Deserializer, FromXml, Serializer, ToXml};
10
11pub mod check;
12pub use check::ContactCheck;
13
14pub mod create;
15pub use create::ContactCreate;
16
17pub mod delete;
18pub use delete::ContactDelete;
19
20pub mod info;
21pub use info::ContactInfo;
22
23pub mod update;
24pub use update::ContactUpdate;
25
26pub const XMLNS: &str = "urn:ietf:params:xml:ns:contact-1.0";
27
28#[derive(Clone, Debug)]
29pub struct Country(celes::Country);
30
31impl<'xml> FromXml<'xml> for Country {
32    fn matches(id: instant_xml::Id<'_>, _: Option<instant_xml::Id<'_>>) -> bool {
33        id == instant_xml::Id {
34            ns: XMLNS,
35            name: "cc",
36        }
37    }
38
39    fn deserialize<'cx>(
40        into: &mut Self::Accumulator,
41        field: &'static str,
42        deserializer: &mut instant_xml::Deserializer<'cx, 'xml>,
43    ) -> Result<(), instant_xml::Error> {
44        from_xml_str(into, field, deserializer)
45    }
46
47    type Accumulator = Option<Self>;
48    const KIND: instant_xml::Kind = instant_xml::Kind::Scalar;
49}
50
51impl ToXml for Country {
52    fn serialize<W: fmt::Write + ?Sized>(
53        &self,
54        field: Option<instant_xml::Id<'_>>,
55        serializer: &mut instant_xml::Serializer<W>,
56    ) -> Result<(), instant_xml::Error> {
57        display_to_xml(&self.0.alpha2, field, serializer)
58    }
59}
60
61impl FromStr for Country {
62    type Err = <celes::Country as FromStr>::Err;
63
64    fn from_str(s: &str) -> Result<Self, Self::Err> {
65        Ok(Self(celes::Country::from_str(s)?))
66    }
67}
68
69impl std::ops::Deref for Country {
70    type Target = celes::Country;
71
72    fn deref(&self) -> &Self::Target {
73        &self.0
74    }
75}
76
77/// The `<authInfo>` tag for domain and contact transactions
78#[derive(Clone, Debug, FromXml, PartialEq, ToXml)]
79#[xml(rename = "authInfo", ns(XMLNS))]
80pub struct ContactAuthInfo<'a> {
81    /// The `<pw>` tag under `<authInfo>`
82    #[xml(rename = "pw")]
83    pub password: Cow<'a, str>,
84}
85
86impl<'a> ContactAuthInfo<'a> {
87    /// Creates a ContactAuthInfo instance with the given password
88    pub fn new(password: &'a str) -> Self {
89        Self {
90            password: password.into(),
91        }
92    }
93}
94
95/// The data for `<voice>` types on domain transactions
96#[derive(Clone, Debug, FromXml, PartialEq, ToXml)]
97#[xml(rename = "voice", ns(XMLNS))]
98pub struct Voice<'a> {
99    /// The value of the 'x' attr on `<voice>` and `<fax>` tags
100    #[xml(rename = "x", attribute)]
101    pub extension: Option<Cow<'a, str>>,
102    /// The inner text on the `<voice>` and `<fax>` tags
103    #[xml(direct)]
104    pub number: Cow<'a, str>,
105}
106
107impl<'a> Voice<'a> {
108    /// Creates a new Phone instance with a given phone number
109    pub fn new(number: &'a str) -> Self {
110        Self {
111            extension: None,
112            number: number.into(),
113        }
114    }
115
116    /// Sets the extension value of the Phone type
117    pub fn set_extension(&mut self, ext: &'a str) {
118        self.extension = Some(ext.into());
119    }
120}
121
122/// The data for `<voice>` and `<fax>` types on domain transactions
123#[derive(Clone, Debug, FromXml, PartialEq, ToXml)]
124#[xml(rename = "fax", ns(XMLNS))]
125pub struct Fax<'a> {
126    /// The value of the 'x' attr on `<voice>` and `<fax>` tags
127    #[xml(rename = "x", attribute)]
128    pub extension: Option<Cow<'a, str>>,
129    /// The inner text on the `<voice>` and `<fax>` tags
130    #[xml(direct)]
131    pub number: Cow<'a, str>,
132}
133
134impl<'a> Fax<'a> {
135    /// Creates a new Phone instance with a given phone number
136    pub fn new(number: &'a str) -> Self {
137        Self {
138            extension: None,
139            number: number.into(),
140        }
141    }
142
143    /// Sets the extension value of the Phone type
144    pub fn set_extension(&mut self, ext: &'a str) {
145        self.extension = Some(ext.into());
146    }
147}
148
149/// The `<addr>` type on contact transactions
150#[derive(Clone, Debug, FromXml, ToXml)]
151#[xml(rename = "addr", ns(XMLNS))]
152pub struct Address<'a> {
153    /// The `<street>` tags under `<addr>`
154    pub street: Vec<Cow<'a, str>>,
155    /// The `<city>` tag under `<addr>`
156    pub city: Cow<'a, str>,
157    /// The `<sp>` tag under `<addr>`
158    #[xml(rename = "sp")]
159    pub province: Option<Cow<'a, str>>,
160    /// The `<pc>` tag under `<addr>`
161    #[xml(rename = "pc")]
162    pub postal_code: Option<Cow<'a, str>>,
163    /// The `<cc>` tag under `<addr>`
164    #[xml(rename = "cc")]
165    pub country: Country,
166}
167
168impl<'a> Address<'a> {
169    /// Creates a new Address instance
170    pub fn new(
171        street: &[&'a str],
172        city: &'a str,
173        province: Option<&'a str>,
174        postal_code: Option<&'a str>,
175        country: Country,
176    ) -> Self {
177        let street = street.iter().map(|&s| s.into()).collect();
178
179        Self {
180            street,
181            city: city.into(),
182            province: province.map(|sp| sp.into()),
183            postal_code: postal_code.map(|pc| pc.into()),
184            country,
185        }
186    }
187}
188
189/// The `<postalInfo>` type on contact transactions
190#[derive(Clone, Debug, FromXml, ToXml)]
191#[xml(rename = "postalInfo", ns(XMLNS))]
192pub struct PostalInfo<'a> {
193    /// The 'type' attr on `<postalInfo>`
194    #[xml(rename = "type", attribute)]
195    pub info_type: Cow<'a, str>,
196    /// The `<name>` tag under `<postalInfo>`
197    pub name: Cow<'a, str>,
198    /// The `<org>` tag under `<postalInfo>`
199    #[xml(rename = "org")]
200    pub organization: Option<Cow<'a, str>>,
201    /// The `<addr>` tag under `<postalInfo>`
202    pub address: Address<'a>,
203}
204
205impl<'a> PostalInfo<'a> {
206    /// Creates a new PostalInfo instance
207    pub fn new(
208        info_type: &'a str,
209        name: &'a str,
210        organization: Option<&'a str>,
211        address: Address<'a>,
212    ) -> Self {
213        Self {
214            info_type: info_type.into(),
215            name: name.into(),
216            organization: organization.map(|org| org.into()),
217            address,
218        }
219    }
220}
221
222/// The `<status>` type on contact transactions
223#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224pub enum Status {
225    ClientDeleteProhibited,
226    ServerDeleteProhibited,
227    ClientTransferProhibited,
228    ServerTransferProhibited,
229    ClientUpdateProhibited,
230    ServerUpdateProhibited,
231    Linked,
232    Ok,
233    PendingCreate,
234    PendingDelete,
235    PendingTransfer,
236    PendingUpdate,
237}
238
239impl Status {
240    pub fn as_str(&self) -> &'static str {
241        use Status::*;
242        match self {
243            ClientDeleteProhibited => "clientDeleteProhibited",
244            ServerDeleteProhibited => "serverDeleteProhibited",
245            ClientTransferProhibited => "clientTransferProhibited",
246            ServerTransferProhibited => "serverTransferProhibited",
247            ClientUpdateProhibited => "clientUpdateProhibited",
248            ServerUpdateProhibited => "serverUpdateProhibited",
249            Linked => "linked",
250            Ok => "ok",
251            PendingCreate => "pendingCreate",
252            PendingDelete => "pendingDelete",
253            PendingTransfer => "pendingTransfer",
254            PendingUpdate => "pendingUpdate",
255        }
256    }
257}
258
259impl ToXml for Status {
260    fn serialize<W: fmt::Write + ?Sized>(
261        &self,
262        _: Option<instant_xml::Id<'_>>,
263        serializer: &mut Serializer<W>,
264    ) -> Result<(), instant_xml::Error> {
265        serializer.write_start("status", XMLNS)?;
266        serializer.write_attr("s", XMLNS, &self.as_str())?;
267        serializer.end_empty()
268    }
269}
270
271impl<'xml> FromXml<'xml> for Status {
272    fn matches(id: instant_xml::Id<'_>, _: Option<instant_xml::Id<'_>>) -> bool {
273        id == instant_xml::Id {
274            ns: XMLNS,
275            name: "status",
276        }
277    }
278
279    fn deserialize<'cx>(
280        into: &mut Self::Accumulator,
281        field: &'static str,
282        deserializer: &mut Deserializer<'cx, 'xml>,
283    ) -> Result<(), instant_xml::Error> {
284        use instant_xml::de::Node;
285        use instant_xml::{Error, Id};
286
287        let node = match deserializer.next() {
288            Some(result) => result?,
289            None => return Err(Error::MissingValue(field)),
290        };
291
292        let attr = match node {
293            Node::Attribute(attr) => attr,
294            Node::Open(_) | Node::Text(_) => return Err(Error::MissingValue(field)),
295            node => return Err(Error::UnexpectedNode(format!("{node:?} in Status"))),
296        };
297
298        let id = deserializer.attribute_id(&attr)?;
299        let expected = Id { ns: "", name: "s" };
300        if id != expected {
301            return Err(Error::MissingValue(field));
302        }
303
304        *into = Some(match attr.value.as_ref() {
305            "clientDeleteProhibited" => Self::ClientDeleteProhibited,
306            "serverDeleteProhibited" => Self::ServerDeleteProhibited,
307            "clientTransferProhibited" => Self::ClientTransferProhibited,
308            "serverTransferProhibited" => Self::ServerTransferProhibited,
309            "clientUpdateProhibited" => Self::ClientUpdateProhibited,
310            "serverUpdateProhibited" => Self::ServerUpdateProhibited,
311            "linked" => Self::Linked,
312            "ok" => Self::Ok,
313            "pendingCreate" => Self::PendingCreate,
314            "pendingDelete" => Self::PendingDelete,
315            "pendingTransfer" => Self::PendingTransfer,
316            "pendingUpdate" => Self::PendingUpdate,
317            val => return Err(Error::UnexpectedValue(format!("invalid status {val:?}"))),
318        });
319
320        deserializer.ignore()?;
321        Ok(())
322    }
323
324    type Accumulator = Option<Self>;
325    const KIND: instant_xml::Kind = instant_xml::Kind::Element;
326}