Files
purchases-ios/Sources/RulesEngine/CustomOperators/CustomOperators.swift
T
Antonio PallaresandCursor 267322158d Add rc.entries and rc.fromEntries custom operators (#7435)
* Add Scope plumbing and custom operator extension point

Thread evaluation scope through the rules engine and delegate unknown
operators to an empty CustomOperators dispatcher, without changing behavior.

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

* Fix project.pbxproj: restore RewardedAdTrackingMetadata entries

The RulesEngine registration edit accidentally dropped four
RewardedAdTrackingMetadata.swift entries from the Xcode project.
Restore them and normalize CustomOperators identifiers to 24 hex chars.

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

* Add rc.entries and rc.fromEntries for keyed object iteration

Introduce pair conversion operators so filter/map/some can iterate
subscriptions, entitlements, and other keyed objects via existing iteration ops.

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

* Use firstArgEvaluated for rc.entries argument convention

Promote the shared unary-arg helper to Operators and align rc.entries
and rc.fromEntries with json-logic-js spread semantics. Update fixtures
for wrapped array operands and add a multi-arg interop case.

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

* Promote firstArgEvaluated to shared Operators helpers

Move the unary-arg helper from LogicOperators to Operators so custom
operators can use the same json-logic-js spread convention.

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

* Address review comments on custom operator extension point

Drop the cross-engine registration note from CustomOperators, and restore
the single-line var extra-args warning that was reflowed without changing
its text.

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

* Reformat rc.entries fixtures so subexpressions read as units

The one-token-per-line formatting spread predicates over dozens of lines,
so a comparison like {"===": [{"var": "0"}, "a"]} took seven lines and its
shape was impossible to see at a glance. Subtrees that fit within 120
columns are now inlined, which keeps each operator and its operands
together on one line while deeply nested structures still expand.

No fixture content changed; the document is byte-identical after parsing,
key order included.

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

* Trim EntriesOperators docs of cross-repo references

Drops the cross-engine contract paragraph and the Audience.swift pointer.
Both described obligations outside this file, which will drift as those
places change.

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

* Make rc.entries throw typeMismatch on non-object, non-array input

Returning [] with a warning let a misaimed rule keep evaluating: a some
over an empty list is simply false, so a rule pointed at a string or a
missing variable reported a confident answer rather than a failure. Null,
undefined, bool, number and string now throw, matching rc.length.

Strings in particular do not yield the character pairs JS Object.entries
produces. This is JSON, not JS, and a string here means the rule targets
the wrong field.

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

* Make rc.fromEntries throw typeMismatch on malformed input

Skipping bad entries produced a partial object indistinguishable from a
legitimate one, letting a rule match on whichever keys happened to survive.
Non-array arguments and entries that are not two-element [key, value] pairs
now throw, so rc.fromEntries fails the same way as rc.entries.

Entry arity is checked exactly: a one-element entry no longer defaults its
value to null, and an over-long entry no longer silently ignores the extra
elements the way JS Object.fromEntries does.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 10:57:04 +00:00

48 lines
1.4 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.split":
return try SplitOperator.opSplit(args: args, vars: vars)
default:
throw EvaluationError.unsupportedOperator(name: operatorName)
}
}
}
}