diff --git a/ACCEPTED_PROPOSALS.md b/ACCEPTED_PROPOSALS.md index 8da32b5..7e22096 100644 --- a/ACCEPTED_PROPOSALS.md +++ b/ACCEPTED_PROPOSALS.md @@ -7,3 +7,4 @@ Define JSON Logic on the JSON Data Model | - Defines JSON Logic on the data mode `val` and context replacements | - Deprecates `var`, `missing` and `missing_some` to the legacy extension

- Defines `val`, `exists` and `??` | https://github.com/orgs/json-logic/discussions/18 | [`val.json`](tests/val.json), [`exists.json`](tests/exists.json), [`coalesce.json`](tests/coalesce.json) | 1 Amend Test Specification / Error Handling | - Defines that errors like `NaN` should bubble up the AST and halt execution, unless handled.

- Extends the test specification to be able to test for errors.

- Introduces two new operators, `throw` and `try`

- Defines that division by zero results in a `NaN` error. | https://github.com/orgs/json-logic/discussions/20 | [`throw.json`](tests/throw.json), [`try.json`](tests/try.json) | 1 Linear Time Ruling | - Establishes a guideline for selecting enabled by default JSON Logic operators.

- All JSON Logic Core operators must be definable to run in a worst case Linear Time; ≤ Θ(n).

- Community Extensions may be recognized with operators that exceed this, but they must not be recommended for enablement by default.

- This is not an implementation mandate, rather a guideline for TC and Organization members to select JSON Logic operators in a way that mitigates denial of service attacks when rules are executed from an untrusted source. | https://github.com/orgs/json-logic/discussions/24 | N/A | 3 + Arithmetic Operators | - The first proposal of several to evaluate existing operators and define any ambiguous behavior / improve consistency of the operators.

- Defined various `NaN` error states (invalid string coercions, divisions by zero)

- Defined Zero Argument and Single Argument and Variadic behavior for each operator (`+`, `-`, `*`, `/`, `%`)

- The variadic operators are treated as `foldLeft`

- Defined numeric coercion rules. | https://github.com/orgs/json-logic/discussions/21 | [plus.json](tests/plus.json), [minus.json](tests/minus.json), [multiply.json](tests/multiply.json), [divide.json](tests/divide.json), [modulo.json](tests/modulo.json) | 1 diff --git a/tests/divide.json b/tests/divide.json new file mode 100644 index 0000000..4e0fd50 --- /dev/null +++ b/tests/divide.json @@ -0,0 +1,193 @@ +[ + "# Collection of Divide Operator Tests", + { + "description": "Divide", + "rule": { "/": [4, 2] }, + "result": 2, + "data": null + }, + { + "description": "Divide to Decimal", + "rule": { "/": [2, 4] }, + "result": 0.5, + "data": null, + "decimal": true + }, + { + "description": "Divide with Multiple Operands", + "rule": { "/": [8, 2, 2] }, + "result": 2, + "data": null + }, + { + "description": "Divide with Multiple Operands (2)", + "rule": { "/": [2, 2, 1] }, + "result": 1, + "data": null + }, + { + "description": "Divide with Negative Numbers", + "rule": { "/": [-1, 2] }, + "result": -0.5, + "data": null, + "decimal": true + }, + { + "description": "Divide with Strings", + "rule": { "/": ["8", "2", "2"] }, + "result": 2, + "data": null + }, + { + "description": "Divide with Booleans", + "rule": { "/": [false, true] }, + "result": 0, + "data": null + }, + { + "description": "Divide with Multiple Value Types", + "rule": { "/": ["8", true, 2] }, + "result": 4, + "data": null + }, + { + "description": "Divide with Multiple Value Types (2)", + "rule": { "/": ["1", 1] }, + "result": 1, + "data": null + }, + { + "description": "Divide with Single Operand (Number)", + "rule": { "/": [1] }, + "result": 1, + "data": null + }, + { + "description": "Divide with zero operands is an error", + "rule": { "/": [] }, + "error": { "type": "Invalid Arguments" }, + "data": null + }, + { + "description": "Divide with Single Operand, Direct (Number)", + "rule": { "/": 1 }, + "result": 1, + "data": null + }, + { + "description": "Divide with Single Operand, Direct (0)", + "rule": { "/": 0 }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide Operator with Single Operand (Number)", + "rule": { "/": [1] }, + "result": 1, + "data": null + }, + { + "description": "Divide Operator with Single Operand (Negative Number)", + "rule": { "/": [-1] }, + "result": -1, + "data": null + }, + { + "description": "Divide Operator with Single Operand, Direct (Number)", + "rule": { "/": 1 }, + "result": 1, + "data": null + }, + { + "description": "Divide Operator with Single Operand, Direct (2)", + "rule": { "/": 2 }, + "result": 0.5, + "decimal": true, + "data": null + }, + { + "description": "Divide Operator with Single Operand, Direct (0)", + "rule": { "/": 0 }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide Operator with Single Operand (String)", + "rule": { "/": ["1"] }, + "result": 1, + "data": null + }, + { + "description": "Divide Operator with Single Operand, Direct (Negative Number String)", + "rule": { "/": "-1" }, + "result": -1, + "data": null + }, + + { + "description": "Divide Operator with Single Operand, Direct (String 0)", + "rule": { "/": "0" }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide Operator with Single Operand, Direct (true)", + "rule": { "/": true }, + "result": 1, + "data": null + }, + { + "description": "Divide Operator with Single Operand, Direct (false)", + "rule": { "/": false }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide Operator with Single Operand, Direct (Empty String)", + "rule": { "/": "" }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide Operator with a Single Operand, Direct (null)", + "rule": { "/": null }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide with val", + "rule": { "/": [{ "val": "x" }, { "val": "y" }] }, + "data": { "x": 8, "y": 2 }, + "result": 4 + }, + { + "description": "Divide by Zero", + "rule": { "/": [0, 0] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide with String produces NaN", + "rule": { "/": [1, "a"] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Divide with Array produces NaN", + "rule": { "/": [1, [1]] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Any division by zero should return NaN", + "rule": { "/": [1, 0] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Any division by zero should return NaN (2)", + "rule": { "/": [8, 2, 0] }, + "error": { "type": "NaN" }, + "data": null + } +] \ No newline at end of file diff --git a/tests/minus.json b/tests/minus.json new file mode 100644 index 0000000..d127d49 --- /dev/null +++ b/tests/minus.json @@ -0,0 +1,136 @@ +[ + "# Collection of Minus Operator Tests", + { + "description": "Subtraction", + "rule": { "-": [1, 2] }, + "result": -1, + "data": null + }, + { + "description": "Subtraction (2)", + "rule": { "-": [5, 12] }, + "result": -7, + "data": null + }, + { + "description": "Subtraction with Multiple Operands", + "rule": { "-": [1, 2, 3, 4] }, + "result": -8, + "data": null + }, + { + "description": "Subtraction with Negative Numbers", + "rule": { "-": [-1, 0, 5] }, + "result": -6, + "data": null + }, + { + "description": "Subtraction with Strings", + "rule": { "-": ["1", "2", "3"] }, + "result": -4, + "data": null + }, + { + "description": "Subtraction with Booleans", + "rule": { "-": [true, false, true] }, + "result": 0, + "data": null + }, + { + "description": "Subtraction with Multiple Value Types", + "rule": { "-": [1, "2", 3, "4", "", true, false, null] }, + "result": -9, + "data": null + }, + { + "description": "Minus Operator with Single Operand (Number)", + "rule": { "-": [1] }, + "result": -1, + "data": null + }, + { + "description": "Minus Operator with Single Operand (Negative Number)", + "rule": { "-": [-1] }, + "result": 1, + "data": null + }, + { + "description": "Minus with zero operands is an error", + "rule": { "-": [] }, + "error": { "type": "Invalid Arguments" }, + "data": null + }, + { + "description": "Minus Operator with Single Operand, Direct (Number)", + "rule": { "-": 1 }, + "result": -1, + "data": null + }, + { + "description": "Minus Operator with Single Operand, Direct (0)", + "rule": { "-": 0 }, + "result": 0, + "data": null + }, + { + "description": "Minus Operator with Single Operand (String)", + "rule": { "-": ["1"] }, + "result": -1, + "data": null + }, + { + "description": "Minus Operator with Single Operand, Direct (Negative Number String)", + "rule": { "-": "-1" }, + "result": 1, + "data": null + }, + + { + "description": "Minus Operator with Single Operand, Direct (String 0)", + "rule": { "-": "0" }, + "result": 0, + "data": null + }, + { + "description": "Minus Operator with Single Operand, Direct (true)", + "rule": { "-": true }, + "result": -1, + "data": null + }, + { + "description": "Minus Operator with Single Operand, Direct (false)", + "rule": { "-": false }, + "result": 0, + "data": null + }, + { + "description": "Minus Operator with Single Operand, Direct (Empty String)", + "rule": { "-": "" }, + "result": 0, + "data": null + }, + { + "description": "Minus Operator with a Single Operand, Direct (null)", + "rule": { "-": null }, + "result": 0, + "data": null + }, + { + "description": "Subtraction with val", + "rule": { "-": [{ "val": "x" }, { "val": "y" }] }, + "data": { "x": 1, "y": 2 }, + "result": -1 + }, + { + "description": "Subtraction with string produces NaN", + "rule": { "-": ["Hey", 1] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Subtraction with Array produces NaN", + "rule": { "-": [[1], 1] }, + "error": { "type": "NaN" }, + "data": null + } +] \ No newline at end of file diff --git a/tests/modulo.json b/tests/modulo.json new file mode 100644 index 0000000..d683870 --- /dev/null +++ b/tests/modulo.json @@ -0,0 +1,191 @@ +[ + "# Collection of Modulo Operator Tests", + { + "description": "Modulo", + "rule": { "%": [4, 2] }, + "result": 0, + "data": null + }, + { + "description": "Modulo (2)", + "rule": { "%": [2, 2] }, + "result": 0, + "data": null + }, + { + "description": "Modulo (3)", + "rule": { "%": [3, 2] }, + "result": 1, + "data": null + }, + { + "description": "Modulo with a Decimal Operand", + "rule": { "%": [1, 0.5] }, + "result": 0, + "data": null, + "decimal": true + }, + { + "description": "Modulo with Multiple Operands", + "rule": { "%": [8, 6, 3] }, + "result": 2, + "data": null + }, + { + "description": "Modulo with Multiple Operands (2)", + "rule": { "%": [2, 2, 1] }, + "result": 0, + "data": null + }, + { + "description": "Modulo with Negative Numbers", + "rule": { "%": [-1, 2] }, + "result": -1, + "data": null + }, + { + "description": "Modulo with Strings", + "rule": { "%": ["8", "2"] }, + "result": 0, + "data": null + }, + { + "description": "Modulo with Booleans", + "rule": { "%": [false, true] }, + "result": 0, + "data": null + }, + { + "description": "Modulo with Multiple Value Types", + "rule": { "%": ["8", 3, true] }, + "result": 0, + "data": null + }, + { + "description": "Modulo with Multiple Value Types (2)", + "rule": { "%": ["1", 1] }, + "result": 0, + "data": null + }, + { + "description": "Modulo with Single Operand (Number)", + "rule": { "%": [1] }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo with Single Operand, Direct (Number)", + "rule": { "%": 1 }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo with Single Operand, Direct (0)", + "rule": { "%": 0 }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand (Number)", + "rule": { "%": [1] }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand (Negative Number)", + "rule": { "%": [-1] }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo with zero operands is an error", + "rule": { "%": [] }, + "error": { "type": "Invalid Arguments" }, + "data": null + }, + { + "description": "Modulo Operator with Single Operand, Direct (Number)", + "rule": { "%": 1 }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand, Direct (0)", + "rule": { "%": 0 }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand (String)", + "rule": { "%": ["1"] }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand, Direct (Negative Number String)", + "rule": { "%": "-1" }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + + { + "description": "Modulo Operator with Single Operand, Direct (String 0)", + "rule": { "%": "0" }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand, Direct (true)", + "rule": { "%": true }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand, Direct (false)", + "rule": { "%": false }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with Single Operand, Direct (Empty String)", + "rule": { "%": "" }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo Operator with a Single Operand, Direct (null)", + "rule": { "%": null }, + "error": { "type": "Invalid Arguments"}, + "data": null + }, + { + "description": "Modulo with val", + "rule": { "%": [{ "val": "x" }, { "val": "y" }] }, + "data": { "x": 11, "y": 6 }, + "result": 5 + }, + { + "description": "Modulo with string produces NaN", + "rule": { "%": ["Hey", 1] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Modulo with array produces NaN", + "rule": { "%": [[1], 1] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Modulo with a negative dividend", + "rule": { "%": [-8, 3] }, + "result": -2, + "data": null + }, + { + "description": "Modulo with a negative divisor", + "rule": { "%": [8, -3] }, + "result": 2, + "data": null + } +] \ No newline at end of file diff --git a/tests/multiply.json b/tests/multiply.json new file mode 100644 index 0000000..796b374 --- /dev/null +++ b/tests/multiply.json @@ -0,0 +1,172 @@ +[ + "# Collection of Multiply Operator Tests", + { + "description": "Multiply", + "rule": { "*": [3, 2] }, + "result": 6, + "data": null + }, + { + "description": "Multiply with Multiple Operands", + "rule": { "*": [5, 10, 2] }, + "result": 100, + "data": null + }, + { + "description": "Multiply with Multiple Operands (2)", + "rule": { "*": [2, 2, 2] }, + "result": 8, + "data": null + }, + { + "description": "Multiply with Negative Numbers", + "rule": { "*": [-1, 2, 5] }, + "result": -10, + "data": null + }, + { + "description": "Multiply with Strings", + "rule": { "*": ["1", "2", "3"] }, + "result": 6, + "data": null + }, + { + "description": "Multiply with Booleans", + "rule": { "*": [true, false, true] }, + "result": 0, + "data": null + }, + { + "description": "Multiply with Multiple Value Types", + "rule": { "*": [1, "2", 3, "4", true] }, + "result": 24, + "data": null + }, + { + "description": "Multiply with Multiple Value Types (2)", + "rule": { "*": [1, "1"] }, + "result": 1, + "data": null + }, + { + "description": "Multiply with Multiple Value Types (3)", + "rule": { "*": ["1", null] }, + "result": 0, + "data": null + }, + { + "description": "Multiply with Single Operand (Number)", + "rule": { "*": [1] }, + "result": 1, + "data": null + }, + { + "description": "Multiply with Single Operand, Direct (Number)", + "rule": { "*": 1 }, + "result": 1, + "data": null + }, + { + "description": "Multiply with Single Operand, Direct (0)", + "rule": { "*": 0 }, + "result": 0, + "data": null + }, + { + "description": "Multiply Operator with Single Operand (Number)", + "rule": { "*": [1] }, + "result": 1, + "data": null + }, + { + "description": "Multiply Operator with Single Operand (Negative Number)", + "rule": { "*": [-1] }, + "result": -1, + "data": null + }, + { + "description": "Multiply with zero operands is 1 (Product)", + "rule": { "*": [] }, + "result": 1, + "data": null + }, + { + "description": "Multiply Operator with Single Operand, Direct (Number)", + "rule": { "*": 1 }, + "result": 1, + "data": null + }, + { + "description": "Multiply Operator with Single Operand, Direct (0)", + "rule": { "*": 0 }, + "result": 0, + "data": null + }, + { + "description": "Multiply Operator with Single Operand (String)", + "rule": { "*": ["1"] }, + "result": 1, + "data": null + }, + { + "description": "Multiply Operator with Single Operand, Direct (Negative Number String)", + "rule": { "*": "-1" }, + "result": -1, + "data": null + }, + + { + "description": "Multiply Operator with Single Operand, Direct (String 0)", + "rule": { "*": "0" }, + "result": 0, + "data": null + }, + { + "description": "Multiply Operator with Single Operand, Direct (true)", + "rule": { "*": true }, + "result": 1, + "data": null + }, + { + "description": "Multiply Operator with Single Operand, Direct (false)", + "rule": { "*": false }, + "result": 0, + "data": null + }, + { + "description": "Multiply Operator with Single Operand, Direct (Empty String)", + "rule": { "*": "" }, + "result": 0, + "data": null + }, + { + "description": "Multiply Operator with a Single Operand, Direct (null)", + "rule": { "*": null }, + "result": 0, + "data": null + }, + { + "description": "Multiply with val", + "rule": { "*": [{ "val": "x" }, { "val": "y" }] }, + "data": { "x": 8, "y": 2 }, + "result": 16 + }, + { + "description": "Multiply with string produces NaN", + "rule": { "*": ["Hey", 1] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Multiply with a single string produces NaN", + "rule": { "*": ["Hey"] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Multiply with Array produces NaN", + "rule": { "*": [[1], 1] }, + "error": { "type": "NaN" }, + "data": null + } +] \ No newline at end of file diff --git a/tests/plus.json b/tests/plus.json new file mode 100644 index 0000000..7dbe94a --- /dev/null +++ b/tests/plus.json @@ -0,0 +1,198 @@ +[ + "# Collection of Plus Operator Tests", + { + "description": "Addition", + "rule": { "+": [1, 2] }, + "result": 3, + "data": null + }, + { + "description": "Addition (2)", + "rule": { "+": [5, 12] }, + "result": 17, + "data": null + }, + { + "description": "Addition with Multiple Operands", + "rule": { "+": [1, 2, 3, 4] }, + "result": 10, + "data": null + }, + { + "description": "Addition with Negative Numbers", + "rule": { "+": [-1, 0, 5] }, + "result": 4, + "data": null + }, + { + "description": "Addition with Strings", + "rule": { "+": ["1", "2", "3"] }, + "result": 6, + "data": null + }, + { + "description": "Addition with Booleans", + "rule": { "+": [true, false, true] }, + "result": 2, + "data": null + }, + { + "description": "Addition with Multiple Value Types", + "rule": { "+": [1, "2", 3, "4", "", true, false, null] }, + "result": 11, + "data": null + }, + { + "description": "Plus Operator with Single Operand (Number)", + "rule": { "+": [1] }, + "result": 1, + "data": null + }, + { + "description": "Plus Operator with Single Operand (Negative Number)", + "rule": { "+": [-1] }, + "result": -1, + "data": null + }, + { + "description": "Plus Operator with zero operands is zero", + "rule": { "+": [] }, + "result": 0, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (Number)", + "rule": { "+": 1 }, + "result": 1, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (0)", + "rule": { "+": 0 }, + "result": 0, + "data": null + }, + { + "description": "Plus Operator with Single Operand (String)", + "rule": { "+": ["1"] }, + "result": 1, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (Negative Number String)", + "rule": { "+": "-1" }, + "result": -1, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (String Decimal)", + "rule": { "+": "1.5" }, + "result": 1.5, + "data": null, + "decimal": true + }, + { + "description": "Plus Operator with Single Operand, Direct (String Negative Decimal)", + "rule": { "+": "-1.5" }, + "result": -1.5, + "data": null, + "decimal": true + }, + { + "description": "Plus Operator with Single Operand, Direct (String 0.5)", + "rule": { "+": "0.5" }, + "result": 0.5, + "data": null, + "decimal": true + }, + { + "description": "Plus Operator with Single Operand, Direct (String 1e2)", + "rule": { "+": "1e2" }, + "result": 100, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (String 0)", + "rule": { "+": "0" }, + "result": 0, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (true)", + "rule": { "+": true }, + "result": 1, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (false)", + "rule": { "+": false }, + "result": 0, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct (Empty String)", + "rule": { "+": "" }, + "result": 0, + "data": null + }, + { + "description": "Plus Operator with a Single Operand, Direct (null)", + "rule": { "+": null }, + "result": 0, + "data": null + }, + { + "description": "Addition with val", + "rule": { "+": [{ "val": "x" }, { "val": "y" }] }, + "result": 3, + "data": { "x": 1, "y": 2 } + }, + { + "description": "Addition with string produces NaN", + "rule": { "+": ["Hey", 1] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Addition with Array produces NaN", + "rule": { "+": [[1], 1] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Addition with Array from context produces NaN", + "rule": { "+": [{ "val": "x" }, 1] }, + "error": { "type": "NaN" }, + "data": { "x": [1] } + }, + { + "description": "Addition with Object produces NaN", + "rule": { "+": [{ "val": "x" }, 1] }, + "error": { "type": "NaN" }, + "data": { "x": {} } + }, + { + "description": "Plus Operator with Single Operand, Invalid String Produces NaN", + "rule": { "+": "Hello" }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Array Input Produces NaN", + "rule": { "+": [[1]] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Object Input Produces NaN", + "rule": { "+": [{}] }, + "error": { "type": "NaN" }, + "data": null + }, + { + "description": "Plus Operator with Single Operand, Direct Object Input Produces NaN", + "rule": { "+": {} }, + "error": { "type": "NaN" }, + "data": null + } +] \ No newline at end of file