json-logic-rb 0.1.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +315 -0
  4. data/lib/json_logic/engine.rb +54 -0
  5. data/lib/json_logic/enumerable_operation.rb +9 -0
  6. data/lib/json_logic/lazy_operation.rb +3 -0
  7. data/lib/json_logic/operation.rb +14 -0
  8. data/lib/json_logic/operations/add.rb +6 -0
  9. data/lib/json_logic/operations/all.rb +16 -0
  10. data/lib/json_logic/operations/and.rb +14 -0
  11. data/lib/json_logic/operations/bool_cast.rb +9 -0
  12. data/lib/json_logic/operations/cat.rb +6 -0
  13. data/lib/json_logic/operations/div.rb +6 -0
  14. data/lib/json_logic/operations/equal.rb +6 -0
  15. data/lib/json_logic/operations/filter.rb +15 -0
  16. data/lib/json_logic/operations/gt.rb +6 -0
  17. data/lib/json_logic/operations/gte.rb +6 -0
  18. data/lib/json_logic/operations/if.rb +15 -0
  19. data/lib/json_logic/operations/in_op.rb +6 -0
  20. data/lib/json_logic/operations/lt.rb +10 -0
  21. data/lib/json_logic/operations/lte.rb +10 -0
  22. data/lib/json_logic/operations/map.rb +10 -0
  23. data/lib/json_logic/operations/max.rb +6 -0
  24. data/lib/json_logic/operations/merge.rb +8 -0
  25. data/lib/json_logic/operations/min.rb +6 -0
  26. data/lib/json_logic/operations/missing.rb +33 -0
  27. data/lib/json_logic/operations/missing_some.rb +27 -0
  28. data/lib/json_logic/operations/mod.rb +6 -0
  29. data/lib/json_logic/operations/mul.rb +6 -0
  30. data/lib/json_logic/operations/none.rb +14 -0
  31. data/lib/json_logic/operations/not.rb +6 -0
  32. data/lib/json_logic/operations/not_equal.rb +6 -0
  33. data/lib/json_logic/operations/or.rb +12 -0
  34. data/lib/json_logic/operations/reduce.rb +19 -0
  35. data/lib/json_logic/operations/some.rb +16 -0
  36. data/lib/json_logic/operations/strict_equal.rb +6 -0
  37. data/lib/json_logic/operations/strict_not_equal.rb +6 -0
  38. data/lib/json_logic/operations/sub.rb +6 -0
  39. data/lib/json_logic/operations/substr.rb +30 -0
  40. data/lib/json_logic/operations/ternary.rb +13 -0
  41. data/lib/json_logic/operations/var.rb +27 -0
  42. data/lib/json_logic/registry.rb +19 -0
  43. data/lib/json_logic/semantics.rb +41 -0
  44. data/lib/json_logic/version.rb +3 -0
  45. data/lib/json_logic.rb +42 -0
  46. data/script/compliance.rb +50 -0
  47. data/spec/tmp/tests.js +0 -0
  48. data/spec/tmp/tests.json +532 -0
  49. data/test/selftest.rb +15 -0
  50. metadata +96 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f6841b0bb7968ba96acb42d78d167b14c9757b66f7e76a87b474a6af22958e5
4
+ data.tar.gz: 1c6a5520ce8de25ad8191542e2e0aeb3ce57846ef40d8bcd66ed0a061a5ed407
5
+ SHA512:
6
+ metadata.gz: 6c988634418f03c386e0c53ccd5323b2566bf13679d81b15c7f22538b7179b3d6a49f9998520d5155fd36e249d0b809951cc164a11d0ae425a9e09e67cf56b2d
7
+ data.tar.gz: 5e8e604fd9cc6cd1c927d071478db8cc4c00d6ca5bced126fba001d481998df551c766312d91aed49800495633220df85391cad88b0e6e98c235c8a9e0dc13c3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,315 @@
1
+ # json-logic-rb
2
+
3
+ Ruby implementation of [JsonLogic](https://jsonlogic.com/) — simple and extensible. Ships with a compliance runner for the official test suite.
4
+
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>
6
+
7
+
8
+ ## Table of Contents
9
+ - [What](#what)
10
+ - [Install](#install)
11
+ - [Quick start](#quick-start)
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--tests)
25
+ - [Security](#security)
26
+ - [License](#license)
27
+ - [Contributing](#contributing)
28
+
29
+
30
+
31
+ ## What
32
+ JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value.
33
+
34
+
35
+
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ gem install json-logic-rb
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ ```ruby
46
+ require "json-logic-rb"
47
+
48
+ rule = { "+" => [1, 2, 3] }
49
+
50
+ JsonLogic.apply(rule)
51
+ # => 6.0
52
+ ```
53
+
54
+ With data:
55
+
56
+ ```ruby
57
+ JsonLogic.apply({ "var" => "user.age" }, { "user" => { "age" => 42 } })
58
+ # => 42
59
+ ```
60
+
61
+
62
+ ## How
63
+
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.
70
+
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)
78
+
79
+
80
+
81
+
82
+ ### 2) Lazy operators
83
+
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.
88
+
89
+ **Groups and references:**
90
+
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)
97
+
98
+
99
+ **How per-item evaluation works:**
100
+
101
+ 1. The first argument is a rule that returns the list of items — evaluated **once** to a Ruby array.
102
+
103
+ 2. The second argument is the per-item rule — evaluated **for each item** with that item as the **current root**.
104
+
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).
106
+
107
+
108
+ **Examples**
109
+ ```ruby
110
+ `# filter: keep numbers >= 2
111
+ JsonLogic.apply(
112
+ { "filter" => [ { "var" => "ints" }, { ">=" => [ { "var" => "" }, 2 ] } ] },
113
+ { "ints" => [1,2,3] }
114
+ )
115
+ # => [2, 3]
116
+
117
+ # reduce: sum using "current" and "accumulator"
118
+ JsonLogic.apply(
119
+ { "reduce" => [
120
+ { "var" => "ints" },
121
+ { "+" => [ { "var" => "accumulator" }, { "var" => "current" } ] }, 0 ]
122
+ },
123
+ { "ints" => [1,2,3,4] }
124
+ )
125
+ # => 10.0
126
+ ```
127
+
128
+ ### Why laziness matters?
129
+
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:
132
+
133
+ ```ruby
134
+ # "or" short-circuits: 1 is truthy, so the right side is NOT evaluated.
135
+ # If the right side were evaluated eagerly, it would attempt 1/0 (error).
136
+ JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] })
137
+ # => 1
138
+
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] }] })
142
+ # => 42
143
+ ```
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
+
146
+
147
+
148
+ ---
149
+
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.)
216
+
217
+ ---
218
+
219
+ ## Extending (add your own operator)
220
+
221
+ ### Operation Type
222
+
223
+ Each operator is a class.
224
+ - **Value operations** inherit `JsonLogic::Operation` (engine passes values).
225
+
226
+ - **Lazy operations** inherit `JsonLogic::LazyOperation` (engine passes raw sub‑rules).
227
+
228
+ - **Enumerable operations** inherit `JsonLogic::EnumerableOperation` (standardized data binding for per‑item rules).
229
+
230
+ ### Guide
231
+
232
+ First, create the Class for you Operation based on it's type:
233
+ ```ruby
234
+ class JsonLogic::Operations::StartsWith < JsonLogic::Operation
235
+ def self.op_name = "starts_with" # {"starts_with": [string, prefix]}
236
+
237
+ def call((str, prefix), _data)
238
+ str.to_s.start_with?(prefix.to_s)
239
+ end
240
+ end
241
+ ```
242
+
243
+ Second, register your operation:
244
+ ```ruby
245
+ JsonLogic::Engine.default.registry.register(JsonLogic::Operations::StartsWith)
246
+ ```
247
+
248
+ Use it!
249
+ ```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"
262
+ ```
263
+
264
+ ---
265
+
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`
269
+
270
+
271
+ ```ruby
272
+ # Main facade
273
+ JsonLogic.apply(rule, data = nil) # => value
274
+
275
+ # Engine/Registry (advanced)
276
+ engine = JsonLogic::Engine.default
277
+ engine.evaluate(rule, data)
278
+
279
+ # Register a custom op class (auto‑registration is also supported)
280
+ engine.registry.register(JsonLogic::Operations::StartsWith)
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Compliance & tests
286
+ Optional: quick self-test
287
+
288
+ ```bash
289
+ ruby test/selftest.rb
290
+ ```
291
+
292
+ Official test suite
293
+ 1. Fetch the official suite
294
+
295
+ ```bash
296
+ mkdir -p spec/tmp
297
+ curl -fsSL https://jsonlogic.com/tests.json -o spec/tmp/tests.json
298
+ ```
299
+ 2. Run it
300
+ ```bash
301
+ ruby script/compliance.rb spec/tmp/tests.json
302
+ ```
303
+
304
+ Expected output
305
+ ```bash
306
+ # => Compliance: X/X passed
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Security
312
+
313
+ - 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).
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'semantics'
4
+
5
+ module JsonLogic
6
+ class Engine
7
+ include Semantics
8
+
9
+ def self.default
10
+ @default ||= new(registry: Registry.new)
11
+ end
12
+
13
+ def initialize(registry:)
14
+ @registry = registry
15
+ end
16
+
17
+ attr_reader :registry
18
+
19
+ def evaluate(rule, data = nil)
20
+ data ||= {}
21
+
22
+ case rule
23
+ when Numeric,
24
+ String,
25
+ TrueClass,
26
+ FalseClass,
27
+ NilClass
28
+ rule
29
+ when Array
30
+ rule.map { |r| evaluate(r, data) }
31
+ when Hash
32
+ name, raw_args = rule.first
33
+ op_class = @registry.fetch(name)
34
+ raise ArgumentError, "unknown operation: #{name}" unless op_class
35
+
36
+ args =
37
+ case raw_args
38
+ when nil then []
39
+ when Array then raw_args
40
+ else [raw_args]
41
+ end
42
+
43
+ if op_class.values_only?
44
+ values = args.map { |a| evaluate(a, data) }
45
+ op_class.new.call(values, data)
46
+ else
47
+ op_class.new.call(args, data)
48
+ end
49
+ else
50
+ rule
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ class JsonLogic::EnumerableOperation < JsonLogic::LazyOperation
2
+ private
3
+
4
+ def resolve_items_and_per_item_rule(rules, data)
5
+ rule_that_returns_items, rule_applied_to_each_item = rules
6
+ items = Array(JsonLogic.apply(rule_that_returns_items, data))
7
+ [items, rule_applied_to_each_item]
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ class JsonLogic::LazyOperation < JsonLogic::Operation
2
+ def self.values_only? = false
3
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonLogic
4
+ class Operation
5
+ def self.op_name = nil
6
+
7
+ def self.values_only? = true
8
+
9
+ # Implement in subclasses.
10
+ def call(args, data)
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Add < JsonLogic::Operation
4
+ def self.op_name = "+"
5
+ def call(values, _data) = values.map!(&:to_f).sum
6
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::All < JsonLogic::EnumerableOperation
4
+ def self.op_name = "all"
5
+
6
+ def call(args, data)
7
+ items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
8
+ return false if items.empty?
9
+
10
+ items.all? do |item|
11
+ JsonLogic::Semantics.truthy?(
12
+ JsonLogic.apply(rule_applied_to_each_item, item)
13
+ )
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::And < JsonLogic::LazyOperation
4
+ def self.op_name = "and"
5
+
6
+ def call(args, data)
7
+ last = nil
8
+ args.each do |a|
9
+ last = JsonLogic.apply(a, data)
10
+ return last unless JsonLogic::Semantics.truthy?(last)
11
+ end
12
+ last
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::BoolCast < JsonLogic::Operation
4
+ def self.op_name = "!!"
5
+
6
+ def call((a), _data)
7
+ JsonLogic::Semantics.truthy?(a)
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Cat < JsonLogic::Operation
4
+ def self.op_name = "cat"
5
+ def call(values, _data) = values.map!(&:to_s).join
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Div < JsonLogic::Operation
4
+ def self.op_name = "/"
5
+ def call((a,b), _data) = (b.to_f == 0 ? nil : a.to_f / b.to_f)
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Equal < JsonLogic::Operation
4
+ def self.op_name = "=="
5
+ def call((a,b), _data) = JsonLogic::Semantics.loose_equal(a,b)
6
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Filter < JsonLogic::EnumerableOperation
4
+ def self.op_name = "filter"
5
+
6
+ def call(args, data)
7
+ items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
8
+
9
+ items.filter do |item|
10
+ JsonLogic::Semantics.truthy?(
11
+ JsonLogic.apply(rule_applied_to_each_item, item)
12
+ )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::GT < JsonLogic::Operation
4
+ def self.op_name = ">"
5
+ def call((a,b), _data) = JsonLogic::Semantics.to_number(a) > JsonLogic::Semantics.to_number(b)
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::GTE < JsonLogic::Operation
4
+ def self.op_name = ">="
5
+ def call((a,b), _data) = JsonLogic::Semantics.to_number(a) >= JsonLogic::Semantics.to_number(b)
6
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::If < JsonLogic::LazyOperation
4
+ def self.op_name = "if"
5
+
6
+ def call(args, data)
7
+ i = 0
8
+ while i < args.size - 1
9
+ return JsonLogic.apply(args[i + 1], data) if JsonLogic::Semantics.truthy?(JsonLogic.apply(args[i], data))
10
+ i += 2
11
+ end
12
+ return JsonLogic.apply(args[-1], data) if args.size.odd?
13
+ nil
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::InOp < JsonLogic::Operation
4
+ def self.op_name = "in"
5
+ def call((a,b), _data) = (b.respond_to?(:include?) && b.include?(a))
6
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::LT < JsonLogic::Operation
4
+ def self.op_name = "<"
5
+ def call(values, _data)
6
+ nums = values.map { |v| JsonLogic::Semantics.to_number(v) }
7
+ return nums[0] < nums[1] if nums.size == 2
8
+ nums.each_cons(2).all? { |a,b| a < b }
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::LTE < JsonLogic::Operation
4
+ def self.op_name = "<="
5
+ def call(values, _data)
6
+ nums = values.map { |v| JsonLogic::Semantics.to_number(v) }
7
+ return nums[0] <= nums[1] if nums.size == 2
8
+ nums.each_cons(2).all? { |a,b| a <= b }
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Map < JsonLogic::EnumerableOperation
4
+ def self.op_name = "map"
5
+
6
+ def call(args, data)
7
+ items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
8
+ items.map { |item| JsonLogic.apply(rule_applied_to_each_item, item) }
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Max < JsonLogic::Operation
4
+ def self.op_name = "max"
5
+ def call(values, _data) = values.max_by { |v| JsonLogic::Semantics.to_number(v) }
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Merge < JsonLogic::Operation
4
+ def self.op_name = "merge"
5
+ def call(values, _data)
6
+ values.flat_map { |v| v.is_a?(Array) ? v : [v] }
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Min < JsonLogic::Operation
4
+ def self.op_name = "min"
5
+ def call(values, _data) = values.min_by { |v| JsonLogic::Semantics.to_number(v) }
6
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonLogic::Operations::Missing < JsonLogic::Operation
4
+ def self.op_name = "missing"
5
+
6
+ def call(values, data)
7
+ keys =
8
+ if values.size == 1 && values.first.is_a?(Array)
9
+ values.first
10
+ else
11
+ values
12
+ end
13
+
14
+ keys.select { |k| dig(data, k).nil? }
15
+ end
16
+
17
+ private
18
+
19
+ def dig(obj, path)
20
+ return nil if obj.nil?
21
+ cur = obj
22
+ path.to_s.split(".").each do |k|
23
+ if cur.is_a?(Array) && k =~ /\A\d+\z/
24
+ cur = cur[k.to_i]
25
+ elsif cur.is_a?(Hash)
26
+ cur = cur[k] || cur[k.to_s] || cur[k.to_sym]
27
+ else
28
+ return nil
29
+ end
30
+ end
31
+ cur
32
+ end
33
+ end