use super::*;
impl MarkdownFormatter {
pub(super) fn format_rdap(&self, response: &RdapResponse) -> String {
let name = response
.domain_name()
.or(response.name.as_deref())
.unwrap_or("Unknown");
let mut output = vec![format!("## RDAP: {}", MdSafe(name)), String::new()];
let mut b = Bullets(&mut output);
b.code_opt("Handle", &response.handle);
b.opt("Registrar", &response.get_registrar());
b.opt("Registrant", &response.get_registrant());
b.opt("Organization", &response.get_registrant_organization());
b.date("Created", response.creation_date());
b.expires(response.expiration_date());
b.date("Updated", response.last_updated());
b.code_list("Status", &response.status);
b.code_list("Nameservers", &response.nameserver_names());
if response.is_dnssec_signed() {
b.raw("DNSSEC", "signed");
}
b.code_opt("Start Address", &response.start_address);
b.code_opt("End Address", &response.end_address);
b.opt("Country", &response.country);
if let Some(start) = response.start_autnum {
let end = response.end_autnum.unwrap_or(start);
b.raw("AS Number", format!("`AS{start}` - `AS{end}`"));
}
let infos = contact::rdap_contacts(response);
b.contacts(contact::rdap_detail_views(&infos));
let billing = response.get_billing_contact();
b.contact("Billing", Contact::rdap(billing.as_ref()));
output.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_markdown_rdap_entity_name_backtick_neutralized() {
use crate::rdap::RdapResponse;
let json = serde_json::json!({
"ldhName": "example.com",
"entities": [
{
"objectClassName": "entity",
"handle": "Evil`Registrar`Co",
"roles": ["registrar"],
}
]
});
let response: RdapResponse = serde_json::from_value(json).unwrap();
let output = MarkdownFormatter::new().format_rdap(&response);
assert!(
output.contains("- **Registrar**: Evil'Registrar'Co"),
"expected backticks neutralized in RDAP output:\n{}",
output
);
assert!(
!output.contains("Evil`Registrar`Co"),
"raw backticks survived into output:\n{}",
output
);
}
#[test]
fn registrant_identity_is_printed_once() {
let response: RdapResponse = serde_json::from_value(serde_json::json!({
"ldhName": "example.com",
"entities": [{
"objectClassName": "entity",
"roles": ["registrant"],
"vcardArray": ["vcard", [
["fn", {}, "text", "Jane Registrant"],
["org", {}, "text", "Example LLC"],
["email", {}, "text", "jane@example.com"]
]]
}]
}))
.unwrap();
for out in [
MarkdownFormatter::new().format_rdap(&response),
MarkdownFormatter::new().format_lookup(&LookupResult::Rdap {
data: Box::new(response.clone()),
whois_fallback: None,
}),
] {
assert_eq!(out.matches("Jane Registrant").count(), 1, "got:\n{out}");
assert_eq!(out.matches("Example LLC").count(), 1, "got:\n{out}");
assert!(out.contains("### Registrant Contact"), "got:\n{out}");
assert!(out.contains("`jane@example.com`"), "got:\n{out}");
}
}
#[test]
fn test_markdown_rdap_domain_fields_precede_contact_sections() {
let json = serde_json::json!({
"ldhName": "example.com",
"status": ["active"],
"events": [
{"eventAction": "registration", "eventDate": "2010-03-15T04:00:00Z"},
{"eventAction": "expiration", "eventDate": "2099-03-15T04:00:00Z"}
],
"nameservers": [{"objectClassName": "nameserver", "ldhName": "ns1.example.com"}],
"entities": [{
"objectClassName": "entity",
"handle": "TECH-1",
"roles": ["technical"],
"vcardArray": ["vcard", [
["fn", {}, "text", "Tech Person"],
["email", {}, "text", "tech@example.com"]
]]
}]
});
let response: RdapResponse = serde_json::from_value(json).unwrap();
let out = MarkdownFormatter::new().format_rdap(&response);
let heading = out
.find("### Tech Contact")
.unwrap_or_else(|| panic!("tech contact missing:\n{out}"));
for field in [
"- **Created**",
"- **Expires**",
"- **Status**",
"- **Nameservers**",
] {
let at = out
.find(field)
.unwrap_or_else(|| panic!("{field} missing:\n{out}"));
assert!(at < heading, "{field} rendered under a contact:\n{out}");
}
assert!(out.contains("tech@example.com"), "contact kept:\n{out}");
}
}