json-logic-rb 0.1.0.beta2 → 0.1.1.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -3
  3. data/lib/json_logic/operation.rb +2 -0
  4. data/lib/json_logic/operations/add.rb +4 -1
  5. data/lib/json_logic/operations/all.rb +1 -3
  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 -0
  9. data/lib/json_logic/operations/div.rb +1 -0
  10. data/lib/json_logic/operations/equal.rb +6 -1
  11. data/lib/json_logic/operations/filter.rb +1 -3
  12. data/lib/json_logic/operations/gt.rb +3 -1
  13. data/lib/json_logic/operations/gte.rb +6 -1
  14. data/lib/json_logic/operations/if.rb +1 -1
  15. data/lib/json_logic/operations/{in_op.rb → in.rb} +2 -1
  16. data/lib/json_logic/operations/lt.rb +5 -3
  17. data/lib/json_logic/operations/lte.rb +5 -3
  18. data/lib/json_logic/operations/max.rb +2 -1
  19. data/lib/json_logic/operations/min.rb +2 -1
  20. data/lib/json_logic/operations/missing_some.rb +1 -0
  21. data/lib/json_logic/operations/mod.rb +1 -0
  22. data/lib/json_logic/operations/none.rb +1 -3
  23. data/lib/json_logic/operations/not.rb +2 -1
  24. data/lib/json_logic/operations/not_equal.rb +6 -1
  25. data/lib/json_logic/operations/or.rb +2 -1
  26. data/lib/json_logic/operations/reduce.rb +1 -1
  27. data/lib/json_logic/operations/some.rb +1 -3
  28. data/lib/json_logic/operations/strict_equal.rb +4 -1
  29. data/lib/json_logic/operations/strict_not_equal.rb +4 -1
  30. data/lib/json_logic/operations/ternary.rb +1 -1
  31. data/lib/json_logic/operations/var.rb +4 -1
  32. data/lib/json_logic/semantics.rb +60 -22
  33. data/lib/json_logic/version.rb +1 -1
  34. metadata +3 -4
  35. data/spec/tmp/tests.js +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 353191c500d952bd851cd63744323aabee9df0ed7ba838496902097e37741ded
4
- data.tar.gz: a38434d883bffd18579ead8ef04bb0a75ccfae121b441d4270233dd8451ade00
3
+ metadata.gz: 4d4064f94160ac9151244253f9a265932a0d30c43288523a73ef9bfcabb97b3b
4
+ data.tar.gz: e49615ccae718c4f8c87f5289e4d4e0c079a9bd516fd0e15399c978995d1599e
5
5
  SHA512:
6
- metadata.gz: a80d3d73a45fccb17750f75ca1875c8e85897c4b5e81ecbabeb6e961f78e0eeb0fb69fb3cf3f9da2ff8bb70f53685b1a80a554b5daa718f4740654e6d3ef9310
7
- data.tar.gz: b79782b7923300af9ac3f24a816f91e29c5b4126d511f0fcbd9f1bb8eda1b859ca796aece57520e401d776fbfee7111f48c1778262de87b3b60d6baaf24dc17c
6
+ metadata.gz: c73281072de2de418a28e3104b5e41fa084e57f8a2c4f2c4cd92bb2d0571d0cde87ffd36a86c7eefee03f690e916eb875fc1bf1800ab40faa9eba371afdc1230
7
+ data.tar.gz: 0a7759d9d56afca69ccc8d7dbd7e623007750288c29ad048719f64f202d124ad8e106545edb99b73489d4f6488df6989394e1bf330bf7e792d0a749d605d089e
data/README.md CHANGED
@@ -21,7 +21,7 @@ Ruby implementation of [JsonLogic](https://jsonlogic.com/) — simple and extens
21
21
  - [Miscellaneous](#miscellaneous)
22
22
  - [Extending (add your own operator)](#extending-add-your-own-operator)
23
23
  - [Public Interface](#public-interface)
24
- - [Compliance & tests](#compliance--tests)
24
+ - [Compliance & tests](#compliance-and-tests)
25
25
  - [Security](#security)
26
26
  - [License](#license)
27
27
  - [Contributing](#contributing)
@@ -107,7 +107,7 @@ The operator then evaluates only the sub-rules it actually needs.
107
107
 
108
108
  **Examples**
109
109
  ```ruby
110
- `# filter: keep numbers >= 2
110
+ # filter: keep numbers >= 2
111
111
  JsonLogic.apply(
112
112
  { "filter" => [ { "var" => "ints" }, { ">=" => [ { "var" => "" }, 2 ] } ] },
113
113
  { "ints" => [1,2,3] }
@@ -282,7 +282,7 @@ engine.registry.register(JsonLogic::Operations::StartsWith)
282
282
 
283
283
  ---
284
284
 
285
- ## Compliance & tests
285
+ ## Compliance and tests
286
286
  Optional: quick self-test
287
287
 
288
288
  ```bash
@@ -313,3 +313,9 @@ Expected output
313
313
  - Rules are **data**, not code; no Ruby eval.
314
314
  - When evaluating untrusted rules, consider adding a timeout and error handling at the call site.
315
315
  - Custom operations should be **pure** (no IO, no network, no shell).
316
+
317
+
318
+ ## Authors
319
+
320
+ - [Valeriya Petrova](https://github.com/piatrova-valeriya1999)
321
+ - [Tavrel Kate](https://github.com/tavrelkate)
@@ -2,6 +2,8 @@
2
2
 
3
3
  module JsonLogic
4
4
  class Operation
5
+ include Semantics
6
+
5
7
  def self.op_name = nil
6
8
 
7
9
  def self.values_only? = true
@@ -2,5 +2,8 @@
2
2
 
3
3
  class JsonLogic::Operations::Add < JsonLogic::Operation
4
4
  def self.op_name = "+"
5
- def call(values, _data) = values.map!(&:to_f).sum
5
+
6
+ def call(values, _data)
7
+ values.map!(&:to_f).sum
8
+ end
6
9
  end
@@ -8,9 +8,7 @@ class JsonLogic::Operations::All < JsonLogic::EnumerableOperation
8
8
  return false if items.empty?
9
9
 
10
10
  items.all? do |item|
11
- JsonLogic::Semantics.truthy?(
12
- JsonLogic.apply(rule_applied_to_each_item, item)
13
- )
11
+ truthy?(JsonLogic.apply(rule_applied_to_each_item, item))
14
12
  end
15
13
  end
16
14
  end
@@ -7,7 +7,7 @@ class JsonLogic::Operations::And < JsonLogic::LazyOperation
7
7
  last = nil
8
8
  args.each do |a|
9
9
  last = JsonLogic.apply(a, data)
10
- return last unless JsonLogic::Semantics.truthy?(last)
10
+ return last unless truthy?(last)
11
11
  end
12
12
  last
13
13
  end
@@ -4,6 +4,6 @@ class JsonLogic::Operations::BoolCast < JsonLogic::Operation
4
4
  def self.op_name = "!!"
5
5
 
6
6
  def call((a), _data)
7
- JsonLogic::Semantics.truthy?(a)
7
+ truthy?(a)
8
8
  end
9
9
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  class JsonLogic::Operations::Cat < JsonLogic::Operation
4
4
  def self.op_name = "cat"
5
+
5
6
  def call(values, _data) = values.map!(&:to_s).join
6
7
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  class JsonLogic::Operations::Div < JsonLogic::Operation
4
4
  def self.op_name = "/"
5
+
5
6
  def call((a,b), _data) = (b.to_f == 0 ? nil : a.to_f / b.to_f)
6
7
  end
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using JsonLogic::Semantics
4
+
3
5
  class JsonLogic::Operations::Equal < JsonLogic::Operation
4
6
  def self.op_name = "=="
5
- def call((a,b), _data) = JsonLogic::Semantics.loose_equal(a,b)
7
+
8
+ def call((a,b), _data)
9
+ a == b
10
+ end
6
11
  end
@@ -7,9 +7,7 @@ class JsonLogic::Operations::Filter < JsonLogic::EnumerableOperation
7
7
  items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
8
8
 
9
9
  items.filter do |item|
10
- JsonLogic::Semantics.truthy?(
11
- JsonLogic.apply(rule_applied_to_each_item, item)
12
- )
10
+ truthy?(JsonLogic.apply(rule_applied_to_each_item, item))
13
11
  end
14
12
  end
15
13
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using JsonLogic::Semantics
4
+
3
5
  class JsonLogic::Operations::GT < JsonLogic::Operation
4
6
  def self.op_name = ">"
5
- def call((a,b), _data) = JsonLogic::Semantics.to_number(a) > JsonLogic::Semantics.to_number(b)
7
+ def call((a,b), _data) = a > b
6
8
  end
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using JsonLogic::Semantics
4
+
3
5
  class JsonLogic::Operations::GTE < JsonLogic::Operation
4
6
  def self.op_name = ">="
5
- def call((a,b), _data) = JsonLogic::Semantics.to_number(a) >= JsonLogic::Semantics.to_number(b)
7
+
8
+ def call((a,b), _data)
9
+ a >= b
10
+ end
6
11
  end
@@ -6,7 +6,7 @@ class JsonLogic::Operations::If < JsonLogic::LazyOperation
6
6
  def call(args, data)
7
7
  i = 0
8
8
  while i < args.size - 1
9
- return JsonLogic.apply(args[i + 1], data) if JsonLogic::Semantics.truthy?(JsonLogic.apply(args[i], data))
9
+ return JsonLogic.apply(args[i + 1], data) if truthy?(JsonLogic.apply(args[i], data))
10
10
  i += 2
11
11
  end
12
12
  return JsonLogic.apply(args[-1], data) if args.size.odd?
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JsonLogic::Operations::InOp < JsonLogic::Operation
3
+ class JsonLogic::Operations::In < JsonLogic::Operation
4
4
  def self.op_name = "in"
5
+
5
6
  def call((a,b), _data) = (b.respond_to?(:include?) && b.include?(a))
6
7
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using JsonLogic::Semantics
4
+
3
5
  class JsonLogic::Operations::LT < JsonLogic::Operation
4
6
  def self.op_name = "<"
7
+
5
8
  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
+ return values[0] < values[1] if values.size == 2
10
+ values.each_cons(2).all? { |a,b| a < b }
9
11
  end
10
12
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using JsonLogic::Semantics
4
+
3
5
  class JsonLogic::Operations::LTE < JsonLogic::Operation
4
6
  def self.op_name = "<="
7
+
5
8
  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
+ return values[0] <= values[1] if values.size == 2
10
+ values.each_cons(2).all? { |a,b| a <= b }
9
11
  end
10
12
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  class JsonLogic::Operations::Max < JsonLogic::Operation
4
4
  def self.op_name = "max"
5
- def call(values, _data) = values.max_by { |v| JsonLogic::Semantics.to_number(v) }
5
+
6
+ def call(values, _data) = values.max
6
7
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  class JsonLogic::Operations::Min < JsonLogic::Operation
4
4
  def self.op_name = "min"
5
- def call(values, _data) = values.min_by { |v| JsonLogic::Semantics.to_number(v) }
5
+
6
+ def call(values, _data) = values.min
6
7
  end
@@ -10,6 +10,7 @@ class JsonLogic::Operations::MissingSome < JsonLogic::Operation
10
10
  end
11
11
 
12
12
  private
13
+
13
14
  def dig(obj, path)
14
15
  return nil if obj.nil?
15
16
  cur = obj
@@ -2,5 +2,6 @@
2
2
 
3
3
  class JsonLogic::Operations::Mod < JsonLogic::Operation
4
4
  def self.op_name = "%"
5
+
5
6
  def call((a,b), _data) = a.to_f % b.to_f
6
7
  end
@@ -6,9 +6,7 @@ class JsonLogic::Operations::None < JsonLogic::EnumerableOperation
6
6
  def call(args, data)
7
7
  items, rule_applied_to_each_item = resolve_items_and_per_item_rule(args, data)
8
8
  items.none? do |item|
9
- JsonLogic::Semantics.truthy?(
10
- JsonLogic.apply(rule_applied_to_each_item, item)
11
- )
9
+ truthy?(JsonLogic.apply(rule_applied_to_each_item, item))
12
10
  end
13
11
  end
14
12
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  class JsonLogic::Operations::Not < JsonLogic::Operation
4
4
  def self.op_name = "!";
5
- def call((a), _data) = !JsonLogic::Semantics.truthy?(a)
5
+
6
+ def call((a), _data) = !truthy?(a)
6
7
  end
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using JsonLogic::Semantics
4
+
3
5
  class JsonLogic::Operations::NotEqual < JsonLogic::Operation
4
6
  def self.op_name = "!="
5
- def call((a,b), _data) = !JsonLogic::Semantics.loose_equal(a,b)
7
+
8
+ def call((a,b), _data)
9
+ !(a == b)
10
+ end
6
11
  end
@@ -5,8 +5,9 @@ class JsonLogic::Operations::Or < JsonLogic::LazyOperation
5
5
  def call(args, data)
6
6
  args.each do |a|
7
7
  v = JsonLogic.apply(a, data)
8
- return v if JsonLogic::Semantics.truthy?(v)
8
+ return v if truthy?(v)
9
9
  end
10
+
10
11
  args.empty? ? nil : JsonLogic.apply(args.last, data)
11
12
  end
12
13
  end
@@ -14,6 +14,6 @@ class JsonLogic::Operations::Reduce < JsonLogic::EnumerableOperation
14
14
  step_rule_applied_per_item,
15
15
  (data || {}).merge("" => item, "current" => item, "accumulator" => memo)
16
16
  )
17
- end
17
+ endgem build
18
18
  end
19
19
  end
@@ -8,9 +8,7 @@ class JsonLogic::Operations::Some < JsonLogic::EnumerableOperation
8
8
  return false if items.empty?
9
9
 
10
10
  items.any? do |item|
11
- JsonLogic::Semantics.truthy?(
12
- JsonLogic.apply(rule_applied_to_each_item, item)
13
- )
11
+ truthy?(JsonLogic.apply(rule_applied_to_each_item, item))
14
12
  end
15
13
  end
16
14
  end
@@ -2,5 +2,8 @@
2
2
 
3
3
  class JsonLogic::Operations::StrictEqual < JsonLogic::Operation
4
4
  def self.op_name = "==="
5
- def call((a,b), _data) = JsonLogic::Semantics.strict_equal(a,b)
5
+
6
+ def call((a,b), _data)
7
+ a === b
8
+ end
6
9
  end
@@ -2,5 +2,8 @@
2
2
 
3
3
  class JsonLogic::Operations::StrictNotEqual < JsonLogic::Operation
4
4
  def self.op_name = "!=="
5
- def call((a,b), _data) = !JsonLogic::Semantics.strict_equal(a,b)
5
+
6
+ def call((a,b), _data)
7
+ !(a === b)
8
+ end
6
9
  end
@@ -4,7 +4,7 @@ class JsonLogic::Operations::Ternary < JsonLogic::LazyOperation
4
4
  def self.op_name = "?:"
5
5
 
6
6
  def call((cond_rule, then_rule, else_rule), data)
7
- if JsonLogic::Semantics.truthy?(JsonLogic.apply(cond_rule, data))
7
+ if truthy?(JsonLogic.apply(cond_rule, data))
8
8
  JsonLogic.apply(then_rule, data)
9
9
  else
10
10
  JsonLogic.apply(else_rule, data)
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonLogic::Operations::Var < JsonLogic::Operation
4
- def self.op_name = "var"; def self.values_only? = false
4
+ def self.op_name = "var";
5
+ def self.values_only? = false
6
+
5
7
  def call((path_rule, fallback_rule), data)
6
8
  path = JsonLogic.apply(path_rule, data)
7
9
  return data if path == ""
@@ -10,6 +12,7 @@ class JsonLogic::Operations::Var < JsonLogic::Operation
10
12
  return nil if fallback_rule.nil?
11
13
  JsonLogic.apply(fallback_rule, data)
12
14
  end
15
+
13
16
  def dig(obj, path)
14
17
  return nil if obj.nil?
15
18
  cur = obj
@@ -2,8 +2,6 @@
2
2
 
3
3
  module JsonLogic
4
4
  module Semantics
5
- NUMERIC_RE = /\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\z/.freeze
6
-
7
5
  module_function
8
6
 
9
7
  def truthy?(v)
@@ -11,42 +9,82 @@ module JsonLogic
11
9
  when nil then false
12
10
  when TrueClass, FalseClass then v
13
11
  when Numeric then !v.zero?
14
- when String then !v.empty?
15
- when Array then !v.empty?
12
+ when String, Array then !v.empty?
16
13
  else true
17
14
  end
18
15
  end
19
16
 
20
- def falsy?(v) = !truthy?(v)
21
-
22
- def to_number(v)
17
+ def num(v)
23
18
  case v
24
- when Integer then v
25
- when Numeric then v.to_f
19
+ when Numeric then v.to_f
20
+ when TrueClass then 1.0
21
+ when FalseClass then 0.0
22
+ when NilClass then 0.0
23
+ when Array then num(v.join(','))
26
24
  when String
27
25
  s = v.strip
28
- return nil unless NUMERIC_RE.match?(s)
29
- s =~ /[.eE]/ ? s.to_f : s.to_i
26
+ return 0.0 if s.empty?
27
+ Float(s) rescue Float::NAN
30
28
  else
31
- nil
29
+ Float::NAN
30
+ end
31
+ end
32
+
33
+ def eq(a, b)
34
+ if a.class == b.class
35
+ return false if a.is_a?(Numeric) && (a.to_f.nan? || b.to_f.nan?)
36
+ return a.is_a?(Numeric) ? (a.to_f == b.to_f) : a.eql?(b)
37
+ end
38
+
39
+ if a == true || a == false || b == true || b == false
40
+ na = num(a); nb = num(b)
41
+ return false if na.nan? || nb.nan?
42
+ return na == nb
32
43
  end
44
+
45
+ if (a.is_a?(String) && b.is_a?(Numeric)) || (a.is_a?(Numeric) && b.is_a?(String))
46
+ na = num(a); nb = num(b)
47
+ return false if na.nan? || nb.nan?
48
+ return na == nb
49
+ end
50
+
51
+ false
33
52
  end
34
53
 
35
- def strict_equal(a,b)
36
- if a.is_a?(Numeric) && b.is_a?(Numeric)
37
- a.to_f == b.to_f
54
+ def cmp(a, b)
55
+ if a.is_a?(String) && b.is_a?(String)
56
+ a <=> b
38
57
  else
39
- a.class == b.class && a == b
58
+ x = num(a); y = num(b)
59
+ return nil if x.nan? || y.nan?
60
+ x <=> y
40
61
  end
41
62
  end
42
63
 
43
- def loose_equal(a, b)
44
- if (a.is_a?(Numeric) || a.is_a?(String)) && (b.is_a?(Numeric) || b.is_a?(String))
45
- na = to_number(a)
46
- nb = to_number(b)
47
- return na == nb unless na.nil? || nb.nil?
64
+ [String, Integer, TrueClass, FalseClass, NilClass, Array].each do |klass|
65
+ refine klass do
66
+ def ==(other) = JsonLogic::Semantics.eq(self, other)
67
+
68
+ def >(other)
69
+ c = JsonLogic::Semantics.cmp(self, other)
70
+ c && c == 1
71
+ end
72
+
73
+ def >=(other)
74
+ c = JsonLogic::Semantics.cmp(self, other)
75
+ c && (c == 1 || c == 0)
76
+ end
77
+
78
+ def <(other)
79
+ c = JsonLogic::Semantics.cmp(self, other)
80
+ c && c == -1
81
+ end
82
+
83
+ def <=(other)
84
+ c = JsonLogic::Semantics.cmp(self, other)
85
+ c && (c == -1 || c == 0)
86
+ end
48
87
  end
49
- a == b
50
88
  end
51
89
  end
52
90
  end
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module JsonLogic; VERSION = '0.1.0.beta2'; end
3
+ module JsonLogic; VERSION = '0.1.1.beta1'; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-logic-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.beta2
4
+ version: 0.1.1.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tavrel Kate
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-06 00:00:00.000000000 Z
11
+ date: 2025-10-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby implementation of JsonLogic. JsonLogic rules are JSON trees. The
14
14
  engine walks that tree and returns a Ruby value. Ships with a compliance runner
@@ -36,7 +36,7 @@ files:
36
36
  - lib/json_logic/operations/gt.rb
37
37
  - lib/json_logic/operations/gte.rb
38
38
  - lib/json_logic/operations/if.rb
39
- - lib/json_logic/operations/in_op.rb
39
+ - lib/json_logic/operations/in.rb
40
40
  - lib/json_logic/operations/lt.rb
41
41
  - lib/json_logic/operations/lte.rb
42
42
  - lib/json_logic/operations/map.rb
@@ -63,7 +63,6 @@ files:
63
63
  - lib/json_logic/semantics.rb
64
64
  - lib/json_logic/version.rb
65
65
  - script/compliance.rb
66
- - spec/tmp/tests.js
67
66
  - spec/tmp/tests.json
68
67
  - test/selftest.rb
69
68
  homepage: https://github.com/tavrelkate/json-logic-rb
data/spec/tmp/tests.js DELETED
File without changes