use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use crate::domain::Tld;
use crate::error::{Error, Result};
use crate::registry::Endpoint;
mod caching;
mod retry;
mod throttle;
#[cfg(any(test, feature = "mock"))]
mod mock;
#[cfg(all(feature = "rdap", any(feature = "blocking", feature = "async")))]
mod rdap;
mod whois43;
pub use caching::CachingTransport;
pub use retry::{RetryPolicy, RetryTransport};
pub use throttle::{ThrottlePolicy, ThrottleTransport};
pub use whois43::referral_host;
#[cfg(any(test, feature = "mock"))]
pub use mock::{MockTransport, Scripted};
#[cfg(all(feature = "rdap", feature = "blocking"))]
pub use rdap::RdapTransport;
#[cfg(feature = "blocking")]
pub use whois43::Whois43Transport;
#[cfg(feature = "async")]
pub use caching::AsyncCachingTransport;
#[cfg(all(feature = "async", feature = "rdap"))]
pub use rdap::AsyncRdapTransport;
#[cfg(feature = "async")]
pub use retry::AsyncRetryTransport;
#[cfg(feature = "async")]
pub use throttle::AsyncThrottleTransport;
#[cfg(feature = "async")]
pub use whois43::AsyncWhois43Transport;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Query {
pub endpoint: Endpoint,
pub wire_name: String,
pub tld: Tld,
}
impl Query {
pub fn new(endpoint: Endpoint, wire_name: impl Into<String>, tld: Tld) -> Self {
Query {
endpoint,
wire_name: wire_name.into(),
tld,
}
}
pub fn cache_key(&self) -> String {
format!("{}|{}", self.endpoint.address(), self.wire_name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResponseKind {
WhoisText,
RdapJson,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawResponse {
endpoint: Endpoint,
kind: ResponseKind,
text: String,
elapsed: Duration,
from_cache: bool,
}
impl RawResponse {
pub fn new(
endpoint: Endpoint,
kind: ResponseKind,
text: impl Into<String>,
elapsed: Duration,
) -> Self {
RawResponse {
endpoint,
kind,
text: text.into(),
elapsed,
from_cache: false,
}
}
pub fn endpoint(&self) -> &Endpoint {
&self.endpoint
}
pub fn kind(&self) -> ResponseKind {
self.kind
}
pub fn text(&self) -> &str {
&self.text
}
pub fn elapsed(&self) -> Duration {
self.elapsed
}
pub fn is_cached(&self) -> bool {
self.from_cache
}
pub(crate) fn mark_cached(mut self) -> Self {
self.from_cache = true;
self
}
pub fn is_blank(&self) -> bool {
self.text.trim().is_empty()
}
}
pub trait Transport: fmt::Debug + Send + Sync {
fn supports(&self, endpoint: &Endpoint) -> bool;
fn fetch(&self, query: &Query) -> Result<RawResponse>;
fn name(&self) -> String;
}
pub trait AsyncTransport: fmt::Debug + Send + Sync {
fn supports(&self, endpoint: &Endpoint) -> bool;
fn fetch<'a>(&'a self, query: &'a Query) -> BoxFuture<'a, Result<RawResponse>>;
fn name(&self) -> String;
}
impl<T: Transport + ?Sized> Transport for Arc<T> {
fn supports(&self, endpoint: &Endpoint) -> bool {
(**self).supports(endpoint)
}
fn fetch(&self, query: &Query) -> Result<RawResponse> {
(**self).fetch(query)
}
fn name(&self) -> String {
(**self).name()
}
}
impl<T: AsyncTransport + ?Sized> AsyncTransport for Arc<T> {
fn supports(&self, endpoint: &Endpoint) -> bool {
(**self).supports(endpoint)
}
fn fetch<'a>(&'a self, query: &'a Query) -> BoxFuture<'a, Result<RawResponse>> {
(**self).fetch(query)
}
fn name(&self) -> String {
(**self).name()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TransportConfig {
pub connect_timeout: Duration,
pub read_timeout: Duration,
pub max_response_bytes: usize,
}
impl TransportConfig {
pub const DEFAULT: TransportConfig = TransportConfig {
connect_timeout: Duration::from_secs(5),
read_timeout: Duration::from_secs(10),
max_response_bytes: 1024 * 1024,
};
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = timeout;
self
}
pub fn read_timeout(mut self, timeout: Duration) -> Self {
self.read_timeout = timeout;
self
}
pub fn max_response_bytes(mut self, bytes: usize) -> Self {
self.max_response_bytes = bytes;
self
}
}
impl Default for TransportConfig {
fn default() -> Self {
TransportConfig::DEFAULT
}
}
#[derive(Debug, Default, Clone)]
pub struct Router {
transports: Vec<Arc<dyn Transport>>,
}
impl Router {
pub fn new() -> Self {
Router {
transports: Vec::new(),
}
}
pub fn with(mut self, transport: impl Transport + 'static) -> Self {
self.transports.push(Arc::new(transport));
self
}
pub fn with_shared(mut self, transport: Arc<dyn Transport>) -> Self {
self.transports.push(transport);
self
}
pub fn handles(&self, endpoint: &Endpoint) -> bool {
self.transports
.iter()
.any(|transport| transport.supports(endpoint))
}
pub fn len(&self) -> usize {
self.transports.len()
}
pub fn is_empty(&self) -> bool {
self.transports.is_empty()
}
}
impl Transport for Router {
fn supports(&self, endpoint: &Endpoint) -> bool {
self.handles(endpoint)
}
fn fetch(&self, query: &Query) -> Result<RawResponse> {
for transport in &self.transports {
if transport.supports(&query.endpoint) {
return transport.fetch(query);
}
}
Err(Error::NoEndpoint {
tld: query.tld.clone(),
detail: format!(
"no transport handles {}; router has [{}]",
query.endpoint,
self.transports
.iter()
.map(|t| t.name())
.collect::<Vec<_>>()
.join(", ")
),
})
}
fn name(&self) -> String {
format!(
"router({})",
self.transports
.iter()
.map(|t| t.name())
.collect::<Vec<_>>()
.join("+")
)
}
}
#[derive(Debug, Default, Clone)]
pub struct AsyncRouter {
transports: Vec<Arc<dyn AsyncTransport>>,
}
impl AsyncRouter {
pub fn new() -> Self {
AsyncRouter {
transports: Vec::new(),
}
}
pub fn with(mut self, transport: impl AsyncTransport + 'static) -> Self {
self.transports.push(Arc::new(transport));
self
}
pub fn with_shared(mut self, transport: Arc<dyn AsyncTransport>) -> Self {
self.transports.push(transport);
self
}
pub fn handles(&self, endpoint: &Endpoint) -> bool {
self.transports
.iter()
.any(|transport| transport.supports(endpoint))
}
pub fn len(&self) -> usize {
self.transports.len()
}
pub fn is_empty(&self) -> bool {
self.transports.is_empty()
}
}
impl AsyncTransport for AsyncRouter {
fn supports(&self, endpoint: &Endpoint) -> bool {
self.handles(endpoint)
}
fn fetch<'a>(&'a self, query: &'a Query) -> BoxFuture<'a, Result<RawResponse>> {
Box::pin(async move {
for transport in &self.transports {
if transport.supports(&query.endpoint) {
return transport.fetch(query).await;
}
}
Err(Error::NoEndpoint {
tld: query.tld.clone(),
detail: format!("no async transport handles {}", query.endpoint),
})
})
}
fn name(&self) -> String {
format!(
"async-router({})",
self.transports
.iter()
.map(|t| t.name())
.collect::<Vec<_>>()
.join("+")
)
}
}
#[cfg(any(feature = "blocking", feature = "async", test))]
pub(crate) fn decode_bytes(bytes: &[u8]) -> String {
match std::str::from_utf8(bytes) {
Ok(text) => text.to_string(),
Err(_) => bytes.iter().map(|&byte| byte as char).collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_utf8_and_falls_back_to_latin1() {
assert_eq!(decode_bytes("Müller".as_bytes()), "Müller");
assert_eq!(
decode_bytes(&[b'M', 0xFC, b'l', b'l', b'e', b'r']),
"Müller"
);
assert_eq!(decode_bytes(b""), "");
}
#[test]
fn cache_key_separates_endpoint_from_name() {
let query = Query::new(
Endpoint::whois("whois.nic.uk"),
"example.co.uk",
Tld::parse("co.uk").unwrap(),
);
assert_eq!(query.cache_key(), "whois.nic.uk|example.co.uk");
}
#[test]
fn blank_detection_ignores_whitespace() {
let response = |text: &str| {
RawResponse::new(
Endpoint::whois("w.example"),
ResponseKind::WhoisText,
text,
Duration::ZERO,
)
};
assert!(response("").is_blank());
assert!(response(" \r\n\t ").is_blank());
assert!(!response("No match").is_blank());
}
}