1use super::*;
2
3#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
4#[serde(rename_all = "camelCase")]
5pub struct Domain {
6 #[serde(with = "serde_str")]
7 name: DomainName,
8 owner: Principal,
9 children: HashMap<Edge, Domain>,
10 subtree_policies: SubtreePolicies,
11 registration_policy: RegistrationPolicy,
12 data: DynamicContent,
13 expires_at_height: BlockHeight,
14}
15
16impl Domain {
17 pub fn name(&self) -> &DomainName {
18 &self.name
19 }
20
21 pub fn owner(&self) -> &Principal {
22 &self.owner
23 }
24
25 pub fn set_owner(&mut self, owner: Principal) {
26 self.owner = owner
27 }
28
29 pub fn child(&self, edge: &Edge) -> Option<&Domain> {
30 self.children.get(edge)
31 }
32
33 pub fn child_mut(&mut self, edge: &Edge) -> Option<&mut Domain> {
34 self.children.get_mut(edge)
35 }
36
37 pub fn child_names(&self) -> Vec<&Edge> {
38 self.children.keys().collect()
39 }
40
41 pub fn insert_or_replace_child(&mut self, domain: Domain) -> Result<Option<Domain>> {
42 ensure!(!domain.name.is_root(), "Attempt to insert root node as child entry");
43 let edge = domain
44 .name
45 .last_edge()
46 .with_context(|| "Implementation error: already checked that domain is not root")?;
47 let old_domain = self.children.insert(edge.to_owned(), domain);
48 Ok(old_domain)
49 }
50
51 pub fn remove_child(&mut self, edge: &Edge) -> Result<Domain> {
52 let domain = self
53 .children
54 .remove(edge)
55 .with_context(|| format!("Attempt to delete nonexisting child: {}", edge))?;
56 Ok(domain)
57 }
58
59 pub fn data(&self) -> &DynamicContent {
60 &self.data
61 }
62
63 pub fn data_mut(&mut self) -> &mut DynamicContent {
64 &mut self.data
65 }
66
67 pub fn set_data(&mut self, data: DynamicContent) {
68 self.data = data;
69 }
70
71 pub fn is_expired_at(&self, height: BlockHeight) -> bool {
72 self.expires_at_height <= height
73 }
74
75 pub fn expires_at_height(&self) -> BlockHeight {
76 self.expires_at_height
77 }
78
79 pub fn set_expires_at_height(&mut self, height: BlockHeight) {
80 self.expires_at_height = height
81 }
82
83 pub fn is_grace_period_over(&self, at_height: BlockHeight) -> bool {
84 const GRACE_PERIOD_BLOCKS: BlockHeight = 5 * 60 * 24 * 30; self.expires_at_height + GRACE_PERIOD_BLOCKS <= at_height
87 }
88
89 pub fn subtree_policies(&self) -> &SubtreePolicies {
90 &self.subtree_policies
91 }
92
93 pub fn registration_policy(&self) -> &RegistrationPolicy {
94 &self.registration_policy
95 }
96
97 pub(crate) fn validate_subtree_policies(
98 &self, state: &State, domain_after_op: &Domain,
99 ) -> Result<()> {
100 self.subtree_policies.validate(state, self, domain_after_op)?;
101 Ok(())
102 }
103 pub(crate) fn new_root() -> Self {
104 let to_edge = |e: &&str| {
105 Edge::new(e).unwrap_or_else(|_| {
106 panic!("Implementation error creating root: {} is not a valid edge name", e)
107 })
108 };
109 let name = |edges: &[&str]| {
110 let edges = edges.iter().map(to_edge).collect();
111 DomainName::new(edges)
112 };
113 let schema = Self {
114 name: name(&["schema"]),
115 owner: Principal::system(),
116 children: Default::default(),
117 subtree_policies: SubtreePolicies::new().with_schema(Self::json_schema_draft6()),
118 registration_policy: RegistrationPolicy::any(),
119 data: json!({}),
120 expires_at_height: BlockHeight::MAX,
121 };
122 let mut root = Self {
123 name: name(&[]),
124 owner: Principal::system(),
125 children: Default::default(),
126 subtree_policies: SubtreePolicies::new().with_expiration(2 * ExpirationPolicy::YEAR),
128 registration_policy: Default::default(),
129 data: json!({}),
130 expires_at_height: BlockHeight::MAX,
131 };
132 root.insert_or_replace_child(schema).unwrap();
133 root
134 }
135
136 pub(crate) fn new(
137 name: DomainName, owner: Principal, subtree_policies: SubtreePolicies,
138 registration_policy: RegistrationPolicy, data: DynamicContent,
139 expires_at_height: BlockHeight,
140 ) -> Self {
141 Self {
142 name,
143 owner,
144 children: Default::default(),
145 subtree_policies,
146 registration_policy,
147 data,
148 expires_at_height,
149 }
150 }
151
152 fn json_schema_draft6() -> Schema {
153 let schema = include_str!("../json-schema-draft6.json");
156 serde_json::from_str(schema).unwrap()
157 }
158}
159
160#[cfg(test)]
161mod test {
162 use super::*;
163
164 fn name(name: &str) -> DomainName {
165 name.parse().unwrap()
166 }
167
168 fn data(content: &str) -> DynamicContent {
169 serde_json::Value::String(content.to_owned())
170 }
171
172 #[test]
173 fn serde() {
174 let wallet_schema = json![{"type": "string"}];
175 let wallet_expiration = 2_628_000;
176 let mut wallet = Domain::new(
177 name(".wallet"),
178 Principal::system(),
179 SubtreePolicies::new().with_schema(wallet_schema).with_expiration(wallet_expiration),
180 RegistrationPolicy::any(),
181 data("hello"),
182 42,
183 );
184 wallet
185 .insert_or_replace_child(Domain::new(
186 name(".wallet.joe"),
187 "pez2CLkBUjHB8w8G87D3YkREjpRuiqPu6BrRsgHMQy2Pzt6".parse().unwrap(),
188 SubtreePolicies::new(),
189 Default::default(),
190 data("world!"),
191 69,
192 ))
193 .unwrap();
194 let serialized = serde_json::to_value(&wallet).unwrap();
195
196 println!("{}", serde_json::to_string_pretty(&wallet).unwrap());
197
198 let expected = json!( {
201 "name": ".wallet",
202 "owner": "system",
203 "children": {
204 "joe": {
205 "name": ".wallet.joe",
206 "owner": "pez2CLkBUjHB8w8G87D3YkREjpRuiqPu6BrRsgHMQy2Pzt6",
207 "children": {},
208 "subtreePolicies": {},
209 "registrationPolicy": "owner",
210 "data": "world!",
211 "expiresAtHeight": 69
212 }
213 },
214 "subtreePolicies": {
215 "schema": { "type": "string" },
216 "expiration": 2_628_000,
217 },
218 "registrationPolicy": "any",
219 "data": "hello",
220 "expiresAtHeight": 42
221 });
222
223 assert_eq!(serialized, expected);
224
225 let deserialized: Domain = serde_json::from_value(expected).unwrap();
226
227 assert_eq!(deserialized, wallet);
228 }
229}