use std::collections::HashSet;
use crate::prelude::{ttl::Ttl0Data, ContentExtensions};
use {
crate::prelude::{Common, Extension, ObjectCommon},
std::{net::IpAddr, str::FromStr},
};
use serde::{Deserialize, Serialize};
use super::{
to_opt_vec, to_opt_vectorstringish, types::Link, CommonFields, Entity, Event, GetSelfLink,
Notice, ObjectCommonFields, Port43, RdapResponseError, Remark, SelfLink, ToChild, ToResponse,
VectorStringish,
};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
pub struct IpAddresses {
#[serde(skip_serializing_if = "Option::is_none")]
pub v6: Option<VectorStringish>,
#[serde(skip_serializing_if = "Option::is_none")]
pub v4: Option<VectorStringish>,
}
#[buildstructor::buildstructor]
impl IpAddresses {
#[builder(visibility = "pub")]
fn new(addresses: Vec<String>) -> Result<Self, RdapResponseError> {
let mut v4: Vec<String> = Vec::new();
let mut v6: Vec<String> = Vec::new();
for addr in addresses {
let ip = IpAddr::from_str(&addr)?;
match ip {
IpAddr::V4(_) => v4.push(addr),
IpAddr::V6(_) => v6.push(addr),
}
}
Ok(Self {
v4: to_opt_vectorstringish(v4),
v6: to_opt_vectorstringish(v6),
})
}
#[allow(dead_code)]
#[builder(entry = "illegal", visibility = "pub(crate)")]
fn new_illegal(v6: Option<Vec<String>>, v4: Option<Vec<String>>) -> Self {
Self {
v4: v4.map(VectorStringish::from),
v6: v6.map(VectorStringish::from),
}
}
pub fn v6s(&self) -> &[String] {
self.v6
.as_ref()
.map(|v| v.vec().as_ref())
.unwrap_or_default()
}
pub fn v4s(&self) -> &[String] {
self.v4
.as_ref()
.map(|v| v.vec().as_ref())
.unwrap_or_default()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Nameserver {
#[serde(flatten)]
pub common: Common,
#[serde(flatten)]
pub object_common: ObjectCommon,
#[serde(rename = "ldhName")]
#[serde(skip_serializing_if = "Option::is_none")]
pub ldh_name: Option<String>,
#[serde(rename = "unicodeName")]
#[serde(skip_serializing_if = "Option::is_none")]
pub unicode_name: Option<String>,
#[serde(rename = "ipAddresses")]
#[serde(skip_serializing_if = "Option::is_none")]
pub ip_addresses: Option<IpAddresses>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl0_data: Option<Ttl0Data>,
}
#[buildstructor::buildstructor]
impl Nameserver {
#[builder(visibility = "pub")]
fn new<T: Into<String>>(
ldh_name: T,
addresses: Vec<String>,
handle: Option<String>,
remarks: Vec<Remark>,
links: Vec<Link>,
events: Vec<Event>,
statuses: Vec<String>,
port_43: Option<Port43>,
entities: Vec<Entity>,
ttl0_data: Option<Ttl0Data>,
redacted: Option<Vec<crate::response::redacted::Redacted>>,
) -> Result<Self, RdapResponseError> {
let ip_addresses = if !addresses.is_empty() {
Some(IpAddresses::builder().addresses(addresses).build()?)
} else {
None
};
Ok(Self {
common: Common::builder().build(),
object_common: ObjectCommon::nameserver()
.and_handle(handle)
.and_remarks(to_opt_vec(remarks))
.and_links(to_opt_vec(links))
.and_events(to_opt_vec(events))
.status(statuses)
.and_port_43(port_43)
.and_entities(to_opt_vec(entities))
.and_redacted(redacted)
.build(),
ldh_name: Some(ldh_name.into()),
unicode_name: None,
ip_addresses,
ttl0_data,
})
}
#[builder(entry = "response_obj", visibility = "pub")]
fn new_response_obj<T: Into<String>>(
ldh_name: T,
addresses: Vec<String>,
handle: Option<String>,
remarks: Vec<Remark>,
links: Vec<Link>,
events: Vec<Event>,
statuses: Vec<String>,
port_43: Option<Port43>,
entities: Vec<Entity>,
notices: Vec<Notice>,
extensions: Vec<Extension>,
ttl0_data: Option<Ttl0Data>,
redacted: Option<Vec<crate::response::redacted::Redacted>>,
) -> Result<Self, RdapResponseError> {
let common = Common::level0()
.extensions(extensions)
.and_notices(to_opt_vec(notices))
.build();
let mut nameserver = Nameserver::builder()
.ldh_name(ldh_name)
.addresses(addresses)
.and_handle(handle)
.remarks(remarks)
.links(links)
.events(events)
.statuses(statuses)
.and_port_43(port_43)
.entities(entities)
.and_ttl0_data(ttl0_data)
.and_redacted(redacted)
.build()?;
nameserver.common = common;
Ok(nameserver)
}
#[builder(entry = "illegal", visibility = "pub(crate)")]
#[allow(dead_code)]
fn new_illegal(ldh_name: Option<String>, ip_addresses: Option<IpAddresses>) -> Self {
Self {
common: Common::level0().build(),
object_common: ObjectCommon::nameserver().build(),
ldh_name,
unicode_name: None,
ip_addresses,
ttl0_data: None,
}
}
pub fn ldh_name(&self) -> Option<&str> {
self.ldh_name.as_deref()
}
pub fn unicode_name(&self) -> Option<&str> {
self.unicode_name.as_deref()
}
pub fn ip_addresses(&self) -> Option<&IpAddresses> {
self.ip_addresses.as_ref()
}
pub fn ttl0_data(&self) -> Option<&Ttl0Data> {
self.ttl0_data.as_ref()
}
}
impl ToResponse for Nameserver {
fn to_response(self) -> super::RdapResponse {
super::RdapResponse::Nameserver(Box::new(self))
}
}
impl GetSelfLink for Nameserver {
fn self_link(&self) -> Option<&Link> {
self.object_common.self_link()
}
}
impl SelfLink for Nameserver {
fn with_self_link(mut self, link: Link) -> Self {
self.object_common = self.object_common.with_self_link(link);
self
}
}
impl ToChild for Nameserver {
fn to_child(mut self) -> Self {
self.common = Common {
rdap_conformance: None,
notices: None,
};
self
}
}
impl CommonFields for Nameserver {
fn common(&self) -> &Common {
&self.common
}
}
impl ObjectCommonFields for Nameserver {
fn object_common(&self) -> &ObjectCommon {
&self.object_common
}
}
impl ContentExtensions for Nameserver {
fn content_extensions(&self) -> std::collections::HashSet<super::ExtensionId> {
let mut exts = HashSet::new();
exts.extend(self.common().content_extensions());
exts.extend(self.object_common().content_extensions());
exts
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use super::Nameserver;
#[test]
fn GIVEN_nameserver_WHEN_deserialize_THEN_success() {
let expected = r#"
{
"objectClassName" : "nameserver",
"handle" : "XXXX",
"ldhName" : "ns1.xn--fo-5ja.example",
"unicodeName" : "ns.fóo.example",
"status" : [ "active" ],
"ipAddresses" :
{
"v4": [ "192.0.2.1", "192.0.2.2" ],
"v6": [ "2001:db8::123" ]
},
"remarks" :
[
{
"description" :
[
"She sells sea shells down by the sea shore.",
"Originally written by Terry Sullivan."
]
}
],
"links" :
[
{
"value" : "https://example.net/nameserver/ns1.xn--fo-5ja.example",
"rel" : "self",
"href" : "https://example.net/nameserver/ns1.xn--fo-5ja.example",
"type" : "application/rdap+json"
}
],
"port43" : "whois.example.net",
"events" :
[
{
"eventAction" : "registration",
"eventDate" : "1990-12-31T23:59:59Z"
},
{
"eventAction" : "last changed",
"eventDate" : "1991-12-31T23:59:59Z",
"eventActor" : "joe@example.com"
}
]
}
"#;
let actual = serde_json::from_str::<Nameserver>(expected);
let actual = actual.unwrap();
assert_eq!(actual.object_common.object_class_name, "nameserver");
assert!(actual.object_common.handle.is_some());
assert!(actual.ldh_name.is_some());
assert!(actual.unicode_name.is_some());
assert!(actual.ip_addresses.is_some());
assert!(actual.object_common.remarks.is_some());
assert!(actual.object_common.status.is_some());
assert!(actual.object_common.links.is_some());
assert!(actual.object_common.events.is_some());
}
}