Migration Guide
This page is a quick conceptual overview. The full v4 → v5 cookbook —
every renamed call, every cargo-feature swap, every error-handling
update — lives in MIGRATION.md
at the repo root. Treat that file as authoritative.
v4 to v5 Migration
v5 is a hard cliff
v5 has no compatibility shim. The pre-release compat feature and
the LegacyApi trait are gone — there is no transitional crate
configuration. Plan a single cutover: update Cargo.toml, run a
find-and-replace pass, and re-run your test suite.
The on-the-wire JSONLogic spec is unchanged — your rules and data still look the same. Everything that changes is on the Rust side.
What changed at a glance
- Type renames.
DataLogic→Engine,CompiledLogic→Logic,Operator→CustomOperator,ArenaValue→DataValue,ArenaContextStack→operator::EvalContext.Evaluatoris gone (args arrive pre-evaluated). - Method renames. Every
evaluate_*is noweval_*.evaluate_str→eval_str,evaluate_borrowed→eval_borrowed. Theserde_json::Value-shaped variants (evaluate_json_value,evaluate_owned,evaluate_ref, …) collapse into one typed entry point:engine.eval_into::<T, _, _>(rule, data)(ordatalogic_rs::eval_into::<T, _, _>(...)at the module level), gated onfeature = "serde_json". - Builder construction.
DataLogic::with_config(c)/with_preserve_structure()/with_config_and_structure(c, s)all collapse intoEngine::builder()with.with_config(c)and.with_templating(s)setters. - Compilation accepts more shapes.
engine.compile(rule)takes anyIntoLogic:&str,&String,&OwnedDataValue,OwnedDataValue,&serde_json::Value(gated onserde_json). - Module-level helpers for one-shot calls.
datalogic_rs::eval,datalogic_rs::eval_str,datalogic_rs::eval_into, anddatalogic_rs::compileuse a shared default engine — no need to construct anEnginefor the simple cases. - Sessions are explicit. Reusable arenas live on
Session(engine.session()); the session never auto-resets, so callers callsession.reset()between batches. - Trace surface is a session.
engine.trace().eval_str(rule, data)returns aTracedRun<R>withresult: Result<R, Error>plusstepsandexpression_tree. Available onfeature = "trace". The oldTracedResulttype is gone — successful and failed runs share the sameTracedRun<R>shape. - Custom operators take pre-evaluated args. Implementations get
args: &[&'a DataValue<'a>], a&mut EvalContext<'_, 'a>, and a&'a bumpalo::Bump; they return&'a DataValue<'a>. - Operator registration is builder-only.
Engineis immutable afterbuild(). Register every custom operator on theEngineBuilderbefore calling.build(). - Error is structured.
Erroris a struct withkind,operator(),node_ids(),tag(), plus a stable JSON wire format. Construct viaError::invalid_arguments(...),Error::type_error(...),Error::custom_message(...),Error::wrap(...). preserveoperator removed. Literal scalars and arrays already pass through inline; templated objects belong in templating mode (rebuild withEngine::builder().with_templating(true).build(), requiresfeature = "templating").- Edition 2024 +
#![forbid(unsafe_code)].
Feature-flag rename
The pre-release compat feature is gone. The replacement is
purpose-named:
| v4 / pre-release feature | v5 feature | What it enables |
|---|---|---|
compat (mixed interop + shims) | serde_json | &serde_json::Value interop and the typed eval_into::<T> paths |
preserve | templating | Templating mode and Engine::builder().with_templating(true) |
trace | trace | engine.trace() (transitively enables serde_json) |
Quick before/after sketch
#![allow(unused)]
fn main() {
// v4
use datalogic_rs::DataLogic;
let mut engine = DataLogic::with_config(my_config);
engine.add_operator("double".to_string(), Box::new(MyOp));
let compiled = engine.compile(&rule_value)?;
let result: Value = engine.evaluate_owned(&compiled, data)?;
}
#![allow(unused)]
fn main() {
// v5
use datalogic_rs::Engine;
let engine = Engine::builder()
.with_config(my_config)
.add_operator("double", MyOp)
.build();
let compiled = engine.compile(&rule_value)?; // accepts &Value via `serde_json`
let result = engine.eval(&compiled, &data_value); // OwnedDataValue
let result_str = engine.eval_str(&compiled, data_str)?; // String (JSON)
let v: serde_json::Value = engine.eval_into(&compiled, &data_value)?; // typed
}
Custom operators
#![allow(unused)]
fn main() {
// v5 (final)
use datalogic_rs::{CustomOperator, DataValue, Engine, Result};
use datalogic_rs::operator::EvalContext;
use bumpalo::Bump;
struct DoubleOperator;
impl CustomOperator for DoubleOperator {
fn evaluate<'a>(
&self,
args: &[&'a DataValue<'a>],
_ctx: &mut EvalContext<'_, 'a>,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
// args are already evaluated — no Evaluator call.
let n = args.first()
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
Ok(arena.alloc(DataValue::from_f64(n * 2.0)))
}
}
let engine = Engine::builder()
.add_operator("double", DoubleOperator)
.build();
}
Where to look next
- The repo-root
MIGRATION.mdhas the per-call cookbook. api/reference.mdcovers every v5 method.getting-started/quick-start.mdwalks through the new module-level helpers.
v3 to v4 Migration
If you’re stepping from v3 directly to v5, the v3 → v4 jump is a
historical layer that no longer matches anything in this codebase. Read
the v4-to-v5 section above and the repo-root
MIGRATION.md; everything you need to land on v5 is covered there.
Getting Help
If you encounter issues during migration:
- Check the API Reference
- Review the examples
- Open an issue on GitHub