///|
pub struct ZoneIndex {
record_values : Array[ZoneRecord]
owner_values : Array[DomainName]
} derive(Eq, Debug)
///|
pub struct CnameResolution {
chain_values : Array[DomainName]
terminal_record_values : Array[ZoneRecord]
} derive(Eq, Debug)
///|
fn contains_owner(owners : Array[DomainName], owner : DomainName) -> Bool {
for existing in owners {
if existing == owner {
return true
}
}
false
}
///|
pub fn index_zone(zone : ZoneDocument) -> ZoneIndex {
let records = zone.records()
let owners : Array[DomainName] = []
for record in records {
let owner = record.owner()
if !contains_owner(owners, owner) {
owners.push(owner)
}
}
{ record_values: records, owner_values: owners }
}
///|
pub fn ZoneIndex::owners(self : ZoneIndex) -> Array[DomainName] {
self.owner_values.copy()
}
///|
pub fn ZoneIndex::owner_count(self : ZoneIndex) -> Int {
self.owner_values.length()
}
///|
pub fn ZoneIndex::records(self : ZoneIndex) -> Array[ZoneRecord] {
self.record_values.copy()
}
///|
pub fn ZoneIndex::records_for_owner(
self : ZoneIndex,
owner : DomainName,
) -> Array[ZoneRecord] {
let result : Array[ZoneRecord] = []
for record in self.record_values {
if record.owner() == owner {
result.push(record)
}
}
result
}
///|
pub fn ZoneIndex::records_for_type(
self : ZoneIndex,
record_type : String,
) -> Array[ZoneRecord] {
let expected = uppercase_ascii(record_type)
let result : Array[ZoneRecord] = []
for record in self.record_values {
if record.record_type() == expected {
result.push(record)
}
}
result
}
///|
pub fn ZoneIndex::lookup(
self : ZoneIndex,
owner : DomainName,
record_type : String,
) -> Array[ZoneRecord] {
let expected = uppercase_ascii(record_type)
let result : Array[ZoneRecord] = []
for record in self.record_values {
if record.owner() == owner && record.record_type() == expected {
result.push(record)
}
}
result
}
///|
fn cname_target(records : Array[ZoneRecord]) -> DomainName? {
for record in records {
match record.data() {
CnameData(value) => return Some(value)
_ => ()
}
}
None
}
///|
fn index_error(message : String) -> ZoneError {
ZoneError::new(IntegrityViolation, message, SourceSpan::point(0, 0))
}
///|
pub fn ZoneIndex::resolve_cname(
self : ZoneIndex,
start : DomainName,
max_hops? : Int = 16,
) -> Result[CnameResolution, ZoneError] {
if max_hops < 0 {
return Err(index_error("CNAME hop limit cannot be negative"))
}
let chain : Array[DomainName] = [start]
let mut current = start
let mut hops = 0
while true {
let records = self.records_for_owner(current)
let target = cname_target(records)
match target {
None =>
return Ok({ chain_values: chain, terminal_record_values: records })
Some(value) => {
if hops >= max_hops {
return Err(
index_error("CNAME chain exceeds the configured hop limit"),
)
}
if contains_owner(chain, value) {
return Err(index_error("CNAME chain contains a cycle"))
}
chain.push(value)
current = value
hops = hops + 1
}
}
}
Err(index_error("CNAME resolution ended unexpectedly"))
}
///|
pub fn CnameResolution::chain(self : CnameResolution) -> Array[DomainName] {
self.chain_values.copy()
}
///|
pub fn CnameResolution::chain_texts(self : CnameResolution) -> Array[String] {
let result : Array[String] = []
for name in self.chain_values {
result.push(name.to_text())
}
result
}
///|
pub fn CnameResolution::terminal_records(
self : CnameResolution,
) -> Array[ZoneRecord] {
self.terminal_record_values.copy()
}