Skip to content

Add JSON Logic log operator#6945

Merged
ajpallares merged 60 commits into
mainfrom
pallares/sdk-4367-ios-log-operator
Jun 9, 2026
Merged

Add JSON Logic log operator#6945
ajpallares merged 60 commits into
mainfrom
pallares/sdk-4367-ios-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-android and hybrids

Motivation

Resolves SDK-4367
Add the JSON Logic log debug operator so predicates can emit values without affecting outcomes. Companion Android PR: RevenueCat/purchases-android#3561

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
Changes the public RulesEngineLogger protocol (removes warn tag, adds log), which any custom logger bridges must adopt; rule outcomes are unchanged aside from new debug-only operator behavior.

Overview
Adds the JSON Logic log operator so predicates can emit debug output without changing evaluation results. MiscOperators.opLog evaluates its argument, sends a JS-style string via RulesEngine.logger.log, and returns the value unchanged (identity passthrough).

RulesEngineLogger is split into warn (engine diagnostics) and log (operator output); warn no longer takes a tag and default PrintLogger messages are unprefixed (no [RulesEngine]). Predicate conformance tests gain expectedLogs (exact ordered match) alongside existing warning checks, plus 13 fixtures in log.json; total fixture count is 360.

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

ajpallares and others added 30 commits June 2, 2026 14:56
…tures

Convert the hand-written operator unit tests for operators available at the
string+array level into declarative JSON predicate fixtures sharing khepri's
conformance format, run by a shared runner that auto-discovers every file in
PredicateFixtures/. `expected` is a khepri-compatible superset (Bool or
{ "error": ... }) plus optional `description` and `expectedWarnings`.

Adds a test-only Value: Decodable conformance so predicates and variables
decode straight into the engine's value model; production Value is unchanged.
Tests that can't be expressed as predicate->Bool stay in Swift; ValueTests
untouched. Test-only change.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 40901e5)
Load the in-repo fixtures once and feed the decoded cases straight into
the parameterized test, instead of collecting IDs and re-reading every
file to find the matching case per ID. PredicateConformanceFixtureCase
gains Identifiable + Sendable (for the arguments) and a
CustomTestStringConvertible extension so each case still displays by id.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Asserting an exact warning count coupled the fixtures to engine
internals. Match warnings by substring only (count-agnostic); an empty
`contains` now asserts that no warning is emitted, preserving the
"does not warn" cases. The three count-only fixtures gain the
"missing variable" substring they actually emit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the khepri single-file plumbing (defaultFixtureURL, fixtureURL,
the no-arg loadCases(), the env var, and the unused fixtureNotFound
error). This PR only runs the in-repo PredicateFixtures/ directory; the
khepri conformance loader lands with its test in the conformance PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
Implements the json-logic-js some/all iteration predicates. Operator coverage
is expressed as JSON predicate fixtures (some.json / all.json) run by the
shared fixture runner introduced downstack, replacing hand-written unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit d11529c)
Implements the json-logic-js min/max operators (variadic flat list, ±∞ for
empty input, NaN propagation for non-numeric operands). Coverage is expressed
as JSON predicate fixtures (min.json / max.json) run by the shared fixture
runner, replacing hand-written unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit e73eabe)
Completes the json-logic-js iteration family (none/map/filter/reduce) on top
of some/all. Coverage is expressed as JSON predicate fixtures
(none.json / map.json / filter.json / reduce.json) run by the shared fixture
runner, replacing hand-written unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 46d6925)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	RevenueCat.xcodeproj/project.pbxproj
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	RevenueCat.xcodeproj/project.pbxproj
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	RevenueCat.xcodeproj/project.pbxproj
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	RevenueCat.xcodeproj/project.pbxproj
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 [""].
Bumps the pinned fixture count to 237. Keeps the corpus in sync with the
purchases-android counterpart.

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), bumping
the pinned count to 244. Also adds the var-null-path Swift test so the
non-fixturizable cases match the purchases-android counterpart. Keeps the
fixture corpus byte-identical across both repos.

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

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

# Conflicts:
#	Tests/RulesEngineInternalTests/PredicateFixtureTests.swift
…json-logic-min-max-operators

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

# Conflicts:
#	Tests/RulesEngineInternalTests/PredicateFixtureTests.swift
…on-logic-iteration-mapping-operators

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

# Conflicts:
#	Tests/RulesEngineInternalTests/PredicateFixtureTests.swift
ajpallares and others added 9 commits June 8, 2026 10:52
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.

Co-authored-by: Cursor <cursoragent@cursor.com>
…erators' into pallares/json-logic-min-max-operators

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

# Conflicts:
#	Tests/RulesEngineInternalTests/PredicateFixtureTests.swift
Remove the duplicate WorkflowsCacheTests.swift PBXFileReference introduced
during a merge, and add a dedicated "Min and max" grouping comment so the
min/max cases are no longer lumped under arithmetic.

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

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

# Conflicts:
#	Tests/RulesEngineInternalTests/PredicateFixtureTests.swift
…teration-mapping-operators

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

# Conflicts:
#	Tests/RulesEngineInternalTests/PredicateFixtureTests.swift
#	Tests/RulesEngineInternalTests/PredicateFixtures/max.json
#	Tests/RulesEngineInternalTests/PredicateFixtures/min.json
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 protocol 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
@ajpallares ajpallares force-pushed the pallares/sdk-4367-ios-log-operator branch from 0ee4942 to b9af9ae Compare June 9, 2026 07:02

@tonidero tonidero left a comment

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.

Just a question about the tests

"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.

Hmm no expected logs in this case? Similar case in some other tests below

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.

Expected logs is an optional parameter that is only asserted when present. However, it doesn't hurt to check for these. So added that in 769d0a3

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

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

# Conflicts:
#	RulesEngineInternal/Operators/Operators.swift
#	Tests/RulesEngineInternalTests/PredicateFixtureTests.swift
@ajpallares ajpallares enabled auto-merge (squash) June 9, 2026 11:53
@ajpallares ajpallares merged commit e196a96 into main Jun 9, 2026
18 of 20 checks passed
@ajpallares ajpallares deleted the pallares/sdk-4367-ios-log-operator branch June 9, 2026 12:03
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