From a7ddf6687ae3ae79830784867f0c92b7a39d1c16 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Mon, 8 Apr 2024 17:27:01 +0200 Subject: [PATCH] Best effort convert OF lists --- Sources/Confidence/ConfidenceValue.swift | 43 +++++++++++++++--- .../Utils/ConfidenceTypeMapper.swift | 32 ++++++++++++- .../ConfidenceTypeMapperTest.swift | 45 +++++++++++++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 6ee62128..6a961cbf 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -35,11 +35,6 @@ public class ConfidenceValue: Equatable, Encodable { self.value = .timestamp(timestamp) } - // TODO: Handle heterogeneous types - public init(valueList: [ConfidenceValue]) { - self.value = .list(valueList.map { $0.value }) - } - public init(boolList: [Bool]) { self.value = .list(boolList.map { .boolean($0) }) } @@ -57,6 +52,9 @@ public class ConfidenceValue: Equatable, Encodable { self.value = .list(doubleList.map { .double($0) }) } + public init(nullList: [()]) { + self.value = .list(nullList.map { .null }) + } public init(dateList: [DateComponents]) { self.value = .list(dateList.map { .date($0) }) @@ -150,6 +148,29 @@ public class ConfidenceValue: Equatable, Encodable { return false } + public func type() -> ConfidenceValueType { + switch value { + case .boolean(_): + return .boolean + case .string(_): + return .string + case .integer(_): + return .integer + case .double(_): + return .double + case .date(_): + return .date + case .timestamp(_): + return .timestamp + case .list(_): + return .list + case .structure(_): + return .structure + case .null: + return .null + } + } + public static func == (lhs: ConfidenceValue, rhs: ConfidenceValue) -> Bool { lhs.value == rhs.value } @@ -162,6 +183,18 @@ extension ConfidenceValue { } } +public enum ConfidenceValueType: CaseIterable { + case boolean + case string + case integer + case double + case date + case timestamp + case list + case structure + case null +} + /// Serializable data structure meant for event sending via Confidence private enum ConfidenceValueInternal: Equatable, Encodable { diff --git a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift index d7ff61da..e81d52bf 100644 --- a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift +++ b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift @@ -26,7 +26,37 @@ public enum ConfidenceTypeMapper { case .date(let value): return ConfidenceValue(timestamp: value) case .list(let values): - return ConfidenceValue(valueList: values.compactMap(convertValue)) + let types = Set(values.map(convertValue).map { $0.type() } ) + guard types.count == 1, let listType = types.first else { + return ConfidenceValue.init(nullList: [()]) + } + switch listType { + case .boolean: + return ConfidenceValue.init(boolList: values.compactMap { $0.asBoolean() }) + case .string: + return ConfidenceValue.init(stringList: values.compactMap { $0.asString() }) + case .integer: + return ConfidenceValue.init(integerList: values.compactMap { $0.asInteger() }) + case .double: + return ConfidenceValue.init(doubleList: values.compactMap { $0.asDouble() }) + // Currently Date Value is converted to Timestamp ConfidenceValue to not lose precision, so this should never happen + case .date: + let componentsToExtract: Set = [.year, .month, .day] + return ConfidenceValue.init(dateList: values.compactMap { + guard let date = $0.asDate() else { + return nil + } + return Calendar.current.dateComponents(componentsToExtract, from: date ) }) + case .timestamp: + return ConfidenceValue.init(timestampList: values.compactMap { $0.asDate() }) + case .list: + return ConfidenceValue.init(nullList: values.compactMap { _ in () }) // List of list not allowed + case .structure: + return ConfidenceValue.init(nullList: values.compactMap { _ in () }) // TODO: List of structures + case .null: + return ConfidenceValue.init(nullList: values.compactMap { _ in () }) + + } case .structure(let values): return ConfidenceValue(structure: values.compactMapValues(convertValue)) case .null: diff --git a/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift index c3a0a45f..ad2d7c48 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift @@ -18,6 +18,51 @@ class ValueConverterTest: XCTestCase { XCTAssertEqual(confidenceStruct, expected) } + func testContextConversionWithLists() throws { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let date1 = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + let date2 = try XCTUnwrap(formatter.date(from: "2022-01-02 12:00:00")) + + let openFeatureCtx = MutableContext( + targetingKey: "userid", + structure: MutableStructure(attributes: ([ + "stringList": .list([.string("test1"), .string("test2")]), + "boolList": .list([.boolean(true), .boolean(false)]), + "integerList": .list([.integer(11), .integer(33)]), + "doubleList": .list([.double(3.14), .double(1.0)]), + "dateList": .list([.date(date1), .date(date2)]), + "nullList": .list([.null, .null]), + "listList": .list([.list([.string("nested_value1")]), .list([.string("nested_value2")])]), + "structList": .list([.structure(["test": .string("nested_test1")]), .structure(["test": .string("nested_test2")])]) + ]))) + let confidenceStruct = ConfidenceTypeMapper.from(ctx: openFeatureCtx) + let expected = [ + "stringList": ConfidenceValue(stringList: ["test1", "test2"]), + "boolList": ConfidenceValue(boolList: [true, false]), + "integerList": ConfidenceValue(integerList: [11, 33]), + "doubleList": ConfidenceValue(doubleList: [3.14, 1.0]), + "dateList": ConfidenceValue(timestampList: [date1, date2]), + "nullList": ConfidenceValue(nullList: [(),()]), + "listList": ConfidenceValue(nullList: [(),()]), + "structList": ConfidenceValue(nullList: [(),()]), + "targeting_key": ConfidenceValue(string: "userid") + ] + XCTAssertEqual(confidenceStruct, expected) + } + + func testContextConversionWithHeterogenousLists() throws { + let openFeatureCtx = MutableContext( + targetingKey: "userid", + structure: MutableStructure(attributes: (["key": .list([.string("test1"), .integer(1)])]))) + let confidenceStruct = ConfidenceTypeMapper.from(ctx: openFeatureCtx) + let expected = [ + "key": ConfidenceValue(nullList: [()]), + "targeting_key": ConfidenceValue(string: "userid") + ] + XCTAssertEqual(confidenceStruct, expected) + } + func testValueConversion() throws { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"