Configuration
Customize evaluation behavior with EvaluationConfig and the
EngineBuilder.
Creating a Configured Engine
use datalogic_rs::{Engine, EvaluationConfig, NanHandling};
// Default configuration
let engine = Engine::new();
// Custom configuration
let config = EvaluationConfig::default()
.with_arithmetic_nan_handling(NanHandling::IgnoreValue);
let engine = Engine::builder().with_config(config).build();
v5 dropped the inherent
Engine::with_config/with_preserve_structure/with_config_and_structureconstructors — use the builder. There is no compatibility shim. See the Migration Guide for the v4 → v5 mapping.
Configuration Options
EvaluationConfig is #[non_exhaustive]. Construct it with default()
(or a preset such as safe_arithmetic() / strict()), then chain the
with_* setters:
use datalogic_rs::{EvaluationConfig, NanHandling, DivisionByZeroHandling};
let config = EvaluationConfig::default()
.with_arithmetic_nan_handling(NanHandling::IgnoreValue)
.with_division_by_zero(DivisionByZeroHandling::ReturnNull)
.with_loose_equality_errors(false);
NaN Handling
Control how non-numeric values are handled in arithmetic operations.
use datalogic_rs::{EvaluationConfig, NanHandling};
// ThrowError (default), IgnoreValue, CoerceToZero, ReturnNull
let config = EvaluationConfig::default()
.with_arithmetic_nan_handling(NanHandling::IgnoreValue);
Behavior comparison for {"+": [1, "text", 2]}:
| Setting | Result |
|---|---|
ThrowError (default) | Err(Thrown { type: "NaN" }) |
IgnoreValue | 3 (skips "text") |
CoerceToZero | 3 ("text" → 0) |
ReturnNull | null |
Division by Zero
use datalogic_rs::{EvaluationConfig, DivisionByZeroHandling};
// ReturnSaturated (default), ThrowError, ReturnNull, ReturnInfinity
let config = EvaluationConfig::default()
.with_division_by_zero(DivisionByZeroHandling::ThrowError);
Behavior comparison for {"/": [10.5, 0]}:
| Setting | Result |
|---|---|
ReturnSaturated (default) | 1.7976931348623157e308 (f64::MAX, sign of dividend) |
ThrowError | Err(Thrown { type: "NaN" }) |
ReturnNull | null |
ReturnInfinity | f64::INFINITY (sign of dividend) as an OwnedDataValue / DataValue; null on the JSON-string paths |
The setting only governs the float path. An integer dividend over an
integer zero ({"/": [10, 0]}, and likewise {"%": [10, 0]}) always
raises Err(Thrown { type: "NaN" }), whatever the setting, because there
is no in-range integer sentinel to return; only a genuinely fractional
dividend such as 10.5 takes the configurable path. ReturnInfinity
yields the infinite f64 from Engine::eval / Session::eval, but
eval_str (and every language binding) renders it as null because JSON
cannot encode infinity.
Truthiness Evaluation
use std::sync::Arc;
use datalogic_rs::{EvaluationConfig, TruthyEvaluator};
use datalogic_rs::datavalue::OwnedDataValue;
// JavaScript (default), Python, StrictBoolean, Custom
let config = EvaluationConfig::default()
.with_truthy_evaluator(TruthyEvaluator::Python);
// Custom truthy: receives an OwnedDataValue (no serde_json required)
let custom = Arc::new(|value: &OwnedDataValue| -> bool {
value.as_f64().map_or(false, |n| n > 0.0)
});
let config = EvaluationConfig::default()
.with_truthy_evaluator(TruthyEvaluator::Custom(custom));
v5 change:
TruthyEvaluator::Customnow takesArc<dyn Fn(&OwnedDataValue) -> bool + Send + Sync>(the canonical owned value type). v4 used&serde_json::Value.
Truthiness comparison:
| Value | JavaScript | Python | StrictBoolean |
|---|---|---|---|
true | truthy | truthy | truthy |
false | falsy | falsy | falsy |
1 | truthy | truthy | truthy |
0 | falsy | falsy | truthy |
"" | falsy | falsy | truthy |
"0" | truthy | truthy | truthy |
[] | falsy | falsy | truthy |
[0] | truthy | truthy | truthy |
{} | falsy | falsy | truthy |
null | falsy | falsy | falsy |
StrictBoolean treats only null and false as falsy; every other
value, including 0, "", and empty collections, is truthy. Python
differs from JavaScript on exactly one value: NaN (which only arises
from arithmetic, never from a JSON literal) is falsy in JavaScript and
truthy in Python.
Loose Equality Errors
Control whether loose equality (==) raises errors for incompatible types.
let config = EvaluationConfig::default()
.with_loose_equality_errors(true); // default
Numeric Coercion
NumericCoercionConfig is #[non_exhaustive] too: start from
default() and chain its own with_* setters, then pass it through
with_numeric_coercion.
use datalogic_rs::{EvaluationConfig, NumericCoercionConfig};
let config = EvaluationConfig::default()
.with_numeric_coercion(
NumericCoercionConfig::default()
.with_empty_string_to_zero(false)
.with_null_to_zero(false)
.with_bool_to_number(false)
.with_reject_non_numeric(true),
);
Max Recursion Depth
Cap the number of nested evaluation-boundary calls before the engine
bails with a ConfigurationError. The limit is tracked per thread and
guards against custom operators that hold an Arc<Engine> and re-enter
via engine.evaluate(...). Pure built-in workloads skip the check
entirely, so they pay nothing.
use datalogic_rs::EvaluationConfig;
// Default is 256: raise it for deeply nested custom-operator graphs,
// lower it to bail sooner.
let config = EvaluationConfig::default()
.with_max_recursion_depth(256);
Configuration Presets
use datalogic_rs::{Engine, EvaluationConfig};
// Lenient arithmetic — IgnoreValue + ReturnNull divide-by-zero
let engine = Engine::builder()
.with_config(EvaluationConfig::safe_arithmetic())
.build();
// Strict — errors for any type mismatch and no numeric coercion
let engine = Engine::builder()
.with_config(EvaluationConfig::strict())
.build();
Configuring from JSON
EvaluationConfig::from_json_str (requires feature = "serde_json")
builds a configuration from a JSON object. This is the wire format the
language bindings use to pass engine configuration across FFI
boundaries through one shared parser; Rust callers normally use the
typed with_* setters above.
All keys are optional. The "preset" key is applied first, then the
remaining keys override individual fields on top of it. Unknown keys
and unknown enum strings are rejected with a ConfigurationError, so
typos fail loudly instead of being silently ignored.
| Key | Value |
|---|---|
preset | "default", "safe_arithmetic", or "strict" |
arithmetic_nan_handling | "throw_error", "ignore_value", "coerce_to_zero", or "return_null" |
division_by_zero | "return_saturated", "throw_error", "return_null", or "return_infinity" |
loose_equality_errors | bool |
truthy_evaluator | "javascript", "python", or "strict_boolean" |
numeric_coercion | object of bools: empty_string_to_zero, null_to_zero, bool_to_number, reject_non_numeric |
max_recursion_depth | integer >= 1 |
Custom truthiness closures (TruthyEvaluator::Custom) cannot be
expressed in JSON; they are available through the Rust API only.
From Rust:
use datalogic_rs::{Engine, EvaluationConfig};
let config = EvaluationConfig::from_json_str(r#"{
"preset": "strict",
"division_by_zero": "return_null",
"numeric_coercion": {"null_to_zero": true},
"max_recursion_depth": 64
}"#).unwrap();
let engine = Engine::builder().with_config(config).build();
The same JSON object is what you hand to a binding’s engine constructor. For example, to start from the lenient preset but use strict-boolean truthiness:
{
"preset": "safe_arithmetic",
"truthy_evaluator": "strict_boolean",
"max_recursion_depth": 128
}
Combining with Templating Mode
Use both configuration and templating mode (requires
feature = "templating"):
let config = EvaluationConfig::default()
.with_arithmetic_nan_handling(NanHandling::CoerceToZero);
let engine = Engine::builder()
.with_config(config)
.with_templating(true)
.build();
Templating mode carries one option of its own,
with_template_key_escape(prefix), unset by default. Without it a
single-key object is always an operator invocation, so a key that names a
built-in (type, map, if, length, …) or a registered custom
operator can never be emitted as an output field. With it, exactly one
leading prefix is stripped from every template key and an escaped key
is never resolved as an operator:
let engine = Engine::builder()
.with_templating(true)
.with_template_key_escape('$')
.build();
// {"$type": {"var": "x"}} -> {"type": 1}
// {"$$type": 1} -> {"$type": 1}
The prefix is a char rather than a fixed $, so payloads that already
use $ keys can choose ~ or # instead. Full rules, including the
duplicate-key and bare-sigil cases, are in
Structured Objects.
Configuration Examples
Lenient Data Processing
let config = EvaluationConfig::default()
.with_arithmetic_nan_handling(NanHandling::IgnoreValue)
.with_division_by_zero(DivisionByZeroHandling::ReturnNull);
let engine = Engine::builder().with_config(config).build();
let r = engine.eval_str(
r#"{"+": [1, "not a number", null, 2]}"#,
r#"{}"#,
).unwrap();
// "3" (ignores non-numeric values)
Strict Validation
let engine = Engine::builder()
.with_config(EvaluationConfig::strict())
.build();
let result = engine.eval_str(r#"{"+": [1, null]}"#, r#"{}"#);
// Err(Thrown { type: "NaN" }): strict mode does not coerce null, "",
// true/false, or non-numeric strings such as "abc" to a number.
// Numeric strings still parse, so {"+": [1, "2"]} is 3 even under strict().
Custom Business Logic Truthiness
use std::sync::Arc;
use datalogic_rs::datavalue::OwnedDataValue;
let custom_truthy = Arc::new(|value: &OwnedDataValue| -> bool {
match value {
OwnedDataValue::Bool(b) => *b,
OwnedDataValue::Number(_) => value.as_f64().map_or(false, |n| n > 0.0),
OwnedDataValue::String(s) => !s.is_empty(),
_ => false,
}
});
let config = EvaluationConfig::default()
.with_truthy_evaluator(TruthyEvaluator::Custom(custom_truthy));
let engine = Engine::builder().with_config(config).build();
// {"if": [0, "yes", "no"]} ⇒ "no"
// {"if": [-5, "yes", "no"]} ⇒ "no"
// {"if": [1, "yes", "no"]} ⇒ "yes"