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.
- checksums.yaml +4 -4
- data/.github/workflows/compat.yml +74 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +1 -1
- data/README.md +19 -18
- data/badges/compat.json +6 -0
- data/bin/test.sh +77 -0
- data/lib/core_ext/array.rb +0 -2
- data/lib/shiny_json_logic/engine.rb +33 -98
- data/lib/shiny_json_logic/errors/base.rb +20 -0
- data/lib/shiny_json_logic/numericals/with_error_handling.rb +17 -0
- data/lib/shiny_json_logic/operations/addition.rb +3 -1
- data/lib/shiny_json_logic/operations/all.rb +0 -4
- data/lib/shiny_json_logic/operations/and.rb +3 -1
- data/lib/shiny_json_logic/operations/base.rb +18 -6
- data/lib/shiny_json_logic/operations/coalesce.rb +3 -1
- data/lib/shiny_json_logic/operations/concatenation.rb +3 -1
- data/lib/shiny_json_logic/operations/different.rb +2 -1
- data/lib/shiny_json_logic/operations/division.rb +8 -2
- data/lib/shiny_json_logic/operations/double_not.rb +3 -1
- data/lib/shiny_json_logic/operations/equal.rb +3 -1
- data/lib/shiny_json_logic/operations/exists.rb +4 -2
- data/lib/shiny_json_logic/operations/filter.rb +3 -1
- data/lib/shiny_json_logic/operations/greater.rb +3 -1
- data/lib/shiny_json_logic/operations/greater_equal.rb +3 -1
- data/lib/shiny_json_logic/operations/if.rb +32 -6
- data/lib/shiny_json_logic/operations/inclusion.rb +3 -1
- data/lib/shiny_json_logic/operations/iterable/base.rb +23 -9
- data/lib/shiny_json_logic/operations/max.rb +3 -1
- data/lib/shiny_json_logic/operations/merge.rb +3 -1
- data/lib/shiny_json_logic/operations/min.rb +3 -1
- data/lib/shiny_json_logic/operations/missing.rb +3 -1
- data/lib/shiny_json_logic/operations/missing_some.rb +5 -2
- data/lib/shiny_json_logic/operations/modulo.rb +10 -2
- data/lib/shiny_json_logic/operations/none.rb +0 -4
- data/lib/shiny_json_logic/operations/not.rb +3 -1
- data/lib/shiny_json_logic/operations/or.rb +3 -1
- data/lib/shiny_json_logic/operations/product.rb +8 -2
- data/lib/shiny_json_logic/operations/reduce.rb +11 -4
- data/lib/shiny_json_logic/operations/smaller.rb +3 -1
- data/lib/shiny_json_logic/operations/smaller_equal.rb +3 -1
- data/lib/shiny_json_logic/operations/some.rb +0 -4
- data/lib/shiny_json_logic/operations/strict_different.rb +2 -1
- data/lib/shiny_json_logic/operations/strict_equal.rb +4 -2
- data/lib/shiny_json_logic/operations/substring.rb +3 -1
- data/lib/shiny_json_logic/operations/subtraction.rb +10 -3
- data/lib/shiny_json_logic/operations/throw.rb +35 -0
- data/lib/shiny_json_logic/operations/try.rb +29 -0
- data/lib/shiny_json_logic/operations/val.rb +5 -1
- data/lib/shiny_json_logic/operations/var.rb +3 -1
- data/lib/shiny_json_logic/operator_solver.rb +66 -0
- data/lib/shiny_json_logic/version.rb +1 -1
- data/lib/shiny_json_logic.rb +6 -1
- data/results/ruby.json +8 -0
- metadata +11 -3
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9900ca19c7b287666c48cd69a5343a876f2c4d9c49e1ff224ae7c1b56cdf99b
|
|
4
|
+
data.tar.gz: eb6909a5a85affc348ddf9d0ed9b5395d384a34bb924aa410da40e5951168538
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/README.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# ShinyJsonLogic ✨
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
[](https://badge.fury.io/rb/shiny_json_logic)
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
> **A boring, correct
|
|
7
|
+
> **A boring, correct and production-ready JSON Logic implementation for Ruby. ✨**
|
|
8
8
|
|
|
9
|
-
**ShinyJsonLogic** is a **pure Ruby**, **zero-dependency** implementation
|
|
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
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
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
|
-
```
|
|
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
|
|
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
|
-
|
|
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
|
|
154
|
+
bundle install
|
|
155
|
+
```
|
|
156
|
+
How to run the compatibility tests:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
bin/test.sh
|
|
159
160
|
```
|
|
160
161
|
|
|
161
162
|
---
|
data/badges/compat.json
ADDED
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})"
|
data/lib/core_ext/array.rb
CHANGED
|
@@ -1,123 +1,58 @@
|
|
|
1
1
|
require "core_ext/array"
|
|
2
2
|
require "core_ext/hash"
|
|
3
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
23
|
+
raw_args.map { |val| call(val, data) }
|
|
55
24
|
else
|
|
56
|
-
|
|
25
|
+
Array.wrap(call(raw_args, data))
|
|
57
26
|
end
|
|
58
|
-
end
|
|
59
27
|
|
|
60
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
113
|
-
|
|
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,19 +3,31 @@ require "shiny_json_logic/truthy"
|
|
|
3
3
|
module ShinyJsonLogic
|
|
4
4
|
module Operations
|
|
5
5
|
class Base
|
|
6
|
-
def initialize(
|
|
7
|
-
@
|
|
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
|
-
|
|
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
|
|
@@ -5,7 +5,8 @@ module ShinyJsonLogic
|
|
|
5
5
|
module Operations
|
|
6
6
|
class Different < Base
|
|
7
7
|
def call
|
|
8
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
|
@@ -3,7 +3,9 @@ require "shiny_json_logic/operations/base"
|
|
|
3
3
|
module ShinyJsonLogic
|
|
4
4
|
module Operations
|
|
5
5
|
class Exists < Base
|
|
6
|
-
|
|
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
|