Skip to content

Add JSON Logic log operator#3561

Merged
ajpallares merged 38 commits into
mainfrom
pallares/sdk-4368-android-log-operator
Jun 9, 2026
Merged

Add JSON Logic log operator#3561
ajpallares merged 38 commits into
mainfrom
pallares/sdk-4368-android-log-operator

Conversation

@ajpallares

@ajpallares ajpallares commented Jun 8, 2026

Copy link
Copy Markdown
Member

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-ios and hybrids

Motivation

Resolves SDK-4368
Add the JSON Logic log debug operator so predicates can emit values without affecting outcomes. Companion iOS PR: RevenueCat/purchases-ios#6945

Description

RulesEngineLogger exposes separate warn (engine diagnostics) and log (log operator passthrough) channels. Fixtures assert operator output via expectedLogs (exact match), distinct from expectedWarnings.

Notes

Logger messages carry no [RulesEngine] tag/prefix anymore. Hosts that bridge the logger (i.e. the SDKs) can prepend their own identifier.


Note

Medium Risk
Public RulesEngineLogger API change (new log method, warn signature change) affects SDK logger bridges; operator behavior is additive and well-covered by fixtures.

Overview
Adds the JSON Logic log operator so predicates can emit debug values without changing evaluation: the operand is evaluated, stringified with jsString, sent on a new RulesEngine.logger.log channel, and returned unchanged (json-logic-js identity behavior).

RulesEngineLogger is split into warn (engine diagnostics) and log (operator output). The optional tag parameter and [RulesEngine] prefix are removed; PrintLogger writes warnings to stderr and log messages to stdout.

Conformance tests gain optional expectedLogs (exact ordered match, unlike substring expectedWarnings), CapturingLogger / CapturingLoggerRule expose captured logs, and log.json adds 13 fixtures (suite count 347 → 360).

Reviewed by Cursor Bugbot for commit 84ce662. Bugbot is set up for automated code reviews on this repo. Configure here.

ajpallares and others added 30 commits June 5, 2026 14:11
…e fixtures

Adds per-operator JSON predicate fixtures (exact copies of the iOS #6885
corpus) under src/test/resources/predicate-fixtures, run by a shared
parameterized runner. Deletes the migrated arithmetic/comparison/equality/
logic suites and trims the accessor/evaluator/string-array suites to the
cases not expressible as predicate->boolean. Test-only change.

Co-authored-by: Cursor <cursoragent@cursor.com>
Expresses the "empty-string key resolves to the whole scope and is not
missing" case as a `{"!!":{"missing":[""]}}` -> false fixture (the string
coercion the other missing fixtures use can't distinguish [] from [""]).
Removes the now-covered Kotlin test and bumps the fixture count to 237.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds fixtures for the empty-segment dot-path splits, var default-vs-null-leaf
behavior, and the non-numeric missing_some threshold (7 new cases), and drops
the now-redundant Kotlin tests. AccessorOperatorsTest keeps only the cases
that cannot be a predicate->boolean fixture (top-level array scope or a
whole-object return). Fixture count is now 244.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror the iOS rules-engine implementation: Kotlin some/all operators
matching json-logic-js semantics, shared JSON predicate fixtures, and
pinned fixture count bumped to 271.

Co-authored-by: Cursor <cursoragent@cursor.com>
place operator docs on each op instead of duplicating at the type level; drop "not vacuous truth" wording

Co-authored-by: Cursor <cursoragent@cursor.com>
Implement variadic min/max with JS Math.min/Math.max semantics: operands
coerce via Number() (toNumberOrNull), empty input yields ±∞, and NaN
operands poison the result. Wire into the operator dispatcher and add
byte-identical min.json/max.json fixtures from purchases-ios, bumping
the pinned fixture count to 305.

Co-authored-by: Cursor <cursoragent@cursor.com>
Completes the json-logic-js iteration family on top of some/all.
none/map/filter rebind vars to the current item; reduce rebinds to
{current, accumulator}. Non-array sources: none -> true, map/filter
-> [], reduce -> seed unchanged. Coverage is the shared declarative
JSON fixtures (none/map/filter/reduce.json, byte-identical to iOS),
bumping the pinned fixture count to 345. Android counterpart of
RevenueCat/purchases-ios#6834.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Label each operator grouping in Operators.dispatch per PR review feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move the string/array group after comparison so the Android dispatcher
mirrors the iOS Operators.dispatch grouping order.

Co-authored-by: Cursor <cursoragent@cursor.com>
Rename the all-match fixture and add cases where only the first or only
the middle item matches, addressing PR review feedback that the existing
some coverage was order dependent. Fixtures kept byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
khepri guards all with notEmpty, so it returns false for an empty array
like json-logic-js; drop the inaccurate claim that khepri returns true.
Fixture kept byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
…teration-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
…erators' into pallares/json-logic-min-max-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/main/kotlin/com/revenuecat/purchases/rules/operators/Operators.kt
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
…in-max-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/main/kotlin/com/revenuecat/purchases/rules/operators/Operators.kt
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
Add a dedicated "Min and max" grouping comment so the min/max cases are not
lumped under arithmetic, matching the iOS dispatcher.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ators' into pallares/json-logic-iteration-mapping-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
Self-documenting replacement for the {"/": [-10,0]} workaround; constants
seeded only by the conformance harness (reservedConstants), engine untouched;
NaN still uses x != x; fixtures kept byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
substr.json and missing_some.json now use the seeded "+Infinity" and
"-Infinity" variables instead of division-by-zero operands. NaN cases
and evaluator.json are unchanged. Fixtures are byte-identical to iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror iOS predicate fixture updates: remove the redundant
"comes from the test-only" clause from 7 fixture descriptions
across min, max, substr, and missing_some. Fixtures kept
byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nfinity-fixture-vars

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/test/resources/predicate-fixtures/max.json
#	rules-engine-internal/src/test/resources/predicate-fixtures/min.json
…ture-vars' into pallares/json-logic-iteration-mapping-operators
Co-authored-by: Cursor <cursoragent@cursor.com>
…ument)

Co-authored-by: Cursor <cursoragent@cursor.com>
@ajpallares ajpallares added the pr:feat A new feature label Jun 8, 2026
@ajpallares ajpallares added pr:other and removed pr:feat A new feature labels Jun 8, 2026
ajpallares and others added 5 commits June 8, 2026 19:34
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Cover logging ±Infinity, NaN, empty object, and empty array. Empty
array and empty args were indistinguishable under substring matching
(they log "" and "null"), so `expectedLogs` now asserts the exact,
ordered list of emitted messages. `expectedWarnings` is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
The tag was always the single `[RulesEngine]` constant and never varied
at any call site, so it added interface surface without flexibility. For
the `log` channel a prefix also defeats the verbatim passthrough. Hosts
that bridge the logger prepend their own identifier when they need one.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ajpallares ajpallares marked this pull request as ready for review June 8, 2026 18:37
@ajpallares ajpallares requested a review from a team as a code owner June 8, 2026 18:37
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.28%. Comparing base (85b12f9) to head (84ce662).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3561   +/-   ##
=======================================
  Coverage   80.28%   80.28%           
=======================================
  Files         377      377           
  Lines       15386    15386           
  Branches     2134     2134           
=======================================
  Hits        12353    12353           
  Misses       2174     2174           
  Partials      859      859           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

"id": "log_is_identity_for_string",
"predicate": { "===": [{ "log": "apple" }, "apple"] },
"variables": {},
"expected": true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in the iOS PR, I wonder if this should be logging...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right. It is logging indeed. But this test was just testing for the log as the identity operator.
However, it doesn't hurt to also check for the expected logs, of course.
Added it in 78d43ef

Co-authored-by: Cursor <cursoragent@cursor.com>
Base automatically changed from pallares/json-logic-iteration-mapping-operators to main June 9, 2026 12:06
…roid-log-operator

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/main/kotlin/com/revenuecat/purchases/rules/operators/Operators.kt
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
@ajpallares ajpallares enabled auto-merge June 9, 2026 12:16
@ajpallares ajpallares added this pull request to the merge queue Jun 9, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 9, 2026
@ajpallares ajpallares added this pull request to the merge queue Jun 9, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 9, 2026
@ajpallares ajpallares added this pull request to the merge queue Jun 9, 2026
Merged via the queue into main with commit 2ba14c8 Jun 9, 2026
37 checks passed
@ajpallares ajpallares deleted the pallares/sdk-4368-android-log-operator branch June 9, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants