use super::{NamespaceScope, NamespaceSelection, NamespaceSource, NamespaceUpdates};
use crate::kv_dc_relay::discovery::DcDiscoveryFilter;
#[cfg(test)]
use dynamo_runtime::discovery::DiscoveryQuery;
use std::collections::HashSet;
use tokio_util::sync::CancellationToken;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct KvDcRelayDiscoveryConfig {
pub namespaces: Vec<String>,
pub endpoint_prefixes: Vec<String>,
pub watch_all: bool,
}
impl KvDcRelayDiscoveryConfig {
pub fn validate(&self) -> anyhow::Result<()> {
anyhow::ensure!(
self.watch_all || !self.namespaces.is_empty(),
"KV DC Relay requires at least one discovery namespace or explicit watch_all"
);
anyhow::ensure!(
!self.watch_all || self.namespaces.is_empty(),
"KV DC Relay watch_all cannot be combined with explicit discovery namespaces"
);
let mut unique_namespaces = HashSet::new();
for namespace in &self.namespaces {
anyhow::ensure!(
!namespace.trim().is_empty(),
"KV DC Relay discovery namespaces must not be empty"
);
anyhow::ensure!(
namespace.trim() == namespace,
"KV DC Relay discovery namespaces must not contain surrounding whitespace"
);
anyhow::ensure!(
unique_namespaces.insert(namespace),
"duplicate KV DC Relay discovery namespace: {namespace}"
);
}
let mut unique_prefixes = HashSet::new();
for prefix in &self.endpoint_prefixes {
anyhow::ensure!(
!prefix.trim().is_empty(),
"KV DC Relay endpoint prefixes must not be empty"
);
anyhow::ensure!(
prefix.trim() == prefix,
"KV DC Relay endpoint prefixes must not contain surrounding whitespace"
);
anyhow::ensure!(
unique_prefixes.insert(prefix),
"duplicate KV DC Relay endpoint prefix: {prefix}"
);
anyhow::ensure!(
self.watch_all
|| self.namespaces.iter().any(|namespace| {
prefix == namespace
|| prefix
.strip_prefix(namespace)
.is_some_and(|suffix| suffix.starts_with('.'))
}),
"KV DC Relay endpoint prefix {prefix} is outside the configured namespaces"
);
}
Ok(())
}
#[cfg(test)]
pub(crate) fn queries(&self) -> Vec<DiscoveryQuery> {
if self.watch_all {
vec![DiscoveryQuery::AllModels]
} else {
self.namespaces
.iter()
.map(|namespace| DiscoveryQuery::NamespacedModels {
namespace: namespace.clone(),
})
.collect()
}
}
pub(crate) fn filter(&self) -> DcDiscoveryFilter {
DcDiscoveryFilter {
endpoint_prefixes: self.endpoint_prefixes.clone(),
}
}
}
pub(crate) struct DiscoveryNamespaces {
pub config: KvDcRelayDiscoveryConfig,
}
impl NamespaceSource for DiscoveryNamespaces {
fn updates(self: Box<Self>, cancel: CancellationToken) -> NamespaceUpdates {
Box::pin(async_stream::stream! {
let scope = if self.config.watch_all {
NamespaceScope::All
} else {
let mut namespaces = self.config.namespaces.clone();
namespaces.sort();
NamespaceScope::Namespaces(namespaces)
};
yield Ok(NamespaceSelection { scope, revision: None });
cancel.cancelled().await;
})
}
}