use async_trait::async_trait;
use crate::discovery::{
Discovery, DiscoveryError, DiscoveryResult, InstanceEndpoint, ServiceInstance,
};
#[derive(Debug, Clone)]
pub struct DnsDiscovery {
service: String,
host: String,
port: u16,
}
impl DnsDiscovery {
pub fn new(service: impl Into<String>, host: impl Into<String>, port: u16) -> Self {
Self {
service: service.into(),
host: host.into(),
port,
}
}
}
#[async_trait]
impl Discovery for DnsDiscovery {
async fn discover(&self, service: &str) -> DiscoveryResult<Vec<ServiceInstance>> {
if service != self.service {
return Err(DiscoveryError::NoInstances {
service: service.to_string(),
});
}
let addresses = tokio::net::lookup_host((self.host.as_str(), self.port))
.await
.map_err(|error| DiscoveryError::Resolve {
host: self.host.clone(),
message: error.to_string(),
})?
.collect::<Vec<_>>();
if addresses.is_empty() {
return Err(DiscoveryError::NoInstances {
service: service.to_string(),
});
}
Ok(addresses
.into_iter()
.enumerate()
.map(|(index, address)| {
ServiceInstance::new(
service,
format!("{}-{index}", self.host),
InstanceEndpoint::from_socket_addr(address),
)
})
.collect())
}
}
#[cfg(test)]
mod tests {
use super::{Discovery, DnsDiscovery};
#[tokio::test]
async fn dns_discovery_reports_unknown_service_as_empty() {
let discovery = DnsDiscovery::new("api", "localhost", 80);
assert!(discovery.discover("other").await.is_err());
}
#[tokio::test]
async fn dns_discovery_resolves_localhost() {
let discovery = DnsDiscovery::new("api", "localhost", 80);
let instances = discovery.discover("api").await.expect("localhost");
assert!(!instances.is_empty());
}
}