jsonatapy 2.2.4__tar.gz → 2.2.5__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.4 → jsonatapy-2.2.5}/CHANGELOG.md +27 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/Cargo.lock +1 -1
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/Cargo.toml +2 -1
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/PKG-INFO +19 -1
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/README.md +18 -0
- jsonatapy-2.2.5/bindings/c/README.md +160 -0
- jsonatapy-2.2.5/bindings/c/examples/smoke.c +133 -0
- jsonatapy-2.2.5/bindings/c/jsonata.h +104 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/pyproject.toml +1 -1
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/__init__.py +5 -0
- jsonatapy-2.2.5/src/capi.rs +543 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/lib.rs +38 -5
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.gitignore +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.gitmodules +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/.gitignore +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/architecture.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/conventions.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/core.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/memory_maintenance.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/suggested_commands.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/task_completion.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/tech_stack.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/project.yml +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/LICENSE +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/benches/evaluator_bench.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/examples/evaluator_demo.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/examples/parser_demo.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/examples/simd_json_bench.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/__main__.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/__init__.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/bindings.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/error_format.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/mcp_server.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/resolve.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/run.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/py.typed +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/ast.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/ast_transform.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/bindings.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/error_format.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/main.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/resolve.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/compiler.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/datetime.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/evaluator.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/functions.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/lazy.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/parser/README.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/parser.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/signature.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/value.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/vm.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/study/cli_fixtures.json +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/study/cli_fixtures_testdata.json +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/study/cli_spec.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/cli_fixtures_test.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/cli_test.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/datetime_picture_suite.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/integration_test.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/parent_and_focus_binding_suite.rs +0 -0
|
@@ -19,6 +19,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
### Security
|
|
21
21
|
|
|
22
|
+
## [2.2.5] - 2026-07-14
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- C API (`capi` cargo feature): use the engine from C, C++, or any language with C interop.
|
|
26
|
+
Eight functions, JSON text in/out (`jsonata_compile`, `jsonata_evaluate`, `jsonata_bind_var`,
|
|
27
|
+
`jsonata_free_expr`, `jsonata_free_string`, `jsonata_last_error_message`,
|
|
28
|
+
`jsonata_last_error_code`, `jsonata_version`), thread-local error slot, and engine panics
|
|
29
|
+
caught at the boundary instead of aborting the host process. Ships with a hand-written
|
|
30
|
+
header (`bindings/c/jsonata.h`), build/link/CMake documentation (`bindings/c/README.md`),
|
|
31
|
+
and a CI-gated smoke test compiled as both C and C++. Build with
|
|
32
|
+
`cargo build --release --features capi`.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Performance regression in v2.2.4 on small/fast expressions (issue #74): the
|
|
36
|
+
`JSONATAPY_FORCE_TREE_WALKER` test toggle read the environment variable on every
|
|
37
|
+
evaluation (both `evaluate()` and `evaluate_json()`), costing ~100-200ns per call —
|
|
38
|
+
10-30% of a sub-microsecond expression. The toggle is now a process-wide atomic seeded
|
|
39
|
+
from the environment once at import (whole-process forcing works unchanged) and flippable
|
|
40
|
+
via a private test hook. Small-expression benchmarks recover 5-16%; the remaining few
|
|
41
|
+
percent vs v2.2.3 on tiny payloads is the documented cost of lazy conversion (which makes
|
|
42
|
+
realistic workloads up to 48% faster, see 2.2.4 notes).
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Benchmark tooling: PR benchmark comments and release regression issues now state their
|
|
46
|
+
comparison baseline explicitly (which commit/release, recorded when, on which runner),
|
|
47
|
+
and the vs-jsonata-js comparison is labeled as such.
|
|
48
|
+
|
|
22
49
|
## [2.2.4] - 2026-07-13
|
|
23
50
|
|
|
24
51
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonata-core"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.5"
|
|
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"
|
|
@@ -63,6 +63,7 @@ simd = ["dep:simd-json"]
|
|
|
63
63
|
python = ["dep:pyo3"]
|
|
64
64
|
cli = ["dep:clap"]
|
|
65
65
|
bench = [] # exposes _bench facade for Criterion benchmarks
|
|
66
|
+
capi = []
|
|
66
67
|
|
|
67
68
|
[dev-dependencies]
|
|
68
69
|
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.5
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -151,6 +151,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
|
|
|
151
151
|
|
|
152
152
|
See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
|
|
153
153
|
|
|
154
|
+
## C / C++ quick start
|
|
155
|
+
|
|
156
|
+
The engine exposes a small C ABI (8 functions, JSON text in/out), usable
|
|
157
|
+
from C, C++, or any language with C interop:
|
|
158
|
+
|
|
159
|
+
```c
|
|
160
|
+
JsonataExpr *expr = jsonata_compile("$sum(items.price)");
|
|
161
|
+
char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
|
|
162
|
+
// result: "5"
|
|
163
|
+
jsonata_free_string(result);
|
|
164
|
+
jsonata_free_expr(expr);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Build with `cargo build --release --features capi` and include
|
|
168
|
+
[`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
|
|
169
|
+
[C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
|
|
170
|
+
CMake), the memory/threading contract, and error handling.
|
|
171
|
+
|
|
154
172
|
---
|
|
155
173
|
|
|
156
174
|
## What is JSONata?
|
|
@@ -102,6 +102,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
|
|
|
102
102
|
|
|
103
103
|
See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
|
|
104
104
|
|
|
105
|
+
## C / C++ quick start
|
|
106
|
+
|
|
107
|
+
The engine exposes a small C ABI (8 functions, JSON text in/out), usable
|
|
108
|
+
from C, C++, or any language with C interop:
|
|
109
|
+
|
|
110
|
+
```c
|
|
111
|
+
JsonataExpr *expr = jsonata_compile("$sum(items.price)");
|
|
112
|
+
char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
|
|
113
|
+
// result: "5"
|
|
114
|
+
jsonata_free_string(result);
|
|
115
|
+
jsonata_free_expr(expr);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Build with `cargo build --release --features capi` and include
|
|
119
|
+
[`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
|
|
120
|
+
[C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
|
|
121
|
+
CMake), the memory/threading contract, and error handling.
|
|
122
|
+
|
|
105
123
|
---
|
|
106
124
|
|
|
107
125
|
## What is JSONata?
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# jsonata-core C API
|
|
2
|
+
|
|
3
|
+
Use [JSONata](https://jsonata.org) — the JSON query and transformation
|
|
4
|
+
language — from C, C++, or any language with C interop, backed by
|
|
5
|
+
[jsonata-core](https://github.com/txjmb/jsonata-core), a high-performance
|
|
6
|
+
Rust implementation with 100% reference-suite compatibility against
|
|
7
|
+
jsonata-js.
|
|
8
|
+
|
|
9
|
+
The API is deliberately small: JSON text in, JSON text out, eight
|
|
10
|
+
functions. See [`jsonata.h`](jsonata.h) for the authoritative contract.
|
|
11
|
+
|
|
12
|
+
```c
|
|
13
|
+
#include "jsonata.h"
|
|
14
|
+
#include <stdio.h>
|
|
15
|
+
|
|
16
|
+
int main(void) {
|
|
17
|
+
JsonataExpr *expr = jsonata_compile("$sum(order.items.(price * qty))");
|
|
18
|
+
if (!expr) {
|
|
19
|
+
char *err = jsonata_last_error_message();
|
|
20
|
+
fprintf(stderr, "compile failed: %s\n", err);
|
|
21
|
+
jsonata_free_string(err);
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
char *result = jsonata_evaluate(expr,
|
|
26
|
+
"{\"order\":{\"items\":[{\"price\":9.99,\"qty\":2},{\"price\":5,\"qty\":1}]}}");
|
|
27
|
+
if (result) {
|
|
28
|
+
printf("total: %s\n", result); /* total: 24.98 */
|
|
29
|
+
jsonata_free_string(result);
|
|
30
|
+
} else {
|
|
31
|
+
char *err = jsonata_last_error_message();
|
|
32
|
+
if (err) { /* error */
|
|
33
|
+
fprintf(stderr, "evaluate failed: %s\n", err);
|
|
34
|
+
jsonata_free_string(err);
|
|
35
|
+
} else { /* JSONata "undefined" — no match */
|
|
36
|
+
printf("no result\n");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
jsonata_free_expr(expr);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Building the shared library
|
|
46
|
+
|
|
47
|
+
Requires a Rust toolchain (rustc 1.70+, [rustup.rs](https://rustup.rs)):
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
git clone https://github.com/txjmb/jsonata-core
|
|
51
|
+
cd jsonata-core
|
|
52
|
+
cargo build --release --features capi
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This produces the shared library in `target/release/`:
|
|
56
|
+
|
|
57
|
+
| Platform | Library |
|
|
58
|
+
|---|---|
|
|
59
|
+
| Linux | `libjsonata_core.so` |
|
|
60
|
+
| macOS | `libjsonata_core.dylib` |
|
|
61
|
+
| Windows | `jsonata_core.dll` (+ `jsonata_core.dll.lib` import lib) |
|
|
62
|
+
|
|
63
|
+
The only header you need is `bindings/c/jsonata.h`.
|
|
64
|
+
|
|
65
|
+
## Compiling and linking
|
|
66
|
+
|
|
67
|
+
**C** (gcc/clang):
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
gcc -Wall -o myapp myapp.c -I/path/to/jsonata-core/bindings/c \
|
|
71
|
+
-L/path/to/jsonata-core/target/release -ljsonata_core
|
|
72
|
+
LD_LIBRARY_PATH=/path/to/jsonata-core/target/release ./myapp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**C++**: the header is C++-safe (`extern "C"` guarded); compile and link
|
|
76
|
+
exactly as above with `g++`/`clang++`.
|
|
77
|
+
|
|
78
|
+
For deployment, install the library somewhere on the loader path (or set
|
|
79
|
+
`rpath`): `-Wl,-rpath,/opt/myapp/lib` at link time avoids
|
|
80
|
+
`LD_LIBRARY_PATH` at run time.
|
|
81
|
+
|
|
82
|
+
**Makefile snippet:**
|
|
83
|
+
|
|
84
|
+
```make
|
|
85
|
+
JSONATA_DIR := /path/to/jsonata-core
|
|
86
|
+
CFLAGS += -I$(JSONATA_DIR)/bindings/c
|
|
87
|
+
LDFLAGS += -L$(JSONATA_DIR)/target/release -Wl,-rpath,$(JSONATA_DIR)/target/release
|
|
88
|
+
LDLIBS += -ljsonata_core
|
|
89
|
+
|
|
90
|
+
myapp: myapp.o
|
|
91
|
+
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**CMake snippet:**
|
|
95
|
+
|
|
96
|
+
```cmake
|
|
97
|
+
set(JSONATA_DIR /path/to/jsonata-core)
|
|
98
|
+
|
|
99
|
+
add_library(jsonata_core SHARED IMPORTED)
|
|
100
|
+
set_target_properties(jsonata_core PROPERTIES
|
|
101
|
+
IMPORTED_LOCATION ${JSONATA_DIR}/target/release/libjsonata_core.so
|
|
102
|
+
INTERFACE_INCLUDE_DIRECTORIES ${JSONATA_DIR}/bindings/c)
|
|
103
|
+
|
|
104
|
+
target_link_libraries(myapp PRIVATE jsonata_core)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
(Optionally wrap the `cargo build` in a CMake `add_custom_command` so the
|
|
108
|
+
Rust library rebuilds with your project.)
|
|
109
|
+
|
|
110
|
+
## API rules (the short version)
|
|
111
|
+
|
|
112
|
+
1. **Ownership:** every `char*` the library returns is yours — free it with
|
|
113
|
+
`jsonata_free_string()`. The single exception is `jsonata_version()`
|
|
114
|
+
(static; never free). Never free library strings with your own
|
|
115
|
+
`free()` — allocators may differ.
|
|
116
|
+
2. **NULL from `jsonata_evaluate` is ambiguous by design:** check
|
|
117
|
+
`jsonata_last_error_message()`. Non-NULL → error. NULL → the JSONata
|
|
118
|
+
result was *undefined* (a normal outcome — e.g. a path that matched
|
|
119
|
+
nothing). This mirrors JSONata semantics, where `undefined` and `null`
|
|
120
|
+
are distinct.
|
|
121
|
+
3. **Error codes:** `jsonata_last_error_code()` returns the JSONata spec
|
|
122
|
+
code (`"T2002"`, `"S0201"`, `"D3030"`, …) when the failure has one,
|
|
123
|
+
NULL otherwise. Error source positions are not exposed (the engine
|
|
124
|
+
does not currently track them).
|
|
125
|
+
4. **Threading:** one `JsonataExpr*` per thread — create, use, and free a
|
|
126
|
+
handle on the same thread. Handles are cheap; compile per thread rather
|
|
127
|
+
than sharing. Different threads with their own handles are fine, and
|
|
128
|
+
the error slot is thread-local.
|
|
129
|
+
5. **Reuse handles:** `jsonata_compile` once, `jsonata_evaluate` many
|
|
130
|
+
times — the expression is JIT-compiled to bytecode on first evaluation
|
|
131
|
+
and reused. Compilation is the expensive step.
|
|
132
|
+
6. **Variables:** `jsonata_bind_var(expr, "x", "[1,2,3]")` binds `$x` for
|
|
133
|
+
all subsequent evaluations on that handle (values are JSON text;
|
|
134
|
+
re-binding replaces).
|
|
135
|
+
7. **Panics:** internal engine panics are caught at the boundary and
|
|
136
|
+
surface as errors prefixed `internal error:` — they will not abort
|
|
137
|
+
your process.
|
|
138
|
+
|
|
139
|
+
## Smoke test
|
|
140
|
+
|
|
141
|
+
[`examples/smoke.c`](examples/smoke.c) exercises the full API surface and
|
|
142
|
+
doubles as usage documentation. Build and run it from the repo root:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
cargo build --release --features capi
|
|
146
|
+
gcc -Wall -Wextra -Werror -o smoke bindings/c/examples/smoke.c \
|
|
147
|
+
-Ltarget/release -ljsonata_core
|
|
148
|
+
LD_LIBRARY_PATH=target/release ./smoke
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
CI compiles and runs it (as both C and C++) on every change to the C ABI.
|
|
152
|
+
|
|
153
|
+
## Related
|
|
154
|
+
|
|
155
|
+
- [jsonatapy](https://pypi.org/project/jsonatapy/) — the Python binding of
|
|
156
|
+
this engine (PyPI, zero runtime dependencies).
|
|
157
|
+
- [`jsonata` CLI](../../docs/) — a jq-style command-line interface to the
|
|
158
|
+
same engine.
|
|
159
|
+
- [jsonata-js](https://github.com/jsonata-js/jsonata) — the reference
|
|
160
|
+
implementation this engine tracks (1682/1682 reference tests passing).
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* smoke.c — compile-and-run smoke test for the jsonata-core C ABI.
|
|
3
|
+
*
|
|
4
|
+
* Exercises every function in jsonata.h against the real library and exits
|
|
5
|
+
* non-zero on the first failure. CI builds and runs this on every PR that
|
|
6
|
+
* touches the C ABI; see bindings/c/README.md for the manual build/run
|
|
7
|
+
* commands.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include <stdio.h>
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
|
|
14
|
+
#include "../jsonata.h"
|
|
15
|
+
|
|
16
|
+
static int failures = 0;
|
|
17
|
+
|
|
18
|
+
#define CHECK(cond, name) \
|
|
19
|
+
do { \
|
|
20
|
+
if (cond) { \
|
|
21
|
+
printf("PASS %s\n", name); \
|
|
22
|
+
} else { \
|
|
23
|
+
printf("FAIL %s (line %d)\n", name, __LINE__); \
|
|
24
|
+
failures++; \
|
|
25
|
+
} \
|
|
26
|
+
} while (0)
|
|
27
|
+
|
|
28
|
+
static char *take_error(void) { return jsonata_last_error_message(); }
|
|
29
|
+
|
|
30
|
+
int main(void) {
|
|
31
|
+
/* version */
|
|
32
|
+
const char *v = jsonata_version();
|
|
33
|
+
CHECK(v != NULL && strlen(v) > 0, "version non-empty");
|
|
34
|
+
|
|
35
|
+
/* simple path */
|
|
36
|
+
{
|
|
37
|
+
JsonataExpr *e = jsonata_compile("user.name");
|
|
38
|
+
CHECK(e != NULL, "compile simple path");
|
|
39
|
+
char *r = jsonata_evaluate(e, "{\"user\":{\"name\":\"Alice\"}}");
|
|
40
|
+
CHECK(r != NULL && strcmp(r, "\"Alice\"") == 0, "evaluate simple path");
|
|
41
|
+
jsonata_free_string(r);
|
|
42
|
+
jsonata_free_expr(e);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* object construction + arithmetic */
|
|
46
|
+
{
|
|
47
|
+
JsonataExpr *e = jsonata_compile("{\"n\": a + b}");
|
|
48
|
+
char *r = jsonata_evaluate(e, "{\"a\":1,\"b\":2}");
|
|
49
|
+
CHECK(r != NULL && strcmp(r, "{\"n\":3}") == 0, "object result");
|
|
50
|
+
jsonata_free_string(r);
|
|
51
|
+
jsonata_free_expr(e);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* undefined result: NULL with EMPTY error slot */
|
|
55
|
+
{
|
|
56
|
+
JsonataExpr *e = jsonata_compile("missing.path");
|
|
57
|
+
char *r = jsonata_evaluate(e, "{\"a\":1}");
|
|
58
|
+
char *err = take_error();
|
|
59
|
+
CHECK(r == NULL && err == NULL, "undefined -> NULL + empty error");
|
|
60
|
+
jsonata_free_string(err);
|
|
61
|
+
jsonata_free_expr(e);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* parse error: NULL handle + message */
|
|
65
|
+
{
|
|
66
|
+
JsonataExpr *e = jsonata_compile("a.b[");
|
|
67
|
+
char *err = take_error();
|
|
68
|
+
CHECK(e == NULL && err != NULL && strlen(err) > 0,
|
|
69
|
+
"parse error -> NULL + message");
|
|
70
|
+
jsonata_free_string(err);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* coded evaluation error: message + spec code */
|
|
74
|
+
{
|
|
75
|
+
JsonataExpr *e = jsonata_compile("$number(b)");
|
|
76
|
+
char *r = jsonata_evaluate(e, "{\"b\":[1]}");
|
|
77
|
+
char *err = take_error();
|
|
78
|
+
char *code = jsonata_last_error_code();
|
|
79
|
+
CHECK(r == NULL && err != NULL, "eval error -> NULL + message");
|
|
80
|
+
CHECK(code != NULL && strcmp(code, "D3030") == 0,
|
|
81
|
+
"eval error -> spec code D3030");
|
|
82
|
+
jsonata_free_string(err);
|
|
83
|
+
jsonata_free_string(code);
|
|
84
|
+
jsonata_free_expr(e);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* variable binding */
|
|
88
|
+
{
|
|
89
|
+
JsonataExpr *e = jsonata_compile("$sum($xs) + n");
|
|
90
|
+
int rc = jsonata_bind_var(e, "$xs", "[1,2,3]");
|
|
91
|
+
CHECK(rc == 0, "bind_var succeeds");
|
|
92
|
+
char *r = jsonata_evaluate(e, "{\"n\":10}");
|
|
93
|
+
CHECK(r != NULL && strcmp(r, "16") == 0, "evaluate with bound var");
|
|
94
|
+
jsonata_free_string(r);
|
|
95
|
+
jsonata_free_expr(e);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* invalid input JSON: NULL + uncoded message */
|
|
99
|
+
{
|
|
100
|
+
JsonataExpr *e = jsonata_compile("a");
|
|
101
|
+
char *r = jsonata_evaluate(e, "{not json");
|
|
102
|
+
char *err = take_error();
|
|
103
|
+
char *code = jsonata_last_error_code();
|
|
104
|
+
CHECK(r == NULL && err != NULL && strstr(err, "invalid input JSON") != NULL,
|
|
105
|
+
"invalid input JSON -> message");
|
|
106
|
+
CHECK(code == NULL, "invalid input JSON -> no spec code");
|
|
107
|
+
jsonata_free_string(err);
|
|
108
|
+
jsonata_free_string(code);
|
|
109
|
+
jsonata_free_expr(e);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* multibyte UTF-8 round trip */
|
|
113
|
+
{
|
|
114
|
+
JsonataExpr *e = jsonata_compile("$uppercase(name)");
|
|
115
|
+
char *r = jsonata_evaluate(e, "{\"name\":\"h\xC3\xA9llo \xE2\x9C\x93\"}");
|
|
116
|
+
CHECK(r != NULL && strcmp(r, "\"H\xC3\x89LLO \xE2\x9C\x93\"") == 0,
|
|
117
|
+
"multibyte UTF-8 round trip");
|
|
118
|
+
jsonata_free_string(r);
|
|
119
|
+
jsonata_free_expr(e);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* NULL tolerance of free functions */
|
|
123
|
+
jsonata_free_expr(NULL);
|
|
124
|
+
jsonata_free_string(NULL);
|
|
125
|
+
CHECK(1, "free(NULL) is a no-op");
|
|
126
|
+
|
|
127
|
+
if (failures == 0) {
|
|
128
|
+
printf("SMOKE OK (jsonata-core %s)\n", v);
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
printf("SMOKE FAILED: %d failure(s)\n", failures);
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* jsonata.h — C API for jsonata-core, a high-performance Rust implementation
|
|
3
|
+
* of the JSONata query and transformation language (https://jsonata.org).
|
|
4
|
+
*
|
|
5
|
+
* Implemented by src/capi.rs (build with `cargo build --release --features
|
|
6
|
+
* capi`, link against the produced libjsonata_core.{so,dylib} /
|
|
7
|
+
* jsonata_core.dll). This header is hand-maintained; CI compiles
|
|
8
|
+
* bindings/c/examples/smoke.c against it to keep the two in sync.
|
|
9
|
+
*
|
|
10
|
+
* ── Contract ────────────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* Strings. All strings crossing the boundary are UTF-8, NUL-terminated.
|
|
13
|
+
* JSON documents and results cross as JSON text; there is no C-visible
|
|
14
|
+
* structured value model. Every char* RETURNED by this library is owned by
|
|
15
|
+
* the caller and must be released with jsonata_free_string() — except
|
|
16
|
+
* jsonata_version(), which returns a static string that must never be
|
|
17
|
+
* freed.
|
|
18
|
+
*
|
|
19
|
+
* Errors. Failures are reported through a thread-local "last error" slot:
|
|
20
|
+
* - jsonata_compile() returns NULL and sets the slot on failure.
|
|
21
|
+
* - jsonata_evaluate() returns NULL in TWO cases, disambiguated by the
|
|
22
|
+
* slot: if jsonata_last_error_message() returns non-NULL it was an
|
|
23
|
+
* error; if it returns NULL the JSONata result was *undefined* (a
|
|
24
|
+
* legitimate result — e.g. a path that matched nothing).
|
|
25
|
+
* - jsonata_bind_var() returns 0 on success, -1 on failure (slot set).
|
|
26
|
+
* Successful calls clear the slot. jsonata_last_error_code() additionally
|
|
27
|
+
* extracts the JSONata specification error code ("T2002", "S0201",
|
|
28
|
+
* "D3030", ...) when the failure has one; errors without a spec code
|
|
29
|
+
* (e.g. invalid input JSON) yield NULL. Error source positions are not
|
|
30
|
+
* available — the engine does not currently track them.
|
|
31
|
+
*
|
|
32
|
+
* Threading. A JsonataExpr* must be created, used, and freed on a single
|
|
33
|
+
* thread (the engine's value model is reference-counted without atomics).
|
|
34
|
+
* Different threads may each use their own handles concurrently; the error
|
|
35
|
+
* slot is thread-local, so per-thread error reporting never races.
|
|
36
|
+
*
|
|
37
|
+
* Panics. Internal engine panics are caught at this boundary and reported
|
|
38
|
+
* as errors (message prefixed "internal error:"); they neither unwind into
|
|
39
|
+
* the caller nor abort the host process.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
#ifndef JSONATA_CORE_H
|
|
43
|
+
#define JSONATA_CORE_H
|
|
44
|
+
|
|
45
|
+
#ifdef __cplusplus
|
|
46
|
+
extern "C" {
|
|
47
|
+
#endif
|
|
48
|
+
|
|
49
|
+
/* An opaque compiled-expression handle. Not thread-safe; see header notes. */
|
|
50
|
+
typedef struct JsonataExpr JsonataExpr;
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* Compile a JSONata expression (UTF-8).
|
|
54
|
+
* Returns a handle, or NULL on failure (check jsonata_last_error_message).
|
|
55
|
+
* Free with jsonata_free_expr().
|
|
56
|
+
*/
|
|
57
|
+
JsonataExpr *jsonata_compile(const char *expr_utf8);
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* Evaluate the compiled expression against a JSON document (UTF-8 JSON
|
|
61
|
+
* text). Returns the result as JSON text (caller frees with
|
|
62
|
+
* jsonata_free_string), or NULL — meaning *undefined* if
|
|
63
|
+
* jsonata_last_error_message() is NULL, otherwise an error.
|
|
64
|
+
* The handle may be reused for any number of evaluations; the expression
|
|
65
|
+
* is compiled to bytecode lazily on first use.
|
|
66
|
+
*/
|
|
67
|
+
char *jsonata_evaluate(JsonataExpr *expr, const char *json_utf8);
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Bind a variable ($name) on the expression from a JSON value (UTF-8 JSON
|
|
71
|
+
* text). Applies to every subsequent jsonata_evaluate() on this handle.
|
|
72
|
+
* A leading '$' in name is accepted and stripped; re-binding an existing
|
|
73
|
+
* name replaces its value. Returns 0 on success, -1 on error (slot set).
|
|
74
|
+
*/
|
|
75
|
+
int jsonata_bind_var(JsonataExpr *expr, const char *name,
|
|
76
|
+
const char *json_value_utf8);
|
|
77
|
+
|
|
78
|
+
/* Free a handle returned by jsonata_compile(). NULL is a no-op. */
|
|
79
|
+
void jsonata_free_expr(JsonataExpr *expr);
|
|
80
|
+
|
|
81
|
+
/* Free a string returned by this library. NULL is a no-op. */
|
|
82
|
+
void jsonata_free_string(char *s);
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* The last error message on this thread, as a fresh copy (caller frees
|
|
86
|
+
* with jsonata_free_string), or NULL if the last call succeeded.
|
|
87
|
+
*/
|
|
88
|
+
char *jsonata_last_error_message(void);
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* The JSONata spec code of the last error on this thread ("T2002", ...),
|
|
92
|
+
* as a fresh copy (caller frees with jsonata_free_string), or NULL when
|
|
93
|
+
* there is no error or the error carries no spec code.
|
|
94
|
+
*/
|
|
95
|
+
char *jsonata_last_error_code(void);
|
|
96
|
+
|
|
97
|
+
/* The jsonata-core version, e.g. "2.2.4". Static string — do NOT free. */
|
|
98
|
+
const char *jsonata_version(void);
|
|
99
|
+
|
|
100
|
+
#ifdef __cplusplus
|
|
101
|
+
} /* extern "C" */
|
|
102
|
+
#endif
|
|
103
|
+
|
|
104
|
+
#endif /* JSONATA_CORE_H */
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jsonatapy"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.5"
|
|
8
8
|
description = "High-performance Python/Rust implementation of JSONata query and transformation language"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "txjmb", email = "txjmb@users.noreply.github.com"}
|
|
@@ -28,6 +28,11 @@ from ._jsonatapy import (
|
|
|
28
28
|
__jsonata_version__,
|
|
29
29
|
__version__,
|
|
30
30
|
)
|
|
31
|
+
|
|
32
|
+
# Private test hooks (not public API): runtime toggle forcing the
|
|
33
|
+
# tree-walking evaluator; seeded from JSONATAPY_FORCE_TREE_WALKER at import.
|
|
34
|
+
from ._jsonatapy import _get_force_tree_walker as _get_force_tree_walker
|
|
35
|
+
from ._jsonatapy import _set_force_tree_walker as _set_force_tree_walker
|
|
31
36
|
from ._jsonatapy import (
|
|
32
37
|
compile as _compile,
|
|
33
38
|
)
|