1use std::net::Ipv6Addr;
2
3use futures::future::join_all;
4use hickory_resolver::{
5 error::ResolveError, proto::rr::rdata::AAAA, IntoName, Name, TokioAsyncResolver,
6};
7
8use crate::{
9 error::Error,
10 resolver::{lookup_txt, Resolver},
11 Node, Peer, Region,
12};
13
14pub struct AppResolver<'r> {
18 domain: Name,
19 resolver: &'r TokioAsyncResolver,
20}
21
22impl<'r> AppResolver<'r> {
23 pub(crate) fn new(app: impl Into<String>, resolver: &'r Resolver) -> Self {
24 let app: String = app.into();
25 let name = Name::from_ascii(app).expect("invalid app name");
26 let domain = name.append_label("internal").unwrap();
27
28 Self {
29 domain,
30 resolver: &resolver.0,
31 }
32 }
33
34 #[cfg(feature = "regions")]
36 #[cfg_attr(docsrs, doc(cfg(feature = "regions")))]
37 pub async fn regions(&self) -> Result<Vec<Region>, Error> {
38 let value = self.txt("regions").await?;
39
40 Ok(value
41 .split(',')
42 .filter_map(|code| code.parse::<Region>().ok())
43 .collect())
44 }
45
46 pub async fn nodes(&self) -> Result<Vec<Node>, Error> {
48 let value = self.txt("vms").await?;
49
50 value.split(',').map(|peer| peer.parse::<Node>()).collect()
51 }
52
53 pub async fn peers(&self) -> Result<Vec<Peer>, Error> {
56 let nodes = self.nodes().await?;
57
58 let addrs = join_all(nodes.iter().map(|node| {
59 let name = Name::from_ascii(&node.id)
60 .expect("invalid node ID")
61 .append_label("vm")
62 .unwrap()
63 .append_domain(&self.domain)
64 .expect("invalid query");
65
66 self.resolver.ipv6_lookup(name)
67 }))
68 .await
69 .into_iter()
70 .collect::<Result<Vec<_>, ResolveError>>()
71 .map_err(Error::from)?;
72
73 Ok(nodes
74 .into_iter()
75 .zip(addrs.into_iter())
76 .filter_map(|(node, addrs)| {
77 if let Some(AAAA(addr)) = addrs.into_iter().next() {
78 Some(node.into_peer(addr))
79 } else {
80 None
81 }
82 })
83 .collect())
84 }
85
86 pub async fn nearest_peer_addresses(&self, n: usize) -> Result<Vec<Ipv6Addr>, Error> {
88 let top = Name::from_ascii(format!("top{n}"))
89 .expect("invalid top n")
90 .append_label("nearest")
91 .unwrap()
92 .append_label("of")
93 .unwrap()
94 .append_domain(&self.domain)
95 .expect("invalid query");
96
97 let results = self.resolver.ipv6_lookup(top).await.map_err(Error::from)?;
98
99 Ok(results.into_iter().map(|r| r.0).collect())
100 }
101
102 pub async fn txt(&self, name: impl IntoName) -> Result<String, Error> {
104 let query = name
105 .into_name()
106 .expect("invalid name")
107 .append_domain(&self.domain)
108 .expect("invalid app domain");
109
110 lookup_txt(self.resolver, query).await
111 }
112}