shiny_json_logic 0.1.6 → 0.1.7

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/compat.yml +74 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +19 -18
  6. data/badges/compat.json +6 -0
  7. data/bin/test.sh +77 -0
  8. data/lib/core_ext/array.rb +0 -2
  9. data/lib/shiny_json_logic/engine.rb +33 -98
  10. data/lib/shiny_json_logic/errors/base.rb +20 -0
  11. data/lib/shiny_json_logic/numericals/with_error_handling.rb +17 -0
  12. data/lib/shiny_json_logic/operations/addition.rb +3 -1
  13. data/lib/shiny_json_logic/operations/all.rb +0 -4
  14. data/lib/shiny_json_logic/operations/and.rb +3 -1
  15. data/lib/shiny_json_logic/operations/base.rb +18 -6
  16. data/lib/shiny_json_logic/operations/coalesce.rb +3 -1
  17. data/lib/shiny_json_logic/operations/concatenation.rb +3 -1
  18. data/lib/shiny_json_logic/operations/different.rb +2 -1
  19. data/lib/shiny_json_logic/operations/division.rb +8 -2
  20. data/lib/shiny_json_logic/operations/double_not.rb +3 -1
  21. data/lib/shiny_json_logic/operations/equal.rb +3 -1
  22. data/lib/shiny_json_logic/operations/exists.rb +4 -2
  23. data/lib/shiny_json_logic/operations/filter.rb +3 -1
  24. data/lib/shiny_json_logic/operations/greater.rb +3 -1
  25. data/lib/shiny_json_logic/operations/greater_equal.rb +3 -1
  26. data/lib/shiny_json_logic/operations/if.rb +32 -6
  27. data/lib/shiny_json_logic/operations/inclusion.rb +3 -1
  28. data/lib/shiny_json_logic/operations/iterable/base.rb +23 -9
  29. data/lib/shiny_json_logic/operations/max.rb +3 -1
  30. data/lib/shiny_json_logic/operations/merge.rb +3 -1
  31. data/lib/shiny_json_logic/operations/min.rb +3 -1
  32. data/lib/shiny_json_logic/operations/missing.rb +3 -1
  33. data/lib/shiny_json_logic/operations/missing_some.rb +5 -2
  34. data/lib/shiny_json_logic/operations/modulo.rb +10 -2
  35. data/lib/shiny_json_logic/operations/none.rb +0 -4
  36. data/lib/shiny_json_logic/operations/not.rb +3 -1
  37. data/lib/shiny_json_logic/operations/or.rb +3 -1
  38. data/lib/shiny_json_logic/operations/product.rb +8 -2
  39. data/lib/shiny_json_logic/operations/reduce.rb +11 -4
  40. data/lib/shiny_json_logic/operations/smaller.rb +3 -1
  41. data/lib/shiny_json_logic/operations/smaller_equal.rb +3 -1
  42. data/lib/shiny_json_logic/operations/some.rb +0 -4
  43. data/lib/shiny_json_logic/operations/strict_different.rb +2 -1
  44. data/lib/shiny_json_logic/operations/strict_equal.rb +4 -2
  45. data/lib/shiny_json_logic/operations/substring.rb +3 -1
  46. data/lib/shiny_json_logic/operations/subtraction.rb +10 -3
  47. data/lib/shiny_json_logic/operations/throw.rb +35 -0
  48. data/lib/shiny_json_logic/operations/try.rb +29 -0
  49. data/lib/shiny_json_logic/operations/val.rb +5 -1
  50. data/lib/shiny_json_logic/operations/var.rb +3 -1
  51. data/lib/shiny_json_logic/operator_solver.rb +66 -0
  52. data/lib/shiny_json_logic/version.rb +1 -1
  53. data/lib/shiny_json_logic.rb +6 -1
  54. data/results/ruby.json +8 -0
  55. metadata +11 -3
  56. data/lib/shiny_json_logic/operations/map.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b8a8110c84c6c3c200990803fee38794b6eca329633637a30f4a7f4c06eee95
4
- data.tar.gz: 714e59c4620f1d3af97f026185d6a1e2131c7b2d3f6c6093a23334edf32b47b9
3
+ metadata.gz: a9900ca19c7b287666c48cd69a5343a876f2c4d9c49e1ff224ae7c1b56cdf99b
4
+ data.tar.gz: eb6909a5a85affc348ddf9d0ed9b5395d384a34bb924aa410da40e5951168538
5
5
  SHA512:
6
- metadata.gz: d0147aaeda5d79d78786840aa872172c7369f14c5cb6281de4662cef2c9afbf9b299d13262a59244a451738e53c632cffa57dfd749b0256f239d970b9e4711da
7
- data.tar.gz: 6e70bebb46e6fed627138f8fc84d91cc4d2db44fb88df32ceea8f65a6f09233907c7c1383950bf6c8baf42ab18ad21f7fc83842f8222f476d77a1853a7bdfa81
6
+ metadata.gz: 45ec24f6e4a8bc0e8c45285fa8e11eabd06c8161957e4798ce3f7ae19d843aec07f64bb901a1c726c0594d8990f5fbad858ccad126fce9a0a937de4431d2245e
7
+ data.tar.gz: 6e398b31cb5f27592158082bffa8f113baa436cafa498b5c7f9c2d20c01d522857d6eb63195cfa6e8acc1669af68081d839461fcf0462fc2e2ee6b1f44c2cced
@@ -0,0 +1,74 @@
1
+ name: Compatibility
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: write
12
+
13
+ jobs:
14
+ compat:
15
+ runs-on: ubuntu-22.04
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Ruby (latest)
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ruby
23
+ bundler-cache: true
24
+ - name: Run test suite (non-blocking)
25
+ continue-on-error: true
26
+ run: |
27
+ set +e
28
+ bin/test.sh
29
+ echo "exit_code=$?" >> $GITHUB_ENV
30
+ set -e
31
+
32
+ - name: Generate compatibility badge
33
+ if: always()
34
+ run: |
35
+ PASSED=$(jq -r '.totals.shiny_json_logic.passed' results/ruby.json)
36
+ TOTAL=$(jq -r '.totals.shiny_json_logic.total' results/ruby.json)
37
+
38
+ PCT=$(awk "BEGIN { printf \"%.1f\", ($PASSED/$TOTAL)*100 }")
39
+
40
+ COLOR="red"
41
+ awk "BEGIN { exit !($PCT >= 90) }" && COLOR="yellow"
42
+ awk "BEGIN { exit !($PCT >= 97) }" && COLOR="green"
43
+
44
+ mkdir -p badges
45
+ cat > badges/compat.json <<EOF
46
+ {
47
+ "schemaVersion": 1,
48
+ "label": "compat",
49
+ "message": "${PCT}% (${PASSED}/${TOTAL})",
50
+ "color": "${COLOR}"
51
+ }
52
+ EOF
53
+
54
+ {
55
+ echo "## JSONLogic compatibility"
56
+ echo ""
57
+ echo "- **${PCT}%** (${PASSED}/${TOTAL})"
58
+ echo "- exit code (ignored): ${exit_code:-0}"
59
+ } >> $GITHUB_STEP_SUMMARY
60
+
61
+ # Commit automático del badge (solo master)
62
+ - name: Commit badge
63
+ if: github.ref == 'refs/heads/master'
64
+ run: |
65
+ if git diff --quiet -- badges/compat.json; then
66
+ echo "No badge change"
67
+ exit 0
68
+ fi
69
+
70
+ git config user.name "github-actions"
71
+ git config user.email "github-actions@github.com"
72
+ git add badges/compat.json
73
+ git commit -m "chore: update compat badge"
74
+ git push
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.7] - 2026-02-01
6
+ ### Added
7
+ - Adds support for `try` and `throw` operators.
8
+ - Introduces context mechanism to allow other operations to leverage context data.
9
+ ### Changed
10
+ - Refactors internal architecture to support context-aware operations.
11
+ - Returns to recursive approach as it is easier to develop this way
12
+ - If now handles rules calculation lazily
13
+
14
+ ## [0.1.6] - 2026-01-25
15
+ ### Added
16
+ - Improve CI support for Ruby 4.0 (no runtime changes).
17
+ - Adds GitHub Actions CI workflow for Ruby 4.0.
18
+
5
19
  ## [0.1.5] - 2026-01-25
6
20
  ### Changed
7
21
  - Improve RubyGems gem description (no runtime changes).
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiny_json_logic (0.1.6)
4
+ shiny_json_logic (0.1.7)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,14 +1,12 @@
1
1
  # ShinyJsonLogic ✨
2
2
 
3
- [![CI](https://github.com/luismoyano/shiny-json-logic-ruby/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/luismoyano/shiny-json-logic-ruby/actions/workflows/ci.yml)
3
+ ![Compatibility](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/luismoyano/shiny-json-logic-ruby/master/badges/compat.json)
4
4
  [![Gem Version](https://badge.fury.io/rb/shiny_json_logic.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/shiny_json_logic)
5
5
  ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7-brightgreen)
6
6
 
7
- > **A boring, correct, and production-ready JSON Logic implementation for Ruby. ✨**
7
+ > **A boring, correct and production-ready JSON Logic implementation for Ruby. ✨**
8
8
 
9
- **ShinyJsonLogic** is a **pure Ruby**, **zero-dependency** implementation of the JSON Logic specification.
10
-
11
- We exist because the original Ruby implementation has been neglected for years.
9
+ **ShinyJsonLogic** is a **pure Ruby**, **zero-dependency** JSON Logic implementation, designed to offer a reliable and well-tested engine for Ruby applications.
12
10
 
13
11
  This gem focuses on predictable behavior, strict spec alignment, high compatibility and long-term maintainability.
14
12
 
@@ -16,12 +14,11 @@ This gem focuses on predictable behavior, strict spec alignment, high compatibil
16
14
 
17
15
  ## Why ShinyJsonLogic?
18
16
 
19
- - **Zero runtime dependencies** (stdlib-only). Just plug & play!
20
- - **Ruby 2.7+ compatible, one of the lowest among other Ruby implementations**
21
- - **Actively maintained**
22
- - **High JSON Logic spec coverage**
23
- - **Iterative approach:** Stop worrying about long statements breaking your app.
24
- - ⭐ **Only Ruby implementation supporting the new standard operations up to date.**
17
+ - 🧩 **Zero runtime dependencies** (stdlib-only). Just plug & play!
18
+ - 🕰️ **Ruby 2.7+ compatible**, one of the lowest minimum versions supported in the Ruby ecosystem.
19
+ - 🔧 **Actively maintained** and continuously improved.
20
+ - 📊 **Highest JSON Logic compatibility in the Ruby ecosystem**, as measured against the official test suites.
21
+ - **Only Ruby implementation supporting the latest standard operators** (`val`, `exists`, `??`, `try`, `throw`)
25
22
 
26
23
  If you want JSON Logic to *just work* in Ruby, this is the safe default.
27
24
 
@@ -49,7 +46,7 @@ gem install shiny_json_logic
49
46
 
50
47
  and require it in your project:
51
48
 
52
- ```rubyruby
49
+ ```ruby
53
50
  require "shiny_json_logic"
54
51
  ```
55
52
 
@@ -102,7 +99,7 @@ Our goal is **full JSON Logic coverage**.
102
99
  Currently implemented operators include:
103
100
 
104
101
  ### Logic
105
- `if`, `and`, `or`, `!`, `!!`
102
+ `if`, `and`, `or`, `!`, `!!`, `?:`, `try`✨, `throw`✨
106
103
 
107
104
  ### Comparison
108
105
  `==`, `===`, `!=`, `!==`, `>`, `>=`, `<`, `<=`
@@ -123,7 +120,7 @@ Currently implemented operators include:
123
120
  `map, reduce, filter, some, all, none`
124
121
 
125
122
  📌 **Note:**
126
- `val`, `exists` and `??` are **only supported by ShinyJsonLogic** at the moment.
123
+ `val`, `exists`, `??`, `try` and `throw` are **only supported by ShinyJsonLogic** among Ruby implementations.
127
124
 
128
125
  (See `lib/shiny_json_logic/operations` for the authoritative list.)
129
126
 
@@ -131,9 +128,8 @@ Currently implemented operators include:
131
128
 
132
129
  ## Compatibility
133
130
 
134
- ShinyJsonLogic is designed to track the official JSON Logic specification as closely as possible.
135
-
136
- A compatibility PR against the JSON Logic test tables is currently in progress and will be linked here once merged.
131
+ Compatibility is measured automatically against the official JSONLogic test suites from `json-logic/compat-tables`.
132
+ See `badges/compat.json` for the exact numbers behind the badge.
137
133
 
138
134
  ---
139
135
 
@@ -155,7 +151,12 @@ bin/console
155
151
  Install locally:
156
152
 
157
153
  ```bash
158
- bundle exec rake install
154
+ bundle install
155
+ ```
156
+ How to run the compatibility tests:
157
+
158
+ ```bash
159
+ bin/test.sh
159
160
  ```
160
161
 
161
162
  ---
@@ -0,0 +1,6 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "label": "compat",
4
+ "message": "76.4% (860/1126)",
5
+ "color": "red"
6
+ }
data/bin/test.sh ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ cd "$ROOT_DIR"
6
+
7
+ # Config
8
+ COMPAT_REF="${COMPAT_REF:-main}"
9
+ COMPAT_REPO="${COMPAT_REPO:-json-logic/compat-tables}"
10
+ TMP_DIR="${TMP_DIR:-$ROOT_DIR/tmp}"
11
+ SUITES_DIR="$TMP_DIR/compat-suites"
12
+
13
+ # Helpers
14
+ log() { printf "\n\033[1m%s\033[0m\n" "$*"; }
15
+
16
+ log "Installing gems"
17
+ bundle install
18
+
19
+ # ---- Compatibility suites (optional but recommended) ----
20
+ # Download ONLY the suites folder from the compat-tables repo archive.
21
+ # This avoids a full git clone and works everywhere as long as curl+tar exist.
22
+
23
+ log "Fetching compat suites from GitHub: $COMPAT_REPO@$COMPAT_REF"
24
+
25
+ rm -rf "$SUITES_DIR"
26
+ mkdir -p "$SUITES_DIR"
27
+
28
+ ARCHIVE_URL="https://codeload.github.com/${COMPAT_REPO}/tar.gz/${COMPAT_REF}"
29
+
30
+ # Extract only: compat-tables-<ref>/suites -> tmp/compat-suites
31
+ # Different refs create different top-level folder names; we strip the first path component.
32
+ curl -fsSL "$ARCHIVE_URL" \
33
+ | tar -xz \
34
+ --strip-components=2 \
35
+ -C "$SUITES_DIR" \
36
+ "compat-tables-${COMPAT_REF}/suites" 2>/dev/null \
37
+
38
+ log "Compat suites ready at: $SUITES_DIR"
39
+
40
+ log "Running compatibility suite"
41
+ COMPAT_OUT="$TMP_DIR/compat-rspec.out"
42
+
43
+ set +e
44
+ COMPAT_SUITES_DIR="$SUITES_DIR" bundle exec rspec spec/compatibility_spec.rb | tee "$COMPAT_OUT"
45
+ COMPAT_EXIT=${PIPESTATUS[0]}
46
+ set -e
47
+
48
+ # Parse totals from RSpec summary
49
+ TOTAL=$(ruby -e '
50
+ s = STDIN.read
51
+ m = s.match(/(\d+)\s+examples?,\s+(\d+)\s+failures?/)
52
+ abort "Could not parse RSpec summary" unless m
53
+ puts m[1]
54
+ ' < "$COMPAT_OUT")
55
+
56
+ FAILURES=$(ruby -e '
57
+ s = STDIN.read
58
+ m = s.match(/(\d+)\s+examples?,\s+(\d+)\s+failures?/)
59
+ abort "Could not parse RSpec summary" unless m
60
+ puts m[2]
61
+ ' < "$COMPAT_OUT")
62
+
63
+ PASSED=$((TOTAL - FAILURES))
64
+
65
+ mkdir -p results
66
+ cat > results/ruby.json <<EOF
67
+ {
68
+ "totals": {
69
+ "shiny_json_logic": {
70
+ "passed": $PASSED,
71
+ "total": $TOTAL
72
+ }
73
+ }
74
+ }
75
+ EOF
76
+
77
+ echo "Compat: ${PASSED}/${TOTAL} (failures: ${FAILURES}, exit: ${COMPAT_EXIT})"
@@ -1,5 +1,3 @@
1
- require 'backport_dig' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
2
-
3
1
  class Array
4
2
  def self.wrap(object)
5
3
  return [] if object.nil?
@@ -1,123 +1,58 @@
1
1
  require "core_ext/array"
2
2
  require "core_ext/hash"
3
- Dir[File.join(__dir__, "operations/**/*.rb")].each do |file|
4
- require file
5
- end
3
+ require "shiny_json_logic/operator_solver"
6
4
 
7
5
  module ShinyJsonLogic
8
6
  class Engine
7
+ attr_reader :errors
8
+
9
9
  def initialize(rule, data = {})
10
10
  @rule = rule
11
- @data = data
11
+ @data = data || {}
12
+ @errors = []
12
13
  end
13
14
 
14
- def call
15
- stack = [[rule, data, :enter]]
16
- results = []
17
-
18
- while stack.any?
19
- node, ctx, state = stack.pop
20
-
21
- if state == :exit
22
- # Already evaluated children, we solve
23
- if node.is_a?(Hash)
24
- op, raw_args = node.to_a.first
25
-
26
- if collection_solvers.key?(op)
27
- results << collection_solvers.fetch(op).new(raw_args, ctx).call
28
- else
29
- if raw_args.is_a?(Array)
30
- argc = raw_args.size
31
- args = results.pop(argc)
32
- results << solvers.fetch(op).new(args, ctx).call
33
- else
34
- args = Array.wrap(results.pop)
35
- results << solvers.fetch(op).new(args, ctx).call
36
- end
37
- end
38
- elsif node.is_a?(Array)
39
- results << results.pop(node.size)
40
- end
41
-
42
- next
43
- end
44
-
45
- # ENTER phase
46
- case node
47
- when Hash
48
- op, raw_args = node.to_a.first
49
-
50
- stack << [node, ctx, :exit]
51
-
52
- unless collection_solvers.key?(op)
15
+ def call(rule = self.rule, data = self.data)
16
+ if rule.is_a?(Hash)
17
+ operation, raw_args = rule.to_a.first
18
+ if operations.collection_solvers.key?(operation)
19
+ solve(operation, raw_args, data)
20
+ else
21
+ evaluated_args =
53
22
  if raw_args.is_a?(Array)
54
- raw_args.reverse_each { |a| stack << [a, ctx, :enter] }
23
+ raw_args.map { |val| call(val, data) }
55
24
  else
56
- stack << [raw_args, ctx, :enter]
25
+ Array.wrap(call(raw_args, data))
57
26
  end
58
- end
59
27
 
60
- when Array
61
- stack << [node, ctx, :exit]
62
- node.reverse_each { |n| stack << [n, ctx, :enter] }
63
-
64
- else
65
- results << node
28
+ solve(operation, evaluated_args, data)
66
29
  end
30
+ elsif rule.is_a?(Array)
31
+ rule.map { |val| call(val, data) }
32
+ else
33
+ rule
67
34
  end
68
-
69
- results.last
70
35
  end
71
36
 
72
37
  private
73
38
 
74
- attr_reader :rule, :data
39
+ attr_reader :rule
40
+ attr_accessor :data
41
+ attr_writer :errors
42
+
43
+ def solve(operation, args, initial_data)
44
+ context = {"rules" => args, "data" => initial_data, "errors" => errors}
45
+ p operation, context
46
+ result, data, errors = operations.solvers.fetch(operation).new(context).call.values_at("result", "data", "errors")
47
+ self.errors = [*self.errors, *errors].uniq
48
+ self.data.merge data if self.data.is_a?(Hash) && data.is_a?(Hash)
49
+ p "RESULT AFTER #{operation.upcase}: #{result.inspect}"
75
50
 
76
- def solvers
77
- @@solvers ||= {
78
- "var" => Operations::Var,
79
- "missing" => Operations::Missing,
80
- "missing_some" => Operations::MissingSome,
81
- "if" => Operations::If,
82
- "==" => Operations::Equal,
83
- "===" => Operations::StrictEqual,
84
- "!=" => Operations::Different,
85
- "!==" => Operations::StrictDifferent,
86
- ">" => Operations::Greater,
87
- ">=" => Operations::GreaterEqual,
88
- "<" => Operations::Smaller,
89
- "<=" => Operations::SmallerEqual,
90
- "!" => Operations::Not,
91
- "or" => Operations::Or,
92
- "and" => Operations::And,
93
- "?:" => Operations::If,
94
- "in" => Operations::Inclusion,
95
- "cat" => Operations::Concatenation,
96
- "%" => Operations::Modulo,
97
- "max" => Operations::Max,
98
- "min" => Operations::Min,
99
- "+" => Operations::Addition,
100
- "*" => Operations::Product,
101
- "-" => Operations::Subtraction,
102
- "/" => Operations::Division,
103
- "substr" => Operations::Substring,
104
- "merge" => Operations::Merge,
105
- "!!" => Operations::DoubleNot,
106
- "val" => Operations::Val,
107
- "??" => Operations::Coalesce,
108
- "exists" => Operations::Exists,
109
- }
51
+ result
110
52
  end
111
53
 
112
- def collection_solvers
113
- @@collection_solvers ||= {
114
- "filter" => Operations::Filter,
115
- "map" => Operations::Map,
116
- "reduce" => Operations::Reduce,
117
- "all" => Operations::All,
118
- "none" => Operations::None,
119
- "some" => Operations::Some,
120
- }
54
+ def operations
55
+ @operations ||= OperatorSolver.new
121
56
  end
122
57
  end
123
58
  end
@@ -0,0 +1,20 @@
1
+ require "securerandom"
2
+
3
+ module ShinyJsonLogic
4
+ module Errors
5
+ class Base < StandardError
6
+ attr_reader :type, :id
7
+ attr_accessor :panic
8
+
9
+ def initialize(type: nil)
10
+ super(type)
11
+ @type = type
12
+ @id = "shiny_error_#{SecureRandom.uuid}"
13
+ end
14
+
15
+ def payload
16
+ @payload ||= { "type" => type }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module ShinyJsonLogic
2
+ module Numericals
3
+ module WithErrorHandling
4
+ def safe_arithmetic(&block)
5
+ result = yield
6
+ result.tap do |res|
7
+ if res.to_f.nan? || res == Float::INFINITY || res == -Float::INFINITY
8
+ self.data["type"] = "NaN"
9
+ error = ShinyJsonLogic::Errors::Base.new(type: "NaN")
10
+ errors.push error
11
+ return error.id
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,7 +3,9 @@ require "shiny_json_logic/operations/base"
3
3
  module ShinyJsonLogic
4
4
  module Operations
5
5
  class Addition < Base
6
- def call
6
+ protected
7
+
8
+ def run
7
9
  rules.map(&:to_f).reduce(:+)
8
10
  end
9
11
  end
@@ -6,10 +6,6 @@ module ShinyJsonLogic
6
6
  class All < Iterable::Base
7
7
  private
8
8
 
9
- def on_each(_item)
10
- ShinyJsonLogic.apply(filter, data)
11
- end
12
-
13
9
  def on_after(results)
14
10
  return false if results.empty?
15
11
 
@@ -4,7 +4,9 @@ require "shiny_json_logic/truthy"
4
4
  module ShinyJsonLogic
5
5
  module Operations
6
6
  class And < Base
7
- def call
7
+ protected
8
+
9
+ def run
8
10
  rules.reduce { |a, b| Truthy.call(a) ? b : a }
9
11
  end
10
12
  end
@@ -3,19 +3,31 @@ require "shiny_json_logic/truthy"
3
3
  module ShinyJsonLogic
4
4
  module Operations
5
5
  class Base
6
- def initialize(rules, data)
7
- @rules = rules
8
- @data = data
6
+ def initialize(context)
7
+ @context = context
8
+ @rules, @data, @errors = @context.values_at("rules", "data", "errors")
9
9
  end
10
10
 
11
11
  def call
12
- raise "Not implemented"
12
+ deliver run
13
+ rescue StandardError => e # TODO: refine error handling
14
+ error_type = e.is_a?(Errors::Base) ? e.type : e.class.to_s
15
+
16
+ Errors::Base.new(type: error_type)
13
17
  end
14
18
 
15
19
  protected
16
20
 
17
- attr_reader :rules
18
- attr_accessor :data
21
+ attr_reader :rules, :context
22
+ attr_accessor :data, :errors
23
+
24
+ def run
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def deliver(result = nil)
29
+ {"result" => result, "data" => self.data, "errors" => self.errors}
30
+ end
19
31
  end
20
32
  end
21
33
  end
@@ -3,7 +3,9 @@ require "shiny_json_logic/operations/base"
3
3
  module ShinyJsonLogic
4
4
  module Operations
5
5
  class Coalesce < Base
6
- def call
6
+ protected
7
+
8
+ def run
7
9
  rules.compact.first
8
10
  end
9
11
  end
@@ -3,7 +3,9 @@ require "shiny_json_logic/operations/base"
3
3
  module ShinyJsonLogic
4
4
  module Operations
5
5
  class Concatenation < Base
6
- def call
6
+ protected
7
+
8
+ def run
7
9
  return rules.map(&:to_s).join if rules.is_a?(Array)
8
10
 
9
11
  rules
@@ -5,7 +5,8 @@ module ShinyJsonLogic
5
5
  module Operations
6
6
  class Different < Base
7
7
  def call
8
- !Operations::Equal.new(rules, data).call
8
+ ctx = Operations::Equal.new(context).call
9
+ {"result" => !ctx["result"], "data" => ctx["data"], "errors" => ctx["errors"]}
9
10
  end
10
11
  end
11
12
  end
@@ -1,10 +1,16 @@
1
1
  require "shiny_json_logic/operations/base"
2
+ require "shiny_json_logic/numericals/with_error_handling"
2
3
 
3
4
  module ShinyJsonLogic
4
5
  module Operations
5
6
  class Division < Base
6
- def call
7
- rules.map(&:to_f).reduce(:/)
7
+ include Numericals::WithErrorHandling
8
+ protected
9
+
10
+ def run
11
+ safe_arithmetic do
12
+ rules.map(&:to_f).reduce(:/)
13
+ end
8
14
  end
9
15
  end
10
16
  end
@@ -4,7 +4,9 @@ require "shiny_json_logic/truthy"
4
4
  module ShinyJsonLogic
5
5
  module Operations
6
6
  class DoubleNot < Base
7
- def call
7
+ protected
8
+
9
+ def run
8
10
  !!Truthy.call(rules.first)
9
11
  end
10
12
  end
@@ -3,7 +3,9 @@ require "shiny_json_logic/operations/base"
3
3
  module ShinyJsonLogic
4
4
  module Operations
5
5
  class Equal < Base
6
- def call
6
+ protected
7
+
8
+ def run
7
9
  rules.map(&:to_s).all? { |v| v == rules[0].to_s }
8
10
  end
9
11
  end
@@ -3,7 +3,9 @@ require "shiny_json_logic/operations/base"
3
3
  module ShinyJsonLogic
4
4
  module Operations
5
5
  class Exists < Base
6
- def call
6
+ protected
7
+
8
+ def run
7
9
  current = data
8
10
 
9
11
  Array.wrap(rules).each do |segment|
@@ -13,7 +15,7 @@ module ShinyJsonLogic
13
15
  end
14
16
 
15
17
  true
16
- rescue
18
+ rescue StandardError
17
19
  false
18
20
  end
19
21
  end