jsonatapy 2.2.8__tar.gz → 2.2.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.gitignore +1 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/architecture.md +6 -3
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/CHANGELOG.md +238 -0
- jsonatapy-2.2.10/CONTRIBUTORS.md +2 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/Cargo.lock +32 -13
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/Cargo.toml +3 -2
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/PKG-INFO +27 -6
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/README.md +26 -5
- jsonatapy-2.2.10/SECURITY.md +47 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/bindings/c/README.md +7 -1
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/bindings/c/examples/smoke.c +21 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/bindings/c/jsonata.h +13 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/pyproject.toml +2 -2
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/ast_transform.rs +41 -144
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/error_format.rs +1 -9
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/main.rs +14 -6
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/builtins.rs +188 -256
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/capi.rs +77 -37
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/compiler.rs +12 -17
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/datetime.rs +2 -20
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/evaluator.rs +663 -1287
- jsonatapy-2.2.10/src/expression.rs +180 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/functions.rs +128 -290
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/lazy.rs +93 -28
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/lib.rs +56 -85
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/signature.rs +45 -136
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/value.rs +42 -29
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/vm.rs +33 -71
- jsonatapy-2.2.10/tests/lambda_closure_suite.rs +586 -0
- jsonatapy-2.2.8/src/parser/README.md +0 -321
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.gitmodules +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/.gitignore +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/conventions.md +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/core.md +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/memory_maintenance.md +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/suggested_commands.md +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/task_completion.md +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/tech_stack.md +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/project.yml +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/LICENSE +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/benches/evaluator_bench.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/evaluator_demo.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/host_functions.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/parser_demo.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/simd_json_bench.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/__init__.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/__main__.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/__init__.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/bindings.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/error_format.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/mcp_server.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/resolve.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/run.py +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/py.typed +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/ast.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/bindings.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/resolve.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/parser.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/study/cli_fixtures.json +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/study/cli_fixtures_testdata.json +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/study/cli_spec.md +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/cli_fixtures_test.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/cli_test.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/datetime_picture_suite.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/builtin_arity.json +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/builtin_differential.json +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/builtin_signatures.json +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/fastpath_differential.json +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/fastpath_known_divergences.json +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/host_functions_test.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/integration_test.rs +0 -0
- {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/parent_and_focus_binding_suite.rs +0 -0
|
@@ -45,9 +45,12 @@ site (grep `unwrap_or(JValue::Null)` / `JValue::Null =>` near field-access code)
|
|
|
45
45
|
### Compilation pipeline (bytecode fast path)
|
|
46
46
|
`evaluator::try_compile_expr(ast)` → `CompiledExpr` (IR) → `compiler::BytecodeCompiler::compile`
|
|
47
47
|
→ `BytecodeProgram` (flat `Vec<Instr>`) → `peephole()` folds (`PushData+GetField` →
|
|
48
|
-
`GetDataField`, `GetVar+GetField` → `GetVarField`, elides `Not+Not`) →
|
|
49
|
-
`
|
|
50
|
-
|
|
48
|
+
`GetDataField`, `GetVar+GetField` → `GetVarField`, elides `Not+Not`) →
|
|
49
|
+
`vm::Vm::with_options(bc, options).run(...)`.
|
|
50
|
+
`JsonataExpression` (lib.rs) caches `bytecode: OnceCell<Option<BytecodeProgram>>`; all 5 evaluate
|
|
51
|
+
entrypoints funnel through `run_eval`, which tries the VM first and falls back to the tree-walker.
|
|
52
|
+
The `vm`/`compiler` modules exist only under the `python`, `capi`, or `bench` features (cfg-gated
|
|
53
|
+
in lib.rs) — a default-feature build, including the Rust CLI, always uses the tree-walker.
|
|
51
54
|
|
|
52
55
|
Key correctness rules baked into the compiler/VM (violate these and specific test-suite groups
|
|
53
56
|
regress):
|
|
@@ -19,6 +19,244 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
### Security
|
|
21
21
|
|
|
22
|
+
## [2.2.10] - 2026-09-24
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `SECURITY.md` security policy: security fixes ship as a new patch release of the latest
|
|
26
|
+
minor line (currently 2.2.x) with no backports, and vulnerabilities are reported privately
|
|
27
|
+
through the repository's Security tab rather than in public issues. (#171)
|
|
28
|
+
- Benchmark tables gain a per-row **jsonata-core (pure Rust)** column, measured by a new
|
|
29
|
+
`benchmarks/rust` harness that evaluates this project's engine as a Rust library with no
|
|
30
|
+
Python boundary. The column previously labelled `jsonatapy (rust)` is relabelled
|
|
31
|
+
`jsonatapy (json I/O)`: it was always the `evaluate_json` path of the same engine, not a
|
|
32
|
+
separate Rust implementation. (#162)
|
|
33
|
+
- The README's performance section now shows an auto-generated chart of the "Realistic
|
|
34
|
+
Workload" benchmarks, regenerated alongside `docs/performance.md` on each release. (#165)
|
|
35
|
+
- `CONTRIBUTORS.md`. (#167)
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Updated Rust dependencies: `simd-json` 0.18.0 → 0.18.1 (#163), `indexmap` 2.14.0 → 2.14.2
|
|
39
|
+
(#168), and `clap` 4.6.6 → 4.6.7, used only by the `jsonata` CLI (#169).
|
|
40
|
+
- CodeQL now runs through GitHub's CodeQL default setup, covering Rust, Python, C/C++,
|
|
41
|
+
JavaScript/TypeScript and GitHub Actions, instead of CodeQL jobs in the Security Scanning
|
|
42
|
+
workflow. (#170, #171)
|
|
43
|
+
- The Security Scanning workflow now audits the resolved `uv.lock` with `pip-audit`, not just
|
|
44
|
+
the packages installed on the runner, and fails on a vulnerable locked dependency. It also
|
|
45
|
+
lists `cargo-audit` warnings such as yanked crates, without failing the build on them. (#170)
|
|
46
|
+
|
|
47
|
+
### Deprecated
|
|
48
|
+
|
|
49
|
+
### Removed
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- The default docs URL (`/stable/`) served the previous release's performance page: the
|
|
53
|
+
release job deployed docs from the release tag, which never contains that release's own
|
|
54
|
+
benchmark numbers. Release docs now take `docs/performance.md` from `main` after the
|
|
55
|
+
benchmark job records it, and the docs workflow can republish the released docs line
|
|
56
|
+
without a new release. (#164)
|
|
57
|
+
- Resolved all 18 open CodeQL code-quality alerts in the benchmark and test scripts, and
|
|
58
|
+
implemented the previously stubbed `test_compile_invalid_expression` test. (#170)
|
|
59
|
+
|
|
60
|
+
### Security
|
|
61
|
+
- `Cargo.lock`: `chacha20` 0.10.1 → 0.10.2. 0.10.1 was yanked; it reaches the build through
|
|
62
|
+
`rand` and was compiled into the 2.2.9 Python wheels and CLI binaries. (#170)
|
|
63
|
+
- `uv.lock`: `anyio` 4.14.1 → 4.15.1 (CVE-2026-63374, CVE-2026-64847, CVE-2026-63349),
|
|
64
|
+
`cryptography` 49.0.0 → 50.0.1 (PYSEC-2026-3552), and `mkdocs-material` 9.7.6 → 9.7.7
|
|
65
|
+
(PYSEC-2026-3864). These are development and documentation dependencies only; the published
|
|
66
|
+
`jsonatapy` package has no Python dependencies and was not affected. (#170)
|
|
67
|
+
|
|
68
|
+
## [2.2.9] "Perform-ata" - 2026-08-31
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- Lambda values now carry their closure directly — `JValue::Lambda(Rc<StoredLambda>)`
|
|
72
|
+
instead of a `lambda_id` name tag resolved through a per-scope side table (#157).
|
|
73
|
+
The `Rc` provides the closure's lifetime, so the side table (`Scope.lambdas`),
|
|
74
|
+
the escape-analysis GC (`extract_lambda_ids`/`collect_lambda_ids` walking every
|
|
75
|
+
block/lambda result, `pop_scope_preserving_lambdas`) and the dual-map handling in
|
|
76
|
+
`unbind`/`clear_current_scope` are gone, along with the O(result size) walk on
|
|
77
|
+
every scope pop. Recursion is late-bound letrec: a closure defined as
|
|
78
|
+
`$f := function ...` records its own name and binds it to itself at each
|
|
79
|
+
invocation, so no `Rc` cycles exist and closures cannot leak (guarded by a
|
|
80
|
+
live-count test). Free-variable capture remains a by-value snapshot at
|
|
81
|
+
definition time, now applied uniformly to lambda-valued variables too.
|
|
82
|
+
Behavioral fixes (all matching jsonata-js, pinned in
|
|
83
|
+
`tests/lambda_closure_suite.rs`): a transform bound to a variable is callable
|
|
84
|
+
and pipeable (`$t := |...|...|; $t(x)`, `x ~> $t`); a partial application
|
|
85
|
+
escaping the scope that defined its target function works; an alias
|
|
86
|
+
(`$g := $f`) survives `$f` being rebound; calling a name rebound to a
|
|
87
|
+
non-function raises T1006 instead of silently invoking the stale lambda; a
|
|
88
|
+
closure captured inside another escaping closure's environment no longer
|
|
89
|
+
dangles. Function equality is now closure identity rather than id-string
|
|
90
|
+
equality. Remaining known divergences from jsonata-js's live-frame capture
|
|
91
|
+
are documented in the same suite's `divergent_from_reference` module.
|
|
92
|
+
- Partial application is a typed representation instead of a stringly-typed
|
|
93
|
+
protocol: `StoredLambda` carries an optional `PartialApplication { target,
|
|
94
|
+
bound_args, placeholder_positions, total_args }`, replacing the
|
|
95
|
+
`"__partial_call:name:bool:n"` marker-string body that was parsed at every
|
|
96
|
+
invocation and the `__bound_arg_N` / `__placeholder_positions` /
|
|
97
|
+
`__total_args` / `__partial_target` values smuggled through `captured_env`.
|
|
98
|
+
A user-defined target is a captured closure (`PartialTarget::Lambda`);
|
|
99
|
+
builtins/host functions stay name-resolved at call time
|
|
100
|
+
(`PartialTarget::Named`), preserving call-time shadowing. Partials no longer
|
|
101
|
+
snapshot the entire environment at creation (the old
|
|
102
|
+
`capture_current_environment()` call was only ever a smuggling container),
|
|
103
|
+
and one behavior corner now matches jsonata-js: rebinding a user function
|
|
104
|
+
after partially applying it no longer changes what the partial calls
|
|
105
|
+
(pinned in `tests/lambda_closure_suite.rs` with eight other partial pins).
|
|
106
|
+
|
|
107
|
+
### Added
|
|
108
|
+
- `jsonata_set_limits` on the C ABI: wall-clock timeout (D1012), max AST recursion
|
|
109
|
+
depth (D1011), and max sequence length (D2015) — the same three guardrails the
|
|
110
|
+
Python bindings expose. C embedders previously had no way to bound a runaway
|
|
111
|
+
expression at all (the cleanup review's finding); 0 = unlimited, resettable per
|
|
112
|
+
handle. Covered by the C/C++ smoke tests and a Rust-side capi test.
|
|
113
|
+
- `jsonata_core::Expression` — a compile-once API for Rust callers that runs
|
|
114
|
+
compilable expressions on the bytecode VM, exactly like the Python and C
|
|
115
|
+
bindings always have. Until now the VM was unreachable from the pure-Rust
|
|
116
|
+
surface: `Evaluator` always tree-walks, so Rust (and CLI) callers silently
|
|
117
|
+
got the slower path. `Expression::compile(src)?.evaluate(&data)` lowers to
|
|
118
|
+
bytecode lazily and falls back to the tree-walker for non-compilable
|
|
119
|
+
expressions; bindings and host functions still go through `Evaluator`. The
|
|
120
|
+
dispatch now lives in one place (`expression::run_compiled`), shared by the
|
|
121
|
+
Rust API, the Python bindings' `run_eval`, and the C ABI (which previously
|
|
122
|
+
carried a hand-copied duplicate of it), and the CLI uses it when no `--arg`
|
|
123
|
+
bindings are given. The `vm`/`compiler` modules are unconditionally compiled
|
|
124
|
+
again (the cfg gates added earlier in this cycle are gone — the default
|
|
125
|
+
build now has a real consumer).
|
|
126
|
+
|
|
127
|
+
### Changed
|
|
128
|
+
- Python→Rust data conversion — the cost that dominates `evaluate(dict)` on array-heavy
|
|
129
|
+
inputs — is faster on two fronts. `lazy::convert` now dispatches on the exact type object
|
|
130
|
+
and iterates lists via borrowed references instead of pyo3's instance-check chain
|
|
131
|
+
(subclasses still take the original chain, so semantics, error types included, are
|
|
132
|
+
unchanged; pinned by `tests/python/test_convert_fastpath.py`). And the published wheels
|
|
133
|
+
now use mimalloc as the global allocator (new opt-in `mimalloc` cargo feature, enabled in
|
|
134
|
+
`pyproject.toml`), roughly halving the per-list/per-object allocation cost that JValue's
|
|
135
|
+
Rc-per-container representation makes the conversion floor. Measured together on the
|
|
136
|
+
benchmark suite's shapes: "Nested Array Access" (`data[1][1][1][1]`, the one row where
|
|
137
|
+
jsonata-js was still ahead) drops ~35% (12.0µs → 7.8µs on the dev container), the four
|
|
138
|
+
`lazy_check.py` gate rows drop 10–17%, and string-heavy conversion roughly halves. The
|
|
139
|
+
structural fix for the nested-array row — lazy list views — remains deferred (see the
|
|
140
|
+
2026-07-12 lazy-views spec's Limitations).
|
|
141
|
+
- The `vm` and `compiler` modules (the bytecode pipeline) are now cfg-gated on the features
|
|
142
|
+
that actually consume them (`python`, `capi`, `bench`) — they were compiled but unreachable
|
|
143
|
+
in a default-feature build, producing 12 permanent dead-code warnings that CI's
|
|
144
|
+
`--all-features` clippy never saw. A default `cargo build` is now warning-clean, and both
|
|
145
|
+
modules carry a header note naming their consumers. List and dict *subclasses* now take the
|
|
146
|
+
boundary converter's fast path too (`PyList_Check`/`PyDict_Check`; iteration was already
|
|
147
|
+
C-level for them, so behavior is unchanged), which let the duplicate slow-path container
|
|
148
|
+
loops be deleted.
|
|
149
|
+
|
|
150
|
+
### Changed (evaluator internals)
|
|
151
|
+
- The vestigial `*_is_explicit_null` plumbing is gone: since the null/undefined split
|
|
152
|
+
(#32) a runtime `JValue::Null` is always an explicit null, yet the flags were still
|
|
153
|
+
computed and threaded through ~20 signatures — nine arithmetic/comparison wrapper
|
|
154
|
+
methods, the `CompiledExpr::ExplicitNull` variant (now `Literal(Null)`), a
|
|
155
|
+
`PushExplicitNull` instruction, and per-instruction bool pairs on the VM's
|
|
156
|
+
arithmetic/comparison opcodes — with both terminal consumers ignoring them (~200
|
|
157
|
+
lines removed). Arithmetic and ordered comparison now have exactly one
|
|
158
|
+
implementation each, shared by the tree-walker, the compiled path, and the VM; as
|
|
159
|
+
part of that, the compiled/VM paths' T2010/T2009 error texts now match the
|
|
160
|
+
tree-walker's richer messages ("Cannot compare X and Y", operator symbol in T2009)
|
|
161
|
+
instead of the generic "Type mismatch in comparison" — same codes, better text, and
|
|
162
|
+
the two engines no longer disagree. `Evaluator::is_truthy` likewise now delegates to
|
|
163
|
+
the shared `compiled_is_truthy` instead of maintaining a twin (#111 had to be fixed
|
|
164
|
+
in both).
|
|
165
|
+
- More single-definition consolidation: the two verbatim 45-line signature-error
|
|
166
|
+
translation blocks (direct vs TCO lambda invocation) are one `coerce_lambda_args`
|
|
167
|
+
helper; the five copies of jsonata-js's `hofFuncArgs` argument shaping are two
|
|
168
|
+
helpers (`hof_array_call_args` for $map/$filter/$single, `hof_object_call_args` for
|
|
169
|
+
$sift/$each); the six copy-pasted encoding builtin arms share `unary_string_encoding`.
|
|
170
|
+
`signature.rs` dropped its dead `return_type` field and test-only constructors, and
|
|
171
|
+
a builtin signature that fails to parse now panics at cache build instead of leaving
|
|
172
|
+
that builtin silently unvalidated. `ast_transform.rs`'s 157-line module header — a
|
|
173
|
+
stale task journal whose ~20 line references were all wrong — is a 30-line statement
|
|
174
|
+
of the actual recursion/drop invariants, and `push_ast_node_children`'s leaf arm
|
|
175
|
+
lists every variant explicitly instead of `_ => {}`, so a future `AstNode` variant
|
|
176
|
+
cannot silently opt out of the stack-overflow-on-drop guard.
|
|
177
|
+
- Five hand-maintained parallel lists of builtin names (the pure set, the arity table,
|
|
178
|
+
the compilable set, the signature table, and a test's own literal copy) are one
|
|
179
|
+
sorted `builtins::BUILTINS` spec table; `is_pure_builtin`, `is_compilable_builtin`,
|
|
180
|
+
`builtin_arity`, and `signature::builtin_signature` all derive from it, and the
|
|
181
|
+
jsonata-js fixture drift tests still police the data. Adding a builtin is now one
|
|
182
|
+
row plus its dispatch arm. Membership and values are unchanged (the table was
|
|
183
|
+
generated mechanically from the lists it replaces).
|
|
184
|
+
- The bare-`AstNode::Name` arm of `evaluate_internal_impl` — a private field-access
|
|
185
|
+
copy that had drifted (kept nulls, never flattened, no tuple or lazy-dict handling)
|
|
186
|
+
and that instrumentation shows no expression in any suite reaches — now delegates
|
|
187
|
+
to `compiled_field_step`, the semantics owner for a single field step.
|
|
188
|
+
|
|
189
|
+
### Deprecated
|
|
190
|
+
|
|
191
|
+
### Removed
|
|
192
|
+
- Repository hygiene: the checked-in `rustup-init.exe` (12.9 MB Windows installer that
|
|
193
|
+
dominated clone size and permanently tripped the file-size lint), the orphaned root
|
|
194
|
+
`node_modules/jsonata/` copy (~830 KB; nothing referenced it — the benchmark harness
|
|
195
|
+
installs its own under `benchmarks/javascript/`, and the reference implementation is the
|
|
196
|
+
`tests/jsonata-js` submodule), and the stale `src/parser/README.md` (every structural
|
|
197
|
+
claim in it — line counts, ranges, even the crate name in its example — was wrong).
|
|
198
|
+
- Dead code that survived the 2.2.8 builtins consolidation: `functions::numeric::{max,
|
|
199
|
+
min, average}` and `functions::object::{keys, lookup}` had no callers and encoded
|
|
200
|
+
pre-#109 semantics the engine no longer has (`Null` instead of `Undefined` on empty
|
|
201
|
+
input, no nested-array recursion) — a trap for anyone fixing a `$max`/`$lookup` bug in
|
|
202
|
+
the wrong place. Their tests, which pinned the wrong behavior, went with them. Also
|
|
203
|
+
removed the unused `datetime::parse_iso8601`. Technically these were `pub` items of the
|
|
204
|
+
crate, so strict semver would call this a breaking change; they were never used by the
|
|
205
|
+
engine itself.
|
|
206
|
+
|
|
207
|
+
### Fixed
|
|
208
|
+
- Two families of tree-walker drift found by unifying the remaining field-extraction
|
|
209
|
+
loops onto `compiled_field_step` (each pinned on BOTH engines in
|
|
210
|
+
`tests/python/test_field_step_and_arraygroup.py` with jsonata-js reference outputs):
|
|
211
|
+
the single-step-Name fast path skipped null-valued fields and returned Null on empty
|
|
212
|
+
(`p` over `[{"p":null},{"p":2}]` was `2` on the tree-walker, `[null,2]` on the VM and
|
|
213
|
+
in the reference — the engines disagreed); and the `.[...]` array-group constructor
|
|
214
|
+
kept undefined elements as null (`foo.blah.[baz]` gave `[[..],[null],[null]]` instead
|
|
215
|
+
of `[[..],[],[]]`, masked downstream by that null-skip), while its empty results
|
|
216
|
+
conflated "constructed empty array over a single value" (kept: `{"a":1}.[b]` is `[]`)
|
|
217
|
+
with "mapped over an empty array" (undefined: `emptyarr.[b]`). Both tree-walker
|
|
218
|
+
Name-step loops (the fast path and the general step loop's inner loop) now delegate
|
|
219
|
+
to `compiled_field_step`, deleting ~100 lines of drifted copies; the reference
|
|
220
|
+
suite's `array-constructor/case013`/`case014` now pass by the correct route. The
|
|
221
|
+
fast path's tuple branch (a third copy, with the same null-skip drift) is deleted
|
|
222
|
+
outright — tuple streams fall through to the general step loop's single tuple
|
|
223
|
+
implementation, and `$#$i.p` over `[{"p":null},{"p":2}]` is now `[null,2]` on both
|
|
224
|
+
engines, matching the reference.
|
|
225
|
+
- Errors carry their JSONata spec code at the front of the message again: the
|
|
226
|
+
`FunctionError` wrapping used to bury codes behind prose prefixes ("Runtime
|
|
227
|
+
error: D3030: Cannot convert 'x' to number"), so the C ABI, the Rust CLI, and
|
|
228
|
+
the Python CLI each grew a different classifier and disagreed on whether the
|
|
229
|
+
same error was coded. The inner message now passes through unwrapped
|
|
230
|
+
("D3030: ..."), `EvaluatorError::code()` / `evaluator::error_code_prefix` is
|
|
231
|
+
the one definition of "coded error" (prefix-anchored), and the C ABI and both
|
|
232
|
+
CLIs classify with it — the same failure now presents identically in every
|
|
233
|
+
binary. The C ABI's mid-string code scan is retired (it existed only to see
|
|
234
|
+
through the now-removed prose prefixes).
|
|
235
|
+
- Number stringification now matches jsonata-js exactly (verified against the pinned
|
|
236
|
+
reference with a 307-case randomized differential over scalars, containers,
|
|
237
|
+
prettified output, and `&` concatenation). Three divergences fixed: non-integers
|
|
238
|
+
were truncated to 14 significant digits instead of rounded to 15 (`$string(1/3)`
|
|
239
|
+
was `0.33333333333333`, now `0.333333333333333` — the old formatter counted the
|
|
240
|
+
`0` of a leading `0.` as significant); integer-valued floats above 2^53 printed
|
|
241
|
+
their exact i64 digits instead of the float's shortest round-trip decimal; and
|
|
242
|
+
numbers nested in containers went through serde with different rules again.
|
|
243
|
+
`$string` now stringifies through its own writer implementing the reference's
|
|
244
|
+
replacer (functions → `""`, non-integers → `toPrecision(15)`), and one
|
|
245
|
+
`value::js_number_to_string` defines JS number printing (plain digits in
|
|
246
|
+
[1e-6, 1e21), exponential with `+` outside, `-0` as `0`) for `$string`, concat,
|
|
247
|
+
`Display`, and `$join`. Pinned by `tests/python/test_number_stringification.py`.
|
|
248
|
+
- `$var.field` over an array containing nested-array elements now recurses into
|
|
249
|
+
them like jsonata-js's `lookup`: `($v := [[{"p":1}],{"p":2}]; $v.p)` is `[1,2]`
|
|
250
|
+
(previously `2` — the tree-walker's two-step fast path hand-rolled its mapping
|
|
251
|
+
loop and silently skipped non-object elements; it now delegates to the shared
|
|
252
|
+
field step). Found while consolidating the seven divergent field-extraction
|
|
253
|
+
loops the cleanup review flagged; pinned by `tests/python/test_var_field_nested.py`.
|
|
254
|
+
- Regex literals now honor the `m` (multiline) flag in `$match` and the `~>`
|
|
255
|
+
chain-pipe, matching `$split`/`$replace` and jsonata-js — the two paths
|
|
256
|
+
previously translated only `i` (see `tests/python/test_regex_flags.py`).
|
|
257
|
+
|
|
258
|
+
### Security
|
|
259
|
+
|
|
22
260
|
## [2.2.8] "Conform-ata" - 2026-08-25
|
|
23
261
|
|
|
24
262
|
Primarily a conformance release, with documentation work on the Rust crate alongside it. Most
|
|
@@ -189,9 +189,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
|
189
189
|
|
|
190
190
|
[[package]]
|
|
191
191
|
name = "chacha20"
|
|
192
|
-
version = "0.10.
|
|
192
|
+
version = "0.10.2"
|
|
193
193
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
194
|
-
checksum = "
|
|
194
|
+
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
|
195
195
|
dependencies = [
|
|
196
196
|
"cfg-if",
|
|
197
197
|
"cpufeatures",
|
|
@@ -240,9 +240,9 @@ dependencies = [
|
|
|
240
240
|
|
|
241
241
|
[[package]]
|
|
242
242
|
name = "clap"
|
|
243
|
-
version = "4.6.
|
|
243
|
+
version = "4.6.7"
|
|
244
244
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
245
|
-
checksum = "
|
|
245
|
+
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
|
246
246
|
dependencies = [
|
|
247
247
|
"clap_builder",
|
|
248
248
|
"clap_derive",
|
|
@@ -250,9 +250,9 @@ dependencies = [
|
|
|
250
250
|
|
|
251
251
|
[[package]]
|
|
252
252
|
name = "clap_builder"
|
|
253
|
-
version = "4.6.
|
|
253
|
+
version = "4.6.7"
|
|
254
254
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
255
|
-
checksum = "
|
|
255
|
+
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
|
256
256
|
dependencies = [
|
|
257
257
|
"anstream",
|
|
258
258
|
"anstyle",
|
|
@@ -262,9 +262,9 @@ dependencies = [
|
|
|
262
262
|
|
|
263
263
|
[[package]]
|
|
264
264
|
name = "clap_derive"
|
|
265
|
-
version = "4.6.
|
|
265
|
+
version = "4.6.7"
|
|
266
266
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
267
|
-
checksum = "
|
|
267
|
+
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
|
|
268
268
|
dependencies = [
|
|
269
269
|
"heck",
|
|
270
270
|
"proc-macro2",
|
|
@@ -544,9 +544,9 @@ dependencies = [
|
|
|
544
544
|
|
|
545
545
|
[[package]]
|
|
546
546
|
name = "indexmap"
|
|
547
|
-
version = "2.14.
|
|
547
|
+
version = "2.14.2"
|
|
548
548
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
549
|
-
checksum = "
|
|
549
|
+
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
|
550
550
|
dependencies = [
|
|
551
551
|
"equivalent",
|
|
552
552
|
"hashbrown 0.17.1",
|
|
@@ -586,7 +586,7 @@ dependencies = [
|
|
|
586
586
|
|
|
587
587
|
[[package]]
|
|
588
588
|
name = "jsonata-core"
|
|
589
|
-
version = "2.2.
|
|
589
|
+
version = "2.2.10"
|
|
590
590
|
dependencies = [
|
|
591
591
|
"assert_cmd",
|
|
592
592
|
"base64",
|
|
@@ -594,6 +594,7 @@ dependencies = [
|
|
|
594
594
|
"clap",
|
|
595
595
|
"criterion",
|
|
596
596
|
"indexmap",
|
|
597
|
+
"mimalloc",
|
|
597
598
|
"num-traits",
|
|
598
599
|
"percent-encoding",
|
|
599
600
|
"predicates",
|
|
@@ -614,6 +615,15 @@ version = "0.2.186"
|
|
|
614
615
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
615
616
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
616
617
|
|
|
618
|
+
[[package]]
|
|
619
|
+
name = "libmimalloc-sys"
|
|
620
|
+
version = "0.1.49"
|
|
621
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
622
|
+
checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
|
|
623
|
+
dependencies = [
|
|
624
|
+
"cc",
|
|
625
|
+
]
|
|
626
|
+
|
|
617
627
|
[[package]]
|
|
618
628
|
name = "linux-raw-sys"
|
|
619
629
|
version = "0.12.1"
|
|
@@ -632,6 +642,15 @@ version = "2.8.3"
|
|
|
632
642
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
633
643
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
634
644
|
|
|
645
|
+
[[package]]
|
|
646
|
+
name = "mimalloc"
|
|
647
|
+
version = "0.1.52"
|
|
648
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
649
|
+
checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
|
|
650
|
+
dependencies = [
|
|
651
|
+
"libmimalloc-sys",
|
|
652
|
+
]
|
|
653
|
+
|
|
635
654
|
[[package]]
|
|
636
655
|
name = "normalize-line-endings"
|
|
637
656
|
version = "0.3.0"
|
|
@@ -1113,9 +1132,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
|
1113
1132
|
|
|
1114
1133
|
[[package]]
|
|
1115
1134
|
name = "simd-json"
|
|
1116
|
-
version = "0.18.
|
|
1135
|
+
version = "0.18.1"
|
|
1117
1136
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1118
|
-
checksum = "
|
|
1137
|
+
checksum = "6ebe141eb4a23ecc4d5de93fa5b139ac65dc07c38037a2fe9a8fb7c9d36bd832"
|
|
1119
1138
|
dependencies = [
|
|
1120
1139
|
"halfbrown",
|
|
1121
1140
|
"ref-cast",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonata-core"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.10"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["txjmb <txjmb@users.noreply.github.com>"]
|
|
6
6
|
description = "High-performance Rust implementation of JSONata query and transformation language"
|
|
@@ -29,7 +29,6 @@ exclude = [
|
|
|
29
29
|
"CLAUDE.MD",
|
|
30
30
|
"pyproject.toml",
|
|
31
31
|
"uv.lock",
|
|
32
|
-
"rustup-init.exe",
|
|
33
32
|
]
|
|
34
33
|
|
|
35
34
|
[lib]
|
|
@@ -56,6 +55,7 @@ rand = "0.10"
|
|
|
56
55
|
stacker = "0.1"
|
|
57
56
|
simd-json = { version = "0.18", optional = true }
|
|
58
57
|
clap = { version = "4.6", features = ["derive"], optional = true }
|
|
58
|
+
mimalloc = { version = "0.1.52", optional = true }
|
|
59
59
|
|
|
60
60
|
[features]
|
|
61
61
|
default = ["simd"]
|
|
@@ -64,6 +64,7 @@ python = ["dep:pyo3"]
|
|
|
64
64
|
cli = ["dep:clap"]
|
|
65
65
|
bench = [] # exposes _bench facade for Criterion benchmarks
|
|
66
66
|
capi = []
|
|
67
|
+
mimalloc = ["dep:mimalloc"]
|
|
67
68
|
|
|
68
69
|
[dev-dependencies]
|
|
69
70
|
criterion = "0.8"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonatapy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.10
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -66,7 +66,26 @@ Project-URL: Repository, https://github.com/txjmb/jsonata-core
|
|
|
66
66
|
[](https://github.com/jsonata-js/jsonata/tree/master/test/test-suite)
|
|
67
67
|
[](https://opensource.org/licenses/MIT)
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
<!-- BEGIN generated performance chart -->
|
|
70
|
+
<!-- Regenerated from the latest benchmark run by
|
|
71
|
+
benchmarks/python/generate_readme_chart.py. Do not hand-edit this block
|
|
72
|
+
or the SVGs it points at; edit the generator instead. -->
|
|
73
|
+
<a href="https://txjmb.github.io/jsonata-core/stable/performance/">
|
|
74
|
+
<picture>
|
|
75
|
+
<source media="(prefers-color-scheme: dark)"
|
|
76
|
+
srcset="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-dark.svg">
|
|
77
|
+
<img width="880"
|
|
78
|
+
alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 7.5x faster than the jsonata-js reference on the geometric mean of 5 e-commerce queries, and the pure-Rust jsonata-core engine is faster still."
|
|
79
|
+
src="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-light.svg">
|
|
80
|
+
</picture>
|
|
81
|
+
</a>
|
|
82
|
+
|
|
83
|
+
*Click the chart for the full category-by-category tables, including jsonata-python
|
|
84
|
+
and jsonata-rs, which are left off the chart because at 68 ms–9 s they would
|
|
85
|
+
flatten everything else into a sliver.*
|
|
86
|
+
<!-- END generated performance chart -->
|
|
87
|
+
|
|
88
|
+
> ### ⚠️ Upgrading to 2.2.8 "Conform-ata"+ — please test before you deploy
|
|
70
89
|
>
|
|
71
90
|
> 2.2.8 is primarily a **conformance** release: it corrects a number of places where this
|
|
72
91
|
> library disagreed with the jsonata-js reference. These issues were found during routine quality checks and documentation improvements. The test harness was inadvertently missing some edge cases. Many of those corrections **change the
|
|
@@ -97,7 +116,7 @@ JSONata implementation in Python, so the goal was to port JSONata to Rust (with
|
|
|
97
116
|
for Python) and see how fast it could go. The answer: faster than V8 for most expression
|
|
98
117
|
workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi. There is also a command-line binary and Python command-line available (works great with uvx) for use in scripting. The Python library is also usable in a command-line fashion, and a C-compatible library is available for those who want to easily use jsonata in C/C++.
|
|
99
118
|
|
|
100
|
-
Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
|
|
119
|
+
Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, expressive and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
|
|
101
120
|
|
|
102
121
|
Release versions will follow the reference jsonata-js project major and minor release numbers, but not necessarily patches. This will make it easier for adopters of this library to understand each release's JSONata API compatibility. As an example, 2.2.8 should be compliant with 2.2.x jsonata-js tests, but may have fixes specific to this library. If a patch release for jsonata-js is relevant for this project, it will be included in a patch release that may or may not follow the patch numbers of the upstream project. "Major" versions of this library that don't include any updates to follow jsonata-js conformance will be "named" in release notes/changelog.
|
|
103
122
|
|
|
@@ -137,10 +156,10 @@ let result = Evaluator::new().evaluate(&ast, &data)?;
|
|
|
137
156
|
```toml
|
|
138
157
|
# Cargo.toml
|
|
139
158
|
[dependencies]
|
|
140
|
-
jsonata-core = "2.2.
|
|
159
|
+
jsonata-core = "2.2.9" # pure Rust, no Python dependency
|
|
141
160
|
|
|
142
161
|
# Optional: disable SIMD for constrained targets
|
|
143
|
-
jsonata-core = { version = "2.2.
|
|
162
|
+
jsonata-core = { version = "2.2.9", default-features = false }
|
|
144
163
|
```
|
|
145
164
|
|
|
146
165
|
---
|
|
@@ -225,7 +244,9 @@ See [official JSONata docs](https://docs.jsonata.org/) for the full language ref
|
|
|
225
244
|
|
|
226
245
|
## Performance
|
|
227
246
|
|
|
228
|
-
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
`jsonata-core` passes **1686/1686** JSONata reference tests and is the fastest JSONata
|
|
229
250
|
implementation available in either Rust or Python:
|
|
230
251
|
|
|
231
252
|
- **~6x faster on average** than the JavaScript reference implementation (V8), across all
|
|
@@ -17,7 +17,26 @@
|
|
|
17
17
|
[](https://github.com/jsonata-js/jsonata/tree/master/test/test-suite)
|
|
18
18
|
[](https://opensource.org/licenses/MIT)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
<!-- BEGIN generated performance chart -->
|
|
21
|
+
<!-- Regenerated from the latest benchmark run by
|
|
22
|
+
benchmarks/python/generate_readme_chart.py. Do not hand-edit this block
|
|
23
|
+
or the SVGs it points at; edit the generator instead. -->
|
|
24
|
+
<a href="https://txjmb.github.io/jsonata-core/stable/performance/">
|
|
25
|
+
<picture>
|
|
26
|
+
<source media="(prefers-color-scheme: dark)"
|
|
27
|
+
srcset="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-dark.svg">
|
|
28
|
+
<img width="880"
|
|
29
|
+
alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 7.5x faster than the jsonata-js reference on the geometric mean of 5 e-commerce queries, and the pure-Rust jsonata-core engine is faster still."
|
|
30
|
+
src="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-light.svg">
|
|
31
|
+
</picture>
|
|
32
|
+
</a>
|
|
33
|
+
|
|
34
|
+
*Click the chart for the full category-by-category tables, including jsonata-python
|
|
35
|
+
and jsonata-rs, which are left off the chart because at 68 ms–9 s they would
|
|
36
|
+
flatten everything else into a sliver.*
|
|
37
|
+
<!-- END generated performance chart -->
|
|
38
|
+
|
|
39
|
+
> ### ⚠️ Upgrading to 2.2.8 "Conform-ata"+ — please test before you deploy
|
|
21
40
|
>
|
|
22
41
|
> 2.2.8 is primarily a **conformance** release: it corrects a number of places where this
|
|
23
42
|
> library disagreed with the jsonata-js reference. These issues were found during routine quality checks and documentation improvements. The test harness was inadvertently missing some edge cases. Many of those corrections **change the
|
|
@@ -48,7 +67,7 @@ JSONata implementation in Python, so the goal was to port JSONata to Rust (with
|
|
|
48
67
|
for Python) and see how fast it could go. The answer: faster than V8 for most expression
|
|
49
68
|
workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi. There is also a command-line binary and Python command-line available (works great with uvx) for use in scripting. The Python library is also usable in a command-line fashion, and a C-compatible library is available for those who want to easily use jsonata in C/C++.
|
|
50
69
|
|
|
51
|
-
Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
|
|
70
|
+
Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, expressive and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
|
|
52
71
|
|
|
53
72
|
Release versions will follow the reference jsonata-js project major and minor release numbers, but not necessarily patches. This will make it easier for adopters of this library to understand each release's JSONata API compatibility. As an example, 2.2.8 should be compliant with 2.2.x jsonata-js tests, but may have fixes specific to this library. If a patch release for jsonata-js is relevant for this project, it will be included in a patch release that may or may not follow the patch numbers of the upstream project. "Major" versions of this library that don't include any updates to follow jsonata-js conformance will be "named" in release notes/changelog.
|
|
54
73
|
|
|
@@ -88,10 +107,10 @@ let result = Evaluator::new().evaluate(&ast, &data)?;
|
|
|
88
107
|
```toml
|
|
89
108
|
# Cargo.toml
|
|
90
109
|
[dependencies]
|
|
91
|
-
jsonata-core = "2.2.
|
|
110
|
+
jsonata-core = "2.2.9" # pure Rust, no Python dependency
|
|
92
111
|
|
|
93
112
|
# Optional: disable SIMD for constrained targets
|
|
94
|
-
jsonata-core = { version = "2.2.
|
|
113
|
+
jsonata-core = { version = "2.2.9", default-features = false }
|
|
95
114
|
```
|
|
96
115
|
|
|
97
116
|
---
|
|
@@ -176,7 +195,9 @@ See [official JSONata docs](https://docs.jsonata.org/) for the full language ref
|
|
|
176
195
|
|
|
177
196
|
## Performance
|
|
178
197
|
|
|
179
|
-
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
`jsonata-core` passes **1686/1686** JSONata reference tests and is the fastest JSONata
|
|
180
201
|
implementation available in either Rust or Python:
|
|
181
202
|
|
|
182
203
|
- **~6x faster on average** than the JavaScript reference implementation (V8), across all
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
This policy covers the **jsonata-core** Rust crate, the **jsonatapy** Python
|
|
4
|
+
package, the `jsonata` command-line tool, and the C API in `bindings/c`, all
|
|
5
|
+
published from this repository.
|
|
6
|
+
|
|
7
|
+
## Supported Versions
|
|
8
|
+
|
|
9
|
+
Security fixes are released as a new patch version of the latest minor line,
|
|
10
|
+
published to [crates.io](https://crates.io/crates/jsonata-core) and
|
|
11
|
+
[PyPI](https://pypi.org/project/jsonatapy/). Older lines do not receive
|
|
12
|
+
backports; upgrade to the latest release to pick up a fix.
|
|
13
|
+
|
|
14
|
+
| Version | Supported |
|
|
15
|
+
| ------- | --------- |
|
|
16
|
+
| 2.2.x (latest patch) | ✅ |
|
|
17
|
+
| < 2.2 | ❌ |
|
|
18
|
+
|
|
19
|
+
## Reporting a Vulnerability
|
|
20
|
+
|
|
21
|
+
**Please do not report security vulnerabilities through public GitHub issues,
|
|
22
|
+
discussions, or pull requests.**
|
|
23
|
+
|
|
24
|
+
Report them privately instead: go to the repository's
|
|
25
|
+
[**Security** tab](https://github.com/txjmb/jsonata-core/security) and choose
|
|
26
|
+
**Report a vulnerability**. Only the maintainers can see the report.
|
|
27
|
+
|
|
28
|
+
Please include:
|
|
29
|
+
|
|
30
|
+
- The affected component (Rust crate, Python package, CLI, or C API) and version
|
|
31
|
+
- A minimal reproduction: the JSONata expression and input data that trigger
|
|
32
|
+
the issue
|
|
33
|
+
- What happens (crash, hang, memory growth, wrong result) and what you expected
|
|
34
|
+
- Your assessment of the impact, if you have one
|
|
35
|
+
|
|
36
|
+
What to expect:
|
|
37
|
+
|
|
38
|
+
- We aim to acknowledge your report within 5 business days, and will keep you
|
|
39
|
+
updated as we investigate.
|
|
40
|
+
- If the report is accepted, we fix it in a new patch release, then publish a
|
|
41
|
+
GitHub Security Advisory and request a CVE where appropriate. We coordinate
|
|
42
|
+
the disclosure date with you and credit you in the advisory unless you prefer
|
|
43
|
+
to stay anonymous.
|
|
44
|
+
- If the report is declined, we explain why.
|
|
45
|
+
|
|
46
|
+
Please give us a reasonable chance to release a fix before disclosing the issue
|
|
47
|
+
publicly.
|
|
@@ -141,7 +141,13 @@ Rust library rebuilds with your project.)
|
|
|
141
141
|
deliberately (e.g. a frozen `$now`, or a disabled `$eval`). `user_data`
|
|
142
142
|
must outlive the handle. Registering a host function routes evaluation
|
|
143
143
|
through the tree-walker, like variable bindings.
|
|
144
|
-
8. **
|
|
144
|
+
8. **Guardrails:** `jsonata_set_limits(expr, timeout_ms, max_stack_depth,
|
|
145
|
+
max_sequence_length)` bounds wall-clock time (D1012), AST recursion
|
|
146
|
+
depth (D1011), and query-result sequence length (D2015) for all
|
|
147
|
+
subsequent evaluations on that handle — the same three limits the
|
|
148
|
+
Python bindings expose. 0 means "unlimited" (the default); call again
|
|
149
|
+
to change or reset.
|
|
150
|
+
9. **Panics:** internal engine panics are caught at the boundary and
|
|
145
151
|
surface as errors prefixed `internal error:` — they will not abort
|
|
146
152
|
your process.
|
|
147
153
|
|
|
@@ -112,6 +112,27 @@ int main(void) {
|
|
|
112
112
|
jsonata_free_expr(e);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/* evaluation guardrails */
|
|
116
|
+
{
|
|
117
|
+
JsonataExpr *e = jsonata_compile("$map([1..100000], function($x) { $x })");
|
|
118
|
+
CHECK(jsonata_set_limits(NULL, 0, 0, 0) == -1, "set_limits NULL handle -> -1");
|
|
119
|
+
CHECK(jsonata_set_limits(e, 0, 0, 10) == 0, "set_limits succeeds");
|
|
120
|
+
char *r = jsonata_evaluate(e, "{}");
|
|
121
|
+
char *err = take_error();
|
|
122
|
+
char *code = jsonata_last_error_code();
|
|
123
|
+
CHECK(r == NULL && err != NULL, "sequence limit -> NULL + message");
|
|
124
|
+
CHECK(code != NULL && strcmp(code, "D2015") == 0,
|
|
125
|
+
"sequence limit -> spec code D2015");
|
|
126
|
+
jsonata_free_string(err);
|
|
127
|
+
jsonata_free_string(code);
|
|
128
|
+
/* lifting the limit makes the same handle work again */
|
|
129
|
+
CHECK(jsonata_set_limits(e, 0, 0, 0) == 0, "set_limits reset succeeds");
|
|
130
|
+
char *r2 = jsonata_evaluate(e, "{}");
|
|
131
|
+
CHECK(r2 != NULL, "evaluate succeeds after limit reset");
|
|
132
|
+
jsonata_free_string(r2);
|
|
133
|
+
jsonata_free_expr(e);
|
|
134
|
+
}
|
|
135
|
+
|
|
115
136
|
/* variable binding */
|
|
116
137
|
{
|
|
117
138
|
JsonataExpr *e = jsonata_compile("$sum($xs) + n");
|