1use std::collections::{BTreeMap, HashMap};
2
3use crate::processors::builtin_processors;
4
5use super::compile::{
6 contains_template_loop, insert_context_text_nodes, insert_named_text_nodes,
7 value_to_structured_template_node,
8};
9use super::error::RenderError;
10use super::nodes::TextGeneratorNode;
11use super::processor::ProcessorRegistry;
12use super::state::{RenderContext, RenderOptions, RenderState};
13use super::structured_template::ParsedTemplateConfig;
14use super::value::{CopperlaceValue, StructuredNode};
15
16pub struct RuleSet {
24 document: StructuredNode,
25 text_rules: HashMap<String, Box<dyn TextGeneratorNode>>,
26 context_defaults: HashMap<String, Box<dyn TextGeneratorNode>>,
27 processors: ProcessorRegistry,
28}
29
30impl RuleSet {
31 pub fn from_config(config: hocon_rs::Value) -> Result<Self, RenderError> {
37 Self::from_config_with_processors(config, ProcessorRegistry::new())
38 }
39
40 pub fn from_config_with_processors(
46 config: hocon_rs::Value,
47 custom_processors: ProcessorRegistry,
48 ) -> Result<Self, RenderError> {
49 Self::from_template_config(
50 ParsedTemplateConfig {
51 value: config,
52 loops: HashMap::new(),
53 },
54 custom_processors,
55 )
56 }
57
58 pub(crate) fn from_template_config(
59 config: ParsedTemplateConfig,
60 custom_processors: ProcessorRegistry,
61 ) -> Result<Self, RenderError> {
62 let ParsedTemplateConfig { value, loops } = config;
63 let hocon_rs::Value::Object(values) = value else {
64 return Err(RenderError::InvalidConfigRoot);
65 };
66
67 let mut processors = builtin_processors();
68 processors.extend(custom_processors);
69
70 let mut document_values = BTreeMap::new();
71 let mut text_rules = HashMap::new();
72 let mut context_defaults = HashMap::new();
73
74 for (name, value) in values {
75 if contains_template_loop(&value, &loops)
76 && (name == "context" || !matches!(&value, hocon_rs::Value::Object(_)))
77 {
78 return Err(RenderError::InvalidExpression(
79 "structured loop blocks must be inside arrays in object-valued rules"
80 .to_string(),
81 ));
82 }
83 document_values.insert(
84 name.clone(),
85 value_to_structured_template_node(value.clone(), &processors, &loops)?,
86 );
87 if name == "context" {
88 if let hocon_rs::Value::Object(context_values) = value {
89 for (context_name, context_value) in context_values {
90 insert_context_text_nodes(
91 &mut context_defaults,
92 context_name,
93 context_value,
94 &processors,
95 )?;
96 }
97 } else {
98 insert_named_text_nodes(&mut text_rules, name, value, &processors, &loops)?;
99 }
100 } else {
101 insert_named_text_nodes(&mut text_rules, name, value, &processors, &loops)?;
102 }
103 }
104
105 Ok(RuleSet {
106 document: StructuredNode::Object(document_values),
107 text_rules,
108 context_defaults,
109 processors,
110 })
111 }
112
113 pub fn render_rule(&self, rule_name: &str) -> Result<String, RenderError> {
118 self.render_rule_with_context(rule_name, RenderContext::new())
119 }
120
121 pub fn render_rule_with_options(
123 &self,
124 rule_name: &str,
125 options: RenderOptions,
126 ) -> Result<String, RenderError> {
127 self.render_rule_with_context_and_options(rule_name, RenderContext::new(), options)
128 }
129
130 pub fn render_rule_with_context(
136 &self,
137 rule_name: &str,
138 context: RenderContext,
139 ) -> Result<String, RenderError> {
140 self.render_rule_with_context_and_options(rule_name, context, RenderOptions::default())
141 }
142
143 pub fn render_rule_with_context_and_options(
145 &self,
146 rule_name: &str,
147 context: RenderContext,
148 options: RenderOptions,
149 ) -> Result<String, RenderError> {
150 let mut state = RenderState::with_context_and_options(self, context, options);
151 self.render_rule_with_state(rule_name, &mut state)
152 }
153
154 pub fn render_rule_inferred(&self, rule_name: &str) -> Result<String, RenderError> {
159 self.render_rule_inferred_with_context(rule_name, RenderContext::new())
160 }
161
162 pub fn render_rule_inferred_with_options(
164 &self,
165 rule_name: &str,
166 options: RenderOptions,
167 ) -> Result<String, RenderError> {
168 self.render_rule_inferred_with_context_and_options(rule_name, RenderContext::new(), options)
169 }
170
171 pub fn render_rule_inferred_with_context(
173 &self,
174 rule_name: &str,
175 context: RenderContext,
176 ) -> Result<String, RenderError> {
177 self.render_rule_inferred_with_context_and_options(
178 rule_name,
179 context,
180 RenderOptions::default(),
181 )
182 }
183
184 pub fn render_rule_inferred_with_context_and_options(
186 &self,
187 rule_name: &str,
188 context: RenderContext,
189 options: RenderOptions,
190 ) -> Result<String, RenderError> {
191 if matches!(
192 self.structured_node(rule_name),
193 Ok(StructuredNode::Object(_))
194 ) {
195 return self
196 .render_rule_structured_with_context_and_options(rule_name, context, options)
197 .and_then(|value| value.to_formatted_json());
198 }
199 self.render_rule_with_context_and_options(rule_name, context, options)
200 }
201
202 pub fn render_rule_structured(&self, rule_name: &str) -> Result<CopperlaceValue, RenderError> {
208 self.render_rule_structured_with_context(rule_name, RenderContext::new())
209 }
210
211 pub fn render_rule_structured_with_options(
213 &self,
214 rule_name: &str,
215 options: RenderOptions,
216 ) -> Result<CopperlaceValue, RenderError> {
217 self.render_rule_structured_with_context_and_options(
218 rule_name,
219 RenderContext::new(),
220 options,
221 )
222 }
223
224 pub fn render_rule_structured_with_context(
226 &self,
227 rule_name: &str,
228 context: RenderContext,
229 ) -> Result<CopperlaceValue, RenderError> {
230 self.render_rule_structured_with_context_and_options(
231 rule_name,
232 context,
233 RenderOptions::default(),
234 )
235 }
236
237 pub fn render_rule_structured_with_context_and_options(
239 &self,
240 rule_name: &str,
241 context: RenderContext,
242 options: RenderOptions,
243 ) -> Result<CopperlaceValue, RenderError> {
244 let node = self.structured_node(rule_name)?;
245 if !matches!(node, StructuredNode::Object(_)) {
246 return Err(RenderError::UnsupportedStructuredTarget(
247 rule_name.to_string(),
248 ));
249 }
250 let mut state = RenderState::with_context_and_options(self, context, options);
251 node.generate_value(&mut state)
252 }
253
254 pub fn structured_document(&self) -> &StructuredNode {
256 &self.document
257 }
258
259 pub(crate) fn structured_node(&self, rule_name: &str) -> Result<&StructuredNode, RenderError> {
260 let mut node = &self.document;
261 for segment in rule_name.split('.') {
262 if segment.is_empty() {
263 return Err(RenderError::UnknownRule(rule_name.to_string()));
264 }
265 let StructuredNode::Object(values) = node else {
266 return Err(RenderError::UnknownRule(rule_name.to_string()));
267 };
268 let Some(next_node) = values.get(segment) else {
269 return Err(RenderError::UnknownRule(rule_name.to_string()));
270 };
271 node = next_node;
272 }
273 Ok(node)
274 }
275
276 pub(crate) fn structured_context_node(&self, name: &str) -> Option<&StructuredNode> {
277 let StructuredNode::Object(document) = &self.document else {
278 return None;
279 };
280 let StructuredNode::Object(context) = document.get("context")? else {
281 return None;
282 };
283
284 let mut node = context.get(name.split('.').next()?)?;
285 for segment in name.split('.').skip(1) {
286 let StructuredNode::Object(values) = node else {
287 return None;
288 };
289 node = values.get(segment)?;
290 }
291 Some(node)
292 }
293
294 pub(crate) fn render_rule_with_state(
295 &self,
296 rule_name: &str,
297 state: &mut RenderState,
298 ) -> Result<String, RenderError> {
299 let Some(rule) = self
300 .text_rules
301 .get(rule_name)
302 .or_else(|| self.context_defaults.get(rule_name))
303 else {
304 return Err(RenderError::UnknownRule(rule_name.to_string()));
305 };
306
307 self.render_node_with_state(rule_name, rule.as_ref(), state, false)
308 }
309
310 pub(crate) fn render_unique_rule_with_state(
311 &self,
312 rule_name: &str,
313 state: &mut RenderState,
314 ) -> Result<String, RenderError> {
315 let Some(rule) = self.text_rules.get(rule_name) else {
316 return Err(RenderError::UnknownRule(rule_name.to_string()));
317 };
318
319 self.render_node_with_state(rule_name, rule.as_ref(), state, true)
320 }
321
322 fn render_node_with_state(
323 &self,
324 rule_name: &str,
325 rule: &dyn TextGeneratorNode,
326 state: &mut RenderState,
327 unique: bool,
328 ) -> Result<String, RenderError> {
329 let existing_calls = state
330 .call_stack
331 .iter()
332 .filter(|name| name.as_str() == rule_name)
333 .count();
334 if state.options.max_recursion_depth == 0 && existing_calls > 0 {
335 let mut cycle = state.call_stack.clone();
336 cycle.push(rule_name.to_string());
337 return Err(RenderError::CircularRuleReference(cycle));
338 }
339 if existing_calls > state.options.max_recursion_depth {
340 return Ok(String::new());
341 }
342
343 state.call_stack.push(rule_name.to_string());
344 let result = if unique {
345 rule.generate_unique_text(rule_name, state)
346 } else {
347 rule.generate_text(state)
348 };
349 state.call_stack.pop();
350 result
351 }
352
353 pub(crate) fn render_context_default_with_state(
354 &self,
355 name: &str,
356 state: &mut RenderState,
357 ) -> Result<Option<String>, RenderError> {
358 let Some(rule) = self.context_defaults.get(name) else {
359 return Ok(None);
360 };
361
362 let existing_calls = state
363 .call_stack
364 .iter()
365 .filter(|rule_name| rule_name.as_str() == name)
366 .count();
367 if state.options.max_recursion_depth == 0 && existing_calls > 0 {
368 let mut cycle = state.call_stack.clone();
369 cycle.push(name.to_string());
370 return Err(RenderError::CircularRuleReference(cycle));
371 }
372 if existing_calls > state.options.max_recursion_depth {
373 return Ok(Some(String::new()));
374 }
375
376 state.call_stack.push(name.to_string());
377 let result = rule.generate_text(state);
378 state.call_stack.pop();
379 result.map(Some)
380 }
381
382 pub(crate) fn process(&self, processor_name: &str, value: &str) -> Result<String, RenderError> {
383 let Some(processor) = self.processors.get(processor_name) else {
384 return Err(RenderError::UnknownProcessor(processor_name.to_string()));
385 };
386
387 processor
388 .process(value)
389 .map_err(|message| RenderError::ProcessorError {
390 processor: processor_name.to_string(),
391 message,
392 })
393 }
394}
395
396pub fn render_config_rule(config: hocon_rs::Value, rule_name: &str) -> Result<String, RenderError> {
401 render_config_rule_with_context(config, rule_name, RenderContext::new())
402}
403
404pub fn render_config_rule_with_context(
406 config: hocon_rs::Value,
407 rule_name: &str,
408 context: RenderContext,
409) -> Result<String, RenderError> {
410 render_config_rule_with_context_and_options(
411 config,
412 rule_name,
413 context,
414 RenderOptions::default(),
415 )
416}
417
418pub fn render_config_rule_with_context_and_options(
420 config: hocon_rs::Value,
421 rule_name: &str,
422 context: RenderContext,
423 options: RenderOptions,
424) -> Result<String, RenderError> {
425 let ruleset = RuleSet::from_config(config)?;
426 ruleset.render_rule_with_context_and_options(rule_name, context, options)
427}
428
429pub fn render_config_rule_structured(
431 config: hocon_rs::Value,
432 rule_name: &str,
433) -> Result<CopperlaceValue, RenderError> {
434 render_config_rule_structured_with_context(config, rule_name, RenderContext::new())
435}
436
437pub fn render_config_rule_structured_with_context(
439 config: hocon_rs::Value,
440 rule_name: &str,
441 context: RenderContext,
442) -> Result<CopperlaceValue, RenderError> {
443 render_config_rule_structured_with_context_and_options(
444 config,
445 rule_name,
446 context,
447 RenderOptions::default(),
448 )
449}
450
451pub fn render_config_rule_structured_with_context_and_options(
453 config: hocon_rs::Value,
454 rule_name: &str,
455 context: RenderContext,
456 options: RenderOptions,
457) -> Result<CopperlaceValue, RenderError> {
458 let ruleset = RuleSet::from_config(config)?;
459 ruleset.render_rule_structured_with_context_and_options(rule_name, context, options)
460}