Skip to content

Commit 4aae450

Browse files
authored
Merge branch 'main' into feat/add-multiprovider
Signed-off-by: Joshua E. <[email protected]>
2 parents 68cb156 + 56b477e commit 4aae450

File tree

10 files changed

+327
-117
lines changed

10 files changed

+327
-117
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Foundation
2+
3+
public typealias EventMetadata = [String: EventMetadataValue]
4+
public typealias EventMetadataValue = MetadataValue

Sources/OpenFeature/FlagEvaluationDetails.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ public struct FlagEvaluationDetails<T: Equatable>: BaseEvaluation, Equatable {
66
public var value: T
77
public var variant: String?
88
public var reason: String?
9-
public var flagMetadata: [String: FlagMetadataValue]
9+
public var flagMetadata: FlagMetadata
1010
public var errorCode: ErrorCode?
1111
public var errorMessage: String?
1212

1313
public init(
1414
flagKey: String,
1515
value: T,
16-
flagMetadata: [String: FlagMetadataValue] = [:],
16+
flagMetadata: FlagMetadata = [:],
1717
variant: String? = nil,
1818
reason: String? = nil,
1919
errorCode: ErrorCode? = nil,
Lines changed: 2 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,4 @@
11
import Foundation
22

3-
public enum FlagMetadataValue: Equatable, Codable {
4-
case boolean(Bool)
5-
case string(String)
6-
case integer(Int64)
7-
case double(Double)
8-
9-
public static func of<T>(_ value: T) -> FlagMetadataValue? {
10-
if let value = value as? Bool {
11-
return .boolean(value)
12-
} else if let value = value as? String {
13-
return .string(value)
14-
} else if let value = value as? Int64 {
15-
return .integer(value)
16-
} else if let value = value as? Double {
17-
return .double(value)
18-
} else {
19-
return nil
20-
}
21-
}
22-
23-
public func getTyped<T>() -> T? {
24-
if let value = self as? T {
25-
return value
26-
}
27-
28-
switch self {
29-
case .boolean(let value): return value as? T
30-
case .string(let value): return value as? T
31-
case .integer(let value): return value as? T
32-
case .double(let value): return value as? T
33-
}
34-
}
35-
36-
public func asBoolean() -> Bool? {
37-
if case let .boolean(bool) = self {
38-
return bool
39-
}
40-
41-
return nil
42-
}
43-
44-
public func asString() -> String? {
45-
if case let .string(string) = self {
46-
return string
47-
}
48-
49-
return nil
50-
}
51-
52-
public func asInteger() -> Int64? {
53-
if case let .integer(int64) = self {
54-
return int64
55-
}
56-
57-
return nil
58-
}
59-
60-
public func asDouble() -> Double? {
61-
if case let .double(double) = self {
62-
return double
63-
}
64-
65-
return nil
66-
}
67-
}
68-
69-
extension FlagMetadataValue: CustomStringConvertible {
70-
public var description: String {
71-
switch self {
72-
case .boolean(let value):
73-
return "\(value)"
74-
case .string(let value):
75-
return value
76-
case .integer(let value):
77-
return "\(value)"
78-
case .double(let value):
79-
return "\(value)"
80-
}
81-
}
82-
}
83-
84-
extension FlagMetadataValue {
85-
public func decode<T: Decodable>() throws -> T {
86-
let data = try JSONSerialization.data(withJSONObject: toJson(value: self))
87-
return try JSONDecoder().decode(T.self, from: data)
88-
}
89-
90-
func toJson(value: FlagMetadataValue) -> Any {
91-
switch value {
92-
case .boolean(let bool):
93-
return bool
94-
case .string(let string):
95-
return string
96-
case .integer(let int64):
97-
return int64
98-
case .double(let double):
99-
return double
100-
}
101-
}
102-
}
3+
public typealias FlagMetadata = [String: FlagMetadataValue]
4+
public typealias FlagMetadataValue = MetadataValue
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import Foundation
2+
3+
/// A structure supporting the addition of arbitrary event data.
4+
/// It supports definition of arbitrary properties, with keys of type string, and values of type boolean, string, or number.
5+
public enum MetadataValue: Equatable, Codable {
6+
case boolean(Bool)
7+
case string(String)
8+
case integer(Int64)
9+
case double(Double)
10+
11+
public static func of<T>(_ value: T) -> MetadataValue? {
12+
switch value {
13+
case let val as Bool:
14+
return .boolean(val)
15+
case let val as String:
16+
return .string(val)
17+
case let val as Int64:
18+
return .integer(val)
19+
case let val as Double:
20+
return .double(val)
21+
default:
22+
return nil
23+
}
24+
}
25+
26+
public func getTyped<T>() -> T? {
27+
if let value = self as? T {
28+
return value
29+
}
30+
31+
switch self {
32+
case .boolean(let value): return value as? T
33+
case .string(let value): return value as? T
34+
case .integer(let value): return value as? T
35+
case .double(let value): return value as? T
36+
}
37+
}
38+
39+
public func asBoolean() -> Bool? {
40+
if case let .boolean(bool) = self {
41+
return bool
42+
}
43+
44+
return nil
45+
}
46+
47+
public func asString() -> String? {
48+
if case let .string(string) = self {
49+
return string
50+
}
51+
52+
return nil
53+
}
54+
55+
public func asInteger() -> Int64? {
56+
if case let .integer(int64) = self {
57+
return int64
58+
}
59+
60+
return nil
61+
}
62+
63+
public func asDouble() -> Double? {
64+
if case let .double(double) = self {
65+
return double
66+
}
67+
68+
return nil
69+
}
70+
}
71+
72+
extension MetadataValue: CustomStringConvertible {
73+
public var description: String {
74+
switch self {
75+
case .boolean(let value):
76+
return "\(value)"
77+
case .string(let value):
78+
return value
79+
case .integer(let value):
80+
return "\(value)"
81+
case .double(let value):
82+
return "\(value)"
83+
}
84+
}
85+
}
86+
87+
extension MetadataValue {
88+
public func decode<T: Decodable>() throws -> T {
89+
let data = try JSONSerialization.data(withJSONObject: toJson(value: self))
90+
return try JSONDecoder().decode(T.self, from: data)
91+
}
92+
93+
func toJson(value: MetadataValue) -> Any {
94+
switch value {
95+
case .boolean(let bool):
96+
return bool
97+
case .string(let string):
98+
return string
99+
case .integer(let int64):
100+
return int64
101+
case .double(let double):
102+
return double
103+
}
104+
}
105+
}

Sources/OpenFeature/OpenFeatureAPI.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,15 @@ public class OpenFeatureAPI {
162162
do {
163163
try await provider.initialize(initialContext: initialContext)
164164
self.providerStatus = .ready
165-
self.eventHandler.send(.ready)
165+
self.eventHandler.send(.ready(nil))
166166
} catch {
167167
switch error {
168-
case OpenFeatureError.providerFatalError:
168+
case OpenFeatureError.providerFatalError(let message):
169169
self.providerStatus = .fatal
170-
self.eventHandler.send(.error(errorCode: .providerFatal))
170+
self.eventHandler.send(.error(ProviderEventDetails(message: message, errorCode: .providerFatal)))
171171
default:
172172
self.providerStatus = .error
173-
self.eventHandler.send(.error(message: error.localizedDescription))
173+
self.eventHandler.send(.error(ProviderEventDetails(message: error.localizedDescription)))
174174
}
175175
}
176176
}
@@ -180,16 +180,16 @@ public class OpenFeatureAPI {
180180
let oldContext = self.evaluationContext
181181
self.evaluationContext = evaluationContext
182182
self.providerStatus = .reconciling
183-
eventHandler.send(.reconciling)
183+
eventHandler.send(.reconciling(nil))
184184
try await self.providerSubject.value?.onContextSet(
185185
oldContext: oldContext,
186186
newContext: evaluationContext
187187
)
188188
self.providerStatus = .ready
189-
eventHandler.send(.contextChanged)
189+
eventHandler.send(.contextChanged(nil))
190190
} catch {
191191
self.providerStatus = .error
192-
eventHandler.send(.error(message: error.localizedDescription))
192+
eventHandler.send(.error(ProviderEventDetails(message: error.localizedDescription)))
193193
}
194194
}
195195

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Foundation
2+
3+
/// A structure defining a provider event payload.
4+
public struct ProviderEventDetails: Equatable {
5+
public let flagsChanged: [String]?
6+
public let message: String?
7+
public let errorCode: ErrorCode?
8+
public let eventMetadata: EventMetadata
9+
10+
public init(
11+
flagsChanged: [String]? = nil,
12+
message: String? = nil,
13+
errorCode: ErrorCode? = nil,
14+
eventMetadata: EventMetadata = [:]
15+
) {
16+
self.flagsChanged = flagsChanged
17+
self.message = message
18+
self.errorCode = errorCode
19+
self.eventMetadata = eventMetadata
20+
}
21+
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import Foundation
22

33
public enum ProviderEvent: Equatable {
4-
case ready
5-
case error(errorCode: ErrorCode? = nil, message: String? = nil)
6-
case configurationChanged
7-
case stale
8-
case reconciling
9-
case contextChanged
4+
case ready(ProviderEventDetails? = nil)
5+
case error(ProviderEventDetails? = nil)
6+
case configurationChanged(ProviderEventDetails? = nil)
7+
case stale(ProviderEventDetails? = nil)
8+
case reconciling(ProviderEventDetails? = nil)
9+
case contextChanged(ProviderEventDetails? = nil)
1010
}

Tests/OpenFeatureTests/Helpers/MockProvider.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class MockProvider: FeatureProvider {
4242
String,
4343
Int64,
4444
EvaluationContext?
45-
) throws -> ProviderEvaluation<Int64> = { _, fallback, _ in
45+
) throws -> ProviderEvaluation<Int64> = { _, fallback, _ in
4646
return ProviderEvaluation(value: fallback, flagMetadata: [:])
4747
},
4848
getDoubleEvaluation: @escaping (
@@ -56,7 +56,7 @@ class MockProvider: FeatureProvider {
5656
String,
5757
Value,
5858
EvaluationContext?
59-
) throws -> ProviderEvaluation<Value> = { _, fallback, _ in
59+
) throws -> ProviderEvaluation<Value> = { _, fallback, _ in
6060
return ProviderEvaluation(value: fallback, flagMetadata: [:])
6161
},
6262
observe: @escaping () -> AnyPublisher<ProviderEvent?, Never> = { Just(nil).eraseToAnyPublisher() }

Tests/OpenFeatureTests/OpenFeatureClientTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@ import XCTest
55

66
final class OpenFeatureClientTests: XCTestCase {
77
func testShouldNowThrowIfHookHasDifferentTypeArgument() {
8+
let readyExpectation = XCTestExpectation(description: "Ready")
9+
let eventState = OpenFeatureAPI.shared.observe().sink { event in
10+
switch event {
11+
case .ready:
12+
readyExpectation.fulfill()
13+
default:
14+
break
15+
}
16+
}
817
OpenFeatureAPI.shared.setProvider(provider: DoSomethingProvider())
918
OpenFeatureAPI.shared.addHooks(hooks: BooleanHookMock())
1019

1120
let client = OpenFeatureAPI.shared.getClient()
21+
wait(for: [readyExpectation], timeout: 2)
1222

1323
let stringDetails = client.getDetails(key: "key", defaultValue: "test")
1424
XCTAssertEqual(stringDetails.value, "tset")
@@ -20,5 +30,6 @@ final class OpenFeatureClientTests: XCTestCase {
2030

2131
let doubleDetails = client.getDetails(key: "key", defaultValue: 123.1)
2232
XCTAssertEqual(doubleDetails.value, 12_310)
33+
XCTAssertNotNil(eventState)
2334
}
2435
}

0 commit comments

Comments
 (0)