json-logic-rb 0.1.5 → 0.2.0.beta1
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.
- checksums.yaml +4 -4
- data/README.md +209 -169
- data/lib/json_logic/engine.rb +22 -21
- data/lib/json_logic/enumerable_operation.rb +27 -5
- data/lib/json_logic/errors/error.rb +29 -0
- data/lib/json_logic/errors/invalid_arguments_error.rb +7 -0
- data/lib/json_logic/errors/logic_error.rb +7 -0
- data/lib/json_logic/errors/nan_error.rb +7 -0
- data/lib/json_logic/ext/array.rb +5 -0
- data/lib/json_logic/operations/add.rb +3 -3
- data/lib/json_logic/operations/all.rb +3 -6
- data/lib/json_logic/operations/and.rb +6 -5
- data/lib/json_logic/operations/bool_cast.rb +2 -3
- data/lib/json_logic/operations/cat.rb +3 -1
- data/lib/json_logic/operations/coalesce.rb +9 -0
- data/lib/json_logic/operations/div.rb +7 -1
- data/lib/json_logic/operations/equal.rb +12 -3
- data/lib/json_logic/operations/exists.rb +14 -0
- data/lib/json_logic/operations/filter.rb +11 -3
- data/lib/json_logic/operations/gt.rb +12 -4
- data/lib/json_logic/operations/gte.rb +12 -4
- data/lib/json_logic/operations/if.rb +2 -0
- data/lib/json_logic/operations/in.rb +2 -0
- data/lib/json_logic/operations/lt.rb +10 -4
- data/lib/json_logic/operations/lte.rb +12 -4
- data/lib/json_logic/operations/map.rb +14 -2
- data/lib/json_logic/operations/max.rb +3 -1
- data/lib/json_logic/operations/merge.rb +4 -3
- data/lib/json_logic/operations/min.rb +3 -1
- data/lib/json_logic/operations/missing.rb +2 -26
- data/lib/json_logic/operations/missing_some.rb +6 -20
- data/lib/json_logic/operations/mod.rb +9 -1
- data/lib/json_logic/operations/mul.rb +4 -1
- data/lib/json_logic/operations/none.rb +4 -5
- data/lib/json_logic/operations/not_equal.rb +12 -3
- data/lib/json_logic/operations/or.rb +5 -1
- data/lib/json_logic/operations/preserve.rb +9 -0
- data/lib/json_logic/operations/reduce.rb +21 -5
- data/lib/json_logic/operations/some.rb +4 -7
- data/lib/json_logic/operations/strict_equal.rb +18 -3
- data/lib/json_logic/operations/strict_not_equal.rb +16 -3
- data/lib/json_logic/operations/sub.rb +8 -1
- data/lib/json_logic/operations/substr.rb +12 -20
- data/lib/json_logic/operations/ternary.rb +1 -7
- data/lib/json_logic/operations/throw.rb +12 -0
- data/lib/json_logic/operations/try.rb +35 -0
- data/lib/json_logic/operations/val.rb +79 -0
- data/lib/json_logic/operations/var.rb +22 -20
- data/lib/json_logic/scope.rb +67 -0
- data/lib/json_logic/semantics.rb +107 -38
- data/lib/json_logic/tree.rb +97 -0
- data/lib/json_logic/version.rb +1 -1
- data/lib/json_logic.rb +12 -0
- data/script/build_tests_json.rb +26 -0
- data/script/compliance.rb +160 -37
- data/spec/tmp/v2/tests.json +16981 -0
- metadata +22 -11
- /data/spec/tmp/{tests.json → v1/tests.json} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe25d64cef43ddb57ad9f94bec45b401ce7ee19b29757108336644342f75f8e1
|
|
4
|
+
data.tar.gz: 21e8ef8421a8768c8be32795c855cc52ec4a4afed79a5875399372417fa06da9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e228a5ed37724d97e54c87e726f772e5f5a749a114a8b02a332bf9131da06c83fedaf0f5d059bd61c94c1c0be73c34ab9fc1cd9d6c6408c958159d951c3a36c9
|
|
7
|
+
data.tar.gz: 805c23fe8af646123f4c5535111d14576ebbf6c1aeeaf6b01247bbc0dd92399e9584ddd869b4522b6a5734c601e701e7736b27b628ccc2cc576f7a26b211fe84
|
data/README.md
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
2
13
|
# json-logic-rb
|
|
3
14
|
|
|
4
|
-
Ruby implementation of [JsonLogic](https://jsonlogic.com/) —
|
|
15
|
+
Ruby implementation of [JsonLogic](https://jsonlogic.com/) — elegant and extensible. Full compliance with both core and community-extended specifications.
|
|
5
16
|
|
|
6
|
-
|
|
17
|
+
[![jsonlogic core][src-core]](https://jsonlogic.com/tests.json) [![jsonlogic community][src-community]](https://github.com/json-logic/compat-tables/tree/main/suites) [](https://github.com/tavrelkate/json-logic-rb/actions/workflows/compliance.yml?query=branch%3Amain) <a href="https://rubygems.org/gems/json-logic-rb"><img alt="rubygems" src="https://img.shields.io/gem/v/json-logic-rb"></a> <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-informational"></a>
|
|
7
18
|
|
|
8
19
|
## Table of Contents
|
|
9
20
|
- [What](#what)
|
|
@@ -12,10 +23,18 @@ Ruby implementation of [JsonLogic](https://jsonlogic.com/) — simple and extens
|
|
|
12
23
|
- [How](#how)
|
|
13
24
|
- [1. Default Operations](#1-default-operations)
|
|
14
25
|
- [2. Lazy Operations](#2-lazy-operations)
|
|
26
|
+
- [Why laziness matters?](#why-laziness-matters)
|
|
27
|
+
- [Compliance and tests](#compliance-and-tests)
|
|
28
|
+
- [Script](#script)
|
|
15
29
|
- [Supported Operations (Built‑in)](#supported-operations-built-in)
|
|
16
30
|
- [Adding Operations](#adding-operations)
|
|
31
|
+
- [Enable JsonLogic Semantics (optional)](#enable-jsonlogic-semantics-optional)
|
|
32
|
+
- [Parameters](#parameters)
|
|
33
|
+
- [Proc / Lambda](#proc--lambda)
|
|
34
|
+
- [Class](#class)
|
|
17
35
|
- [JsonLogic Semantic](#jsonlogic-semantic)
|
|
18
|
-
- [
|
|
36
|
+
- [Comparisons](#comparisons)
|
|
37
|
+
- [Truthiness](#truthiness)
|
|
19
38
|
- [Security](#security)
|
|
20
39
|
- [License](#license)
|
|
21
40
|
- [Authors](#authors)
|
|
@@ -27,9 +46,20 @@ JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby va
|
|
|
27
46
|
|
|
28
47
|
## Install
|
|
29
48
|
|
|
49
|
+
Download the gem locally
|
|
30
50
|
```bash
|
|
31
51
|
gem install json-logic-rb
|
|
32
52
|
```
|
|
53
|
+
If needed – add to your Gemfile
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
gem "json-logic-rb"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then install
|
|
60
|
+
```shell
|
|
61
|
+
bundle install
|
|
62
|
+
```
|
|
33
63
|
|
|
34
64
|
## Quick start
|
|
35
65
|
|
|
@@ -45,42 +75,39 @@ JsonLogic.apply(rule)
|
|
|
45
75
|
With data:
|
|
46
76
|
|
|
47
77
|
```ruby
|
|
48
|
-
|
|
78
|
+
require 'json_logic'
|
|
79
|
+
|
|
80
|
+
rule = { "var" => "user.age" }
|
|
81
|
+
data = { "user" => { "age" => 42 } }
|
|
82
|
+
|
|
83
|
+
JsonLogic.apply(rule, data)
|
|
49
84
|
# => 42
|
|
50
85
|
```
|
|
51
86
|
|
|
52
87
|
## How
|
|
53
88
|
|
|
54
|
-
There are
|
|
89
|
+
There are two types of operations: [Default Operations](#1-default-operations) and [Lazy Operations](#2-lazy-operations).
|
|
55
90
|
|
|
56
91
|
### 1. Default Operations
|
|
57
92
|
|
|
58
|
-
For **Default Operations**, the
|
|
93
|
+
For **Default Operations**, the it evaluates all arguments first and then calls the operator with the resulting Ruby values.
|
|
59
94
|
This matches the reference behavior for arithmetic, comparisons, string operations, and other pure operations that do not control evaluation order.
|
|
60
95
|
|
|
61
96
|
**Groups and references:**
|
|
62
97
|
|
|
63
98
|
- [Numeric operations](https://jsonlogic.com/operations.html#numeric-operations)
|
|
64
99
|
- [String operations](https://jsonlogic.com/operations.html#string-operations)
|
|
65
|
-
- [Array operations](https://jsonlogic.com/operations.html#array-operations) — simple
|
|
100
|
+
- [Array operations](https://jsonlogic.com/operations.html#array-operations) — simple transform like `merge`.
|
|
66
101
|
|
|
67
102
|
### 2. Lazy Operations
|
|
68
103
|
|
|
69
|
-
Some operations must control
|
|
104
|
+
Some operations must control whether and when their arguments are evaluated. They implement branching, short-circuiting, or “apply a rule per item” semantics. For these **Lazy Operations**, the engine passes raw sub-rules and data. The operator then evaluates only the sub-rules it actually needs.
|
|
70
105
|
|
|
71
106
|
**Groups and references:**
|
|
72
107
|
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- **Enumerable operators** — `map`, `filter`, `reduce`, `all`, `none`, `some`
|
|
77
|
-
[Array operations](https://jsonlogic.com/operations.html#array-operations)
|
|
78
|
-
|
|
79
|
-
**How enumerable per-item evaluation works:**
|
|
80
|
-
|
|
81
|
-
1. The first argument is a rule that returns the list of items — evaluated **once** to a Ruby array.
|
|
82
|
-
2. The second argument is the per-item rule — evaluated **for each item** with that item as the **current root**.
|
|
83
|
-
3. For `reduce`, the current item is also available as `"current"`, and the running total as `"accumulator"`.
|
|
108
|
+
- [Logic and Boolean Operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations) — short-circuit/branching like `or`.
|
|
109
|
+
- [Comparison operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations) — equality/ordering like `==`.
|
|
110
|
+
- [Array operations](https://jsonlogic.com/operations.html#array-operations) — enumerable evaluation like `map`.
|
|
84
111
|
|
|
85
112
|
|
|
86
113
|
**Example #1**
|
|
@@ -94,168 +121,214 @@ JsonLogic.apply(
|
|
|
94
121
|
# => [2, 3]
|
|
95
122
|
```
|
|
96
123
|
|
|
97
|
-
**Example #2**
|
|
98
|
-
|
|
99
|
-
```ruby
|
|
100
|
-
# reduce: sum using "current" and "accumulator"
|
|
101
|
-
JsonLogic.apply(
|
|
102
|
-
{ "reduce" => [
|
|
103
|
-
{ "var" => "ints" },
|
|
104
|
-
{ "+" => [ { "var" => "accumulator" }, { "var" => "current" } ] }, 0 ]
|
|
105
|
-
},
|
|
106
|
-
{ "ints" => [1,2,3,4] }
|
|
107
|
-
)
|
|
108
|
-
# => 10.0
|
|
109
|
-
```
|
|
110
|
-
|
|
111
124
|
### Why laziness matters?
|
|
112
125
|
|
|
113
|
-
Lazy operations
|
|
126
|
+
Lazy operations prevent evaluation of branches you do not need.
|
|
114
127
|
|
|
128
|
+
If hypothetically division by zero raises an error, lazy control would avoid it.
|
|
115
129
|
```ruby
|
|
116
|
-
# "or" short-circuits: 1 is truthy, so the right side is NOT evaluated.
|
|
117
|
-
# If the right side were evaluated eagerly, it would attempt 1/0 (error).
|
|
118
130
|
JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] })
|
|
119
131
|
# => 1
|
|
120
132
|
```
|
|
121
133
|
|
|
122
|
-
> In this gem
|
|
134
|
+
> In this gem division returns nil on divide‑by‑zero, but this example show why lazy evaluation is required by the spec: branching and boolean operators must not evaluate unused branches.
|
|
123
135
|
|
|
124
|
-
## Supported Operations (Built‑in)
|
|
125
136
|
|
|
126
137
|
|
|
127
|
-
Below is a list that mirrors the sections on [jsonlogic.com/operations.html](https://jsonlogic.com/operations.html) and shows what this gem (library) implements. From the reference page’s list, everything except `log` is implemented.
|
|
128
|
-
|
|
129
|
-
| Operator | Supported |
|
|
130
|
-
|---|---:|
|
|
131
|
-
| `var` | ✅ |
|
|
132
|
-
| `missing` | ✅ |
|
|
133
|
-
| `missing_some` | ✅ |
|
|
134
|
-
|[Logic and Boolean Operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations])
|
|
135
|
-
| `if` | ✅ |
|
|
136
|
-
| `==` | ✅ |
|
|
137
|
-
| `===` | ✅ |
|
|
138
|
-
| `!=` | ✅ |
|
|
139
|
-
| `!==` | ✅ |
|
|
140
|
-
| `!` | ✅ |
|
|
141
|
-
| `!!` | ✅ |
|
|
142
|
-
| `or` | ✅ |
|
|
143
|
-
| `and` | ✅ |
|
|
144
|
-
| `?:` | ✅ |
|
|
145
|
-
|[Numeric Operations](https://jsonlogic.com/operations.html#numeric-operations)|
|
|
146
|
-
| `map` | ✅ |
|
|
147
|
-
| `reduce` | ✅ |
|
|
148
|
-
| `filter` | ✅ |
|
|
149
|
-
| `all` | ✅ |
|
|
150
|
-
| `none` | ✅ |
|
|
151
|
-
| `some` | ✅ |
|
|
152
|
-
| `merge` | ✅ |
|
|
153
|
-
| `in` | ✅ |
|
|
154
|
-
|[Array Operations](https://jsonlogic.com/operations.html#array-operations)|
|
|
155
|
-
| `map` | ✅ |
|
|
156
|
-
| `reduce` | ✅ |
|
|
157
|
-
| `filter` | ✅ |
|
|
158
|
-
| `all` | ✅ |
|
|
159
|
-
| `none` | ✅ |
|
|
160
|
-
| `some` | ✅ |
|
|
161
|
-
| `merge` | ✅ |
|
|
162
|
-
| `in` | ✅ |
|
|
163
|
-
|[String Operations](https://jsonlogic.com/operations.html#string-operations)|
|
|
164
|
-
| `in` | ✅ |
|
|
165
|
-
| `cat` | ✅ |
|
|
166
|
-
| `substr` | ✅ |
|
|
167
|
-
|Miscellaneous|
|
|
168
|
-
| `log` | 🚫 |
|
|
169
138
|
|
|
170
|
-
## Adding Operations
|
|
171
139
|
|
|
172
|
-
Need a custom operation? It’s straightforward.
|
|
173
140
|
|
|
174
|
-
|
|
141
|
+
## Compliance and tests
|
|
175
142
|
|
|
176
|
-
|
|
143
|
+
The JsonLogic specification provides test suites — concrete inputs with expected outputs that validate the implementation of operations. The specification come in two variants:
|
|
144
|
+
- [![jsonlogic core][src-core]](https://jsonlogic.com/tests.json) — [original JsonLogic website](https://jsonlogic.com/tests.json);
|
|
145
|
+
- [![jsonlogic community][src-community]](https://github.com/json-logic/compat-tables/tree/main/suites) — [extensions built on top of the core](https://github.com/json-logic/compat-tables/tree/main/suites);
|
|
177
146
|
|
|
178
|
-
|
|
179
|
-
JsonLogic.add_operation("times2") { |(value), _| value.to_i * 2 }
|
|
180
|
-
```
|
|
147
|
+
The "extra" exists because "core" hasn't changed in years — and that’s fine, "core" is a solid, finished foundation. Think of it as v1, while "extra" is the v2+ evolution as there are no visible plans to change the original.
|
|
181
148
|
|
|
182
|
-
|
|
149
|
+
### Script
|
|
183
150
|
|
|
184
|
-
|
|
185
|
-
JsonLogic.apply({ "times2" => [21] })
|
|
186
|
-
# => 42
|
|
187
|
-
```
|
|
151
|
+
Download test suite v1:
|
|
188
152
|
|
|
189
|
-
|
|
190
|
-
|
|
153
|
+
```bash
|
|
154
|
+
mkdir -p spec/tmp/v1
|
|
155
|
+
curl -L https://jsonlogic.com/tests.json -o spec/tmp/v1/tests.json
|
|
156
|
+
```
|
|
191
157
|
|
|
158
|
+
Download test suite v2:
|
|
192
159
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
-
|
|
196
|
-
|
|
197
|
-
class JsonLogic::Operations::StartsWith < JsonLogic::Operation; end
|
|
160
|
+
```bash
|
|
161
|
+
mkdir -p spec/tmp/v2
|
|
162
|
+
git clone https://github.com/json-logic/compat-tables.git /tmp/compat-tables
|
|
163
|
+
ruby script/build_tests_json.rb /tmp/compat-tables/suites spec/tmp/v2/tests.json
|
|
198
164
|
```
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
165
|
+
|
|
166
|
+
Run by version:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
ruby script/compliance.rb -v 1
|
|
170
|
+
ruby script/compliance.rb -v 2
|
|
202
171
|
```
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
172
|
+
|
|
173
|
+
Run by file path:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
ruby script/compliance.rb -f spec/tmp/v2/tests.json
|
|
206
177
|
```
|
|
207
|
-
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Supported Operations (Built‑in)
|
|
181
|
+
|
|
182
|
+
Don’t expect JsonLogic to include every specialized operation. It’s intentionally small and not a programming language. It will never do everything.
|
|
183
|
+
|
|
184
|
+
You can add custom operations yourself — check out [§Adding Operations](https://www.google.com/search?q=%23adding-operations)— or consider if the logic can be expressed with what’s already there.
|
|
185
|
+
|
|
186
|
+
If a feature is simple, lightweight, and universally needed — open an issue or discussion.
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
| Operator | Supported | Source |
|
|
190
|
+
|---|---:|---|
|
|
191
|
+
| [Data / Presence](https://jsonlogic.com/operations.html#accessing-data) | | |
|
|
192
|
+
| `var` | ✅ |  |
|
|
193
|
+
| `val` | ✅ |  |
|
|
194
|
+
| `missing` | ✅ |  |
|
|
195
|
+
| `missing_some` | ✅ |  |
|
|
196
|
+
| `exists` | ✅ |  |
|
|
197
|
+
| [Logic and Boolean Operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations) | | |
|
|
198
|
+
| `if` | ✅ |  |
|
|
199
|
+
| `?:` | ✅ |  |
|
|
200
|
+
| `and` | ✅ |  |
|
|
201
|
+
| `or` | ✅ |  |
|
|
202
|
+
| `!` | ✅ |  |
|
|
203
|
+
| `!!` | ✅ |  |
|
|
204
|
+
| [Comparison Operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations) | | |
|
|
205
|
+
| `==` | ✅ |  |
|
|
206
|
+
| `===` | ✅ |  |
|
|
207
|
+
| `!=` | ✅ |  |
|
|
208
|
+
| `!==` | ✅ |  |
|
|
209
|
+
| `>` | ✅ |  |
|
|
210
|
+
| `>=` | ✅ |  |
|
|
211
|
+
| `<` | ✅ |  |
|
|
212
|
+
| `<=` | ✅ |  |
|
|
213
|
+
| [Numeric Operations](https://jsonlogic.com/operations.html#numeric-operations) | | |
|
|
214
|
+
| `+` | ✅ |  |
|
|
215
|
+
| `-` | ✅ |  |
|
|
216
|
+
| `*` | ✅ |  |
|
|
217
|
+
| `/` | ✅ |  |
|
|
218
|
+
| `%` | ✅ |  |
|
|
219
|
+
| `min` | ✅ |  |
|
|
220
|
+
| `max` | ✅ |  |
|
|
221
|
+
| [Array Operations](https://jsonlogic.com/operations.html#array-operations) | | |
|
|
222
|
+
| `map` | ✅ |  |
|
|
223
|
+
| `reduce` | ✅ |  |
|
|
224
|
+
| `filter` | ✅ |  |
|
|
225
|
+
| `all` | ✅ |  |
|
|
226
|
+
| `none` | ✅ |  |
|
|
227
|
+
| `some` | ✅ |  |
|
|
228
|
+
| `merge` | ✅ |  |
|
|
229
|
+
| [String Operations](https://jsonlogic.com/operations.html#string-operations) | | |
|
|
230
|
+
| `in` | ✅ |  |
|
|
231
|
+
| `cat` | ✅ |  |
|
|
232
|
+
| `substr` | ✅ |  |
|
|
233
|
+
| [Community Extensions](https://github.com/json-logic/compat-tables/tree/main/suites) | | |
|
|
234
|
+
| `??` | ✅ |  |
|
|
235
|
+
| `try` | ✅ |  |
|
|
236
|
+
| `throw` | ✅ |  |
|
|
237
|
+
| `preserve` | ✅ |  |
|
|
238
|
+
| Docs-only / Not implemented | | |
|
|
239
|
+
| `log` | 🚫 |  |
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
[src-core]: https://img.shields.io/badge/jsonlogic-core-2ea44f?style=flat-square
|
|
243
|
+
[src-community]: https://img.shields.io/badge/jsonlogic--community-extra-0366d6?style=flat-square
|
|
244
|
+
|
|
245
|
+
## Adding Operations
|
|
246
|
+
|
|
247
|
+
Need a custom Operation? It’s straightforward. Start small with a Proc or Lambda. If needed – promote it to a Class.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
### Enable JsonLogic Semantics (optional)
|
|
252
|
+
Enable semantics to mirror JsonLogic’s comparison and truthiness in Ruby.
|
|
253
|
+
|
|
254
|
+
See [§JsonLogic Semantic](#jsonlogic-semantic) for details.
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
### Parameters
|
|
258
|
+
|
|
259
|
+
Operator function use a consistent call shape:
|
|
260
|
+
|
|
261
|
+
- First parameter: **array of operator arguments** (you can destructure it).
|
|
262
|
+
|
|
263
|
+
- Second parameter: current **data**.
|
|
208
264
|
```ruby
|
|
209
|
-
|
|
265
|
+
->((string, prefix), data) { string.to_s.start_with?(prefix.to_s) }
|
|
210
266
|
```
|
|
211
267
|
|
|
212
|
-
|
|
268
|
+
### Proc / Lambda
|
|
269
|
+
|
|
270
|
+
Pick the Operation type.
|
|
213
271
|
|
|
214
|
-
|
|
215
|
-
Enable semantics to mirror JsonLogic’s comparison/truthiness in Ruby:
|
|
272
|
+
[Default Operation](#1-default-operations) mode passes values.
|
|
216
273
|
|
|
217
274
|
```ruby
|
|
218
|
-
|
|
275
|
+
JsonLogic.add_operation("starts_with") do |(string_value, prefix_value), _data|
|
|
276
|
+
string_value.to_s.start_with?(prefix_value.to_s)
|
|
277
|
+
end
|
|
219
278
|
```
|
|
279
|
+
[Lazy Operation](#2-lazy-operations) mode passes raw rules (you evaluate them):
|
|
220
280
|
|
|
281
|
+
```ruby
|
|
282
|
+
JsonLogic.add_operation("starts_with", lazy: true) do |(string_rule, prefix_rule), data|
|
|
283
|
+
string_value = JsonLogic.apply(string_rule, data)
|
|
284
|
+
prefix_value = JsonLogic.apply(prefix_rule, data)
|
|
285
|
+
string_value.to_s.start_with?(prefix_value.to_s)
|
|
286
|
+
end
|
|
287
|
+
```
|
|
221
288
|
|
|
222
|
-
See [§
|
|
289
|
+
See [§How](https://github.com/tavrelkate/json-logic-rb?tab=readme-ov-file#how) for details.
|
|
223
290
|
|
|
224
|
-
|
|
291
|
+
Use immediately:
|
|
225
292
|
|
|
226
|
-
|
|
293
|
+
```ruby
|
|
294
|
+
JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] })
|
|
295
|
+
```
|
|
227
296
|
|
|
228
|
-
- The first parameter is the **array of operator arguments**.
|
|
229
|
-
- The second is the current **data**.
|
|
230
297
|
|
|
298
|
+
### Class
|
|
231
299
|
|
|
300
|
+
Pick the Operation type. It has the same call shape.
|
|
232
301
|
|
|
233
|
-
|
|
302
|
+
[Default Operation](#1-default-operations) – Inherit `JsonLogic::Operation`.
|
|
234
303
|
|
|
235
304
|
```ruby
|
|
236
305
|
class JsonLogic::Operations::StartsWith < JsonLogic::Operation
|
|
237
306
|
def self.name = "starts_with"
|
|
238
|
-
def call(
|
|
239
|
-
# str, prefix are ALREADY evaluated to Ruby values
|
|
240
|
-
str.to_s.start_with?(prefix.to_s)
|
|
241
|
-
end
|
|
307
|
+
def call(string_value, prefix_value), _data) = string_value.to_s.start_with?(prefix_value.to_s)
|
|
242
308
|
end
|
|
243
309
|
```
|
|
244
310
|
|
|
245
|
-
|
|
311
|
+
[Lazy Operation](#2-lazy-operations) – Inherit `JsonLogic::LazyOperation`.
|
|
312
|
+
|
|
313
|
+
Register explicitly:
|
|
246
314
|
|
|
247
315
|
```ruby
|
|
248
316
|
JsonLogic::Engine.default.registry.register(JsonLogic::Operations::StartsWith)
|
|
249
317
|
```
|
|
250
318
|
|
|
251
|
-
|
|
319
|
+
Now, Class is ready to use.
|
|
252
320
|
|
|
253
|
-
```
|
|
254
|
-
{ "starts_with"
|
|
321
|
+
```ruby
|
|
322
|
+
JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] })
|
|
255
323
|
```
|
|
256
324
|
|
|
257
325
|
|
|
258
326
|
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
259
332
|
## JsonLogic Semantic
|
|
260
333
|
|
|
261
334
|
All supported Operations follow JsonLogic semantics.
|
|
@@ -281,13 +354,14 @@ As JsonLogic primary developed in JavaScript it inherits JavaScript's type coerc
|
|
|
281
354
|
```ruby
|
|
282
355
|
using JsonLogic::Semantics
|
|
283
356
|
|
|
284
|
-
1 >= "1.0"
|
|
357
|
+
1 >= "1.0"
|
|
358
|
+
# => true
|
|
285
359
|
```
|
|
286
360
|
|
|
287
361
|
### Truthiness
|
|
288
362
|
|
|
289
|
-
JsonLogic’s truthiness differs from Ruby’s (see
|
|
290
|
-
In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty arrays are
|
|
363
|
+
JsonLogic’s truthiness differs from Ruby’s (see [Json Logic Website Truthy and Falsy](https://jsonlogic.com/truthy.html)).
|
|
364
|
+
In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty arrays are falsey too.
|
|
291
365
|
|
|
292
366
|
**In Ruby:**
|
|
293
367
|
```ruby
|
|
@@ -295,57 +369,23 @@ In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty
|
|
|
295
369
|
# => true
|
|
296
370
|
```
|
|
297
371
|
|
|
298
|
-
While JsonLogic as was mentioned before has it's own truthiness
|
|
372
|
+
While JsonLogic as was mentioned before has it's own truthiness.
|
|
299
373
|
|
|
300
374
|
**In Ruby (with JsonLogic Semantic):**
|
|
301
375
|
|
|
302
376
|
```ruby
|
|
303
|
-
|
|
377
|
+
using JsonLogic::Semantics
|
|
304
378
|
|
|
305
|
-
|
|
379
|
+
!![]
|
|
306
380
|
# => false
|
|
307
381
|
```
|
|
308
382
|
|
|
309
|
-
|
|
310
|
-
## Compliance and tests
|
|
311
|
-
|
|
312
|
-
Optional: quick self-test
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
```bash
|
|
317
|
-
ruby test/selftest.rb
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Official test suite
|
|
322
|
-
|
|
323
|
-
1. Fetch the official suite
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
```bash
|
|
328
|
-
mkdir -p spec/tmp
|
|
329
|
-
curl -fsSL https://jsonlogic.com/tests.json -o spec/tmp/tests.json
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
2. Run it
|
|
333
|
-
|
|
334
|
-
```bash
|
|
335
|
-
ruby script/compliance.rb spec/tmp/tests.json
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
Expected output
|
|
339
|
-
|
|
340
|
-
```bash
|
|
341
|
-
# => Compliance: X/X passed
|
|
342
|
-
```
|
|
343
|
-
|
|
344
383
|
## Security
|
|
345
384
|
|
|
346
|
-
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
385
|
+
- RULES ARE DATA; NO RUBY EVAL;
|
|
386
|
+
- OPERATIONS ARE PURE; NO IO, NO NETWORK; NO SHELL;
|
|
387
|
+
- RULES HAVE NO WRITE ACCESS TO ANYTHING;
|
|
388
|
+
|
|
349
389
|
|
|
350
390
|
## License
|
|
351
391
|
|
data/lib/json_logic/engine.rb
CHANGED
|
@@ -17,34 +17,35 @@ module JsonLogic
|
|
|
17
17
|
attr_reader :registry
|
|
18
18
|
|
|
19
19
|
def evaluate(rule, data = nil)
|
|
20
|
-
data ||= {}
|
|
21
|
-
|
|
22
20
|
case rule
|
|
23
|
-
when Numeric,
|
|
24
|
-
String,
|
|
25
|
-
TrueClass,
|
|
26
|
-
FalseClass,
|
|
27
|
-
NilClass
|
|
21
|
+
when Numeric, String, TrueClass, FalseClass, NilClass
|
|
28
22
|
rule
|
|
29
23
|
when Array
|
|
30
24
|
rule.map { |r| evaluate(r, data) }
|
|
31
25
|
when Hash
|
|
26
|
+
unless rule.one?
|
|
27
|
+
return rule.transform_values { |value| evaluate(value, data) }
|
|
28
|
+
end
|
|
29
|
+
|
|
32
30
|
name, raw_args = rule.first
|
|
33
31
|
op_class = @registry.fetch(name)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
unless op_class
|
|
33
|
+
return rule.transform_values { |value| evaluate(value, data) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
args = op_class.values_only? ? Array.wrap(evaluate(raw_args, data)) : raw_args
|
|
37
|
+
begin
|
|
38
|
+
result = op_class.new.call(args, data)
|
|
39
|
+
raise JsonLogic::NaNError.new if result.is_a?(Float) && (result.nan? || result.infinite?)
|
|
40
|
+
result
|
|
41
|
+
rescue JsonLogic::Error
|
|
42
|
+
raise
|
|
43
|
+
rescue ArgumentError, IndexError, TypeError, NoMethodError
|
|
44
|
+
raise JsonLogic::InvalidArgumentsError.new
|
|
45
|
+
rescue ZeroDivisionError, FloatDomainError
|
|
46
|
+
raise JsonLogic::NaNError.new
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
raise JsonLogic::Error.new(e.message.to_s)
|
|
48
49
|
end
|
|
49
50
|
else
|
|
50
51
|
rule
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
class JsonLogic::EnumerableOperation < JsonLogic::LazyOperation
|
|
2
|
-
|
|
2
|
+
def call(args, data)
|
|
3
|
+
raise JsonLogic::InvalidArgumentsError.new unless args.is_a?(Array) && args.size >= 2
|
|
4
|
+
raise JsonLogic::InvalidArgumentsError.new if args[0].nil?
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
items, rule = extract_items_and_rule(args, data)
|
|
7
|
+
raise JsonLogic::InvalidArgumentsError.new unless items.is_a?(Array)
|
|
8
|
+
|
|
9
|
+
call_with_values(evaluate_values(items, rule, data))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
def call_with_values(_values)
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extract_items_and_rule(rules, data)
|
|
19
|
+
items_rule, rule = rules
|
|
20
|
+
items = JsonLogic.apply(items_rule, data)
|
|
21
|
+
[items, rule]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def evaluate_values(items, rule, data)
|
|
25
|
+
return [] if rule.nil?
|
|
26
|
+
|
|
27
|
+
items.each_with_index.map do |item, index|
|
|
28
|
+
JsonLogic.apply(rule, JsonLogic::Scope.new(item, data, index))
|
|
29
|
+
end
|
|
8
30
|
end
|
|
9
31
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JsonLogic
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
DEFAULT_MESSAGE = "Error"
|
|
6
|
+
|
|
7
|
+
attr_reader :type, :message, :payload
|
|
8
|
+
|
|
9
|
+
def initialize(message = nil)
|
|
10
|
+
@message = message_or_default(message)
|
|
11
|
+
|
|
12
|
+
super(@message)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def payload
|
|
16
|
+
{ "type" => @message }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def message_or_default(message)
|
|
22
|
+
self.class < JsonLogic::Error || message.empty? ? default_message : message.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def default_message
|
|
26
|
+
self.class::DEFAULT_MESSAGE
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|