instant_epp/domain/
mod.rs

1//! Mapping for EPP domain objects
2//!
3//! As described in [RFC 5731](https://tools.ietf.org/html/rfc5731).
4
5use std::borrow::Cow;
6use std::fmt;
7use std::net::IpAddr;
8use std::str::FromStr;
9
10use instant_xml::OptionAccumulator;
11use instant_xml::{Accumulate, Deserializer, FromXml, Serializer, ToXml};
12
13use crate::Error;
14
15pub mod check;
16pub use check::DomainCheck;
17
18pub mod create;
19pub use create::DomainCreate;
20
21pub mod delete;
22pub use delete::DomainDelete;
23
24pub mod info;
25pub use info::DomainInfo;
26
27pub mod renew;
28pub use renew::DomainRenew;
29
30pub mod transfer;
31pub use transfer::DomainTransfer;
32
33pub mod update;
34pub use update::DomainUpdate;
35
36pub const XMLNS: &str = "urn:ietf:params:xml:ns:domain-1.0";
37
38/// The `<hostAttr>` type for domain transactions
39#[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
40#[xml(rename = "hostAttr", ns(XMLNS))]
41pub struct HostAttr<'a> {
42    /// The `<hostName>` tag
43    #[xml(rename = "hostName")]
44    pub name: Cow<'a, str>,
45    /// The `<hostAddr>` tags
46    #[xml(
47        rename = "hostAddr",
48        serialize_with = "serialize_host_addrs_option",
49        deserialize_with = "deserialize_host_addrs_option"
50    )]
51    pub addresses: Option<Vec<IpAddr>>,
52}
53
54fn deserialize_host_addrs_option<'xml>(
55    into: &mut OptionAccumulator<Vec<IpAddr>, Vec<IpAddr>>,
56    field: &'static str,
57    deserializer: &mut Deserializer<'_, 'xml>,
58) -> Result<(), instant_xml::Error> {
59    let mut value = <Option<Vec<HostAddr<'static>>> as FromXml<'xml>>::Accumulator::default();
60    <Option<Vec<HostAddr<'static>>>>::deserialize(&mut value, field, deserializer)?;
61    let new = match value.try_done(field)? {
62        Some(new) => new,
63        None => return Ok(()),
64    };
65
66    let into = into.get_mut();
67    for addr in new {
68        match IpAddr::from_str(&addr.address) {
69            Ok(ip) => into.push(ip),
70            Err(_) => {
71                return Err(instant_xml::Error::UnexpectedValue(format!(
72                    "invalid IP address '{}'",
73                    &addr.address
74                )))
75            }
76        }
77    }
78
79    Ok(())
80}
81
82/// The `<hostAddr>` types domain or host transactions
83#[derive(Debug, FromXml, ToXml)]
84#[xml(rename = "hostAddr", ns(super::domain::XMLNS))]
85struct HostAddr<'a> {
86    #[xml(attribute, rename = "ip")]
87    ip_version: Option<Cow<'a, str>>,
88    #[xml(direct)]
89    address: Cow<'a, str>,
90}
91
92impl From<&IpAddr> for HostAddr<'static> {
93    fn from(addr: &IpAddr) -> Self {
94        Self {
95            ip_version: Some(match addr {
96                IpAddr::V4(_) => "v4".into(),
97                IpAddr::V6(_) => "v6".into(),
98            }),
99            address: addr.to_string().into(),
100        }
101    }
102}
103
104pub(crate) fn serialize_host_addrs_option<T: AsRef<[IpAddr]>, W: fmt::Write + ?Sized>(
105    addrs: &Option<T>,
106    serializer: &mut Serializer<'_, W>,
107) -> Result<(), instant_xml::Error> {
108    let addrs = match addrs {
109        Some(addrs) => addrs.as_ref(),
110        None => return Ok(()),
111    };
112
113    for addr in addrs {
114        HostAddr::from(addr).serialize(None, serializer)?;
115    }
116
117    Ok(())
118}
119
120#[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
121#[xml(rename = "hostObj", ns(XMLNS))]
122pub struct HostObj<'a> {
123    #[xml(direct)]
124    pub name: Cow<'a, str>,
125}
126
127#[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
128#[xml(forward)]
129pub enum HostInfo<'a> {
130    Attr(HostAttr<'a>),
131    Obj(HostObj<'a>),
132}
133
134#[derive(Debug, FromXml, ToXml)]
135#[xml(rename = "ns", ns(XMLNS))]
136pub struct NameServers<'a> {
137    pub ns: Cow<'a, [HostInfo<'a>]>,
138}
139
140/// The `<contact>` type on domain creation and update requests
141#[derive(Debug, FromXml, ToXml)]
142#[xml(rename = "contact", ns(XMLNS))]
143pub struct DomainContact<'a> {
144    /// The contact type attr (usually admin, billing, or tech in most registries)
145    #[xml(attribute, rename = "type")]
146    pub contact_type: Cow<'a, str>,
147    /// The contact id
148    #[xml(direct)]
149    pub id: Cow<'a, str>,
150}
151
152/// The `<period>` type for registration, renewal or transfer on domain transactions
153#[derive(Clone, Copy, Debug, ToXml)]
154#[xml(rename = "period", ns(XMLNS))]
155pub struct Period {
156    /// The interval (usually 'y' indicating years)
157    #[xml(attribute)]
158    unit: char,
159    /// The length of the registration, renewal or transfer period (usually in years)
160    #[xml(direct)]
161    length: u8,
162}
163
164impl Period {
165    pub fn years(length: u8) -> Result<Self, Error> {
166        Self::new(length, 'y')
167    }
168
169    pub fn months(length: u8) -> Result<Self, Error> {
170        Self::new(length, 'm')
171    }
172
173    fn new(length: u8, unit: char) -> Result<Self, Error> {
174        match length {
175            1..=99 => Ok(Self { length, unit }),
176            0 | 100.. => Err(Error::Other(
177                "Period length must be greater than 0 and less than 100".into(),
178            )),
179        }
180    }
181}
182
183pub const ONE_YEAR: Period = Period {
184    unit: 'y',
185    length: 1,
186};
187
188pub const TWO_YEARS: Period = Period {
189    unit: 'y',
190    length: 2,
191};
192
193pub const THREE_YEARS: Period = Period {
194    unit: 'y',
195    length: 3,
196};
197
198pub const ONE_MONTH: Period = Period {
199    unit: 'm',
200    length: 1,
201};
202
203pub const SIX_MONTHS: Period = Period {
204    unit: 'm',
205    length: 6,
206};
207
208/// The `<authInfo>` tag for domain and contact transactions
209#[derive(Clone, Debug, FromXml, ToXml)]
210#[xml(rename = "authInfo", ns(XMLNS))]
211pub struct DomainAuthInfo<'a> {
212    /// The `<pw>` tag under `<authInfo>`
213    #[xml(rename = "pw")]
214    pub password: Cow<'a, str>,
215}
216
217impl<'a> DomainAuthInfo<'a> {
218    /// Creates a DomainAuthInfo instance with the given password
219    pub fn new(password: &'a str) -> Self {
220        Self {
221            password: password.into(),
222        }
223    }
224}
225
226/// The `<status>` type on contact transactions
227#[derive(Clone, Copy, Debug, Eq, PartialEq)]
228pub enum Status {
229    ClientDeleteProhibited,
230    ServerDeleteProhibited,
231    ClientHold,
232    ServerHold,
233    ClientRenewProhibited,
234    ServerRenewProhibited,
235    ClientTransferProhibited,
236    ServerTransferProhibited,
237    ClientUpdateProhibited,
238    ServerUpdateProhibited,
239    Inactive,
240    Ok,
241    PendingCreate,
242    PendingDelete,
243    PendingRenew,
244    PendingTransfer,
245    PendingUpdate,
246}
247
248impl Status {
249    pub fn as_str(&self) -> &'static str {
250        use Status::*;
251        match self {
252            ClientDeleteProhibited => "clientDeleteProhibited",
253            ServerDeleteProhibited => "serverDeleteProhibited",
254            ClientHold => "clientHold",
255            ServerHold => "serverHold",
256            ClientRenewProhibited => "clientRenewProhibited",
257            ServerRenewProhibited => "serverRenewProhibited",
258            ClientTransferProhibited => "clientTransferProhibited",
259            ServerTransferProhibited => "serverTransferProhibited",
260            ClientUpdateProhibited => "clientUpdateProhibited",
261            ServerUpdateProhibited => "serverUpdateProhibited",
262            Inactive => "inactive",
263            Ok => "ok",
264            PendingCreate => "pendingCreate",
265            PendingDelete => "pendingDelete",
266            PendingRenew => "pendingRenew",
267            PendingTransfer => "pendingTransfer",
268            PendingUpdate => "pendingUpdate",
269        }
270    }
271}
272
273impl ToXml for Status {
274    fn serialize<W: fmt::Write + ?Sized>(
275        &self,
276        _: Option<instant_xml::Id<'_>>,
277        serializer: &mut Serializer<W>,
278    ) -> Result<(), instant_xml::Error> {
279        serializer.write_start("status", XMLNS)?;
280        serializer.write_attr("s", XMLNS, &self.as_str())?;
281        serializer.end_empty()
282    }
283}
284
285impl<'xml> FromXml<'xml> for Status {
286    fn matches(id: instant_xml::Id<'_>, _: Option<instant_xml::Id<'_>>) -> bool {
287        id == instant_xml::Id {
288            ns: XMLNS,
289            name: "status",
290        }
291    }
292
293    fn deserialize<'cx>(
294        into: &mut Self::Accumulator,
295        field: &'static str,
296        deserializer: &mut Deserializer<'cx, 'xml>,
297    ) -> Result<(), instant_xml::Error> {
298        use instant_xml::de::Node;
299        use instant_xml::{Error, Id};
300
301        let node = match deserializer.next() {
302            Some(result) => result?,
303            None => return Err(Error::MissingValue(field)),
304        };
305
306        let attr = match node {
307            Node::Attribute(attr) => attr,
308            Node::Open(_) | Node::Text(_) => return Err(Error::MissingValue(field)),
309            node => return Err(Error::UnexpectedNode(format!("{node:?} in Status"))),
310        };
311
312        let id = deserializer.attribute_id(&attr)?;
313        let expected = Id { ns: "", name: "s" };
314        if id != expected {
315            return Err(Error::MissingValue(field));
316        }
317
318        *into = Some(match attr.value.as_ref() {
319            "clientDeleteProhibited" => Self::ClientDeleteProhibited,
320            "serverDeleteProhibited" => Self::ServerDeleteProhibited,
321            "clientHold" => Self::ClientHold,
322            "serverHold" => Self::ServerHold,
323            "clientRenewProhibited" => Self::ClientRenewProhibited,
324            "serverRenewProhibited" => Self::ServerRenewProhibited,
325            "clientTransferProhibited" => Self::ClientTransferProhibited,
326            "serverTransferProhibited" => Self::ServerTransferProhibited,
327            "clientUpdateProhibited" => Self::ClientUpdateProhibited,
328            "serverUpdateProhibited" => Self::ServerUpdateProhibited,
329            "inactive" => Self::Inactive,
330            "ok" => Self::Ok,
331            "pendingCreate" => Self::PendingCreate,
332            "pendingDelete" => Self::PendingDelete,
333            "pendingRenew" => Self::PendingRenew,
334            "pendingTransfer" => Self::PendingTransfer,
335            "pendingUpdate" => Self::PendingUpdate,
336            val => return Err(Error::UnexpectedValue(format!("invalid status {val:?}"))),
337        });
338
339        deserializer.ignore()?;
340        Ok(())
341    }
342
343    type Accumulator = Option<Self>;
344    const KIND: instant_xml::Kind = instant_xml::Kind::Element;
345}