Skip to content

Commit

Permalink
fix(provider): throw OpenFeature exceptions on failed evaluations
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklasl committed Jul 10, 2024
1 parent a5e0f33 commit 76a9a6e
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 12 deletions.
26 changes: 19 additions & 7 deletions Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,31 @@ public class ConfidenceFeatureProvider: FeatureProvider {
public func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws
-> OpenFeature.ProviderEvaluation<Bool>
{
confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
}

public func getStringEvaluation(key: String, defaultValue: String, context: EvaluationContext?) throws
-> OpenFeature.ProviderEvaluation<String>
{
confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
}

public func getIntegerEvaluation(key: String, defaultValue: Int64, context: EvaluationContext?) throws
-> OpenFeature.ProviderEvaluation<Int64>
{
confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
}

public func getDoubleEvaluation(key: String, defaultValue: Double, context: EvaluationContext?) throws
-> OpenFeature.ProviderEvaluation<Double>
{
confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
}

public func getObjectEvaluation(key: String, defaultValue: OpenFeature.Value, context: EvaluationContext?)
throws -> OpenFeature.ProviderEvaluation<OpenFeature.Value>
{
confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
}

public func observe() -> AnyPublisher<OpenFeature.ProviderEvent, Never> {
Expand All @@ -134,8 +134,20 @@ public class ConfidenceFeatureProvider: FeatureProvider {
}

extension Evaluation {
func toProviderEvaluation() -> ProviderEvaluation<T> {
ProviderEvaluation(
func toProviderEvaluation() throws -> ProviderEvaluation<T> {
if let errorCode = self.errorCode {
switch errorCode {
case .providerNotReady:
throw OpenFeatureError.providerNotReadyError
case .invalidContext:
throw OpenFeatureError.invalidContextError
case .flagNotFound:
throw OpenFeatureError.flagNotFoundError(key: self.errorMessage ?? "unknown key")
case .evaluationError:
throw OpenFeatureError.generalError(message: self.errorMessage ?? "unknown error")
}
}
return ProviderEvaluation(
value: self.value,
variant: self.variant,
reason: self.reason.rawValue,
Expand Down
99 changes: 94 additions & 5 deletions Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import XCTest
@testable import Confidence

class ConfidenceProviderTest: XCTestCase {
private var readyExpectation = XCTestExpectation(description: "Ready")
private var errorExpectation = XCTestExpectation(description: "Error")

func testErrorFetchOnInit() async throws {
let readyExpectation = XCTestExpectation(description: "Ready")
class FakeClient: ConfidenceResolveClient {
func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult {
throw ConfidenceError.internalError(message: "test")
Expand All @@ -28,7 +26,7 @@ class ConfidenceProviderTest: XCTestCase {

let cancellable = OpenFeatureAPI.shared.observe().sink { event in
if event == .ready {
self.readyExpectation.fulfill()
readyExpectation.fulfill()
} else {
print(event)
}
Expand All @@ -38,6 +36,7 @@ class ConfidenceProviderTest: XCTestCase {
}

func testErrorStorageOnInit() async throws {
let errorExpectation = XCTestExpectation(description: "Error")
class FakeStorage: Storage {
func save(data: Encodable) throws {
// no-op
Expand Down Expand Up @@ -66,12 +65,102 @@ class ConfidenceProviderTest: XCTestCase {

let cancellable = OpenFeatureAPI.shared.observe().sink { event in
if event == .error {
self.errorExpectation.fulfill()
errorExpectation.fulfill()
} else {
print(event)
}
}
await fulfillment(of: [errorExpectation], timeout: 5.0)
cancellable.cancel()
}

func testProviderThrowsOpenFeatureErrors() async throws {
let context = MutableContext(targetingKey: "t")
let readyExpectation = XCTestExpectation(description: "Ready")
let storage = StorageMock()
class FakeClient: ConfidenceResolveClient {
var resolvedValues: [ResolvedValue] = [
ResolvedValue(
variant: "variant1",
value: .init(structure: ["int": .init(integer: 42)]),
flag: "flagName",
resolveReason: .match)
]
func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult {
return .init(resolvedValues: resolvedValues, resolveToken: "token")
}
}

let confidence = Confidence.Builder(clientSecret: "test")
.withContext(initialContext: ["targeting_key": .init(string: "t")])
.withFlagResolverClient(flagResolver: FakeClient())
.withStorage(storage: storage)
.build()

let provider = ConfidenceFeatureProvider(confidence: confidence, initializationStrategy: .fetchAndActivate)
OpenFeatureAPI.shared.setProvider(provider: provider)
let cancellable = OpenFeatureAPI.shared.observe().sink { event in
if event == .ready {
readyExpectation.fulfill()
} else {
print(event)
}
}
await fulfillment(of: [readyExpectation], timeout: 1.0)
cancellable.cancel()
let evaluation = try provider.getIntegerEvaluation(key: "flagName.int", defaultValue: -1, context: context)
XCTAssertEqual(evaluation.value, 42)

XCTAssertThrowsError(try provider.getIntegerEvaluation(
key: "flagNotFound.something",
defaultValue: -1,
context: context))
{ error in
// Optionally, you can also check the properties of the error
if let specificError = error as? OpenFeatureError {
XCTAssertEqual(specificError.errorCode(), ErrorCode.flagNotFound)
}
}
}
}

private class StorageMock: Storage {
var data = ""
var saveExpectation: XCTestExpectation?
private let storageQueue = DispatchQueue(label: "com.confidence.storagemock")

convenience init(data: Encodable) throws {
self.init()
try self.save(data: data)
}

func save(data: Encodable) throws {
try storageQueue.sync {
let dataB = try JSONEncoder().encode(data)
self.data = String(decoding: dataB, as: UTF8.self)

saveExpectation?.fulfill()
}
}

func load<T>(defaultValue: T) throws -> T where T: Decodable {
try storageQueue.sync {
if data.isEmpty {
return defaultValue
}
return try JSONDecoder().decode(T.self, from: try XCTUnwrap(data.data(using: .utf8)))
}
}

func clear() throws {
storageQueue.sync {
data = ""
}
}

func isEmpty() -> Bool {
storageQueue.sync {
return data.isEmpty
}
}
}

0 comments on commit 76a9a6e

Please sign in to comment.