Skip to main content

datalogic_rs/
builder.rs

1//! Builder for [`Engine`].
2//!
3//! The single entry point for non-default engine construction
4//! (config, custom operators, templating). Replaces the four ad-hoc
5//! 4.x constructors (`new`, `with_preserve_structure`, `with_config`,
6//! `with_config_and_structure`).
7
8use std::collections::HashMap;
9
10use crate::CustomOperator;
11use crate::config::EvaluationConfig;
12use crate::engine::Engine;
13
14/// Builder for [`Engine`]. Construct via [`Engine::builder`].
15///
16/// ```
17/// use datalogic_rs::Engine;
18///
19/// let engine = Engine::builder().build();
20/// # let _ = engine;
21/// ```
22///
23/// # Defaults
24///
25/// `Engine::builder().build()` produces the same engine as
26/// [`Engine::new`] / [`Engine::default`]:
27///
28/// - **`config`** — [`EvaluationConfig::default`]: JavaScript-flavoured
29///   truthiness, NaN errors on bad arithmetic input, `±f64::MAX` on
30///   division by zero, `loose_equality_errors = true`,
31///   `max_recursion_depth = 256`, and the implicit `null`/`bool`/
32///   `""` → 0 numeric coercions enabled. Override with [`Self::with_config`];
33///   [`EvaluationConfig::safe_arithmetic`] / [`EvaluationConfig::strict`]
34///   are alternative starting points.
35/// - **`templating`** — `false` (templating mode off). Set with
36///   [`Self::with_templating`]; only effective when the crate is
37///   built with `feature = "templating"`.
38/// - **`operators`** — empty. Add custom operators with
39///   [`Self::add_operator`] before [`Self::build`] freezes the set.
40/// - **`constant_folding`** — `true`. The compile pipeline pre-computes
41///   constant sub-expressions during [`Engine::compile`]. Disable with
42///   [`Self::with_constant_folding`] when you need every operator to
43///   survive in the compiled tree (e.g. for tooling that walks the
44///   structure or applies its own rewrites).
45#[must_use = "the builder is consumed by `.build()`"]
46pub struct EngineBuilder {
47    config: EvaluationConfig,
48    templating: bool,
49    constant_folding: bool,
50    operators: HashMap<String, Box<dyn CustomOperator>>,
51}
52
53impl Default for EngineBuilder {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl EngineBuilder {
60    /// Fresh builder with default config and no custom operators.
61    #[inline]
62    pub fn new() -> Self {
63        Self {
64            config: EvaluationConfig::default(),
65            templating: false,
66            constant_folding: true,
67            operators: HashMap::new(),
68        }
69    }
70
71    /// Set the evaluation config.
72    #[inline]
73    #[must_use = "builder methods return a new builder; chain into `.build()`"]
74    pub fn with_config(mut self, config: EvaluationConfig) -> Self {
75        self.config = config;
76        self
77    }
78
79    /// Toggle templating mode (multi-key objects compile to output-shaping
80    /// templates; unknown operator keys pass through verbatim). Only
81    /// effective when the crate is built with `feature = "templating"`.
82    #[inline]
83    #[must_use = "builder methods return a new builder; chain into `.build()`"]
84    pub fn with_templating(mut self, on: bool) -> Self {
85        self.templating = on;
86        self
87    }
88
89    /// Toggle the compile-time constant-folding pass. Default: `true`
90    /// (folding enabled). Pass `false` when every operator must survive
91    /// in the compiled tree — debuggers, alternate evaluators, or any
92    /// caller that walks the compiled structure and would be surprised
93    /// to see a `{"+": [1, 2]}` collapsed to a `3` literal.
94    // The trace-feature addendum links to `Engine::trace`, which only
95    // exists with `feature = "trace"`. Gate the whole paragraph behind
96    // the same feature so `cargo doc` without `--all-features` doesn't
97    // break on the intra-doc link.
98    #[cfg_attr(feature = "trace", doc = "")]
99    #[cfg_attr(
100        feature = "trace",
101        doc = "The trace surface ([`crate::Engine::trace`]) always disables folding"
102    )]
103    #[cfg_attr(
104        feature = "trace",
105        doc = "internally regardless of this setting, since traces would otherwise"
106    )]
107    #[cfg_attr(feature = "trace", doc = "lose the folded operators as steps.")]
108    #[inline]
109    #[must_use = "builder methods return a new builder; chain into `.build()`"]
110    pub fn with_constant_folding(mut self, on: bool) -> Self {
111        self.constant_folding = on;
112        self
113    }
114
115    /// Register a [`CustomOperator`] under `name`. Multiple calls with the
116    /// same name overwrite the prior registration.
117    ///
118    /// Accepts both typed operators (`T: CustomOperator + 'static`) and
119    /// pre-boxed trait objects (`Box<dyn CustomOperator>`) — the bare
120    /// `Box<dyn CustomOperator>` itself implements `CustomOperator`
121    /// (delegating to the inner), so a single entry point covers both
122    /// shapes:
123    ///
124    /// ```ignore
125    /// builder
126    ///     .add_operator("typed", MyOp)                            // typed
127    ///     .add_operator("dyn", boxed_op_from_registry as Box<_>)  // pre-boxed
128    /// ```
129    ///
130    /// Operator registration is builder-only; once [`Self::build`] hands
131    /// you an [`Engine`], its operator set is frozen.
132    ///
133    /// **Built-ins always win.** If `name` collides with a built-in
134    /// JSONLogic operator (`+`, `if`, `var`, `map`, …), the built-in is
135    /// dispatched and the registered custom op is never reached. To
136    /// extend the operator set, choose a name that doesn't parse as a
137    /// built-in.
138    #[inline]
139    #[must_use = "builder methods return a new builder; chain into `.build()`"]
140    pub fn add_operator<T>(mut self, name: impl Into<String>, operator: T) -> Self
141    where
142        T: CustomOperator + 'static,
143    {
144        self.operators.insert(name.into(), Box::new(operator));
145        self
146    }
147
148    /// Finalise the builder into an immutable [`Engine`] engine.
149    pub fn build(self) -> Engine {
150        Engine::from_builder_parts(
151            self.config,
152            self.templating,
153            self.constant_folding,
154            self.operators,
155        )
156    }
157}