mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
* Add rc.regexMatch custom operator Co-authored-by: Cursor <cursoragent@cursor.com> * Report why a regex pattern failed to compile The engine discarded the compile error and said only that the pattern was bad, which leaves whoever authored the rule to find the mistake by eye. NSRegularExpression has little to add today: every syntax failure is the same NSCocoaErrorDomain 2048 whose description restates the pattern. The catch is here so the message keeps its shape across the engines, where the JVM names the fault and points at the offset. Co-authored-by: Cursor <cursoragent@cursor.com> * Name the engines the regex operators actually run on The divergence list said ICU here and java.util.regex on Android, which is wrong: Android's java.util.regex has wrapped ICU4C since 2.3, so both devices run ICU and Funnels is the outlier. Read the old note and you would expect \d to behave differently on the two devices when it behaves differently in Funnels instead. Adds the rule the old list was missing — ICU rejects a bare literal } that JS accepts — and the one genuine device difference, unknown escapes. Co-authored-by: Cursor <cursoragent@cursor.com> * Pin how the regex operators read an empty input An empty input was untested, and it is the input a variable lookup is most likely to produce by accident. All three engines agree: the string is still searched, so a pattern that can match nothing succeeds on it and one that needs a character does not. The empty *pattern* is deliberately not fixtured. ICU refuses to compile it while JS reads it as a match at every position, so it is a divergence rather than a shared rule, and it is now recorded as one. Co-authored-by: Cursor <cursoragent@cursor.com> * Delegate regex syntax to the platform's own documentation The divergence list restated what each engine does, which the vendor pages already document and do more reliably than a comment that has to be kept true by hand. The syntax reference now lives behind a link, leaving only what this file decides for itself. Co-authored-by: Cursor <cursoragent@cursor.com> * Name only the pattern when a regex fails to compile The Android branch hand-assembled the first line of PatternSyntaxException's own message, and its iOS counterpart appended a localizedDescription that only repeats the pattern back. Neither is asserted anywhere, and the useful half is OpenJDK phrasing that a device's ICU-backed engine words differently. Co-authored-by: Cursor <cursoragent@cursor.com> * Bind both regex operands before reading them The arity guard proves the count matches what the caller asked for, not that it reaches two, so a caller passing a smaller count would index past the end. An out-of-bounds read traps, and a trap is the one failure the engine cannot hand back as an error. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
61 lines
1.8 KiB
Swift
61 lines
1.8 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 {
|
|
|
|
// swiftlint:disable:next cyclomatic_complexity
|
|
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.let":
|
|
return try LetOperator.opLet(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.regexMatch":
|
|
return try RegexOperators.opRegexMatch(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.slice":
|
|
return try SliceOperator.opSlice(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)
|
|
}
|
|
}
|
|
}
|
|
}
|