Skip to content

Commit 56b477e

Browse files
authored
feat!: add provider event details (#77)
Signed-off-by: jescriba <joshua.escribano@includedhealth.com> Signed-off-by: Joshua E. <escribirajoshua@gmail.com>
1 parent 28ccd3e commit 56b477e

File tree

13 files changed

+476
-126
lines changed

13 files changed

+476
-126
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
platform: [iOS, macOS, watchOS, tvOS]
1717
include:
1818
- platform: iOS
19-
destination: "platform=iOS Simulator,name=iPhone 15"
19+
destination: "platform=iOS Simulator,name=iPhone 16"
2020
- platform: macOS
2121
destination: "platform=macOS"
2222
- platform: watchOS
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/EvalContextTests.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,12 @@ final class EvalContextTests: XCTestCase {
144144
originalContext.add(key: "integer", value: .integer(42))
145145
originalContext.add(key: "boolean", value: .boolean(true))
146146
originalContext.add(key: "list", value: .list([.string("item1"), .integer(100)]))
147-
originalContext.add(key: "structure", value: .structure([
148-
"nested-string": .string("nested-value"),
149-
"nested-int": .integer(200),
150-
]))
147+
originalContext.add(
148+
key: "structure",
149+
value: .structure([
150+
"nested-string": .string("nested-value"),
151+
"nested-int": .integer(200),
152+
]))
151153

152154
guard let copiedContext = originalContext.deepCopy() as? MutableContext else {
153155
XCTFail("Failed to cast to MutableContext")
@@ -207,10 +209,12 @@ final class EvalContextTests: XCTestCase {
207209
originalContext.add(key: "double", value: .double(3.14159))
208210
originalContext.add(key: "date", value: .date(date))
209211
originalContext.add(key: "list", value: .list([.string("list-item"), .integer(999)]))
210-
originalContext.add(key: "structure", value: .structure([
211-
"struct-key": .string("struct-value"),
212-
"struct-number": .integer(777),
213-
]))
212+
originalContext.add(
213+
key: "structure",
214+
value: .structure([
215+
"struct-key": .string("struct-value"),
216+
"struct-number": .integer(777),
217+
]))
214218

215219
guard let copiedContext = originalContext.deepCopy() as? MutableContext else {
216220
XCTFail("Failed to cast to MutableContext")

0 commit comments

Comments
 (0)