mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
Brings in: - The `RulesEngine` → `RulesEngineInternal` module rename from #6787. - The `no_leaking_rules_engine_internal_import` SwiftLint rule update from #6788. - Plus the `main`-merge cascade carried by #6788. Conflicts resolved: - `RulesEngineInternal/RulesEngineInternal.swift`: kept this branch's `Rules.logger` / `LoggerStorage` storage, dropped the `@_spi(Internal)` marker on `public enum Rules` (since #6787 dropped those markers; the new SwiftLint rule replaces that gate). - `RevenueCat.xcodeproj/project.pbxproj`: kept this branch's full file list (Evaluator, Logger, Value, RuleError, Operators/*, plus the new test files and helpers), then applied the producer-side rename to every `RulesEngine\b` / `RulesEngineTests\b` token (target name, group name, path, productName, bundle IDs `com.revenuecat.RulesEngineInternal[Tests]`, framework / xctest output names, and the `RulesEngine.swift` → `RulesEngineInternal.swift` file ref). Other rename follow-ups in this branch's added files: - `Tests/RulesEngineInternalTests/*.swift` (8 files) — every `[@_spi(Internal) ]@testable import RulesEngine` updated to `@testable import RulesEngineInternal`. The `@_spi(Internal)` attribute is no longer needed because the producer no longer uses SPI markers. - `Projects/RulesEngineInternal/`, `RulesEngineInternal/`, and `Tests/RulesEngineInternalTests/` directories were moved automatically by git's directory-rename detection (15 file renames). Left intentionally unchanged: - `RulesEngineInternal/Logger.swift` — the development-only `PrintLogger` keeps its `[RulesEngine]` log prefix because that string identifies the subsystem (the rules engine) for log readers, not the producer-side module name. The injected production logger replaces this struct entirely once the engine is wired into the SDK, so the prefix is throwaway. - `RulesEngineLogger` protocol name — internal type name within the module, not part of the import-site signal that motivated the module rename. - The `Rules` namespace name — separate identifier from the module name, kept stable so call sites still write `Rules.something`. Verified locally: - `swift build --target RulesEngineInternal` ✔ - `swift test --filter RulesEngineInternalTests` ✔ — 75 tests passed - `xcodebuild -scheme RulesEngineInternal -destination 'generic/platform=iOS' build` ✔ - `swiftlint --no-cache` ✔ — 0 violations across 1332 files - `git ls-files | xargs perl -ne 'if (/\bRulesEngine\b/ && !/RulesEngineInternal/) { ... }'` returns only the `[RulesEngine]` log prefix mentioned above. Co-authored-by: Cursor <cursoragent@cursor.com>
96 lines
3.5 KiB
Swift
96 lines
3.5 KiB
Swift
//
|
|
// LogicOperators.swift
|
|
//
|
|
// Created by Antonio Pallares.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Logic operators: `!`, `!!`, `and`, `or`, `if`.
|
|
///
|
|
/// `and`, `or`, and `if` short-circuit and therefore can't use the eager
|
|
/// `evalArgs` helpers — they evaluate their arguments one at a time.
|
|
enum LogicOperators {
|
|
|
|
/// `{"!": x}` — boolean negation. Coerces to bool first per JSON Logic
|
|
/// truthiness rules.
|
|
static func opNot(args: Value, vars: Value) throws -> Value {
|
|
let value = try firstArgEvaluated(args, vars: vars)
|
|
return .bool(!value.isTruthy)
|
|
}
|
|
|
|
/// `{"!!": x}` — boolean cast. Spec: equivalent to `!!x` in JS.
|
|
static func opNotNot(args: Value, vars: Value) throws -> Value {
|
|
let value = try firstArgEvaluated(args, vars: vars)
|
|
return .bool(value.isTruthy)
|
|
}
|
|
|
|
/// `{"and": [a, b, c]}` — short-circuit AND. Returns the first falsy
|
|
/// value or, if all are truthy, the last value (matches JS / JSON Logic
|
|
/// semantics: `and` returns the actual value, not a coerced boolean).
|
|
///
|
|
/// Empty input returns `.null` to mirror the json-logic-js spec, where
|
|
/// the empty-args case falls through to an uninitialized `current`
|
|
/// (i.e. `undefined`). Both are falsy, so observers that only check
|
|
/// truthiness keep working; the visible difference shows up in
|
|
/// constructs like `{"if": [{"and": []}, then, else]}`, which the spec
|
|
/// routes to `else`.
|
|
static func opAnd(args: Value, vars: Value) throws -> Value {
|
|
let items = Operators.argsAsList(args)
|
|
// Empty input falls through with `last == .null` (≈ JS `undefined`).
|
|
var last: Value = .null
|
|
for item in items {
|
|
last = try Evaluator.evaluateValue(item, vars: vars)
|
|
if !last.isTruthy {
|
|
return last
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
/// `{"or": [a, b, c]}` — short-circuit OR. Returns the first truthy
|
|
/// value or, if all are falsy, the last value.
|
|
///
|
|
/// Empty input returns `.null` to mirror the json-logic-js spec — same
|
|
/// reasoning as `opAnd`.
|
|
static func opOr(args: Value, vars: Value) throws -> Value {
|
|
let items = Operators.argsAsList(args)
|
|
// Empty input falls through with `last == .null` (≈ JS `undefined`).
|
|
var last: Value = .null
|
|
for item in items {
|
|
last = try Evaluator.evaluateValue(item, vars: vars)
|
|
if last.isTruthy {
|
|
return last
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
/// `{"if": [cond, then, else]}` — also supports chained
|
|
/// `[c1, t1, c2, t2, ..., else]` (think `else if`). Without an `else`
|
|
/// clause and with no truthy condition, returns `null`.
|
|
static func opIf(args: Value, vars: Value) throws -> Value {
|
|
let items = Operators.argsAsList(args)
|
|
var index = 0
|
|
while index + 1 < items.count {
|
|
let condition = try Evaluator.evaluateValue(items[index], vars: vars)
|
|
if condition.isTruthy {
|
|
return try Evaluator.evaluateValue(items[index + 1], vars: vars)
|
|
}
|
|
index += 2
|
|
}
|
|
if index < items.count {
|
|
return try Evaluator.evaluateValue(items[index], vars: vars)
|
|
}
|
|
return .null
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private static func firstArgEvaluated(_ args: Value, vars: Value) throws -> Value {
|
|
let items = Operators.argsAsList(args)
|
|
let first = items.first ?? .null
|
|
return try Evaluator.evaluateValue(first, vars: vars)
|
|
}
|
|
}
|