1use std::fmt;
2use std::path::Path;
3use std::str::FromStr;
4
5use crate::render::config_loader;
6use crate::render::{
7 CopperlaceValue, ProcessorRegistry, RenderContext, RenderError, RenderOptions, RuleSet,
8};
9
10#[derive(Debug, PartialEq, Eq)]
12pub enum ConfigError {
13 Parse(String),
15 Render(RenderError),
17}
18
19impl fmt::Display for ConfigError {
20 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 ConfigError::Parse(error) => write!(formatter, "failed to parse config: {error}"),
23 ConfigError::Render(error) => write!(formatter, "{error}"),
24 }
25 }
26}
27
28impl std::error::Error for ConfigError {}
29
30impl From<RenderError> for ConfigError {
31 fn from(error: RenderError) -> Self {
32 ConfigError::Render(error)
33 }
34}
35
36pub struct Copperlace {
42 ruleset: RuleSet,
43}
44
45impl Copperlace {
46 #[allow(clippy::should_implement_trait)]
52 pub fn from_str(config: &str) -> Result<Self, ConfigError> {
53 Ok(Self {
54 ruleset: ruleset_from_str(config)?,
55 })
56 }
57
58 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
64 Ok(Self {
65 ruleset: ruleset_from_file(path)?,
66 })
67 }
68
69 pub fn render(&self, rule_name: &str) -> Result<String, RenderError> {
74 self.ruleset.render_rule(rule_name)
75 }
76
77 pub fn render_with_options(
79 &self,
80 rule_name: &str,
81 options: RenderOptions,
82 ) -> Result<String, RenderError> {
83 self.ruleset.render_rule_with_options(rule_name, options)
84 }
85
86 pub fn render_with_context(
91 &self,
92 rule_name: &str,
93 context: RenderContext,
94 ) -> Result<String, RenderError> {
95 self.ruleset.render_rule_with_context(rule_name, context)
96 }
97
98 pub fn render_with_context_and_options(
100 &self,
101 rule_name: &str,
102 context: RenderContext,
103 options: RenderOptions,
104 ) -> Result<String, RenderError> {
105 self.ruleset
106 .render_rule_with_context_and_options(rule_name, context, options)
107 }
108
109 pub fn render_inferred(&self, rule_name: &str) -> Result<String, RenderError> {
111 self.ruleset.render_rule_inferred(rule_name)
112 }
113
114 pub fn render_inferred_with_options(
116 &self,
117 rule_name: &str,
118 options: RenderOptions,
119 ) -> Result<String, RenderError> {
120 self.ruleset
121 .render_rule_inferred_with_options(rule_name, options)
122 }
123
124 pub fn render_inferred_with_context(
126 &self,
127 rule_name: &str,
128 context: RenderContext,
129 ) -> Result<String, RenderError> {
130 self.ruleset
131 .render_rule_inferred_with_context(rule_name, context)
132 }
133
134 pub fn render_inferred_with_context_and_options(
136 &self,
137 rule_name: &str,
138 context: RenderContext,
139 options: RenderOptions,
140 ) -> Result<String, RenderError> {
141 self.ruleset
142 .render_rule_inferred_with_context_and_options(rule_name, context, options)
143 }
144
145 pub fn render_structured(&self, rule_name: &str) -> Result<CopperlaceValue, RenderError> {
147 self.ruleset.render_rule_structured(rule_name)
148 }
149
150 pub fn render_structured_with_options(
152 &self,
153 rule_name: &str,
154 options: RenderOptions,
155 ) -> Result<CopperlaceValue, RenderError> {
156 self.ruleset
157 .render_rule_structured_with_options(rule_name, options)
158 }
159
160 pub fn render_structured_with_context(
162 &self,
163 rule_name: &str,
164 context: RenderContext,
165 ) -> Result<CopperlaceValue, RenderError> {
166 self.ruleset
167 .render_rule_structured_with_context(rule_name, context)
168 }
169
170 pub fn render_structured_with_context_and_options(
172 &self,
173 rule_name: &str,
174 context: RenderContext,
175 options: RenderOptions,
176 ) -> Result<CopperlaceValue, RenderError> {
177 self.ruleset
178 .render_rule_structured_with_context_and_options(rule_name, context, options)
179 }
180}
181
182impl FromStr for Copperlace {
183 type Err = ConfigError;
184
185 fn from_str(config: &str) -> Result<Self, Self::Err> {
186 Ok(Self {
187 ruleset: ruleset_from_str(config)?,
188 })
189 }
190}
191
192pub fn ruleset_from_str(config: &str) -> Result<RuleSet, ConfigError> {
194 ruleset_from_str_with_processors(config, ProcessorRegistry::new())
195}
196
197pub(crate) fn ruleset_from_str_with_processors(
198 config: &str,
199 processors: ProcessorRegistry,
200) -> Result<RuleSet, ConfigError> {
201 let parsed = config_loader::load_str(config, None).map_err(ConfigError::Parse)?;
202 RuleSet::from_template_config(parsed, processors).map_err(ConfigError::Render)
203}
204
205fn config_options_for_file(path: &Path) -> hocon_rs::ConfigOptions {
206 let mut locations = Vec::new();
207 if let Some(parent) = path.parent() {
208 locations.push(parent.to_string_lossy().to_string());
209 }
210 if let Ok(current_dir) = std::env::current_dir() {
211 let current_dir = current_dir.to_string_lossy().to_string();
212 if !locations.iter().any(|location| location == ¤t_dir) {
213 locations.push(current_dir);
214 }
215 }
216 hocon_rs::ConfigOptions::new(false, locations)
217}
218
219pub fn ruleset_from_file(path: impl AsRef<Path>) -> Result<RuleSet, ConfigError> {
221 ruleset_from_file_with_processors(path, ProcessorRegistry::new())
222}
223
224pub(crate) fn ruleset_from_file_with_processors(
225 path: impl AsRef<Path>,
226 processors: ProcessorRegistry,
227) -> Result<RuleSet, ConfigError> {
228 let path = path.as_ref();
229 let parsed = config_loader::load_file(path, config_options_for_file(path))
230 .map_err(ConfigError::Parse)?;
231 RuleSet::from_template_config(parsed, processors).map_err(ConfigError::Render)
232}
233
234pub fn render_str(config: &str, rule_name: &str) -> Result<String, ConfigError> {
240 render_str_with_context(config, rule_name, RenderContext::new())
241}
242
243pub fn render_str_with_context(
245 config: &str,
246 rule_name: &str,
247 context: RenderContext,
248) -> Result<String, ConfigError> {
249 render_str_with_context_and_options(config, rule_name, context, RenderOptions::default())
250}
251
252pub fn render_str_with_context_and_options(
254 config: &str,
255 rule_name: &str,
256 context: RenderContext,
257 options: RenderOptions,
258) -> Result<String, ConfigError> {
259 ruleset_from_str(config)?
260 .render_rule_with_context_and_options(rule_name, context, options)
261 .map_err(ConfigError::Render)
262}
263
264pub fn render_str_inferred(config: &str, rule_name: &str) -> Result<String, ConfigError> {
266 render_str_inferred_with_context(config, rule_name, RenderContext::new())
267}
268
269pub fn render_str_inferred_with_context(
271 config: &str,
272 rule_name: &str,
273 context: RenderContext,
274) -> Result<String, ConfigError> {
275 render_str_inferred_with_context_and_options(
276 config,
277 rule_name,
278 context,
279 RenderOptions::default(),
280 )
281}
282
283pub fn render_str_inferred_with_context_and_options(
285 config: &str,
286 rule_name: &str,
287 context: RenderContext,
288 options: RenderOptions,
289) -> Result<String, ConfigError> {
290 ruleset_from_str(config)?
291 .render_rule_inferred_with_context_and_options(rule_name, context, options)
292 .map_err(ConfigError::Render)
293}
294
295pub fn render_str_structured(
297 config: &str,
298 rule_name: &str,
299) -> Result<CopperlaceValue, ConfigError> {
300 render_str_structured_with_context(config, rule_name, RenderContext::new())
301}
302
303pub fn render_str_structured_with_context(
305 config: &str,
306 rule_name: &str,
307 context: RenderContext,
308) -> Result<CopperlaceValue, ConfigError> {
309 render_str_structured_with_context_and_options(
310 config,
311 rule_name,
312 context,
313 RenderOptions::default(),
314 )
315}
316
317pub fn render_str_structured_with_context_and_options(
319 config: &str,
320 rule_name: &str,
321 context: RenderContext,
322 options: RenderOptions,
323) -> Result<CopperlaceValue, ConfigError> {
324 ruleset_from_str(config)?
325 .render_rule_structured_with_context_and_options(rule_name, context, options)
326 .map_err(ConfigError::Render)
327}
328
329pub fn render_file(path: impl AsRef<Path>, rule_name: &str) -> Result<String, ConfigError> {
335 render_file_with_context(path, rule_name, RenderContext::new())
336}
337
338pub fn render_file_with_context(
340 path: impl AsRef<Path>,
341 rule_name: &str,
342 context: RenderContext,
343) -> Result<String, ConfigError> {
344 render_file_with_context_and_options(path, rule_name, context, RenderOptions::default())
345}
346
347pub fn render_file_with_context_and_options(
349 path: impl AsRef<Path>,
350 rule_name: &str,
351 context: RenderContext,
352 options: RenderOptions,
353) -> Result<String, ConfigError> {
354 ruleset_from_file(path)?
355 .render_rule_with_context_and_options(rule_name, context, options)
356 .map_err(ConfigError::Render)
357}
358
359pub fn render_file_inferred(
361 path: impl AsRef<Path>,
362 rule_name: &str,
363) -> Result<String, ConfigError> {
364 render_file_inferred_with_context(path, rule_name, RenderContext::new())
365}
366
367pub fn render_file_inferred_with_context(
369 path: impl AsRef<Path>,
370 rule_name: &str,
371 context: RenderContext,
372) -> Result<String, ConfigError> {
373 render_file_inferred_with_context_and_options(
374 path,
375 rule_name,
376 context,
377 RenderOptions::default(),
378 )
379}
380
381pub fn render_file_inferred_with_context_and_options(
383 path: impl AsRef<Path>,
384 rule_name: &str,
385 context: RenderContext,
386 options: RenderOptions,
387) -> Result<String, ConfigError> {
388 ruleset_from_file(path)?
389 .render_rule_inferred_with_context_and_options(rule_name, context, options)
390 .map_err(ConfigError::Render)
391}
392
393pub fn render_file_structured(
395 path: impl AsRef<Path>,
396 rule_name: &str,
397) -> Result<CopperlaceValue, ConfigError> {
398 render_file_structured_with_context(path, rule_name, RenderContext::new())
399}
400
401pub fn render_file_structured_with_context(
403 path: impl AsRef<Path>,
404 rule_name: &str,
405 context: RenderContext,
406) -> Result<CopperlaceValue, ConfigError> {
407 render_file_structured_with_context_and_options(
408 path,
409 rule_name,
410 context,
411 RenderOptions::default(),
412 )
413}
414
415pub fn render_file_structured_with_context_and_options(
417 path: impl AsRef<Path>,
418 rule_name: &str,
419 context: RenderContext,
420 options: RenderOptions,
421) -> Result<CopperlaceValue, ConfigError> {
422 ruleset_from_file(path)?
423 .render_rule_structured_with_context_and_options(rule_name, context, options)
424 .map_err(ConfigError::Render)
425}