///|
pub(all) enum RecordChangeKind {
RecordAdded
RecordRemoved
RecordTtlChanged
RecordDataChanged
} derive(Eq, Debug)
///|
pub struct RecordChange {
kind_value : RecordChangeKind
left_value : ZoneRecord?
right_value : ZoneRecord?
} derive(Eq, Debug)
///|
pub struct ZoneComparison {
change_values : Array[RecordChange]
added_count_value : Int
removed_count_value : Int
ttl_changed_count_value : Int
data_changed_count_value : Int
} derive(Eq, Debug)
///|
fn records_exactly_equal(left : ZoneRecord, right : ZoneRecord) -> Bool {
left.owner() == right.owner() &&
left.ttl() == right.ttl() &&
left.record_class() == right.record_class() &&
left.record_type() == right.record_type() &&
left.data() == right.data()
}
///|
fn records_share_identity(left : ZoneRecord, right : ZoneRecord) -> Bool {
left.owner() == right.owner() &&
left.record_class() == right.record_class() &&
left.record_type() == right.record_type()
}
///|
fn comparison_error(message : String) -> ZoneError {
ZoneError::new(IntegrityViolation, message, SourceSpan::point(0, 0))
}
///|
fn check_change_limit(
changes : Array[RecordChange],
maximum : Int,
) -> Result[Unit, ZoneError] {
if changes.length() > maximum {
Err(
comparison_error(
"zone comparison exceeds the configured difference evidence limit",
),
)
} else {
Ok(())
}
}
///|
pub fn compare_zones(
left : ZoneDocument,
right : ZoneDocument,
max_changes? : Int = 10000,
) -> Result[ZoneComparison, ZoneError] {
if max_changes < 0 {
return Err(comparison_error("maximum zone changes cannot be negative"))
}
let left_records = left.records()
let right_records = right.records()
let left_used = Array::make(left_records.length(), false)
let right_used = Array::make(right_records.length(), false)
let changes : Array[RecordChange] = []
let mut added = 0
let mut removed = 0
let mut ttl_changed = 0
let mut data_changed = 0
for left_index, left_record in left_records {
for right_index, right_record in right_records {
if !right_used[right_index] &&
records_exactly_equal(left_record, right_record) {
left_used[left_index] = true
right_used[right_index] = true
break
}
}
}
for left_index, left_record in left_records {
if left_used[left_index] {
continue
}
let mut matched = -1
for right_index, right_record in right_records {
if !right_used[right_index] &&
records_share_identity(left_record, right_record) {
matched = right_index
break
}
}
if matched >= 0 {
let right_record = right_records[matched]
left_used[left_index] = true
right_used[matched] = true
let kind = if left_record.data() == right_record.data() {
ttl_changed = ttl_changed + 1
RecordTtlChanged
} else {
data_changed = data_changed + 1
RecordDataChanged
}
changes.push({
kind_value: kind,
left_value: Some(left_record),
right_value: Some(right_record),
})
match check_change_limit(changes, max_changes) {
Ok(_) => ()
Err(error) => return Err(error)
}
}
}
for index, record in left_records {
if !left_used[index] {
removed = removed + 1
changes.push({
kind_value: RecordRemoved,
left_value: Some(record),
right_value: None,
})
match check_change_limit(changes, max_changes) {
Ok(_) => ()
Err(error) => return Err(error)
}
}
}
for index, record in right_records {
if !right_used[index] {
added = added + 1
changes.push({
kind_value: RecordAdded,
left_value: None,
right_value: Some(record),
})
match check_change_limit(changes, max_changes) {
Ok(_) => ()
Err(error) => return Err(error)
}
}
}
Ok({
change_values: changes,
added_count_value: added,
removed_count_value: removed,
ttl_changed_count_value: ttl_changed,
data_changed_count_value: data_changed,
})
}
///|
pub fn RecordChange::kind(self : RecordChange) -> RecordChangeKind {
self.kind_value
}
///|
pub fn RecordChange::left(self : RecordChange) -> ZoneRecord? {
self.left_value
}
///|
pub fn RecordChange::right(self : RecordChange) -> ZoneRecord? {
self.right_value
}
///|
pub fn ZoneComparison::changes(self : ZoneComparison) -> Array[RecordChange] {
self.change_values.copy()
}
///|
pub fn ZoneComparison::added_count(self : ZoneComparison) -> Int {
self.added_count_value
}
///|
pub fn ZoneComparison::removed_count(self : ZoneComparison) -> Int {
self.removed_count_value
}
///|
pub fn ZoneComparison::ttl_changed_count(self : ZoneComparison) -> Int {
self.ttl_changed_count_value
}
///|
pub fn ZoneComparison::data_changed_count(self : ZoneComparison) -> Int {
self.data_changed_count_value
}
///|
pub fn ZoneComparison::identical(self : ZoneComparison) -> Bool {
self.change_values.length() == 0
}