Skip to content

Commit 09d2871

Browse files
authored
feat: add support for provider fatal error (#44)
1 parent 84e6f11 commit 09d2871

File tree

4 files changed

+52
-1
lines changed

4 files changed

+52
-1
lines changed

Sources/OpenFeature/exceptions/ErrorCode.swift

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ public enum ErrorCode: Int {
88
case targetingKeyMissing
99
case invalidContext
1010
case general
11+
case providerFatal
1112
}

Sources/OpenFeature/exceptions/OpenFeatureError.swift

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public enum OpenFeatureError: Error, Equatable {
99
case typeMismatchError
1010
case valueNotConvertableError
1111
case providerNotReadyError
12+
case providerFatarError(message: String)
1213

1314
public func errorCode() -> ErrorCode {
1415
switch self {
@@ -28,6 +29,8 @@ public enum OpenFeatureError: Error, Equatable {
2829
return .general
2930
case .providerNotReadyError:
3031
return .providerNotReady
32+
case .providerFatarError:
33+
return .providerFatal
3134
}
3235
}
3336
}
@@ -51,6 +54,8 @@ extension OpenFeatureError: CustomStringConvertible {
5154
return "Could not convert value"
5255
case .providerNotReadyError:
5356
return "The value was resolved before the provider was ready"
57+
case .providerFatarError(let message):
58+
return "A fatal error occurred in the provider: \(message)"
5459
}
5560
}
5661
}

Tests/OpenFeatureTests/FlagEvaluationTests.swift

+29
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,35 @@ final class FlagEvaluationTests: XCTestCase {
236236
XCTAssertNotNil(eventState)
237237
}
238238

239+
func testBrokenProviderThrowsFatal() {
240+
let provider = AlwaysBrokenProvider()
241+
provider.throwFatal = true
242+
let fatalExpectation = XCTestExpectation(description: "NotReady")
243+
244+
let eventState = provider.observe().sink { event in
245+
switch event {
246+
case .notReady:
247+
// The provider starts in this state.
248+
return
249+
case .error:
250+
fatalExpectation.fulfill()
251+
default:
252+
XCTFail("Unexpected event")
253+
}
254+
}
255+
OpenFeatureAPI.shared.setProvider(provider: provider)
256+
wait(for: [fatalExpectation], timeout: 5)
257+
258+
let client = OpenFeatureAPI.shared.getClient()
259+
260+
XCTAssertFalse(client.getValue(key: "testkey", defaultValue: false))
261+
let details = client.getDetails(key: "testkey", defaultValue: false)
262+
263+
XCTAssertEqual(details.errorCode, .providerFatal)
264+
XCTAssertEqual(details.reason, Reason.error.rawValue)
265+
XCTAssertEqual(details.errorMessage, "A fatal error occurred in the provider: Always broken")
266+
}
267+
239268
func testClientMetadata() {
240269
let client1 = OpenFeatureAPI.shared.getClient()
241270
XCTAssertNil(client1.metadata.name)

Tests/OpenFeatureTests/Helpers/AlwaysBrokenProvider.swift

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import Foundation
21
import Combine
2+
import Foundation
33

44
@testable import OpenFeature
55

66
class AlwaysBrokenProvider: FeatureProvider {
77
var metadata: ProviderMetadata = AlwaysBrokenMetadata()
88
var hooks: [any Hook] = []
9+
var throwFatal = false
910
private let eventHandler = EventHandler()
1011

1112
func onContextSet(oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext) {
@@ -19,30 +20,45 @@ class AlwaysBrokenProvider: FeatureProvider {
1920
func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws
2021
-> OpenFeature.ProviderEvaluation<Bool>
2122
{
23+
if self.throwFatal {
24+
throw OpenFeatureError.providerFatarError(message: "Always broken")
25+
}
2226
throw OpenFeatureError.flagNotFoundError(key: key)
2327
}
2428

2529
func getStringEvaluation(key: String, defaultValue: String, context: EvaluationContext?) throws
2630
-> OpenFeature.ProviderEvaluation<String>
2731
{
32+
if self.throwFatal {
33+
throw OpenFeatureError.providerFatarError(message: "Always broken")
34+
}
2835
throw OpenFeatureError.flagNotFoundError(key: key)
2936
}
3037

3138
func getIntegerEvaluation(key: String, defaultValue: Int64, context: EvaluationContext?) throws
3239
-> OpenFeature.ProviderEvaluation<Int64>
3340
{
41+
if self.throwFatal {
42+
throw OpenFeatureError.providerFatarError(message: "Always broken")
43+
}
3444
throw OpenFeatureError.flagNotFoundError(key: key)
3545
}
3646

3747
func getDoubleEvaluation(key: String, defaultValue: Double, context: EvaluationContext?) throws
3848
-> OpenFeature.ProviderEvaluation<Double>
3949
{
50+
if self.throwFatal {
51+
throw OpenFeatureError.providerFatarError(message: "Always broken")
52+
}
4053
throw OpenFeatureError.flagNotFoundError(key: key)
4154
}
4255

4356
func getObjectEvaluation(key: String, defaultValue: OpenFeature.Value, context: EvaluationContext?) throws
4457
-> OpenFeature.ProviderEvaluation<OpenFeature.Value>
4558
{
59+
if self.throwFatal {
60+
throw OpenFeatureError.providerFatarError(message: "Always broken")
61+
}
4662
throw OpenFeatureError.flagNotFoundError(key: key)
4763
}
4864

0 commit comments

Comments
 (0)