use crate::{DiskCache, DnsClient, FullCheckJob, FullVerdict, PreCheck, RuleSet, Status};
#[derive(Debug, Clone)]
pub struct CheckResult {
pub input: String,
pub domain: String,
pub zone: String,
pub status: Status,
pub detail: String,
pub from_cache: bool,
}
pub fn check_many(
rules: &RuleSet,
dns: &DnsClient,
cache: Option<&DiskCache>,
inputs: &[String],
cache_ttl_secs: i64,
concurrency: usize,
) -> Vec<CheckResult> {
let mut results: Vec<Option<CheckResult>> = vec![None; inputs.len()];
let mut pending: Vec<(usize, String, String, Option<String>)> = Vec::new();
for (i, raw) in inputs.iter().enumerate() {
let cleaned = raw.trim().trim_end_matches('.').to_ascii_lowercase();
if !cleaned.contains('.') {
results[i] = Some(CheckResult {
input: raw.clone(),
domain: cleaned,
zone: String::new(),
status: Status::Invalid,
detail: "input has no TLD".to_string(),
from_cache: false,
});
continue;
}
if let Some(c) = cache {
if let Ok(Some(row)) = c.get(&cleaned, cache_ttl_secs) {
let status = parse_status(&row.status);
results[i] = Some(CheckResult {
input: raw.clone(),
domain: row.domain,
zone: row.zone,
status,
detail: row.detail,
from_cache: true,
});
continue;
}
}
match rules.precheck(&cleaned) {
PreCheck::Verdict {
status,
detail,
zone,
registered,
} => {
results[i] = Some(CheckResult {
input: raw.clone(),
domain: registered,
zone,
status,
detail,
from_cache: false,
});
}
PreCheck::Proceed {
zone,
registered,
rdap,
..
} => {
pending.push((i, zone, registered, rdap));
}
}
}
if !pending.is_empty() {
let jobs: Vec<FullCheckJob> = pending
.iter()
.map(|(_, zone, registered, rdap)| FullCheckJob {
zone: zone.clone(),
registered: registered.clone(),
rdap_url: rdap.clone(),
})
.collect();
let verdicts = dns.check_full_batch(jobs, concurrency);
for ((i, zone, registered, _), v) in pending.into_iter().zip(verdicts) {
results[i] = Some(verdict_to_result(&inputs[i], zone, registered, v));
}
}
if let Some(c) = cache {
for r in results.iter().flatten() {
if r.from_cache || matches!(r.status, Status::Unknown) {
continue;
}
let _ = c.put(&r.domain, &r.zone, r.status.as_str(), &r.detail);
}
}
results.into_iter().flatten().collect()
}
fn verdict_to_result(input: &str, zone: String, registered: String, v: FullVerdict) -> CheckResult {
let status = match v.kind {
"registered" => Status::Registered,
"available" => Status::Available,
_ => Status::Unknown,
};
CheckResult {
input: input.to_string(),
domain: registered,
zone,
status,
detail: v.detail,
from_cache: false,
}
}
fn parse_status(s: &str) -> Status {
match s {
"available" => Status::Available,
"registered" => Status::Registered,
"reserved" => Status::Reserved,
"invalid" => Status::Invalid,
_ => Status::Unknown,
}
}