diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SplitProvider.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SplitProvider.xcscheme
new file mode 100644
index 0000000..fb2cde7
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/SplitProvider.xcscheme
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Sources/SplitProvider/Evaluator.swift b/Sources/SplitProvider/Evaluator.swift
new file mode 100644
index 0000000..aa50769
--- /dev/null
+++ b/Sources/SplitProvider/Evaluator.swift
@@ -0,0 +1,36 @@
+// Created by Martin Cardozo on 24/10/2025.
+
+import Split
+import OpenFeature
+
+final class Evaluator {
+
+ private let splitClient: SplitClient?
+
+ init(splitClient: SplitClient?) {
+ self.splitClient = splitClient
+ }
+
+ private func parseValue(_ value: String, as type: T.Type) -> T? {
+ switch type {
+ case is Bool.Type:
+ return (value.lowercased() == "true") as? T
+ case is Int64.Type:
+ return Int64(value) as? T
+ case is Double.Type:
+ return Double(value) as? T
+ case is String.Type:
+ return value as? T
+ case is OpenFeature.Value.Type:
+ return OpenFeature.Value.string(value) as? T
+ default:
+ return nil
+ }
+ }
+
+ internal func evaluate(key: String, defaultValue: T, context: (any EvaluationContext)?) -> ProviderEvaluation {
+ let treatment = splitClient?.getTreatment(key) ?? Constants.CONTROL.rawValue
+ let value = parseValue(treatment, as: T.self) ?? defaultValue
+ return ProviderEvaluation(value: value)
+ }
+}
diff --git a/Sources/SplitProvider/SplitProvider.swift b/Sources/SplitProvider/SplitProvider.swift
index 354d714..ec9e1a0 100644
--- a/Sources/SplitProvider/SplitProvider.swift
+++ b/Sources/SplitProvider/SplitProvider.swift
@@ -17,6 +17,7 @@ public class SplitProvider: FeatureProvider {
public var metadata: any OpenFeature.ProviderMetadata = SplitProviderMetadata()
private let eventHandler = EventHandler()
private var splitContext: InitContext?
+ internal var evaluator: Evaluator!
// MARK: Custom Initialization
public init(_ config: SplitClientConfig? = nil) {
@@ -45,12 +46,11 @@ public class SplitProvider: FeatureProvider {
// 2. Client setup
splitContext = InitContext(apiKey: API_KEY, userKey: USER_KEY)
let key: Key = Key(matchingKey: USER_KEY)
- if factory == nil {
- factory = DefaultSplitFactoryBuilder().setApiKey(API_KEY).setKey(key).setConfig(splitClientConfig ?? SplitClientConfig()).build()
- }
+ if factory == nil { factory = DefaultSplitFactoryBuilder().setApiKey(API_KEY).setKey(key).setConfig(splitClientConfig ?? SplitClientConfig()).build() }
splitClient = factory?.client
+ if evaluator == nil { evaluator = Evaluator(splitClient: splitClient) }
- // 3. Wait for SDK
+ // 3. Subscribe to events and wait for SDK
await withCheckedContinuation { (continuation: CheckedContinuation) in
var didResume = false
@@ -62,7 +62,7 @@ public class SplitProvider: FeatureProvider {
if error {
eventHandler.send(.error(errorCode: .general, message: "Provider timed out"))
} else {
- continuation.resume()
+ continuation.resume() // Pass control to openFeature again
}
}
@@ -80,32 +80,34 @@ public class SplitProvider: FeatureProvider {
// MARK: Evaluation Methods
extension SplitProvider {
-
- public func getBooleanEvaluation(key: String, defaultValue: Bool, context: (any OpenFeature.EvaluationContext)?) throws -> OpenFeature.ProviderEvaluation {
- ProviderEvaluation(value: false)
+
+ public func getBooleanEvaluation(key: String, defaultValue: Bool, context: (any EvaluationContext)?) throws -> ProviderEvaluation {
+ evaluator.evaluate(key: key, defaultValue: defaultValue, context: context) ?? ProviderEvaluation(value: defaultValue)
}
- public func getStringEvaluation(key: String, defaultValue: String, context: (any OpenFeature.EvaluationContext)?) throws -> OpenFeature.ProviderEvaluation {
- ProviderEvaluation(value: splitClient?.getTreatment(key) ?? Constants.CONTROL.rawValue)
+ public func getStringEvaluation(key: String, defaultValue: String, context: (any EvaluationContext)?) throws -> ProviderEvaluation {
+ evaluator.evaluate(key: key, defaultValue: defaultValue, context: context) ?? ProviderEvaluation(value: defaultValue)
}
- public func getIntegerEvaluation(key: String, defaultValue: Int64, context: (any OpenFeature.EvaluationContext)?) throws -> OpenFeature.ProviderEvaluation {
- ProviderEvaluation(value: 1)
+ public func getIntegerEvaluation(key: String, defaultValue: Int64, context: (any EvaluationContext)?) throws -> ProviderEvaluation {
+ evaluator.evaluate(key: key, defaultValue: defaultValue, context: context) ?? ProviderEvaluation(value: defaultValue)
}
- public func getDoubleEvaluation(key: String, defaultValue: Double, context: (any OpenFeature.EvaluationContext)?) throws -> OpenFeature.ProviderEvaluation {
- ProviderEvaluation(value: 1.0)
+ public func getDoubleEvaluation(key: String, defaultValue: Double, context: (any EvaluationContext)?) throws -> ProviderEvaluation {
+ evaluator.evaluate(key: key, defaultValue: defaultValue, context: context) ?? ProviderEvaluation(value: defaultValue)
}
- public func getObjectEvaluation(key: String, defaultValue: OpenFeature.Value, context: (any OpenFeature.EvaluationContext)?) throws -> OpenFeature.ProviderEvaluation {
- throw SplitError.notImplemented
+ public func getObjectEvaluation(key: String, defaultValue: Value, context: (any EvaluationContext)?) throws -> ProviderEvaluation {
+ evaluator.evaluate(key: key, defaultValue: defaultValue, context: context) ?? ProviderEvaluation(value: defaultValue)
}
public func observe() -> AnyPublisher {
- eventHandler.publisher.eraseToAnyPublisher()
+ eventHandler.publisher.eraseToAnyPublisher()
}
}
+// MARK: Open Feature
struct SplitProviderMetadata: ProviderMetadata {
let name: String? = Constants.PROVIDER_NAME.rawValue
}
+
diff --git a/Tests/SplitProviderTests/EvaluatorTests.swift b/Tests/SplitProviderTests/EvaluatorTests.swift
new file mode 100644
index 0000000..ff0bac6
--- /dev/null
+++ b/Tests/SplitProviderTests/EvaluatorTests.swift
@@ -0,0 +1,62 @@
+// Created by Martin Cardozo on 24/10/2025.
+
+import XCTest
+import OpenFeature
+@testable import SplitProvider
+
+final class EvaluatorTests: XCTestCase {
+
+ private func makeEvaluator(with treatment: String) -> Evaluator {
+ let client = ClientMock()
+ client.treatment = treatment
+ return Evaluator(splitClient: client)
+ }
+
+ func testBoolTrue() {
+ let SUT = makeEvaluator(with: "true")
+ let result = SUT.evaluate(key: "flag", defaultValue: false, context: nil)
+ XCTAssertEqual(result.value, true)
+ }
+
+ func testBoolFalse() {
+ let SUT = makeEvaluator(with: "FA LSE")
+ let result = SUT.evaluate(key: "flag", defaultValue: true, context: nil)
+ XCTAssertEqual(result.value, false)
+ }
+
+ func testInt64() {
+ let SUT = makeEvaluator(with: "123")
+ let result = SUT.evaluate(key: "flag", defaultValue: Int64(0), context: nil)
+ XCTAssertEqual(result.value, 123)
+ }
+
+ func testDouble() {
+ let SUT = makeEvaluator(with: "3.14")
+ let result = SUT.evaluate(key: "flag", defaultValue: 0.0, context: nil)
+ XCTAssertEqual(result.value, 3.14, accuracy: 0.0001)
+ }
+
+ func testString() {
+ let SUT = makeEvaluator(with: "banana")
+ let result = SUT.evaluate(key: "flag", defaultValue: "default", context: nil)
+ XCTAssertEqual(result.value, "banana")
+ }
+
+ func testValue() {
+ let SUT = makeEvaluator(with: "json_string")
+ let result = SUT.evaluate(key: "flag", defaultValue: OpenFeature.Value.string("default"), context: nil)
+ XCTAssertEqual(result.value, .string("json_string"))
+ }
+
+ func testInvalidTypeFallsBackToDefault() {
+ let SUT = makeEvaluator(with: "notAnInt")
+ let result = SUT.evaluate(key: "flag", defaultValue: Int64(42), context: nil)
+ XCTAssertEqual(result.value, 42, "Should return default when conversion fails")
+ }
+
+ func testWhenSplitClientIsNilReturnsControl() {
+ let SUT = Evaluator(splitClient: nil)
+ let result = SUT.evaluate(key: "flag", defaultValue: "default", context: nil)
+ XCTAssertEqual(result.value, Constants.CONTROL.rawValue)
+ }
+}
diff --git a/Tests/SplitProviderTests/SplitProviderTests.swift b/Tests/SplitProviderTests/SplitProviderTests.swift
index 1008e4f..dac2145 100644
--- a/Tests/SplitProviderTests/SplitProviderTests.swift
+++ b/Tests/SplitProviderTests/SplitProviderTests.swift
@@ -19,11 +19,11 @@ final class SplitProviderTests: XCTestCase {
override func tearDown() {
providerCancellable?.cancel()
}
+}
- func testNameIsCorrect() {
- XCTAssertTrue(SplitProvider().metadata.name == Constants.PROVIDER_NAME.rawValue)
- }
-
+// MARK: Setup Tests
+extension SplitProviderTests {
+
func testCorrectInitialization() {
let readyExp = expectation(description: "SDK Ready")
@@ -36,7 +36,7 @@ final class SplitProviderTests: XCTestCase {
switch event {
case .ready:
readyExp.fulfill()
- case .error(let errorCode, _):
+ case .error:
nonErrorExp.fulfill()
default:
break
@@ -130,10 +130,10 @@ final class SplitProviderTests: XCTestCase {
provider = SplitProvider()
// Setup events observer
- providerCancellable = OpenFeatureAPI.shared.observe().sink { [weak self] event in
+ providerCancellable = OpenFeatureAPI.shared.observe().sink { event in
switch event {
case .ready:
- self?.eval("mauro-test-flag")
+ break
case .error(let errorCode, _):
if errorCode == .invalidContext {
errorFired = true
@@ -220,13 +220,90 @@ final class SplitProviderTests: XCTestCase {
wait(for: [errorExp], timeout: 4)
}
+
+ func testNameIsCorrect() {
+ XCTAssertTrue(SplitProvider().metadata.name == Constants.PROVIDER_NAME.rawValue)
+ }
+}
- fileprivate func eval(_ flag: String) {
- do {
- let eval = try provider.getStringEvaluation(key: flag, defaultValue: "", context: nil)
- print("Flag value:", eval.value)
- } catch {
- print("Provider error:", error)
- }
+// MARK: Evaluation Tests
+extension SplitProviderTests {
+
+ func testBooleanEvaluationTrue() throws {
+ let client = ClientMock()
+ let evaluator = Evaluator(splitClient: client)
+ client.treatment = "true"
+
+ let provider = SplitProvider()
+ provider.splitClient = client
+ provider.evaluator = evaluator
+
+ let result = try provider.getBooleanEvaluation(key: "flag", defaultValue: false, context: nil)
+ XCTAssertEqual(result.value, true)
+ }
+
+ func testBooleanEvaluationWrong() throws {
+ let client = ClientMock()
+ let evaluator = Evaluator(splitClient: client)
+ client.treatment = "tru"
+
+ let provider = SplitProvider()
+ provider.splitClient = client
+ provider.evaluator = evaluator
+
+ let result = try provider.getBooleanEvaluation(key: "flag", defaultValue: false, context: nil)
+ XCTAssertEqual(result.value, false, "Treatment should be the default value")
+ }
+
+ func testBooleanEvaluationFalse() throws {
+ let client = ClientMock()
+ let evaluator = Evaluator(splitClient: client)
+ client.treatment = "false"
+
+ let provider = SplitProvider()
+ provider.splitClient = client
+ provider.evaluator = evaluator
+
+ let result = try provider.getBooleanEvaluation(key: "flag", defaultValue: false, context: nil)
+ XCTAssertEqual(result.value, false)
+ }
+
+ func testIntegerEvaluation() throws {
+ let client = ClientMock()
+ let evaluator = Evaluator(splitClient: client)
+ client.treatment = "123"
+
+ let provider = SplitProvider()
+ provider.splitClient = client
+ provider.evaluator = evaluator
+
+ let result = try provider.getIntegerEvaluation(key: "flag", defaultValue: 0, context: nil)
+ XCTAssertEqual(result.value, 123)
+ }
+
+ func testDoubleEvaluation() throws {
+ let client = ClientMock()
+ let evaluator = Evaluator(splitClient: client)
+ client.treatment = "3.14"
+
+ let provider = SplitProvider()
+ provider.splitClient = client
+ provider.evaluator = evaluator
+
+ let result = try provider.getDoubleEvaluation(key: "flag", defaultValue: 0.0, context: nil)
+ XCTAssertEqual(result.value, 3.14, accuracy: 0.0001)
+ }
+
+ func testStringEvaluation() throws {
+ let client = ClientMock()
+ let evaluator = Evaluator(splitClient: client)
+ client.treatment = "hello"
+
+ let provider = SplitProvider()
+ provider.splitClient = client
+ provider.evaluator = evaluator
+
+ let result = try provider.getStringEvaluation(key: "flag", defaultValue: "default", context: nil)
+ XCTAssertEqual(result.value, "hello")
}
}