Skip to main content

rdap_types/
domain.rs

1//! Normalised RDAP domain response type.
2//!
3//! Follows RFC 9083 §5.3 (Domain Object Class).
4
5use serde::{Deserialize, Serialize};
6
7use crate::common::{RdapEntity, RdapEvent, RdapLink, RdapRemark, RdapStatus, ResponseMeta};
8
9/// Normalised RDAP response for a domain query.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct DomainResponse {
13    /// The original query string.
14    pub query: String,
15
16    /// LDH (letters, digits, hyphens) form of the domain name.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub ldh_name: Option<String>,
19
20    /// Unicode (internationalised) form of the domain name.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub unicode_name: Option<String>,
23
24    /// Registry handle / ROID.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub handle: Option<String>,
27
28    /// Current status flags.
29    #[serde(default, skip_serializing_if = "Vec::is_empty")]
30    pub status: Vec<RdapStatus>,
31
32    /// Delegated nameservers.
33    #[serde(default, skip_serializing_if = "Vec::is_empty")]
34    pub nameservers: Vec<String>,
35
36    /// Registrar summary (extracted from entities for convenience).
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub registrar: Option<RegistrarSummary>,
39
40    /// All associated entities (registrant, admin, tech, abuse, etc.).
41    #[serde(default, skip_serializing_if = "Vec::is_empty")]
42    pub entities: Vec<RdapEntity>,
43
44    /// Lifecycle events (registration, expiration, last changed, …).
45    #[serde(default, skip_serializing_if = "Vec::is_empty")]
46    pub events: Vec<RdapEvent>,
47
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub links: Vec<RdapLink>,
50
51    #[serde(default, skip_serializing_if = "Vec::is_empty")]
52    pub remarks: Vec<RdapRemark>,
53
54    /// Query metadata (source server, timestamp, cache status).
55    pub meta: ResponseMeta,
56}
57
58impl DomainResponse {
59    /// Returns the expiration date string from events, if present.
60    pub fn expiration_date(&self) -> Option<&str> {
61        self.events
62            .iter()
63            .find(|e| e.event_action == "expiration")
64            .map(|e| e.event_date.as_str())
65    }
66
67    /// Returns the registration date string from events, if present.
68    pub fn registration_date(&self) -> Option<&str> {
69        self.events
70            .iter()
71            .find(|e| e.event_action == "registration")
72            .map(|e| e.event_date.as_str())
73    }
74
75    /// Returns `true` if the domain has an "active" status.
76    pub fn is_active(&self) -> bool {
77        self.status.iter().any(|s| matches!(s, RdapStatus::Active))
78    }
79}
80
81/// Condensed registrar information extracted from the entities list.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct RegistrarSummary {
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub name: Option<String>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub handle: Option<String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub url: Option<String>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub abuse_email: Option<String>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub abuse_phone: Option<String>,
94}