Skip to main content

tuwunel_core/config/regenerate/
tree.rs

1use 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}