1use std::collections::{BTreeMap, HashMap};
2
3use crate::processors::builtin_processors;
4
5use super::compile::{
6 insert_context_text_nodes, insert_named_text_nodes, value_to_structured_node,
7};
8use super::error::RenderError;
9use super::nodes::TextGeneratorNode;
10use super::processor::ProcessorRegistry;
11use super::state::{RenderContext, RenderState};
12use super::value::{CopperlaceValue, StructuredNode};
13
14pub struct RuleSet {
22 document: StructuredNode,
23 text_rules: HashMap<String, Box<dyn TextGeneratorNode>>,
24 context_defaults: HashMap<String, Box<dyn TextGeneratorNode>>,
25 processors: ProcessorRegistry,
26}
27
28impl RuleSet {
29 pub fn from_config(config: hocon_rs::Value) -> Result<Self, RenderError> {
35 Self::from_config_with_processors(config, ProcessorRegistry::new())
36 }
37
38 pub fn from_config_with_processors(
44 config: hocon_rs::Value,
45 custom_processors: ProcessorRegistry,
46 ) -> Result<Self, RenderError> {
47 let hocon_rs::Value::Object(values) = config else {
48 return Err(RenderError::InvalidConfigRoot);
49 };
50
51 let mut processors = builtin_processors();
52 processors.extend(custom_processors);
53
54 let mut document_values = BTreeMap::new();
55 let mut text_rules = HashMap::new();
56 let mut context_defaults = HashMap::new();
57
58 for (name, value) in values {
59 document_values.insert(
60 name.clone(),
61 value_to_structured_node(value.clone(), &processors)?,
62 );
63 if name == "context" {
64 if let hocon_rs::Value::Object(context_values) = value {
65 for (context_name, context_value) in context_values {
66 insert_context_text_nodes(
67 &mut context_defaults,
68 context_name,
69 context_value,
70 &processors,
71 )?;
72 }
73 } else {
74 insert_named_text_nodes(&mut text_rules, name, value, &processors)?;
75 }
76 } else {
77 insert_named_text_nodes(&mut text_rules, name, value, &processors)?;
78 }
79 }
80
81 Ok(RuleSet {
82 document: StructuredNode::Object(document_values),
83 text_rules,
84 context_defaults,
85 processors,
86 })
87 }
88
89 pub fn render_rule(&self, rule_name: &str) -> Result<String, RenderError> {
94 self.render_rule_with_context(rule_name, RenderContext::new())
95 }
96
97 pub fn render_rule_with_context(
103 &self,
104 rule_name: &str,
105 context: RenderContext,
106 ) -> Result<String, RenderError> {
107 let mut state = RenderState::with_context(self, context);
108 self.render_rule_with_state(rule_name, &mut state)
109 }
110
111 pub fn render_rule_inferred(&self, rule_name: &str) -> Result<String, RenderError> {
116 self.render_rule_inferred_with_context(rule_name, RenderContext::new())
117 }
118
119 pub fn render_rule_inferred_with_context(
121 &self,
122 rule_name: &str,
123 context: RenderContext,
124 ) -> Result<String, RenderError> {
125 if matches!(
126 self.structured_node(rule_name),
127 Ok(StructuredNode::Object(_))
128 ) {
129 return self
130 .render_rule_structured_with_context(rule_name, context)
131 .and_then(|value| value.to_formatted_json());
132 }
133 self.render_rule_with_context(rule_name, context)
134 }
135
136 pub fn render_rule_structured(&self, rule_name: &str) -> Result<CopperlaceValue, RenderError> {
142 self.render_rule_structured_with_context(rule_name, RenderContext::new())
143 }
144
145 pub fn render_rule_structured_with_context(
147 &self,
148 rule_name: &str,
149 context: RenderContext,
150 ) -> Result<CopperlaceValue, RenderError> {
151 let node = self.structured_node(rule_name)?;
152 if !matches!(node, StructuredNode::Object(_)) {
153 return Err(RenderError::UnsupportedStructuredTarget(
154 rule_name.to_string(),
155 ));
156 }
157 let mut state = RenderState::with_context(self, context);
158 node.generate_value(&mut state)
159 }
160
161 pub fn structured_document(&self) -> &StructuredNode {
163 &self.document
164 }
165
166 fn structured_node(&self, rule_name: &str) -> Result<&StructuredNode, RenderError> {
167 let mut node = &self.document;
168 for segment in rule_name.split('.') {
169 if segment.is_empty() {
170 return Err(RenderError::UnknownRule(rule_name.to_string()));
171 }
172 let StructuredNode::Object(values) = node else {
173 return Err(RenderError::UnknownRule(rule_name.to_string()));
174 };
175 let Some(next_node) = values.get(segment) else {
176 return Err(RenderError::UnknownRule(rule_name.to_string()));
177 };
178 node = next_node;
179 }
180 Ok(node)
181 }
182
183 pub(crate) fn render_rule_with_state(
184 &self,
185 rule_name: &str,
186 state: &mut RenderState,
187 ) -> Result<String, RenderError> {
188 let Some(rule) = self
189 .text_rules
190 .get(rule_name)
191 .or_else(|| self.context_defaults.get(rule_name))
192 else {
193 return Err(RenderError::UnknownRule(rule_name.to_string()));
194 };
195
196 if state.call_stack.iter().any(|name| name == rule_name) {
197 let mut cycle = state.call_stack.clone();
198 cycle.push(rule_name.to_string());
199 return Err(RenderError::CircularRuleReference(cycle));
200 }
201
202 state.call_stack.push(rule_name.to_string());
203 let result = rule.generate_text(state);
204 state.call_stack.pop();
205 result
206 }
207
208 pub(crate) fn render_context_default_with_state(
209 &self,
210 name: &str,
211 state: &mut RenderState,
212 ) -> Result<Option<String>, RenderError> {
213 let Some(rule) = self.context_defaults.get(name) else {
214 return Ok(None);
215 };
216
217 if state.call_stack.iter().any(|rule_name| rule_name == name) {
218 let mut cycle = state.call_stack.clone();
219 cycle.push(name.to_string());
220 return Err(RenderError::CircularRuleReference(cycle));
221 }
222
223 state.call_stack.push(name.to_string());
224 let result = rule.generate_text(state);
225 state.call_stack.pop();
226 result.map(Some)
227 }
228
229 pub(crate) fn process(&self, processor_name: &str, value: &str) -> Result<String, RenderError> {
230 let Some(processor) = self.processors.get(processor_name) else {
231 return Err(RenderError::UnknownProcessor(processor_name.to_string()));
232 };
233
234 processor
235 .process(value)
236 .map_err(|message| RenderError::ProcessorError {
237 processor: processor_name.to_string(),
238 message,
239 })
240 }
241}
242
243pub fn render_config_rule(config: hocon_rs::Value, rule_name: &str) -> Result<String, RenderError> {
248 render_config_rule_with_context(config, rule_name, RenderContext::new())
249}
250
251pub fn render_config_rule_with_context(
253 config: hocon_rs::Value,
254 rule_name: &str,
255 context: RenderContext,
256) -> Result<String, RenderError> {
257 let ruleset = RuleSet::from_config(config)?;
258 ruleset.render_rule_with_context(rule_name, context)
259}
260
261pub fn render_config_rule_structured(
263 config: hocon_rs::Value,
264 rule_name: &str,
265) -> Result<CopperlaceValue, RenderError> {
266 render_config_rule_structured_with_context(config, rule_name, RenderContext::new())
267}
268
269pub fn render_config_rule_structured_with_context(
271 config: hocon_rs::Value,
272 rule_name: &str,
273 context: RenderContext,
274) -> Result<CopperlaceValue, RenderError> {
275 let ruleset = RuleSet::from_config(config)?;
276 ruleset.render_rule_structured_with_context(rule_name, context)
277}