Files
purchases-ios/Sources/RulesEngine/CustomOperators/LengthOperator.swift
T
Antonio PallaresandCursor 2a59bec6e0 Add rc.length operator for strings and arrays (#7437)
* 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>

* 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>

* Add rc.length operator for string and array lengths

UTF-16 code-unit count for strings (JS String.length parity), element
count for arrays, typeMismatch for other inputs. Thirteen new fixtures.

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>

* Trim rc.length doc comment

Drop the rationale contrasting rc.length's throwing behavior with
rc.entries' lenient empty result.

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

* Pin that rc.length ignores extra arguments

The doc comment claimed extra args are silently ignored but no fixture
covered it. Under single-operand semantics ["abc", "ignored"] would
measure the arg list itself and yield 2, so the fixture fails if the
spread convention regresses.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 07:10:49 +00:00

50 lines
1.8 KiB
Swift

//
// LengthOperator.swift
//
// Created by Antonio Pallares.
//
import Foundation
extension RulesEngine {
/// `rc.length` — element count for arrays, UTF-16 code-unit count for
/// strings (JS `String.length` parity).
enum LengthOperator {
/// `{"rc.length": value}` — returns a numeric length suitable for
/// arithmetic and comparisons.
///
/// - **String**: length in **UTF-16 code units**, not Swift grapheme
/// clusters — matches JS `String.length` and the same choice in
/// `AccessorOperators.opMissingSome` (`string.utf16.count`).
/// - **Array**: element count (`.count`).
/// - **Anything else**: throws `EvaluationError.typeMismatch`.
///
/// Non-string/non-array inputs throw rather than defaulting to `0`.
/// A silent zero would make `{"==": [{"rc.length": {"var": "missing"}},
/// 0]}` quietly true when a key is absent.
///
/// Uses `Operators.firstArgEvaluated` spread semantics; extra arguments
/// are silently ignored. Literal array operands must be wrapped
/// (e.g. `{"rc.length": [[1, 2, 3]]}`), not passed as a multi-element
/// arg list.
static func opLength(args: Value, vars: Scope) throws -> Value {
let input = try Operators.firstArgEvaluated(args, vars: vars)
switch input {
case .string(let string):
return .int(Int64(string.utf16.count))
case .array(let items):
return .int(Int64(items.count))
default:
throw EvaluationError.typeMismatch(
message: "operator 'rc.length' expected string or array, got \(input)"
)
}
}
}
}