Skip to content

Commit bd70399

Browse files
feat: Openfeature SDK spec 0.8 support (#15)
* feat: Openfeature SDK spec 0.8 support Signed-off-by: Thomas Poignant <[email protected]> * fix(lint): Fix linting issues Signed-off-by: Thomas Poignant <[email protected]> * Use correct dependency Signed-off-by: Thomas Poignant <[email protected]> --------- Signed-off-by: Thomas Poignant <[email protected]>
1 parent 6b90274 commit bd70399

11 files changed

+167
-195
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ let package = Package(
1818
targets: ["OFREP"])
1919
],
2020
dependencies: [
21-
.package(url: "https://github.com/open-feature/swift-sdk.git", from: "0.2.0")
21+
.package(url: "https://github.com/open-feature/swift-sdk.git", from: "0.3.0"),
2222
],
2323
targets: [
2424
.target(

Sources/GOFeatureFlag/hook/data_collector_bool_hook.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ class BooleanHook: Hook {
6666
self.dataCollectorMngr.appendFeatureEvent(event: event)
6767
}
6868

69-
func finallyAfter<HookValue>(ctx: HookContext<HookValue>, hints: [String: Any]) {
69+
func finally<HookValue>(
70+
ctx: HookContext<HookValue>, details: FlagEvaluationDetails<HookValue>, hints: [String: Any]) {
7071
return
7172
}
7273
}

Sources/GOFeatureFlag/hook/data_collector_double_hook.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ class DoubleHook: Hook {
6767
self.dataCollectorMngr.appendFeatureEvent(event: event)
6868
}
6969

70-
func finallyAfter<HookValue>(ctx: HookContext<HookValue>, hints: [String: Any]) {
70+
func finally<HookValue>(
71+
ctx: HookContext<HookValue>, details: FlagEvaluationDetails<HookValue>, hints: [String: Any]) {
7172
return
7273
}
7374
}

Sources/GOFeatureFlag/hook/data_collector_integer_hook.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ class IntegerHook: Hook {
6363
self.dataCollectorMngr.appendFeatureEvent(event: event)
6464
}
6565

66-
func finallyAfter<HookValue>(ctx: HookContext<HookValue>, hints: [String: Any]) {
66+
func finally<HookValue>(
67+
ctx: HookContext<HookValue>, details: FlagEvaluationDetails<HookValue>, hints: [String: Any]) {
6768
return
6869
}
6970
}

Sources/GOFeatureFlag/hook/data_collector_object_hook.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ class ObjectHook: Hook {
6565
self.dataCollectorMngr.appendFeatureEvent(event: event)
6666
}
6767

68-
func finallyAfter<HookValue>(ctx: HookContext<HookValue>, hints: [String: Any]) {
68+
func finally<HookValue>(
69+
ctx: HookContext<HookValue>, details: FlagEvaluationDetails<HookValue>, hints: [String: Any]) {
6970
return
7071
}
7172
}

Sources/GOFeatureFlag/hook/data_collector_string_hook.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ class StringHook: Hook {
6666
self.dataCollectorMngr.appendFeatureEvent(event: event)
6767
}
6868

69-
func finallyAfter<HookValue>(ctx: HookContext<HookValue>, hints: [String: Any]) {
69+
func finally<HookValue>(
70+
ctx: HookContext<HookValue>, details: FlagEvaluationDetails<HookValue>, hints: [String: Any]) {
7071
return
7172
}
7273
}

Sources/GOFeatureFlag/provider.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ public final class GoFeatureFlagProvider: FeatureProvider {
3838
)
3939
}
4040

41-
public func initialize(initialContext: (any OpenFeature.EvaluationContext)?) {
41+
public func initialize(initialContext: OpenFeature.EvaluationContext?) async throws {
4242
self.hooks = dataCollectorMngr.getHooks()
43-
self.ofrepProvider.initialize(initialContext: initialContext)
43+
try await self.ofrepProvider.initialize(initialContext: initialContext)
4444

4545
if self.options.dataCollectorInterval > 0 {
4646
self.hooks.append(BooleanHook(dataCollectorMngr: self.dataCollectorMngr))
@@ -54,8 +54,8 @@ public final class GoFeatureFlagProvider: FeatureProvider {
5454

5555
public func onContextSet(
5656
oldContext: (any OpenFeature.EvaluationContext)?,
57-
newContext: any OpenFeature.EvaluationContext) {
58-
self.ofrepProvider.onContextSet(
57+
newContext: any OpenFeature.EvaluationContext) async throws {
58+
try await self.ofrepProvider.onContextSet(
5959
oldContext: oldContext,
6060
newContext: newContext)
6161
}
@@ -115,7 +115,7 @@ public final class GoFeatureFlagProvider: FeatureProvider {
115115
context: context)
116116
}
117117

118-
public func observe() -> AnyPublisher<OpenFeature.ProviderEvent, Never> {
118+
public func observe() -> AnyPublisher<OpenFeature.ProviderEvent?, Never> {
119119
return self.ofrepProvider.observe()
120120
}
121121
}

Sources/OFREP/ofrep.swift

+38-39
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ struct Metadata: ProviderMetadata {
77
}
88

99
public class OfrepProvider: FeatureProvider {
10-
private let eventHandler = EventHandler(ProviderEvent.notReady)
10+
private let eventHandler = EventHandler()
1111
private var evaluationContext: OpenFeature.EvaluationContext?
1212

1313
private var options: OfrepProviderOptions
@@ -26,54 +26,53 @@ public class OfrepProvider: FeatureProvider {
2626
self.ofrepAPI = OfrepAPI(networkingService: networkService, options: self.options)
2727
}
2828

29-
public func observe() -> AnyPublisher<OpenFeature.ProviderEvent, Never> {
30-
return eventHandler.observe()
31-
}
32-
3329
public var hooks: [any Hook] = []
3430
public var metadata: ProviderMetadata = Metadata()
3531

36-
public func initialize(initialContext: (any OpenFeature.EvaluationContext)?) {
32+
public func observe() -> AnyPublisher<OpenFeature.ProviderEvent?, Never> {
33+
return eventHandler.observe()
34+
}
35+
36+
public func initialize(initialContext: (any OpenFeature.EvaluationContext)?) async throws {
3737
self.evaluationContext = initialContext
38-
Task {
39-
do {
40-
let status = try await self.evaluateFlags(context: self.evaluationContext)
41-
if self.options.pollInterval > 0 {
42-
self.startPolling(pollInterval: self.options.pollInterval)
43-
}
38+
do {
39+
let status = try await self.evaluateFlags(context: self.evaluationContext)
40+
if self.options.pollInterval > 0 {
41+
self.startPolling(pollInterval: self.options.pollInterval)
42+
}
4443

45-
if status == .successWithChanges {
46-
self.eventHandler.send(.ready)
47-
return
48-
}
49-
self.eventHandler.send(.error)
50-
} catch {
51-
// TODO: Should be FATAL here
52-
self.eventHandler.send(.error)
44+
if status == .successWithChanges {
45+
return
46+
}
47+
48+
throw OpenFeatureError.generalError(message: "impossible to initialize the provider, receive unknown status")
49+
} catch {
50+
switch error {
51+
case OfrepError.apiUnauthorizedError, OfrepError.forbiddenError:
52+
throw OpenFeatureError.providerFatalError(message: error.localizedDescription)
53+
default:
54+
throw error
5355
}
5456
}
5557
}
5658

5759
public func onContextSet(oldContext: (any OpenFeature.EvaluationContext)?,
58-
newContext: any OpenFeature.EvaluationContext) {
59-
self.eventHandler.send(.stale)
60+
newContext: any OpenFeature.EvaluationContext) async throws {
6061
self.evaluationContext = newContext
61-
Task {
62-
do {
63-
let status = try await self.evaluateFlags(context: newContext)
64-
if(status == .successWithChanges || status == .successNoChanges ) {
65-
self.eventHandler.send(.ready)
66-
}
67-
} catch let error as OfrepError {
68-
switch error {
69-
case .apiTooManyRequestsError:
70-
return // we want to stay stale in that case so we ignore the error.
71-
default:
72-
throw error
73-
}
74-
} catch {
75-
self.eventHandler.send(.error)
62+
do {
63+
let status = try await self.evaluateFlags(context: newContext)
64+
if(status == .successWithChanges || status == .successNoChanges ) {
65+
return
7666
}
67+
} catch let error as OfrepError {
68+
switch error {
69+
case .apiTooManyRequestsError:
70+
return // we want to stay stale in that case so we ignore the error.
71+
default:
72+
throw error
73+
}
74+
} catch {
75+
throw error
7776
}
7877
}
7978

@@ -282,10 +281,10 @@ public class OfrepProvider: FeatureProvider {
282281
weakSelf.eventHandler.send(.stale)
283282
throw error
284283
default:
285-
weakSelf.eventHandler.send(.error)
284+
throw error
286285
}
287286
} catch {
288-
weakSelf.eventHandler.send(.error)
287+
throw error
289288
}
290289
}
291290
}

Tests/GOFeatureFlagTests/goff_api_tests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class GoffApiTests: XCTestCase {
3030
FeatureEvent(kind: "feature", userKey: "981f2662-1fb4-4732-ac6d-8399d9205aa9", creationDate: Int64(Date().timeIntervalSince1970), key: "flag-1", variation: "enabled", value: JSONValue.bool(true), default: false, version: nil, source: "PROVIDER_CACHE")
3131
]
3232
do {
33-
let (response, _) = try await goffAPI.postDataCollector(events: events)
33+
_ = try await goffAPI.postDataCollector(events: events)
3434
XCTFail("Expected to throw GoFeatureFlagError.apiUnauthorizedError, but no error was thrown.")
3535
} catch let error as GoFeatureFlagError {
3636
switch error {
@@ -52,7 +52,7 @@ class GoffApiTests: XCTestCase {
5252
FeatureEvent(kind: "feature", userKey: "981f2662-1fb4-4732-ac6d-8399d9205aa9", creationDate: Int64(Date().timeIntervalSince1970), key: "flag-1", variation: "enabled", value: JSONValue.bool(true), default: false, version: nil, source: "PROVIDER_CACHE")
5353
]
5454
do {
55-
let (response, _) = try await goffAPI.postDataCollector(events: events)
55+
_ = try await goffAPI.postDataCollector(events: events)
5656
XCTFail("Expected to throw GoFeatureFlagError.forbiddenError, but no error was thrown.")
5757
} catch let error as GoFeatureFlagError {
5858
switch error {

Tests/GOFeatureFlagTests/provider_tests.swift

+9-8
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,19 @@ class GoFeatureFlagProviderTests: XCTestCase {
2323
let evaluationCtx = MutableContext(targetingKey: "ede04e44-463d-40d1-8fc0-b1d6855578d0")
2424
let api = OpenFeatureAPI()
2525
await api.setProviderAndWait(provider: provider, initialContext: evaluationCtx)
26+
XCTAssertEqual(api.getProviderStatus(), ProviderStatus.ready)
27+
28+
2629
let client = api.getClient()
27-
30+
let expectation = self.expectation(description: "Waiting for delay")
2831
_ = client.getBooleanDetails(key: "my-flag", defaultValue: false)
2932
_ = client.getBooleanDetails(key: "my-flag", defaultValue: false)
3033
_ = client.getIntegerDetails(key: "int-flag", defaultValue: 1)
3134
_ = client.getDoubleDetails(key: "double-flag", defaultValue: 1.0)
3235
_ = client.getStringDetails(key: "string-flag", defaultValue: "default")
3336
_ = client.getObjectDetails(key: "object-flag", defaultValue: Value.null)
3437

35-
let expectation = self.expectation(description: "Waiting for delay")
36-
37-
DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { expectation.fulfill() }
38+
DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) { expectation.fulfill() }
3839
await fulfillment(of: [expectation], timeout: 3.0)
3940

4041
XCTAssertEqual(1, mockNetworkService.dataCollectorCallCounter)
@@ -149,8 +150,8 @@ class GoFeatureFlagProviderTests: XCTestCase {
149150
_ = client.getIntegerDetails(key: "int-flag", defaultValue: 1)
150151

151152
let expectation = self.expectation(description: "Waiting for delay")
152-
DispatchQueue.global().asyncAfter(deadline: .now() + 3.0) { expectation.fulfill() }
153-
await fulfillment(of: [expectation], timeout: 4.0)
153+
DispatchQueue.global().asyncAfter(deadline: .now() + 4.0) { expectation.fulfill() }
154+
await fulfillment(of: [expectation], timeout: 5.0)
154155

155156
XCTAssertEqual(1, mockNetworkService.dataCollectorCallCounter)
156157
XCTAssertEqual(3, mockNetworkService.dataCollectorEventCounter)
@@ -160,8 +161,8 @@ class GoFeatureFlagProviderTests: XCTestCase {
160161
_ = client.getObjectDetails(key: "object-flag", defaultValue: Value.null)
161162

162163
let expectation2 = self.expectation(description: "Waiting for delay")
163-
DispatchQueue.global().asyncAfter(deadline: .now() + 3.0) { expectation2.fulfill() }
164-
await fulfillment(of: [expectation2], timeout: 4.0)
164+
DispatchQueue.global().asyncAfter(deadline: .now() + 4.0) { expectation2.fulfill() }
165+
await fulfillment(of: [expectation2], timeout: 5.0)
165166

166167
XCTAssertEqual(2, mockNetworkService.dataCollectorCallCounter)
167168
XCTAssertEqual(6, mockNetworkService.dataCollectorEventCounter)

0 commit comments

Comments
 (0)