use std::sync::Arc;
use crate::domain::Tld;
use crate::registry::{Registry, RegistryProvider};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LayerStrategy {
#[default]
Override,
Union,
}
#[derive(Debug, Clone, Default)]
pub struct LayeredRegistry {
layers: Vec<Arc<dyn RegistryProvider>>,
strategy: LayerStrategy,
}
impl LayeredRegistry {
pub fn new(strategy: LayerStrategy) -> Self {
LayeredRegistry {
layers: Vec::new(),
strategy,
}
}
pub fn layer(mut self, provider: impl RegistryProvider + 'static) -> Self {
self.layers.push(Arc::new(provider));
self
}
pub fn shared_layer(mut self, provider: Arc<dyn RegistryProvider>) -> Self {
self.layers.push(provider);
self
}
pub fn strategy(&self) -> LayerStrategy {
self.strategy
}
pub fn layer_count(&self) -> usize {
self.layers.len()
}
}
impl RegistryProvider for LayeredRegistry {
fn get(&self, tld: &Tld) -> Option<Arc<Registry>> {
let mut merged: Option<Arc<Registry>> = None;
for layer in &self.layers {
let Some(found) = layer.get(tld) else {
continue;
};
merged = Some(match merged {
None => found,
Some(base) => Arc::new(match self.strategy {
LayerStrategy::Override => base.overlay(&found),
LayerStrategy::Union => base.union(&found),
}),
});
}
merged
}
fn tlds(&self) -> Vec<Tld> {
let mut tlds: Vec<Tld> = self.layers.iter().flat_map(|layer| layer.tlds()).collect();
tlds.sort();
tlds.dedup();
tlds
}
fn describe(&self) -> String {
let layers: Vec<String> = self.layers.iter().map(|layer| layer.describe()).collect();
format!("layered[{:?}]: {}", self.strategy, layers.join(" < "))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::registry::{Endpoint, JsonRegistry};
fn provider(json: &str) -> JsonRegistry {
JsonRegistry::from_json(json, "test").unwrap()
}
const BASE: &str = r#"{"registries":[{
"tlds":["example"],
"whois":"old.example",
"available":["No match"],
"note":"bundled"
}]}"#;
#[test]
fn override_replaces_endpoints() {
let registry = LayeredRegistry::new(LayerStrategy::Override)
.layer(provider(BASE))
.layer(provider(
r#"{"registries":[{"tlds":["example"],"whois":"new.example"}]}"#,
));
let entry = registry.get(&Tld::parse("example").unwrap()).unwrap();
assert_eq!(entry.endpoints().len(), 1);
assert_eq!(entry.endpoints()[0].address(), "new.example");
assert_eq!(entry.available_markers(), ["No match"]);
assert_eq!(entry.note(), Some("bundled"));
}
#[test]
fn union_keeps_both_sets_of_endpoints_lower_first() {
let registry = LayeredRegistry::new(LayerStrategy::Union)
.layer(provider(BASE))
.layer(provider(
r#"{"registries":[{"tlds":["example"],"rdap":["https://rdap.example/"]}]}"#,
));
let entry = registry.get(&Tld::parse("example").unwrap()).unwrap();
assert_eq!(entry.endpoints().len(), 2);
assert_eq!(entry.endpoints()[0], Endpoint::whois("old.example"));
assert!(entry.endpoints()[1].is_rdap());
}
#[test]
fn union_does_not_duplicate_a_shared_endpoint() {
let registry = LayeredRegistry::new(LayerStrategy::Union)
.layer(provider(BASE))
.layer(provider(BASE));
let entry = registry.get(&Tld::parse("example").unwrap()).unwrap();
assert_eq!(entry.endpoints().len(), 1);
}
#[test]
fn a_tld_only_the_upper_layer_knows_is_still_found() {
let registry = LayeredRegistry::new(LayerStrategy::Override)
.layer(provider(BASE))
.layer(provider(
r#"{"registries":[{"tlds":["other"],"whois":"w.other"}]}"#,
));
assert!(registry.get(&Tld::parse("other").unwrap()).is_some());
assert!(registry.get(&Tld::parse("example").unwrap()).is_some());
assert!(registry.get(&Tld::parse("absent").unwrap()).is_none());
}
#[test]
fn tlds_are_the_deduplicated_union() {
let registry = LayeredRegistry::new(LayerStrategy::Override)
.layer(provider(BASE))
.layer(provider(
r#"{"registries":[{"tlds":["example","other"],"whois":"w.other"}]}"#,
));
let listed: Vec<String> = registry
.tlds()
.iter()
.map(|t| t.ascii().to_string())
.collect();
assert_eq!(listed, ["example", "other"]);
}
#[test]
fn an_empty_stack_resolves_nothing() {
let registry = LayeredRegistry::new(LayerStrategy::Override);
assert!(registry.get(&Tld::parse("com").unwrap()).is_none());
assert!(registry.tlds().is_empty());
}
}