Files
purchases-ios/Sources/RulesEngine/CustomOperators/CustomOperators.swift
T
Antonio PallaresandCursor 4bd110f723 Add rc.sortBy custom operator (#7504)
* Add rc.sortBy custom operator

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>

* Reject non-finite rc.sortBy keys

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>

* Note ascending order in the rc.sortBy summary

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

* Trim the rc.sortBy doc comment

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

* Trim the rc.sortBy stability comment

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

* Order rc.sortBy string keys by UTF-16 code unit

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>

* Keep the rc.sortBy ordering fixtures platform-neutral

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>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 08:10:18 +02:00

54 lines
1.6 KiB
Swift

//
// CustomOperators.swift
//
// Created by Antonio Pallares.
//
import Foundation
extension RulesEngine {
/// RevenueCat-specific JSON Logic operators.
///
/// Operators here are namespaced under `rc.` so they cannot collide with a
/// future standard JSON Logic operator.
enum CustomOperators {
static func dispatch(
op operatorName: String,
args: Value,
vars: Scope
) throws -> Value {
switch operatorName {
case "rc.entries":
return try EntriesOperators.opEntries(args: args, vars: vars)
case "rc.fromEntries":
return try EntriesOperators.opFromEntries(args: args, vars: vars)
case "rc.length":
return try LengthOperator.opLength(args: args, vars: vars)
case "rc.lower":
return try CaseOperators.opLower(args: args, vars: vars)
case "rc.upper":
return try CaseOperators.opUpper(args: args, vars: vars)
case "rc.rootVar":
return try RootVarOperator.opRootVar(args: args, vars: vars)
case "rc.semverCompare":
return try SemverOperator.opSemverCompare(args: args, vars: vars)
case "rc.sortBy":
return try SortByOperator.opSortBy(args: args, vars: vars)
case "rc.split":
return try SplitOperator.opSplit(args: args, vars: vars)
default:
throw EvaluationError.unsupportedOperator(name: operatorName)
}
}
}
}