Skip to main content

domain_check_lib/
lib.rs

1//! # Domain Check Library
2//!
3//! A fast, robust library for checking domain availability using RDAP and WHOIS protocols.
4//!
5//! This library provides both high-level and low-level APIs for domain availability checking,
6//! with support for concurrent processing, multiple protocols, and comprehensive error handling.
7//!
8//! ## Quick Start
9//!
10//! ```rust,no_run
11//! use domain_check_lib::{DomainChecker, CheckConfig};
12//!
13//! #[tokio::main]
14//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
15//!     let checker = DomainChecker::new();
16//!     let result = checker.check_domain("example.com").await?;
17//!     
18//!     println!("Domain: {} - Available: {:?}", result.domain, result.available);
19//!     Ok(())
20//! }
21//! ```
22//!
23//! ## Features
24//!
25//! - **RDAP Protocol**: Modern registration data access protocol
26//! - **WHOIS Fallback**: Automatic fallback when RDAP is unavailable
27//! - **Concurrent Processing**: Efficient parallel domain checking
28//! - **Bootstrap Registry**: Dynamic RDAP endpoint discovery
29//! - **Configurable**: Extensive configuration options
30
31// Re-export main public API types and functions
32// This makes them available as domain_check_lib::TypeName
33pub use checker::DomainChecker;
34pub use config::{load_env_config, ConfigManager, FileConfig, GenerationConfig};
35pub use error::DomainCheckError;
36pub use protocols::registry::{
37    get_all_known_tlds, get_available_presets, get_preset_tlds, get_preset_tlds_with_custom,
38    get_whois_server, initialize_bootstrap,
39};
40pub use types::{CheckConfig, CheckMethod, DomainInfo, DomainResult, OutputMode};
41pub use utils::expand_domain_inputs;
42
43// Public modules
44pub mod generate;
45
46// Re-export generation types for convenience
47pub use generate::{apply_affixes, estimate_pattern_count, expand_pattern, generate_names};
48pub use types::{GenerateConfig, GenerationResult};
49
50// Internal modules - these are not part of the public API
51mod checker;
52mod concurrent;
53mod config;
54mod error;
55mod protocols;
56mod types;
57mod utils;
58
59// Type alias for convenience
60pub type Result<T> = std::result::Result<T, DomainCheckError>;
61
62// Library version and metadata
63pub const VERSION: &str = env!("CARGO_PKG_VERSION");
64pub const AUTHOR: &str = env!("CARGO_PKG_AUTHORS");
65
66/// Initialize the library with default settings.
67///
68/// This function can be called to set up global state like logging,
69/// registry caches, etc. It's optional - the library will work without it.
70pub fn init() {
71    // Future: Initialize global caches, logging, etc.
72    // For now, this is a no-op but provides future extensibility
73}
74
75/// Get library information for debugging or display purposes.
76pub fn info() -> LibraryInfo {
77    LibraryInfo {
78        version: VERSION,
79        author: AUTHOR,
80        features: get_enabled_features(),
81    }
82}
83
84/// Information about the library build and features
85#[derive(Debug, Clone)]
86pub struct LibraryInfo {
87    pub version: &'static str,
88    pub author: &'static str,
89    pub features: Vec<&'static str>,
90}
91
92/// Get list of enabled features at compile time.
93// Allow: each push is behind a #[cfg(feature)], so init-then-push is the only idiomatic way.
94#[allow(clippy::vec_init_then_push)]
95fn get_enabled_features() -> Vec<&'static str> {
96    let mut features = Vec::new();
97
98    #[cfg(feature = "rdap")]
99    features.push("rdap");
100
101    #[cfg(feature = "whois")]
102    features.push("whois");
103
104    #[cfg(feature = "bootstrap")]
105    features.push("bootstrap");
106
107    #[cfg(feature = "debug")]
108    features.push("debug");
109
110    features
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    // ── Constants ──────────────────────────────────────────────────────
118
119    #[test]
120    fn test_version_is_not_empty() {
121        assert!(!VERSION.is_empty());
122    }
123
124    #[test]
125    fn test_version_is_semver() {
126        let parts: Vec<&str> = VERSION.split('.').collect();
127        assert!(parts.len() >= 2, "VERSION should be semver: {}", VERSION);
128        for part in &parts {
129            assert!(
130                part.parse::<u32>().is_ok(),
131                "non-numeric semver part: {}",
132                part
133            );
134        }
135    }
136
137    #[test]
138    fn test_author_is_not_empty() {
139        assert!(!AUTHOR.is_empty());
140    }
141
142    // ── init() ─────────────────────────────────────────────────────────
143
144    #[test]
145    fn test_init_does_not_panic() {
146        init(); // no-op, should not panic
147        init(); // idempotent
148    }
149
150    // ── info() ─────────────────────────────────────────────────────────
151
152    #[test]
153    fn test_info_version_matches_constant() {
154        let info = info();
155        assert_eq!(info.version, VERSION);
156    }
157
158    #[test]
159    fn test_info_author_matches_constant() {
160        let info = info();
161        assert_eq!(info.author, AUTHOR);
162    }
163
164    #[test]
165    fn test_info_has_default_features() {
166        let info = info();
167        // With default features enabled, rdap, whois, and bootstrap should be present
168        assert!(info.features.contains(&"rdap"), "missing rdap feature");
169        assert!(info.features.contains(&"whois"), "missing whois feature");
170        assert!(
171            info.features.contains(&"bootstrap"),
172            "missing bootstrap feature"
173        );
174    }
175
176    #[test]
177    fn test_library_info_debug() {
178        let info = info();
179        let debug = format!("{:?}", info);
180        assert!(debug.contains("LibraryInfo"));
181        assert!(debug.contains(VERSION));
182    }
183
184    #[test]
185    fn test_library_info_clone() {
186        let info = info();
187        let cloned = info.clone();
188        assert_eq!(info.version, cloned.version);
189        assert_eq!(info.author, cloned.author);
190        assert_eq!(info.features, cloned.features);
191    }
192
193    // ── Result type alias ──────────────────────────────────────────────
194
195    #[test]
196    fn test_result_type_alias_ok() {
197        let result: Result<i32> = Ok(42);
198        assert!(result.is_ok());
199    }
200
201    #[test]
202    fn test_result_type_alias_err() {
203        let result: Result<i32> = Err(DomainCheckError::invalid_domain("bad", "invalid"));
204        assert!(result.is_err());
205    }
206
207    // ── Public re-exports ──────────────────────────────────────────────
208
209    #[test]
210    fn test_check_config_default() {
211        let config = CheckConfig::default();
212        assert!(config.concurrency > 0);
213    }
214
215    #[test]
216    fn test_domain_result_construction() {
217        let result = DomainResult {
218            domain: "example.com".to_string(),
219            available: Some(true),
220            info: None,
221            check_duration: None,
222            method_used: CheckMethod::Rdap,
223            error_message: None,
224        };
225        assert_eq!(result.domain, "example.com");
226        assert_eq!(result.available, Some(true));
227    }
228
229    #[test]
230    fn test_check_method_variants() {
231        let _rdap = CheckMethod::Rdap;
232        let _whois = CheckMethod::Whois;
233        let _unknown = CheckMethod::Unknown;
234    }
235
236    #[test]
237    fn test_output_mode_variants() {
238        let _streaming = OutputMode::Streaming;
239        let _collected = OutputMode::Collected;
240        let _auto = OutputMode::Auto;
241    }
242
243    #[test]
244    fn test_domain_checker_new() {
245        let checker = DomainChecker::new();
246        let config = checker.config();
247        assert!(config.concurrency > 0);
248    }
249}