1use 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#[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
40#[xml(rename = "hostAttr", ns(XMLNS))]
41pub struct HostAttr<'a> {
42 #[xml(rename = "hostName")]
44 pub name: Cow<'a, str>,
45 #[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#[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#[derive(Debug, FromXml, ToXml)]
142#[xml(rename = "contact", ns(XMLNS))]
143pub struct DomainContact<'a> {
144 #[xml(attribute, rename = "type")]
146 pub contact_type: Cow<'a, str>,
147 #[xml(direct)]
149 pub id: Cow<'a, str>,
150}
151
152#[derive(Clone, Copy, Debug, ToXml)]
154#[xml(rename = "period", ns(XMLNS))]
155pub struct Period {
156 #[xml(attribute)]
158 unit: char,
159 #[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#[derive(Clone, Debug, FromXml, ToXml)]
210#[xml(rename = "authInfo", ns(XMLNS))]
211pub struct DomainAuthInfo<'a> {
212 #[xml(rename = "pw")]
214 pub password: Cow<'a, str>,
215}
216
217impl<'a> DomainAuthInfo<'a> {
218 pub fn new(password: &'a str) -> Self {
220 Self {
221 password: password.into(),
222 }
223 }
224}
225
226#[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}