Skip to content

Add rc.sortBy custom operator - #7504

Open
ajpallares wants to merge 8 commits into
mainfrom
pallares/rules-engine-rc-sort-by
Open

Add rc.sortBy custom operator#7504
ajpallares wants to merge 8 commits into
mainfrom
pallares/rules-engine-rc-sort-by

Conversation

@ajpallares

@ajpallares ajpallares commented Aug 25, 2026

Copy link
Copy Markdown
Member

Checklist

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

Motivation

Ordering a list is the one thing reduce cannot fake: comparing an item against the others in the same collection needs a scope the engine does not expose. Sorting through rc.entries key ordering is the closest workaround, and it collapses duplicate keys and misorders numbers.

Description

{"rc.sortBy": [array, keyExpression]} returns the original items in ascending key order. The key template is evaluated once per item with scope rebound to it, exactly as map does, so rc.rootVar is how a key reaches top-level data.

  • Keys must be all strings or all numbers. A mix, or a null/bool/array/object key, throws typeMismatch rather than inventing a cross-type ordering. String keys compare by UTF-16 code unit, as JS and Kotlin do, not by Swift's <.
  • Ties keep input order. Swift does not guarantee sorted(by:) is stable, so the tiebreak is explicit.
  • Always ascending. A third argument is rejected rather than ignored. To sort descending on a numeric key, negate it: {"-": [0, {"var": "score"}]}.

Note

Medium Risk
New Rules Engine operator affects predicate evaluation for any rule using rc.sortBy; behavior is heavily specified (UTF-16, stability, errors) but cross-platform parity with Android/hybrids is still a follow-up per the PR checklist.

Overview
Adds rc.sortBy to the Rules Engine so predicates can return an array’s original elements in ascending order by a per-item key expression—addressing cases where reduce or rc.entries ordering cannot compare items in-collection or preserve duplicates.

The operator takes [array, keyExpression]; the key is evaluated with scope rebound to each item (like map), wired through CustomOperators and a new SortByOperator. Keys must be homogeneous strings or numbers (numeric compare for numbers, UTF-16 code unit order for strings to match JS/Kotlin); mixed or invalid types, wrong arity, and non-finite numeric keys throw typeMismatch. Stable ties use explicit index tie-breaking because Swift’s sort is not guaranteed stable. Descending numeric sort is done by negating the key; negating string dates is rejected (NaN).

Conformance coverage adds rc_sort_by.json fixtures and bumps the pinned predicate fixture count 519 → 544.

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

Orders an array by a key computed per item, ascending, with ties keeping
their input order. Keys must be all strings or all numbers so the two
engines never have to invent a cross-type ordering.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ajpallares ajpallares added the pr:feat A new feature label Aug 25, 2026
@ajpallares ajpallares mentioned this pull request Aug 25, 2026
2 tasks
ajpallares and others added 5 commits August 26, 2026 11:48
A NaN key compares false in both directions, so every pair tied and the
input came back unsorted with no error. Negating a string key to attempt
a descending sort produced exactly that.

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

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

# Conflicts:
#	Tests/UnitTests/RulesEngine/PredicateFixtureTests.swift
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ajpallares
ajpallares marked this pull request as ready for review August 26, 2026 10:41
@ajpallares
ajpallares requested a review from a team as a code owner August 26, 2026 10:41
@ajpallares
ajpallares requested a review from a team August 26, 2026 10:42

@rickvdl rickvdl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice! Just one concern :)

Comment thread Sources/RulesEngine/CustomOperators/SortByOperator.swift Outdated
Swift's < orders by Unicode scalar and treats canonically equivalent
strings as equal, while JS and Kotlin order by UTF-16 code unit. The two
disagree once a scalar above the surrogate range meets an astral one, so
the same rule would sort differently on iOS and Android.

Co-authored-by: Cursor <cursoragent@cursor.com>
The fixture files are byte-identical across the iOS and Android engines, so
a description that explains the contract in terms of one host language's
string comparison does not read correctly in the other repository.

Co-authored-by: Cursor <cursoragent@cursor.com>
ajpallares added a commit to RevenueCat/purchases-android that referenced this pull request Aug 26, 2026
Kotlin's String.compareTo already compares code units, so this only ports
the fixtures added to RevenueCat/purchases-ios#7504 to keep the two fixture
sets byte-identical.

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

Copy link
Copy Markdown

❌ CI Job Failed — run-test-ios-26

View Build

Failed Tests

  • StoreKit2TransactionListenerTransactionUpdatesTests/testNotifiesDelegateForExistingTransactions()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants