tuwunel_core/config/regenerate/
tree.rs1use figment::value::{Dict, Value};
2use smallstr::SmallString;
3use smallvec::SmallVec;
4use toml_writer::TomlWrite as _;
5
6use super::SectionSpec;
7use crate::implement;
8
9pub(super) type ConfigPath<'a> = SmallVec<[PathPart<'a>; 4]>;
10type SchemaPath<'a> = SmallVec<[&'a str; 4]>;
11type SectionPath<'a> = SmallVec<[SectionPart<'a>; 4]>;
12type Instances<'a> = SmallVec<[Instance<'a>; 1]>;
13type ResolvedSection = SmallString<[u8; 48]>;
14
15#[derive(Clone, Copy, Eq, PartialEq)]
16pub(super) enum PathPart<'a> {
17 Key(&'a str),
18 Index(usize),
19}
20
21#[derive(Clone, Copy)]
22enum SectionPart<'a> {
23 Literal(&'a str),
24 Dynamic(&'a str),
25}
26
27#[derive(Clone, Copy)]
28enum SectionShape {
29 Table,
30 Array,
31}
32
33#[derive(Clone, Copy)]
34enum Node<'a> {
35 Root(&'a Dict),
36 Value(&'a Value),
37}
38
39pub(super) struct Instance<'a> {
40 pub(super) section: ResolvedSection,
41 pub(super) path: ConfigPath<'a>,
42 pub(super) values: &'a Dict,
43}
44
45pub(super) fn resolve_instances<'a>(spec: &SectionSpec, values: &'a Dict) -> Instances<'a> {
46 let shape = if spec.section.starts_with('[') {
47 SectionShape::Array
48 } else {
49 SectionShape::Table
50 };
51
52 let section = spec.section.trim_matches(['[', ']']);
53 let parts = section.split('.').collect::<SchemaPath<'_>>();
54 let mut path = ConfigPath::new();
55 let mut section_path = SectionPath::new();
56 let mut instances = Instances::new();
57
58 resolve_parts(
59 Node::Root(values),
60 &parts,
61 shape,
62 &mut path,
63 &mut section_path,
64 &mut instances,
65 );
66
67 instances
68}
69
70fn resolve_parts<'a>(
71 node: Node<'a>,
72 parts: &[&'a str],
73 shape: SectionShape,
74 path: &mut ConfigPath<'a>,
75 section: &mut SectionPath<'a>,
76 instances: &mut Instances<'a>,
77) {
78 let Some((part, tail)) = parts.split_first() else {
79 resolve_leaf(node, shape, path, section, instances);
80 return;
81 };
82
83 if *part == "global" && path.is_empty() {
84 section.push(SectionPart::Literal(part));
85 resolve_parts(node, tail, shape, path, section, instances);
86 section.pop();
87
88 return;
89 }
90
91 if let Some(choices) = part
92 .strip_prefix('<')
93 .and_then(|part| part.strip_suffix('>'))
94 {
95 let Some(values) = node.as_dict() else {
96 return;
97 };
98
99 if choices.contains('|') {
100 for choice in choices.split('|') {
101 let Some(value) = values.get(choice) else {
102 continue;
103 };
104
105 path.push(PathPart::Key(choice));
106 section.push(SectionPart::Literal(choice));
107 resolve_parts(Node::Value(value), tail, shape, path, section, instances);
108 section.pop();
109 path.pop();
110 }
111 } else {
112 for (key, value) in values {
113 path.push(PathPart::Key(key));
114 section.push(SectionPart::Dynamic(key));
115 resolve_parts(Node::Value(value), tail, shape, path, section, instances);
116 section.pop();
117 path.pop();
118 }
119 }
120
121 return;
122 }
123
124 let Some(value) = node
125 .as_dict()
126 .and_then(|values| values.get(*part))
127 else {
128 return;
129 };
130
131 path.push(PathPart::Key(part));
132 section.push(SectionPart::Literal(part));
133 resolve_parts(Node::Value(value), tail, shape, path, section, instances);
134 section.pop();
135 path.pop();
136}
137
138fn resolve_leaf<'a>(
139 node: Node<'a>,
140 shape: SectionShape,
141 path: &mut ConfigPath<'a>,
142 section: &SectionPath<'a>,
143 instances: &mut Instances<'a>,
144) {
145 match shape {
146 | SectionShape::Table =>
147 if let Some(values) = node.as_dict() {
148 instances.push(Instance {
149 section: render_section_path(section),
150 path: path.clone(),
151 values,
152 });
153 },
154 | SectionShape::Array => {
155 let Some(values) = node.as_array() else {
156 return;
157 };
158
159 for (index, value) in values.iter().enumerate() {
160 let Some(values) = value.as_dict() else {
161 continue;
162 };
163
164 path.push(PathPart::Index(index));
165 instances.push(Instance {
166 section: render_section_path(section),
167 path: path.clone(),
168 values,
169 });
170
171 path.pop();
172 }
173 },
174 }
175}
176
177#[implement(Node, generics = "<'a>", params = "<'a>")]
178fn as_dict(self) -> Option<&'a Dict> {
179 match self {
180 | Self::Root(values) => Some(values),
181 | Self::Value(value) => value.as_dict(),
182 }
183}
184
185#[implement(Node, generics = "<'a>", params = "<'a>")]
186fn as_array(self) -> Option<&'a [Value]> {
187 match self {
188 | Self::Root(_) => None,
189 | Self::Value(value) => value.as_array(),
190 }
191}
192
193pub(super) fn normalize_aliases(values: &mut Dict, schema: &[&SectionSpec]) {
194 for &spec in schema {
195 let shape = if spec.section.starts_with('[') {
196 SectionShape::Array
197 } else {
198 SectionShape::Table
199 };
200
201 let section = spec.section.trim_matches(['[', ']']);
202 let parts = section.split('.').collect::<SchemaPath<'_>>();
203 let parts = parts.strip_prefix(&["global"]).unwrap_or(&parts);
204
205 normalize_dict(values, parts, shape, spec);
206 }
207}
208
209fn normalize_dict(values: &mut Dict, parts: &[&str], shape: SectionShape, spec: &SectionSpec) {
210 let Some((part, tail)) = parts.split_first() else {
211 if matches!(shape, SectionShape::Table) {
212 normalize_fields(values, spec);
213 }
214
215 return;
216 };
217
218 if let Some(choices) = part
219 .strip_prefix('<')
220 .and_then(|part| part.strip_suffix('>'))
221 {
222 if choices.contains('|') {
223 for choice in choices.split('|') {
224 if let Some(value) = values.get_mut(choice) {
225 normalize_value(value, tail, shape, spec);
226 }
227 }
228 } else {
229 for value in values.values_mut() {
230 normalize_value(value, tail, shape, spec);
231 }
232 }
233
234 return;
235 }
236
237 if tail.is_empty() {
238 rename_alias(values, part, spec.aliases);
239 }
240
241 if let Some(value) = values.get_mut(*part) {
242 normalize_value(value, tail, shape, spec);
243 }
244}
245
246fn normalize_value(value: &mut Value, parts: &[&str], shape: SectionShape, spec: &SectionSpec) {
247 if parts.is_empty() {
248 match (shape, value) {
249 | (SectionShape::Table, Value::Dict(_, values)) => normalize_fields(values, spec),
250 | (SectionShape::Array, Value::Array(_, values)) => values
251 .iter_mut()
252 .filter_map(|value| match value {
253 | Value::Dict(_, values) => Some(values),
254 | _ => None,
255 })
256 .for_each(|values| normalize_fields(values, spec)),
257 | _ => {},
258 }
259
260 return;
261 }
262
263 if let Value::Dict(_, values) = value {
264 normalize_dict(values, parts, shape, spec);
265 }
266}
267
268fn normalize_fields(values: &mut Dict, spec: &SectionSpec) {
269 for field in spec.fields {
270 rename_alias(values, field.name, field.aliases);
271 }
272}
273
274fn rename_alias(values: &mut Dict, canonical: &str, aliases: &[&str]) {
275 if values.contains_key(canonical) {
276 return;
277 }
278
279 let Some(alias) = aliases
280 .iter()
281 .find(|alias| values.contains_key(**alias))
282 .copied()
283 else {
284 return;
285 };
286
287 let Some(value) = values.remove(alias) else {
288 return;
289 };
290
291 values.insert(canonical.to_owned(), value);
292}
293
294fn render_section_path(parts: &[SectionPart<'_>]) -> ResolvedSection {
295 let mut section = ResolvedSection::new();
296
297 for (index, part) in parts.iter().enumerate() {
298 if index > 0 {
299 section
300 .key_sep()
301 .expect("written to section buffer");
302 }
303
304 match part {
305 | SectionPart::Literal(part) => section.push_str(part),
306 | SectionPart::Dynamic(part) => section
307 .key(*part)
308 .expect("written to section buffer"),
309 }
310 }
311
312 section
313}
314
315pub(super) fn find_path<'a>(values: &'a Dict, path: &[PathPart<'_>]) -> Option<&'a Value> {
316 let (head, tail) = path.split_first()?;
317 let PathPart::Key(key) = head else {
318 return None;
319 };
320
321 let value = values.get(*key)?;
322
323 find_value(value, tail)
324}
325
326fn find_value<'a>(value: &'a Value, path: &[PathPart<'_>]) -> Option<&'a Value> {
327 let Some((head, tail)) = path.split_first() else {
328 return Some(value);
329 };
330
331 let value = match head {
332 | PathPart::Key(key) => value.as_dict()?.get(*key)?,
333 | PathPart::Index(index) => value.as_array()?.get(*index)?,
334 };
335
336 find_value(value, tail)
337}
338
339pub(super) fn remove_path(values: &mut Dict, path: &[PathPart<'_>]) {
340 let Some((head, tail)) = path.split_first() else {
341 return;
342 };
343
344 let PathPart::Key(key) = head else {
345 return;
346 };
347
348 if tail.is_empty() {
349 values.remove(*key);
350 return;
351 }
352
353 if let Some(value) = values.get_mut(*key) {
354 remove_value_path(value, tail);
355 }
356}
357
358fn remove_value_path(value: &mut Value, path: &[PathPart<'_>]) {
359 let Some((head, tail)) = path.split_first() else {
360 return;
361 };
362
363 match head {
364 | PathPart::Index(index) => {
365 let Value::Array(_, values) = value else {
366 return;
367 };
368
369 if let Some(value) = values.get_mut(*index) {
370 remove_value_path(value, tail);
371 }
372 },
373 | PathPart::Key(key) => {
374 let Value::Dict(_, values) = value else {
375 return;
376 };
377
378 if tail.is_empty() {
379 values.remove(*key);
380 return;
381 }
382
383 let Some(value) = values.get_mut(*key) else {
384 return;
385 };
386
387 remove_value_path(value, tail);
388 },
389 }
390}