json-logic-rb 0.1.1.beta3 → 0.1.2

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +202 -166
  3. data/lib/json_logic/operation.rb +1 -1
  4. data/lib/json_logic/operations/add.rb +1 -1
  5. data/lib/json_logic/operations/all.rb +1 -1
  6. data/lib/json_logic/operations/and.rb +1 -1
  7. data/lib/json_logic/operations/bool_cast.rb +1 -1
  8. data/lib/json_logic/operations/cat.rb +1 -1
  9. data/lib/json_logic/operations/div.rb +1 -1
  10. data/lib/json_logic/operations/equal.rb +1 -1
  11. data/lib/json_logic/operations/filter.rb +1 -1
  12. data/lib/json_logic/operations/gt.rb +1 -1
  13. data/lib/json_logic/operations/gte.rb +1 -1
  14. data/lib/json_logic/operations/if.rb +1 -1
  15. data/lib/json_logic/operations/in.rb +1 -1
  16. data/lib/json_logic/operations/lt.rb +1 -1
  17. data/lib/json_logic/operations/lte.rb +1 -1
  18. data/lib/json_logic/operations/map.rb +1 -1
  19. data/lib/json_logic/operations/max.rb +1 -1
  20. data/lib/json_logic/operations/merge.rb +1 -1
  21. data/lib/json_logic/operations/min.rb +1 -1
  22. data/lib/json_logic/operations/missing.rb +1 -1
  23. data/lib/json_logic/operations/missing_some.rb +1 -1
  24. data/lib/json_logic/operations/mod.rb +1 -1
  25. data/lib/json_logic/operations/mul.rb +1 -1
  26. data/lib/json_logic/operations/none.rb +1 -1
  27. data/lib/json_logic/operations/not.rb +1 -1
  28. data/lib/json_logic/operations/not_equal.rb +1 -1
  29. data/lib/json_logic/operations/or.rb +1 -1
  30. data/lib/json_logic/operations/reduce.rb +1 -1
  31. data/lib/json_logic/operations/some.rb +1 -1
  32. data/lib/json_logic/operations/strict_equal.rb +3 -3
  33. data/lib/json_logic/operations/strict_not_equal.rb +1 -1
  34. data/lib/json_logic/operations/sub.rb +1 -1
  35. data/lib/json_logic/operations/substr.rb +1 -1
  36. data/lib/json_logic/operations/ternary.rb +1 -1
  37. data/lib/json_logic/operations/var.rb +1 -1
  38. data/lib/json_logic/registry.rb +1 -1
  39. data/lib/json_logic/version.rb +1 -1
  40. data/lib/json_logic.rb +13 -3
  41. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49483cded1408b390afa16c3b95a5c6ef8bc54052f3295ec8c9bc6ff80ae1c48
4
- data.tar.gz: 3e98022c63ae218d3410696ab664bc71da00e282c04c8c63535d03c819bfe831
3
+ metadata.gz: d726f7f3bf1bd6cb21653d3764b60bb78d1d1e098f0f0f42cc821dbf3f5da9b1
4
+ data.tar.gz: 3a3816024e8d4ae47bced0a6a04abd92bdab15bd8f134c616d2f8333759f8f27
5
5
  SHA512:
6
- metadata.gz: c0451ee54551025d9c56d0b92e3211f2135fdcf248714915278f4f9087f8bf862db657f5467902d6b26ea12497d5055c4390fac1dc668f0cc34f660a9db044f1
7
- data.tar.gz: ce9bea35b50f696dc41b57dd100ea686104094c850114d7b0a2658900dfa56c1a9b34fa5e2a9672cccc25f6f3d6a22db456de8c023aa46d3f099a22a3d5e45b8
6
+ metadata.gz: 9a83a929d74d39baf96a5721206e1d4935c5b432876d734a3b6d83e912224b60a5e1e2051242522cdfa25014c11138f3b7dbff9c13c7c01e26d6be11127da679
7
+ data.tar.gz: 4dd0db70c83bf78025d2c44c96f4e1cdbb1a3f9884b7f3e8f5031e1393d7cc3b2575979894393a37e15f804dfdec0e6dc5b3c1e7822d94681de61bbdedba88c2
data/README.md CHANGED
@@ -1,39 +1,30 @@
1
- # json-logic-rb
2
1
 
3
- Ruby implementation of [JsonLogic](https://jsonlogic.com/) — simple and extensible. Ships with a compliance runner for the official test suite.
2
+ # json-logic-rb
4
3
 
5
- <a href="#"><img alt="build" src="https://img.shields.io/github/actions/workflow/status/your-org/json-logic-rb/ci.yml?branch=main"> <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>
4
+ Ruby implementation of [JsonLogic](https://jsonlogic.com/) simple and extensible. Ships with a compliance runner for the official test suite.
6
5
 
6
+ <a href="#"><img alt="build" src="https://img.shields.io/github/actions/workflow/status/your-org/json-logic-rb/ci-complience?branch=main"> <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
7
 
8
8
  ## Table of Contents
9
9
  - [What](#what)
10
10
  - [Install](#install)
11
11
  - [Quick start](#quick-start)
12
12
  - [How](#how)
13
- - [1) Operators (default)](#1-operators-default)
14
- - [2) Lazy operators](#2-lazy--operators)
15
- - [Supported Built-in Operations](#supported-built-in-operations)
16
- - [Accessing Data](#accessing-data)
17
- - [Logic and Boolean Operations](#logic-and-boolean-operations)
18
- - [Numeric Operations](#numeric-operations)
19
- - [Array Operations](#array-operations)
20
- - [String Operations](#string-operations)
21
- - [Miscellaneous](#miscellaneous)
22
- - [Extending (add your own operator)](#extending-add-your-own-operator)
23
- - [Public Interface](#public-interface)
24
- - [Compliance & tests](#compliance-and-tests)
13
+ - [1. Default Operations](#1-default-operations)
14
+ - [2. Lazy Operations](#2-lazy-operations)
15
+ - [Supported Operations (Builtin)](#supported-operations-built-in)
16
+ - [Adding Operations](#adding-operations)
17
+ - [JsonLogic Semantic](#jsonlogic-semantic)
18
+ - [Compliance and tests](#compliance-and-tests)
25
19
  - [Security](#security)
26
20
  - [License](#license)
27
- - [Contributing](#contributing)
28
-
21
+ - [Authors](#authors)
29
22
 
23
+ ---
30
24
 
31
25
  ## What
32
26
  JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value.
33
27
 
34
-
35
-
36
-
37
28
  ## Install
38
29
 
39
30
  ```bash
@@ -58,54 +49,42 @@ JsonLogic.apply({ "var" => "user.age" }, { "user" => { "age" => 42 } })
58
49
  # => 42
59
50
  ```
60
51
 
61
-
62
52
  ## How
63
53
 
64
- There are **two kinds of operators** in this implementation. This mapping follows the official behavior described on [jsonlogic.com](https://jsonlogic.com).
65
-
66
- ### 1) Operators (default)
67
-
68
- For **operators**, the engine **evaluates all arguments first** and then calls the operator with the **resulting Ruby values**.
69
- This matches the reference behavior for arithmetic, comparisons, string operations, and other pure operators that do not control evaluation order.
54
+ There are **two types of operations** in this implementation: Default Operations and Lazy Operations.
70
55
 
71
- **Official docs:**
72
-
73
- - Numeric Operations — [https://jsonlogic.com/operations.html#numeric-operations](https://jsonlogic.com/operations.html#numeric-operations)
74
-
75
- - String Operations — [https://jsonlogic.com/operations.html#string-operations](https://jsonlogic.com/operations.html#string-operations)
76
-
77
- - Array Operations (simple transforms like `merge`, membership `in`) — [https://jsonlogic.com/operations.html#array-operations](https://jsonlogic.com/operations.html#array-operations)
56
+ ### 1. Default Operations
78
57
 
58
+ For **Default Operations**, the engine **evaluates all arguments first** and then calls the operator with the **resulting Ruby values**.
59
+ This matches the reference behavior for arithmetic, comparisons, string operations, and other pure operations that do not control evaluation order.
79
60
 
61
+ **Groups and references:**
80
62
 
63
+ - [Numeric operations](https://jsonlogic.com/operations.html#numeric-operations)
64
+ - [String operations](https://jsonlogic.com/operations.html#string-operations)
65
+ - [Array operations](https://jsonlogic.com/operations.html#array-operations) — simple transforms like `merge`, membership `in`.
81
66
 
82
- ### 2) Lazy operators
67
+ ### 2. Lazy Operations
83
68
 
84
- Some operators must control **whether** and **when** their arguments are evaluated.
85
- They implement branching, short-circuiting, or “apply a rule per item” semantics.
86
- For these **lazy operators**, the engine passes **raw sub-rules** and current `data`.
87
- The operator then evaluates only the sub-rules it actually needs.
69
+ 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 current data. The operator then evaluates only the sub-rules it actually needs.
88
70
 
89
71
  **Groups and references:**
90
72
 
91
- - **Branching / boolean control** — `if`, `?:`, `and`, `or`, `var`
92
- Docs: Logic and Boolean Operations — [https://jsonlogic.com/operations.html#logic-and-boolean-operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations)
93
- Truthiness table used by these operators — [https://jsonlogic.com/truthy.html](https://jsonlogic.com/truthy.html)
94
-
95
- - **Enumerable operators** — `map`, `filter`, `reduce`, `all`, `none`, `some`
96
- Docs: Array Operations — [https://jsonlogic.com/operations.html#array-operations](https://jsonlogic.com/operations.html#array-operations)
73
+ - **Branching / boolean control** — `if`, `?:`, `and`, `or`, `var`
74
+ [Logic & boolean operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations) • [Truthiness](https://jsonlogic.com/truthy.html)
97
75
 
76
+ - **Enumerable operators** — `map`, `filter`, `reduce`, `all`, `none`, `some`
77
+ [Array operations](https://jsonlogic.com/operations.html#array-operations)
98
78
 
99
- **How per-item evaluation works:**
79
+ **How enumerable per-item evaluation works:**
100
80
 
101
- 1. The first argument is a rule that returns the list of items — evaluated **once** to a Ruby array.
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"`.
102
84
 
103
- 2. The second argument is the per-item rule — evaluated **for each item** with that item as the **current root**.
104
85
 
105
- 3. For `reduce`, the current item is also available as `"current"`, and the running total as `"accumulator"` (this mirrors the reference usage in docs and tests).
86
+ **Example #1**
106
87
 
107
-
108
- **Examples**
109
88
  ```ruby
110
89
  # filter: keep numbers >= 2
111
90
  JsonLogic.apply(
@@ -113,7 +92,11 @@ JsonLogic.apply(
113
92
  { "ints" => [1,2,3] }
114
93
  )
115
94
  # => [2, 3]
95
+ ```
96
+
97
+ **Example #2**
116
98
 
99
+ ```ruby
117
100
  # reduce: sum using "current" and "accumulator"
118
101
  JsonLogic.apply(
119
102
  { "reduce" => [
@@ -127,193 +110,246 @@ JsonLogic.apply(
127
110
 
128
111
  ### Why laziness matters?
129
112
 
130
- Lazy operators **prevent evaluation** of branches you do not need.
131
- If division by zero raised an error (hypothetically), lazy control would avoid it:
113
+ Lazy operations **prevent evaluation** of branches you do not need. If division by zero raised an error (hypothetically), lazy control would avoid it:
132
114
 
133
115
  ```ruby
134
116
  # "or" short-circuits: 1 is truthy, so the right side is NOT evaluated.
135
117
  # If the right side were evaluated eagerly, it would attempt 1/0 (error).
136
118
  JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] })
137
119
  # => 1
120
+ ```
138
121
 
139
- # "if" evaluates only the 'then' branch when condition is true.
140
- # The 'else' branch with 1/0 is never evaluated.
141
- JsonLogic.apply({ "if" => [true, 42, { "/" => [1, 0] }] })
122
+ > In this gem `/` returns `nil` on divide‑by‑zero, but these examples show **why** lazy evaluation is required by the spec: branching and boolean operators must **not** evaluate unused branches.
123
+
124
+ ## Supported Operations (Built‑in)
125
+
126
+
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
+
170
+ ## Adding Operations
171
+
172
+ Need a custom operation? It’s straightforward.
173
+
174
+ ### Quick — register a Proc or Lambda
175
+
176
+ Register little anonymous functions, by passing a Proc or Lambda.
177
+
178
+ ```ruby
179
+ JsonLogic.add_operation("times2") { |(value), _| value.to_i * 2 }
180
+ ```
181
+
182
+ Once the function added, you can use it in your logic.
183
+
184
+ ```ruby
185
+ JsonLogic.apply({ "times2" => [21] })
142
186
  # => 42
143
187
  ```
144
- >In this gem (library) `/` returns `nil` on divide-by-zero, but these examples show **why** lazy evaluation is required by the spec: branching and boolean operators must **not** evaluate unused branches.
145
188
 
189
+ Is useful for rapid prototyping with minimal boilerplate;
190
+ Later you can “promote” it into a full class or use additional features.
146
191
 
147
192
 
148
- ---
193
+ ### 1) Pick the Operation type
194
+ Choose one of:
195
+ - **Default**
196
+ ```ruby
197
+ class JsonLogic::Operations::StartsWith < JsonLogic::Operation; end
198
+ ```
199
+ For anonymous functions:
200
+ ```ruby
201
+ JsonLogic.add_operation("starts_with", lazy: false) do; end
202
+ ```
203
+ - **Lazy**
204
+ ```ruby
205
+ class JsonLogic::Operations::StartsWith < JsonLogic::LazyOperation; end
206
+ ```
207
+ For anonymous functions:
208
+ ```ruby
209
+ JsonLogic.add_operation("starts_with", lazy: true) do; end
210
+ ```
149
211
 
150
- ## Supported Built-in Operations
151
-
152
- Below is a checklist that mirrors the sections on [**jsonlogic.com/operations.html**](https://jsonlogic.com/operations.html) and shows what this gem (library) implements.
153
-
154
-
155
- ### Accessing Data
156
- | Operator | Supported | Notes |
157
- |---|---:|---|
158
- | `var` | ✅ | |
159
- | `missing` | ✅ | Returns array of missing keys; works with complex sources (e.g. `merge` result). |
160
- | `missing_some` | ✅ | Returns `[]` when the minimum is met, otherwise missing keys. |
161
-
162
-
163
- ### [Logic and Boolean Operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations])
164
- | Operator | Supported | Notes |
165
- |---|---:|---|
166
- | `if` | ✅ | |
167
- | `==` | ✅ | |
168
- | `===` | ✅ | Strict equality (same type and value). |
169
- | `!=` | ✅ | |
170
- | `!==` | ✅ | |
171
- | `!` | ✅ | Follows JsonLogic truthiness. |
172
- | `!!` | ✅ | Follows JsonLogic truthiness. |
173
- | `or` | ✅ | Returns first truthy / last value; |
174
- | `and` | ✅ | Returns first falsy / last value; |
175
- | `?:` | ✅ | Returns the truth. |
176
-
177
-
178
- ### [Numeric Operations](https://jsonlogic.com/operations.html#numeric-operations)
179
- | Operator / Topic | Supported | Notes |
180
- |---|---:|---|
181
- | `>` `>=` `<` `<=` | ✅ | |
182
- | Between (pattern) | ✅ | Use `<`/`<=` with 3 args (not a separate op). |
183
- | `max` / `min` | ✅ | |
184
- | `+` `-` `*` `/` | ✅ | Unary `-` negates; unary `+` casts to number; `/` returns `nil` on divide‑by‑zero. |
185
- | `%` | ✅ | |
186
-
187
-
188
- ### [Array Operations](https://jsonlogic.com/operations.html#array-operations)
189
- | Operator | Supported | Notes |
190
- |---|---:|---|
191
- | `map` | ✅ | |
192
- | `reduce` | ✅ | Per‑item rule sees `"current"` and `"accumulator"`. |
193
- | `filter` | ✅ | Keeps items where per‑item rule is truthy (follows JsonLogic truthiness). |
194
- | `all` | ✅ | `false` on empty; all per‑item are truthy (follows JsonLogic truthiness). |
195
- | `none` | ✅ | `true` on empty; none per‑item are truthy (follows JsonLogic truthiness). |
196
- | `some` | ✅ | `false` on empty; any per‑item is truthy (follows JsonLogic truthiness). |
197
- | `merge` | ✅ | Flattens arguments into a single array; non‑arrays cast to singleton arrays. |
198
- | `in` | ✅ | If second arg is an array: membership test. |
199
-
200
-
201
- ### [String Operations](https://jsonlogic.com/operations.html#string-operations)
202
- | Operator | Supported | Notes |
203
- |---|---:|---|
204
- | `in` | ✅ | If second arg is a string: substring test. |
205
- | `cat` | ✅ | Concatenate arguments as strings (no delimiter). |
206
- | `substr` | ✅ | Start index can be negative; length can be omitted or negative (mirrors the official behavior). |
207
-
208
-
209
- ### Miscellaneous
210
- | Operator | Supported | Notes |
211
- |---|---:|---|
212
- | `log` | 🚫 | Not implemented by default (and not part of the compliance tests). |
213
-
214
- **Summary:** From the reference page’s list, everything except `log` is implemented.
215
- (“Between” is not a standalone operator, but the `<`/`<=` 3‑argument form is supported.)
212
+ See [§How](#how) for details.
216
213
 
217
- ---
214
+ ### 2) Enable JsonLogic Semantics (optional)
215
+ Enable semantics to mirror JsonLogic’s comparison/truthiness in Ruby:
216
+
217
+ ```ruby
218
+ using JsonLogic::Semantics
219
+ ```
218
220
 
219
- ## Extending (add your own operator)
220
221
 
221
- ### Operation Type
222
+ See [§JsonLogic Semantic](#jsonlogic-semantic) for details.
222
223
 
223
- Each operator is a class.
224
- - **Value operations** inherit `JsonLogic::Operation` (engine passes values).
224
+ ### 3) Create an Operation and provide a machine name
225
225
 
226
- - **Lazy operations** inherit `JsonLogic::LazyOperation` (engine passes raw sub‑rules).
226
+ Operation methods use a consistent call shape.
227
227
 
228
- - **Enumerable operations** inherit `JsonLogic::EnumerableOperation` (standardized data binding for per‑item rules).
228
+ - The first parameter is the **array of operator arguments**.
229
+ - The second is the current **data**.
229
230
 
230
- ### Guide
231
231
 
232
- First, create the Class for you Operation based on it's type:
232
+
233
+ Thanks to Ruby’s destructuring, you can unpack the argument array right in the method signature.
234
+
233
235
  ```ruby
234
236
  class JsonLogic::Operations::StartsWith < JsonLogic::Operation
235
- def self.op_name = "starts_with" # {"starts_with": [string, prefix]}
236
-
237
+ def self.name = "starts_with"
237
238
  def call((str, prefix), _data)
239
+ # str, prefix are ALREADY evaluated to Ruby values
238
240
  str.to_s.start_with?(prefix.to_s)
239
241
  end
240
242
  end
241
243
  ```
242
244
 
243
- Second, register your operation:
245
+ ### 4) Register the new operation
246
+
244
247
  ```ruby
245
248
  JsonLogic::Engine.default.registry.register(JsonLogic::Operations::StartsWith)
246
249
  ```
247
250
 
248
- Use it!
251
+ After registration, use it in rules:
252
+
253
+ ```json
254
+ { "starts_with": [ { "var": "email" }, "admin@" ] }
255
+ ```
256
+
257
+
258
+
259
+ ## JsonLogic Semantic
260
+
261
+ All supported Operations follow JsonLogic semantics.
262
+
263
+ ### Comparisons
264
+ As JsonLogic primary developed in JavaScript it inherits JavaScript's type coercion in build-in Operations. JsonLogic (JS‑style) comparisons coerce types; Ruby does not.
265
+
266
+ **JavaScript:**
267
+
268
+ ```js
269
+ 1 >= "1.0" // true
270
+ ```
271
+
272
+ **Ruby:**
273
+
249
274
  ```ruby
250
- rule = {
251
- "if" => [
252
- { "starts_with" => [ { "var" => "email" }, "admin@" ] },
253
- "is_admin",
254
- "regular_user"
255
- ]
256
- }
257
-
258
- p JsonLogic.apply(rule, { "email" => "admin@example.com" })
259
- # => "is_admin"
260
- p JsonLogic.apply(rule, { "email" => "user@example.com" })
261
- # => "regular_user"
275
+ 1 >= "1.0"
276
+ # ArgumentError: comparison of Integer with String failed
262
277
  ```
263
278
 
264
- ---
279
+ **Ruby (with JsonLogic semantics enabled):**
265
280
 
266
- ## Public Interface
267
- Use the high-level facade to evaluate a JsonLogic rule against input and get a plain Ruby value back.
268
- If you need more control (e.g., custom operator sets or multiple engines), use `JsonLogic::Engine`
281
+ ```ruby
282
+ using JsonLogic::Semantics
283
+
284
+ 1 >= "1.0" # => true
285
+ ```
269
286
 
287
+ ### Truthiness
270
288
 
289
+ JsonLogic’s truthiness differs from Ruby’s (see <https://jsonlogic.com/truthy.html>).
290
+ In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty arrays are also falsey.
291
+
292
+ **In Ruby:**
271
293
  ```ruby
272
- # Main facade
273
- JsonLogic.apply(rule, data = nil) # => value
294
+ !![]
295
+ # => true
296
+ ```
297
+
298
+ While JsonLogic as was mentioned before has it's own truthiness:
274
299
 
275
- # Engine/Registry (advanced)
276
- engine = JsonLogic::Engine.default
277
- engine.evaluate(rule, data)
300
+ **In Ruby (with JsonLogic Semantic):**
301
+
302
+ ```ruby
303
+ include JsonLogic::Semantics
278
304
 
279
- # Register a custom op class (auto‑registration is also supported)
280
- engine.registry.register(JsonLogic::Operations::StartsWith)
305
+ truthy?([])
306
+ # => false
281
307
  ```
282
308
 
283
- ---
284
309
 
285
310
  ## Compliance and tests
311
+
286
312
  Optional: quick self-test
287
313
 
314
+
315
+
288
316
  ```bash
289
317
  ruby test/selftest.rb
290
318
  ```
291
319
 
320
+
292
321
  Official test suite
322
+
293
323
  1. Fetch the official suite
294
324
 
325
+
326
+
295
327
  ```bash
296
328
  mkdir -p spec/tmp
297
329
  curl -fsSL https://jsonlogic.com/tests.json -o spec/tmp/tests.json
298
330
  ```
331
+
299
332
  2. Run it
333
+
300
334
  ```bash
301
335
  ruby script/compliance.rb spec/tmp/tests.json
302
336
  ```
303
337
 
304
- Expected output
338
+ Expected output
339
+
305
340
  ```bash
306
341
  # => Compliance: X/X passed
307
342
  ```
308
343
 
309
- ---
310
-
311
344
  ## Security
312
345
 
313
346
  - Rules are **data**, not code; no Ruby eval.
314
- - When evaluating untrusted rules, consider adding a timeout and error handling at the call site.
315
- - Custom operations should be **pure** (no IO, no network, no shell).
347
+ - Operations are **pure** (no IO, no network, no shell).
348
+ - Rules have **no write** access to anything.
349
+
350
+ ## License
316
351
 
352
+ MIT — see [LICENSE](LICENSE).
317
353
 
318
354
  ## Authors
319
355
 
@@ -4,7 +4,7 @@ module JsonLogic
4
4
  class Operation
5
5
  include Semantics
6
6
 
7
- def self.op_name = nil
7
+ def self.name = nil
8
8
 
9
9
  def self.values_only? = true
10
10
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Add < JsonLogic::Operation
4
- def self.op_name = "+"
4
+ def self.name = "+"
5
5
 
6
6
  def call(values, _data)
7
7
  values.map!(&:to_f).sum
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::All < JsonLogic::EnumerableOperation
6
- def self.op_name = "all"
6
+ def self.name = "all"
7
7
 
8
8
  def call(args, data)
9
9
  items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::And < JsonLogic::LazyOperation
6
- def self.op_name = "and"
6
+ def self.name = "and"
7
7
 
8
8
  def call(args, data)
9
9
  last = nil
@@ -2,7 +2,7 @@
2
2
 
3
3
  using JsonLogic::Semantics
4
4
  class JsonLogic::Operations::BoolCast < JsonLogic::Operation
5
- def self.op_name = "!!"
5
+ def self.name = "!!"
6
6
 
7
7
  def call((a), _data)
8
8
  !!a
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Cat < JsonLogic::Operation
4
- def self.op_name = "cat"
4
+ def self.name = "cat"
5
5
 
6
6
  def call(values, _data) = values.map!(&:to_s).join
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Div < JsonLogic::Operation
4
- def self.op_name = "/"
4
+ def self.name = "/"
5
5
 
6
6
  def call((a,b), _data) = (b.to_f == 0 ? nil : a.to_f / b.to_f)
7
7
  end
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::Equal < JsonLogic::Operation
6
- def self.op_name = "=="
6
+ def self.name = "=="
7
7
 
8
8
  def call((a,b), _data)
9
9
  a == b
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::Filter < JsonLogic::EnumerableOperation
6
- def self.op_name = "filter"
6
+ def self.name = "filter"
7
7
 
8
8
  def call(args, data)
9
9
  items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
@@ -3,6 +3,6 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::GT < JsonLogic::Operation
6
- def self.op_name = ">"
6
+ def self.name = ">"
7
7
  def call((a,b), _data) = a > b
8
8
  end
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::GTE < JsonLogic::Operation
6
- def self.op_name = ">="
6
+ def self.name = ">="
7
7
 
8
8
  def call((a,b), _data)
9
9
  a >= b
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::If < JsonLogic::LazyOperation
6
- def self.op_name = "if"
6
+ def self.name = "if"
7
7
 
8
8
  def call(args, data)
9
9
  i = 0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::In < JsonLogic::Operation
4
- def self.op_name = "in"
4
+ def self.name = "in"
5
5
 
6
6
  def call((a,b), _data) = (b.respond_to?(:include?) && b.include?(a))
7
7
  end
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::LT < JsonLogic::Operation
6
- def self.op_name = "<"
6
+ def self.name = "<"
7
7
 
8
8
  def call(values, _data)
9
9
  return values[0] < values[1] if values.size == 2
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::LTE < JsonLogic::Operation
6
- def self.op_name = "<="
6
+ def self.name = "<="
7
7
 
8
8
  def call(values, _data)
9
9
  return values[0] <= values[1] if values.size == 2
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Map < JsonLogic::EnumerableOperation
4
- def self.op_name = "map"
4
+ def self.name = "map"
5
5
 
6
6
  def call(args, data)
7
7
  items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Max < JsonLogic::Operation
4
- def self.op_name = "max"
4
+ def self.name = "max"
5
5
 
6
6
  def call(values, _data) = values.max
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Merge < JsonLogic::Operation
4
- def self.op_name = "merge"
4
+ def self.name = "merge"
5
5
  def call(values, _data)
6
6
  values.flat_map { |v| v.is_a?(Array) ? v : [v] }
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Min < JsonLogic::Operation
4
- def self.op_name = "min"
4
+ def self.name = "min"
5
5
 
6
6
  def call(values, _data) = values.min
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Missing < JsonLogic::Operation
4
- def self.op_name = "missing"
4
+ def self.name = "missing"
5
5
 
6
6
  def call(values, data)
7
7
  keys =
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::MissingSome < JsonLogic::Operation
4
- def self.op_name = "missing_some"
4
+ def self.name = "missing_some"
5
5
 
6
6
  def call((min_ok, list), data)
7
7
  arr = list.is_a?(Array) ? list : Array(list)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Mod < JsonLogic::Operation
4
- def self.op_name = "%"
4
+ def self.name = "%"
5
5
 
6
6
  def call((a,b), _data) = a.to_f % b.to_f
7
7
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Mul < JsonLogic::Operation
4
- def self.op_name = "*"
4
+ def self.name = "*"
5
5
  def call(values, _data) = values.map!(&:to_f).inject(1){|m,v| m * v }
6
6
  end
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::None < JsonLogic::EnumerableOperation
6
- def self.op_name = "none"
6
+ def self.name = "none"
7
7
 
8
8
  def call(args, data)
9
9
  items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::Not < JsonLogic::Operation
6
- def self.op_name = "!";
6
+ def self.name = "!";
7
7
 
8
8
  def call((a), _data) = !a
9
9
  end
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::NotEqual < JsonLogic::Operation
6
- def self.op_name = "!="
6
+ def self.name = "!="
7
7
 
8
8
  def call((a,b), _data)
9
9
  !(a == b)
@@ -2,7 +2,7 @@
2
2
 
3
3
  using JsonLogic::Semantics
4
4
  class JsonLogic::Operations::Or < JsonLogic::LazyOperation
5
- def self.op_name = "or"
5
+ def self.name = "or"
6
6
 
7
7
  def call(args, data)
8
8
  args.each do |a|
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Reduce < JsonLogic::EnumerableOperation
4
- def self.op_name = "reduce"
4
+ def self.name = "reduce"
5
5
 
6
6
  def call(args, data)
7
7
  rule_that_returns_items, step_rule_applied_per_item, rule_that_returns_initial_accumulator = args
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::Some < JsonLogic::EnumerableOperation
6
- def self.op_name = "some"
6
+ def self.name = "some"
7
7
 
8
8
  def call(args, data)
9
9
  items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::StrictEqual < JsonLogic::Operation
4
- def self.op_name = "==="
5
-
6
- def call((a,b), _data)
4
+ def self.name = "==="
5
+
6
+ def call((a,b), _data)
7
7
  a === b
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::StrictNotEqual < JsonLogic::Operation
6
- def self.op_name = "!=="
6
+ def self.name = "!=="
7
7
 
8
8
  def call((a,b), _data)
9
9
  !(a === b)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Sub < JsonLogic::Operation
4
- def self.op_name = "-"
4
+ def self.name = "-"
5
5
  def call(values, _data) = (values.size == 1 ? -values[0].to_f : values[0].to_f - values[1].to_f)
6
6
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Substr < JsonLogic::Operation
4
- def self.op_name = "substr"
4
+ def self.name = "substr"
5
5
 
6
6
  def call(values, _data)
7
7
  s, i, len = values
@@ -3,7 +3,7 @@
3
3
  using JsonLogic::Semantics
4
4
 
5
5
  class JsonLogic::Operations::Ternary < JsonLogic::LazyOperation
6
- def self.op_name = "?:"
6
+ def self.name = "?:"
7
7
 
8
8
  def call((cond_rule, then_rule, else_rule), data)
9
9
  if !!JsonLogic.apply(cond_rule, data)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Var < JsonLogic::Operation
4
- def self.op_name = "var";
4
+ def self.name = "var";
5
5
  def self.values_only? = false
6
6
 
7
7
  def call((path_rule, fallback_rule), data)
@@ -7,7 +7,7 @@ module JsonLogic
7
7
  end
8
8
 
9
9
  def register(op_class)
10
- name = op_class.op_name or raise ArgumentError, 'op_name missing'
10
+ name = op_class.name or raise ArgumentError, 'name missing'
11
11
  @map[name.to_s] = op_class
12
12
  self
13
13
  end
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module JsonLogic; VERSION = '0.1.1.beta3'; end
3
+ module JsonLogic; VERSION = '0.1.2'; end
data/lib/json_logic.rb CHANGED
@@ -14,10 +14,10 @@ module JsonLogic
14
14
  end
15
15
 
16
16
 
17
- # Load operation classes (each file defines one class with .op_name)
17
+ # Load operation classes (each file defines one class with .name)
18
18
  Dir[File.join(__dir__, 'json_logic', 'operations', '*.rb')].sort.each { |f| require f }
19
19
 
20
- # Auto-register all operation classes with .op_name
20
+ # Auto-register all operation classes with .name
21
21
  module JsonLogic
22
22
  module Loader
23
23
  module_function
@@ -25,7 +25,7 @@ module JsonLogic
25
25
  def register_all!(registry)
26
26
  ObjectSpace.each_object(Class) do |klass|
27
27
  next unless klass < JsonLogic::Operation
28
- next unless klass.respond_to?(:op_name) && klass.op_name && !klass.op_name.to_s.empty?
28
+ next unless klass.respond_to?(:name) && klass.name && !klass.name.to_s.empty?
29
29
 
30
30
  registry.register(klass)
31
31
  end
@@ -36,6 +36,16 @@ module JsonLogic
36
36
  def apply(rule, data = nil)
37
37
  Engine.default.evaluate(rule, data)
38
38
  end
39
+
40
+ def add_operation(name, lazy: false, &block)
41
+ base = lazy ? JsonLogic::LazyOperation : JsonLogic::Operation
42
+ klass = Class.new(base) do
43
+ define_singleton_method(:name) { name.to_s }
44
+ define_method(:call) { |args, data| block.call(args, data) }
45
+ end
46
+ JsonLogic::Engine.default.registry.register(klass)
47
+ klass
48
+ end
39
49
  end
40
50
  end
41
51
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-logic-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1.beta3
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tavrel Kate