// MoonDNS - Pure MoonBit Authoritative DNS Server
// Copyright 2026 chgttyyr
// Licensed under the Apache-2.0 License.
///| Validate apex records, owner boundaries, and CNAME exclusivity.
///|
/// The returned diagnostics are stable and follow record insertion order.
pub fn Zone::validate(self : Zone) -> Array[String] {
let errors : Array[String] = []
let origin = @domain.normalize_fqdn(self.origin)
let mut apex_soa_count = 0
let mut has_apex_ns = false
for record in self.records {
let owner = @domain.normalize_fqdn(record.name)
if !@domain.is_subdomain_of(owner, origin) {
errors.push("owner " + owner + " is outside zone " + origin)
}
if record.rtype == @types.RType::SOA && @domain.domain_equal(owner, origin) {
apex_soa_count = apex_soa_count + 1
}
if record.rtype == @types.RType::NS && @domain.domain_equal(owner, origin) {
has_apex_ns = true
}
}
if apex_soa_count == 0 {
errors.push("missing apex SOA for " + origin)
} else if apex_soa_count > 1 {
errors.push(
"multiple apex SOA records for " +
origin +
" (found " +
apex_soa_count.to_string() +
")",
)
}
if !has_apex_ns {
errors.push("missing apex NS for " + origin)
}
for i = 0; i < self.records.length(); i = i + 1 {
let record = self.records[i]
if record.rtype == @types.RType::CNAME {
let owner = @domain.normalize_fqdn(record.name)
let mut seen_cname = false
for prior = 0; prior < i; prior = prior + 1 {
if self.records[prior].rtype == @types.RType::CNAME &&
@domain.domain_equal(self.records[prior].name, owner) {
seen_cname = true
}
}
if !seen_cname {
let mut has_other_type = false
for other in self.records {
if @domain.domain_equal(other.name, owner) &&
other.rtype != @types.RType::CNAME {
has_other_type = true
}
}
if has_other_type {
errors.push("CNAME owner " + owner + " also has other record types")
}
}
}
}
errors
}