tld/lib.rs
1//! # `tld`
2//!
3//! A compile-time static perfect hash map of all official top-level domains (TLDs),
4//! automatically synchronized from the [IANA Root Zone Database](https://data.iana.org/TLD/tlds-alpha-by-domain.txt).
5//!
6//! ## Features
7//!
8//! - **Zero Runtime Allocation / O(1) Lookups**: Backed by [`phf::Set`].
9//! - **`no_std` Compatible**: Can be used in embedded or `no_std` crates.
10//! - **Case-insensitive helper**: [`exist_case_insensitive`] for convenience.
11//!
12//! ## Example
13//!
14//! ```rust
15//! use tld::{exist, exist_case_insensitive, TLD};
16//!
17//! // Fast exact lookup (requires lowercase ASCII string)
18//! assert!(exist("com"));
19//! assert!(exist("org"));
20//! assert!(exist("io"));
21//! assert!(exist("uk"));
22//! assert!(!exist("invalidtld"));
23//!
24//! // Case-insensitive lookup helper
25//! assert!(exist_case_insensitive("COM"));
26//! assert!(exist_case_insensitive("Xn--FIQS8S"));
27//!
28//! // Direct access to the compile-time phf::Set
29//! assert!(TLD.contains("net"));
30//! assert!(TLD.len() > 1400);
31//! ```
32
33#![no_std]
34#![deny(missing_docs)]
35#![warn(
36 missing_debug_implementations,
37 missing_copy_implementations,
38 trivial_casts,
39 trivial_numeric_casts,
40 unused_import_braces,
41 unused_qualifications
42)]
43
44include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
45
46/// Checks if the given ASCII lowercase string is a valid Top-Level Domain (TLD)
47/// in the official IANA database.
48///
49/// Note: This function performs an exact match and expects lowercased input.
50/// For case-insensitive lookup, see [`exist_case_insensitive`].
51///
52/// # Examples
53///
54/// ```
55/// assert!(tld::exist("com"));
56/// assert!(tld::exist("io"));
57/// assert!(tld::exist("lt"));
58/// assert!(!exist_invalid("example"));
59///
60/// fn exist_invalid(s: &str) -> bool {
61/// tld::exist(s)
62/// }
63/// ```
64#[inline]
65#[must_use]
66pub fn exist(s: &str) -> bool {
67 TLD.contains(s)
68}
69
70/// Checks if the given string is a valid Top-Level Domain (TLD), ignoring ASCII case.
71///
72/// This performs an O(1) lookup without allocating memory on the heap.
73///
74/// # Examples
75///
76/// ```
77/// assert!(tld::exist_case_insensitive("COM"));
78/// assert!(tld::exist_case_insensitive("cOm"));
79/// assert!(tld::exist_case_insensitive("io"));
80/// assert!(!tld::exist_case_insensitive("nonexistent"));
81/// ```
82#[inline]
83#[must_use]
84pub fn exist_case_insensitive(s: &str) -> bool {
85 // If all characters are already lowercase ASCII, avoid buffer entirely
86 if s.bytes().all(|b| !b.is_ascii_uppercase()) {
87 return TLD.contains(s);
88 }
89
90 // Maximum TLD length is 63 octets per RFC 1035 / RFC 1123
91 let bytes = s.as_bytes();
92 if bytes.len() > 63 || bytes.is_empty() {
93 return false;
94 }
95
96 let mut buf = [0u8; 63];
97 for (i, &b) in bytes.iter().enumerate() {
98 buf[i] = b.to_ascii_lowercase();
99 }
100
101 if let Ok(lower_str) = core::str::from_utf8(&buf[..bytes.len()]) {
102 TLD.contains(lower_str)
103 } else {
104 false
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_tld_set() {
114 assert!(TLD.get_key("aaa").is_some());
115 assert!(TLD.get_key("#33dawaaa").is_none());
116 assert!(TLD.get_key("aco").is_some());
117 assert!(TLD.get_key("uk").is_some());
118 assert!(TLD.get_key("ye").is_some());
119 assert!(TLD.get_key("com").is_some());
120 assert!(TLD.get_key("de").is_some());
121 assert!(TLD.get_key("fr").is_some());
122 assert!(TLD.get_key("ag").is_some());
123 assert!(TLD.get_key("ru").is_some());
124 assert!(TLD.get_key("nl").is_some());
125 assert!(TLD.get_key("lt").is_some());
126 assert!(TLD.get_key("amex").is_some());
127 assert!(TLD.get_key("zw").is_some());
128 }
129
130 #[test]
131 fn test_exist() {
132 assert!(exist("fr"));
133 assert!(exist("de"));
134 assert!(exist("zw"));
135 assert!(!exist("a9292zw"));
136 assert!(!exist("mcd"));
137 assert!(!exist(""));
138 }
139
140 #[test]
141 fn test_exist_case_insensitive() {
142 assert!(exist_case_insensitive("FR"));
143 assert!(exist_case_insensitive("De"));
144 assert!(exist_case_insensitive("zW"));
145 assert!(exist_case_insensitive("COM"));
146 assert!(!exist_case_insensitive("A9292ZW"));
147 assert!(!exist_case_insensitive("MCD"));
148 assert!(!exist_case_insensitive(""));
149 assert!(exist_case_insensitive("xn--fiqs8s"));
150 assert!(exist_case_insensitive("XN--FIQS8S"));
151 }
152}