Skip to content

Commit b6dc16b

Browse files
feat: OpenFeature Provider support for Value def
1 parent 765d4de commit b6dc16b

File tree

3 files changed

+741
-10
lines changed

3 files changed

+741
-10
lines changed

Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,11 @@ public class ConfidenceFeatureProvider: FeatureProvider {
9999
public func getObjectEvaluation(key: String, defaultValue: OpenFeature.Value, context: EvaluationContext?)
100100
throws -> OpenFeature.ProviderEvaluation<OpenFeature.Value>
101101
{
102-
// Convert Struct Value to
103-
try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation()
102+
guard let nativeDefault = defaultValue.asNativeDictionary() else {
103+
throw OpenFeatureError.generalError(message: "Unexpected error handling the default value")
104+
}
105+
let evaluation = confidence.getEvaluation(key: key, defaultValue: nativeDefault)
106+
return try evaluation.toProviderEvaluationWithValueConversion()
104107
}
105108

106109
public func observe() -> AnyPublisher<OpenFeature.ProviderEvent?, Never> {
@@ -118,7 +121,8 @@ public class ConfidenceFeatureProvider: FeatureProvider {
118121
}
119122

120123
extension Evaluation {
121-
func toProviderEvaluation() throws -> ProviderEvaluation<T> {
124+
/// Throws an OpenFeature error if this evaluation contains an error code
125+
private func throwIfError() throws {
122126
if let errorCode = self.errorCode {
123127
switch errorCode {
124128
case .providerNotReady:
@@ -137,12 +141,152 @@ extension Evaluation {
137141
throw OpenFeatureError.generalError(message: message)
138142
}
139143
}
144+
}
145+
146+
func toProviderEvaluation() throws -> ProviderEvaluation<T> {
147+
try throwIfError()
140148
return ProviderEvaluation(
141149
value: self.value,
142150
variant: self.variant,
143-
reason: self.reason.rawValue,
151+
reason: Reason.targetingMatch.rawValue, // TODO VERIFY THIS!
152+
errorCode: nil,
153+
errorMessage: nil
154+
)
155+
}
156+
}
157+
158+
extension Evaluation where T == [String: Any] {
159+
func toProviderEvaluationWithValueConversion() throws -> ProviderEvaluation<OpenFeature.Value> {
160+
try throwIfError()
161+
let openFeatureValue = OpenFeature.Value.fromNativeDictionary(self.value)
162+
return ProviderEvaluation(
163+
value: openFeatureValue,
164+
variant: self.variant,
165+
reason: Reason.targetingMatch.rawValue, // TODO VERIFY THIS!
144166
errorCode: nil,
145167
errorMessage: nil
146168
)
147169
}
148170
}
171+
172+
extension OpenFeature.Value {
173+
/// Converts an OpenFeature Value to a Dictionary of native Swift types.
174+
///
175+
/// - Returns: A dictionary where keys are Strings and values are native Swift types:
176+
/// - Bool for boolean values
177+
/// - String for string values
178+
/// - Int64 for integer values
179+
/// - Double for double values
180+
/// - Date for date values
181+
/// - [Any] for list values (recursively converted)
182+
/// - [String: Any] for nested structure values (recursively converted)
183+
/// - NSNull for null values
184+
/// - Returns: nil if the Value is not a structure type
185+
public func asNativeDictionary() -> [String: Any]? {
186+
guard case let .structure(valueMap) = self else {
187+
return nil
188+
}
189+
190+
return valueMap.mapValues { value in
191+
return value.asNativeType()
192+
}
193+
}
194+
195+
/// Converts an OpenFeature Value to its corresponding native Swift type.
196+
///
197+
/// - Returns: The native Swift representation:
198+
/// - Bool for boolean values
199+
/// - String for string values
200+
/// - Int64 for integer values
201+
/// - Double for double values
202+
/// - Date for date values
203+
/// - [Any] for list values (recursively converted)
204+
/// - [String: Any] for structure values (recursively converted)
205+
/// - NSNull for null values
206+
public func asNativeType() -> Any {
207+
switch self {
208+
case .boolean(let value):
209+
return value
210+
case .string(let value):
211+
return value
212+
case .integer(let value):
213+
return value
214+
case .double(let value):
215+
return value
216+
case .date(let value):
217+
return value
218+
case .list(let values):
219+
return values.map { $0.asNativeType() }
220+
case .structure(let valueMap):
221+
return valueMap.mapValues { $0.asNativeType() }
222+
case .null:
223+
return NSNull()
224+
}
225+
}
226+
227+
/// Creates an OpenFeature Value from a native Swift dictionary.
228+
///
229+
/// - Parameter dictionary: A dictionary with String keys and Any values
230+
/// - Returns: An OpenFeature Value structure containing the converted dictionary
231+
public static func fromNativeDictionary(_ dictionary: [String: Any]) -> OpenFeature.Value {
232+
let convertedMap = dictionary.mapValues { value in
233+
return fromNativeType(value)
234+
}
235+
return .structure(convertedMap)
236+
}
237+
238+
/// Creates an OpenFeature Value from a native Swift type.
239+
///
240+
/// - Parameter value: The native Swift value to convert
241+
/// - Returns: The corresponding OpenFeature Value
242+
public static func fromNativeType(_ value: Any) -> OpenFeature.Value {
243+
// Handle numeric types first
244+
if let boolValue = value as? Bool {
245+
return .boolean(boolValue)
246+
}
247+
if let stringValue = value as? String {
248+
return .string(stringValue)
249+
}
250+
251+
// Handle integer types
252+
if let intValue = value as? Int {
253+
return .integer(Int64(intValue))
254+
}
255+
if let int64Value = value as? Int64 {
256+
return .integer(int64Value)
257+
}
258+
if let int32Value = value as? Int32 {
259+
return .integer(Int64(int32Value))
260+
}
261+
262+
// Handle floating point types
263+
if let doubleValue = value as? Double {
264+
return .double(doubleValue)
265+
}
266+
if let floatValue = value as? Float {
267+
return .double(Double(floatValue))
268+
}
269+
270+
// Handle other types
271+
return handleOtherNativeTypes(value)
272+
}
273+
274+
private static func handleOtherNativeTypes(_ value: Any) -> OpenFeature.Value {
275+
if let dateValue = value as? Date {
276+
return .date(dateValue)
277+
}
278+
if let arrayValue = value as? [Any] {
279+
let convertedArray = arrayValue.map { fromNativeType($0) }
280+
return .list(convertedArray)
281+
}
282+
if let dictValue = value as? [String: Any] {
283+
let convertedDict = dictValue.mapValues { fromNativeType($0) }
284+
return .structure(convertedDict)
285+
}
286+
if value is NSNull {
287+
return .null
288+
}
289+
// For unknown types, convert to string representation
290+
return .string(String(describing: value))
291+
}
292+
}

0 commit comments

Comments
 (0)