aboutsummaryrefslogtreecommitdiff
path: root/src/tlds.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tlds.rs')
-rw-r--r--src/tlds.rs179
1 files changed, 179 insertions, 0 deletions
diff --git a/src/tlds.rs b/src/tlds.rs
new file mode 100644
index 0000000..2835c24
--- /dev/null
+++ b/src/tlds.rs
@@ -0,0 +1,179 @@
+use std::collections::HashMap;
+use std::sync::OnceLock;
+
+/// whois server overrides from Lists.toml ("io:whois.nic.io" style entries)
+#[derive(Debug, Clone, Default)]
+pub struct WhoisOverrides {
+ map: HashMap<String, String>,
+}
+
+impl WhoisOverrides {
+ pub fn get_server(&self, tld: &str) -> Option<&str> {
+ self.map.get(&tld.to_lowercase()).map(|s| s.as_str())
+ }
+}
+
+/// a named TLD list from Lists.toml
+struct NamedList {
+ name: String,
+ tlds: Vec<String>,
+}
+
+struct ParsedLists {
+ lists: Vec<NamedList>,
+ whois_overrides: WhoisOverrides,
+}
+
+/// parse a single entry: "tld" or "tld:whois_server"
+fn parse_entry(entry: &str) -> (String, Option<String>) {
+ if let Some(pos) = entry.find(':') {
+ (entry[..pos].to_string(), Some(entry[pos + 1..].to_string()))
+ } else {
+ (entry.to_string(), None)
+ }
+}
+
+/// parse entries, pull out TLD names and whois overrides
+fn parse_list(entries: &[toml::Value], overrides: &mut HashMap<String, String>) -> Vec<String> {
+ entries
+ .iter()
+ .filter_map(|v| v.as_str())
+ .map(|entry| {
+ let (tld, server) = parse_entry(entry);
+ if let Some(s) = server {
+ overrides.insert(tld.to_lowercase(), s);
+ }
+ tld
+ })
+ .collect()
+}
+
+static PARSED_LISTS: OnceLock<ParsedLists> = OnceLock::new();
+
+fn parsed_lists() -> &'static ParsedLists {
+ PARSED_LISTS.get_or_init(|| {
+ let raw: toml::Value = toml::from_str(include_str!("../Lists.toml"))
+ .expect("Lists.toml must be valid TOML");
+
+ let table = raw.as_table().expect("Lists.toml must be a TOML table");
+
+ // Build list names in the order build.rs discovered them
+ let ordered_names: Vec<&str> = env!("HOARDOM_LIST_NAMES").split(',').collect();
+
+ let mut overrides = HashMap::new();
+ let mut lists = Vec::new();
+
+ for name in &ordered_names {
+ if let Some(toml::Value::Array(arr)) = table.get(*name) {
+ let tlds = parse_list(arr, &mut overrides);
+ lists.push(NamedList {
+ name: name.to_string(),
+ tlds,
+ });
+ }
+ }
+
+ ParsedLists {
+ lists,
+ whois_overrides: WhoisOverrides { map: overrides },
+ }
+ })
+}
+
+/// list names from Lists.toml, in order
+pub fn list_names() -> Vec<&'static str> {
+ parsed_lists()
+ .lists
+ .iter()
+ .map(|l| l.name.as_str())
+ .collect()
+}
+
+/// first list name (the default)
+pub fn default_list_name() -> &'static str {
+ list_names().first().copied().unwrap_or("standard")
+}
+
+/// get TLDs for a list name (case insensitive), None if not found
+pub fn get_tlds(name: &str) -> Option<Vec<&'static str>> {
+ let lower = name.to_lowercase();
+ parsed_lists()
+ .lists
+ .iter()
+ .find(|l| l.name == lower)
+ .map(|l| l.tlds.iter().map(String::as_str).collect())
+}
+
+/// get TLDs for a list name, falls back to default if not found
+pub fn get_tlds_or_default(name: &str) -> Vec<&'static str> {
+ get_tlds(name).unwrap_or_else(|| get_tlds(default_list_name()).unwrap_or_default())
+}
+
+/// the builtin whois overrides from Lists.toml
+pub fn whois_overrides() -> &'static WhoisOverrides {
+ &parsed_lists().whois_overrides
+}
+
+pub fn apply_top_tlds(tlds: Vec<&'static str>, top: &[String]) -> Vec<&'static str> {
+ let mut result: Vec<&'static str> = Vec::with_capacity(tlds.len());
+ // first add the top ones in the order specified
+ for t in top {
+ let lower = t.to_lowercase();
+ if let Some(&found) = tlds.iter().find(|&&tld| tld.to_lowercase() == lower) {
+ if !result.contains(&found) {
+ result.push(found);
+ }
+ }
+ }
+ // then add the rest
+ for tld in &tlds {
+ if !result.contains(tld) {
+ result.push(tld);
+ }
+ }
+ result
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_entry_bare() {
+ let (tld, server) = parse_entry("com");
+ assert_eq!(tld, "com");
+ assert_eq!(server, None);
+ }
+
+ #[test]
+ fn test_parse_entry_with_override() {
+ let (tld, server) = parse_entry("io:whois.nic.io");
+ assert_eq!(tld, "io");
+ assert_eq!(server, Some("whois.nic.io".to_string()));
+ }
+
+ #[test]
+ fn test_whois_overrides_populated() {
+ let overrides = whois_overrides();
+ // io should have an override since our Lists.toml has "io:whois.nic.io"
+ assert!(overrides.get_server("io").is_some());
+ // com should not (it has RDAP)
+ assert!(overrides.get_server("com").is_none());
+ }
+
+ #[test]
+ fn test_top_tlds_reorder() {
+ let tlds = vec!["com", "net", "org", "ch", "de"];
+ let top = vec!["ch".to_string(), "de".to_string()];
+ let result = apply_top_tlds(tlds, &top);
+ assert_eq!(result, vec!["ch", "de", "com", "net", "org"]);
+ }
+
+ #[test]
+ fn test_top_tlds_missing_ignored() {
+ let tlds = vec!["com", "net"];
+ let top = vec!["swiss".to_string()];
+ let result = apply_top_tlds(tlds, &top);
+ assert_eq!(result, vec!["com", "net"]);
+ }
+}