1use adns_proto::{Name, Record, SoaData, Type, TypeData};
2
3use crate::Zone;
4
5pub struct ZoneUpdate {
6 pub zone_name: Name,
8 pub actions: Vec<ZoneUpdateAction>,
9}
10
11pub enum ZoneUpdateAction {
12 CreateZone(Name),
13 DeleteRecords(Name, Option<Type>),
14 DeleteRecord(Name, TypeData),
15 AddRecord(Record),
16}
17
18impl ZoneUpdate {
19 pub fn apply_to(&self, root_zone: &mut Zone) {
20 let zone = if self.zone_name.as_ref().is_empty() {
21 root_zone
22 } else {
23 root_zone.zones.entry(self.zone_name.clone()).or_default()
24 };
25 for action in &self.actions {
26 action.apply_to(&self.zone_name, zone);
27 }
28 }
29}
30
31impl ZoneUpdateAction {
32 pub fn apply_to(&self, zone_name: &Name, zone: &mut Zone) {
33 match self {
34 ZoneUpdateAction::DeleteRecords(name, None) => {
35 if name == zone_name {
36 zone.records.retain(|record| {
37 &record.name != name
38 || record.type_ == Type::SOA
39 || record.type_ == Type::NS
40 });
41 } else {
42 zone.records.retain(|record| &record.name != name);
43 }
44 }
45 ZoneUpdateAction::DeleteRecords(name, Some(type_)) => {
46 if name == zone_name && (*type_ == Type::SOA || *type_ == Type::NS) {
47 return;
48 }
49 zone.records
50 .retain(|record| &record.name != name || record.type_ != *type_);
51 }
52 ZoneUpdateAction::DeleteRecord(name, data) => {
53 if name == zone_name
54 && (data.dns_type() == Type::SOA
55 || (data.dns_type() == Type::NS
56 && zone.records.iter().filter(|x| x.type_ == Type::NS).count() <= 1))
57 {
58 return;
59 }
60 zone.records.retain(|record| {
61 &record.name != name || record.type_ != data.dns_type() || &record.data != data
62 });
63 }
64 ZoneUpdateAction::AddRecord(record) => {
65 if record.type_ == Type::CNAME {
66 if zone.records.len() > 1
67 || zone
68 .records
69 .first()
70 .map(|x| x.type_ != Type::CNAME)
71 .unwrap_or_default()
72 {
73 return;
74 }
75 } else if zone.records.iter().any(|x| x.type_ == Type::CNAME) {
76 return;
77 }
78 if record.type_ == Type::SOA {
79 let Record { data: TypeData::SOA(SoaData { serial: new_serial, .. }), .. } = &record else {
80 return;
81 };
82 if let Some(Record {
83 data: TypeData::SOA(SoaData { serial, .. }),
84 ..
85 }) = zone
86 .records
87 .iter()
88 .find(|x| x.name == record.name && x.type_ == Type::SOA)
89 {
90 if serial > new_serial {
91 return;
92 }
93 }
94 }
95 for zone_record in zone.records.iter_mut().filter(|zone_record| {
96 zone_record.name == record.name && zone_record.type_ == record.type_
97 }) {
98 if record.type_ == Type::CNAME
99 || record.type_ == Type::SOA
100 || record.data == zone_record.data
101 {
102 *zone_record = record.clone();
103 return;
104 }
105 }
106 zone.records.push(record.clone());
107 }
108 ZoneUpdateAction::CreateZone(name) => {
109 zone.zones.insert(name.clone(), Default::default());
110 }
111 }
112 }
113}