From d3ac9a3e513f31706c5afaa6d25c6d413a287f55 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Tue, 2 Apr 2024 15:49:03 +0200 Subject: [PATCH 01/18] Add ConfidenceValue --- Sources/Confidence/Confidence.swift | 10 +- .../ConfidenceError.swift | 0 .../Confidence/ConfidenceEventSender.swift | 3 +- Sources/Confidence/ConfidenceValue.swift | 185 ++++++++++++++++++ Sources/Confidence/Contextual.swift | 7 +- .../Apply/FlagApplierWithRetries.swift | 1 + .../Cache/DefaultStorage.swift | 1 + .../Cache/InMemoryProviderCache.swift | 3 +- .../LocalStorageResolver.swift | 1 + .../Utils/HttpStatusCode+Error.swift | 1 + .../Helpers/AlwaysFailCache.swift | 1 + .../LocalStorageResolverTest.swift | 1 + 12 files changed, 202 insertions(+), 12 deletions(-) rename Sources/{ConfidenceProvider => Confidence}/ConfidenceError.swift (100%) create mode 100644 Sources/Confidence/ConfidenceValue.swift diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 99d1e282..0fa2f7ab 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -1,7 +1,7 @@ import Foundation public class Confidence: ConfidenceEventSender { - public var context: [String: String] + public var context: ConfidenceStruct public let clientSecret: String public var timeout: TimeInterval public var region: ConfidenceRegion @@ -21,11 +21,11 @@ public class Confidence: ConfidenceEventSender { } // TODO: Implement actual event uploading to the backend - public func send(eventName: String) { - print("Sending \(eventName) - Targeting key: \(context["targeting_key"] ?? "UNKNOWN")") + public func send(definition: String, payload: ConfidenceStruct) { + print("Sending \(definition) - Targeting key: \(payload)") } - public func updateContextEntry(key: String, value: String) { + public func updateContextEntry(key: String, value: ConfidenceValue) { context[key] = value } @@ -38,7 +38,7 @@ public class Confidence: ConfidenceEventSender { } // TODO: Implement creation of child instances - public func withContext(_ context: [String: String]) -> Self { + public func withContext(_ context: ConfidenceStruct) -> Self { return self } } diff --git a/Sources/ConfidenceProvider/ConfidenceError.swift b/Sources/Confidence/ConfidenceError.swift similarity index 100% rename from Sources/ConfidenceProvider/ConfidenceError.swift rename to Sources/Confidence/ConfidenceError.swift diff --git a/Sources/Confidence/ConfidenceEventSender.swift b/Sources/Confidence/ConfidenceEventSender.swift index 4747a543..7fdb49ef 100644 --- a/Sources/Confidence/ConfidenceEventSender.swift +++ b/Sources/Confidence/ConfidenceEventSender.swift @@ -1,7 +1,6 @@ import Foundation /// Sends events to Confidence. Contextual data is appended to each event -// TODO: Add functions for sending events with payload public protocol ConfidenceEventSender: Contextual { - func send(eventName: String) + func send(definition: String, payload: ConfidenceStruct) } diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift new file mode 100644 index 00000000..3ea17ee4 --- /dev/null +++ b/Sources/Confidence/ConfidenceValue.swift @@ -0,0 +1,185 @@ +import Foundation + +public typealias ConfidenceStruct = [String: ConfidenceValue] + +/// Serializable data structure meant for event sending via Confidence +public enum ConfidenceValue: Equatable, Codable { + case boolean(Bool) + case string(String) + case integer(Int64) + case double(Double) + case date(DateComponents) + case timestamp(Date) + case list([ConfidenceValue]) + case structure([String: ConfidenceValue]) + case null + + public static func of(_ value: T) -> ConfidenceValue { + if let value = value as? Bool { + return .boolean(value) + } else if let value = value as? String { + return .string(value) + } else if let value = value as? Int64 { + return .integer(value) + } else if let value = value as? Double { + return .double(value) + } else if let value = value as? DateComponents { + return .date(value) + } else if let value = value as? Date { + return .timestamp(value) + } else { + return .null + } + } + + public func getTyped() -> T? { + if let value = self as? T { + return value + } + + switch self { + case .boolean(let value): return value as? T + case .string(let value): return value as? T + case .integer(let value): return value as? T + case .double(let value): return value as? T + case .date(let value): return value as? T + case .timestamp(let value): return value as? T + case .list(let value): return value as? T + case .structure(let value): return value as? T + case .null: return nil + } + } + + public func asBoolean() -> Bool? { + if case let .boolean(bool) = self { + return bool + } + + return nil + } + + public func asString() -> String? { + if case let .string(string) = self { + return string + } + + return nil + } + + public func asInteger() -> Int64? { + if case let .integer(int64) = self { + return int64 + } + + return nil + } + + public func asDouble() -> Double? { + if case let .double(double) = self { + return double + } + + return nil + } + + public func asDate() -> DateComponents? { + if case let .date(date) = self { + return date + } + + return nil + } + + public func asTimestamp() -> Date? { + if case let .timestamp(date) = self { + return date + } + + return nil + } + + public func asList() -> [ConfidenceValue]? { + if case let .list(values) = self { + return values + } + + return nil + } + + public func asStructure() -> [String: ConfidenceValue]? { + if case let .structure(values) = self { + return values + } + + return nil + } + + public func isNull() -> Bool { + if case .null = self { + return true + } + + return false + } +} + +extension ConfidenceValue: CustomStringConvertible { + public var description: String { + switch self { + case .boolean(let value): + return "\(value)" + case .string(let value): + return value + case .integer(let value): + return "\(value)" + case .double(let value): + return "\(value)" + case .date(let value): + return "\(value)" + case .timestamp(let value): + return "\(value)" + case .list(value: let values): + return "\(values.map { value in value.description })" + case .structure(value: let values): + return "\(values.mapValues { value in value.description })" + case .null: + return "null" + } + } +} + +extension ConfidenceValue { + public func decode() throws -> T { + let data = try JSONSerialization.data(withJSONObject: toJson(value: self)) + return try JSONDecoder().decode(T.self, from: data) + } + + func toJson(value: ConfidenceValue) throws -> Any { + switch value { + case .boolean(let bool): + return bool + case .string(let string): + return string + case .integer(let int64): + return int64 + case .double(let double): + return double + case .date(let dateComponents): + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM-yyyy" + if let date = Calendar.current.date(from: dateComponents) { + return dateFormatter.string(from: date) + } else { + throw ConfidenceError.internalError(message: "Error serializing 'date' value") + } + case .timestamp(let date): + return date.timeIntervalSinceReferenceDate + case .list(let list): + return try list.map(self.toJson) + case .structure(let structure): + return try structure.mapValues(self.toJson) + case .null: + return NSNull() + } + } +} diff --git a/Sources/Confidence/Contextual.swift b/Sources/Confidence/Contextual.swift index 9b6859ce..390279a6 100644 --- a/Sources/Confidence/Contextual.swift +++ b/Sources/Confidence/Contextual.swift @@ -3,13 +3,12 @@ import Foundation /// A Contextual implementer maintains context data and can create child instances /// that can still access their parent's data public protocol Contextual { - // TODO: Add complex type to the context Dictionary - var context: [String: String] { get set } + var context: ConfidenceStruct { get set } - func updateContextEntry(key: String, value: String) + func updateContextEntry(key: String, value: ConfidenceValue) func removeContextEntry(key: String) func clearContext() /// Creates a child Contextual instance that still has access /// to its parent context - func withContext(_ context: [String: String]) -> Self + func withContext(_ context: ConfidenceStruct) -> Self } diff --git a/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift b/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift index 4eb2e182..cf03c2ce 100644 --- a/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift +++ b/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence import OpenFeature import os diff --git a/Sources/ConfidenceProvider/Cache/DefaultStorage.swift b/Sources/ConfidenceProvider/Cache/DefaultStorage.swift index d4942f73..ddf5ed4d 100644 --- a/Sources/ConfidenceProvider/Cache/DefaultStorage.swift +++ b/Sources/ConfidenceProvider/Cache/DefaultStorage.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence public class DefaultStorage: Storage { private let storageQueue = DispatchQueue(label: "com.confidence.storage") diff --git a/Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift b/Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift index 8e703b71..24ca8a48 100644 --- a/Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift +++ b/Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift @@ -1,5 +1,6 @@ -import Combine import Foundation +import Combine +import Confidence import OpenFeature import os diff --git a/Sources/ConfidenceProvider/ConfidenceClient/LocalStorageResolver.swift b/Sources/ConfidenceProvider/ConfidenceClient/LocalStorageResolver.swift index eb272314..ba395350 100644 --- a/Sources/ConfidenceProvider/ConfidenceClient/LocalStorageResolver.swift +++ b/Sources/ConfidenceProvider/ConfidenceClient/LocalStorageResolver.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence import OpenFeature public class LocalStorageResolver: Resolver { diff --git a/Sources/ConfidenceProvider/Utils/HttpStatusCode+Error.swift b/Sources/ConfidenceProvider/Utils/HttpStatusCode+Error.swift index f76c6b8c..7f5ae5a3 100644 --- a/Sources/ConfidenceProvider/Utils/HttpStatusCode+Error.swift +++ b/Sources/ConfidenceProvider/Utils/HttpStatusCode+Error.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence import OpenFeature extension HTTPURLResponse { diff --git a/Tests/ConfidenceProviderTests/Helpers/AlwaysFailCache.swift b/Tests/ConfidenceProviderTests/Helpers/AlwaysFailCache.swift index 330d4a89..566c34dd 100644 --- a/Tests/ConfidenceProviderTests/Helpers/AlwaysFailCache.swift +++ b/Tests/ConfidenceProviderTests/Helpers/AlwaysFailCache.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence import OpenFeature @testable import ConfidenceProvider diff --git a/Tests/ConfidenceProviderTests/LocalStorageResolverTest.swift b/Tests/ConfidenceProviderTests/LocalStorageResolverTest.swift index 5fc4e0b4..beb55a8f 100644 --- a/Tests/ConfidenceProviderTests/LocalStorageResolverTest.swift +++ b/Tests/ConfidenceProviderTests/LocalStorageResolverTest.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence import OpenFeature import XCTest From 8412c8f028e76a3c99277ed046ded312fd035348 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Tue, 2 Apr 2024 16:13:55 +0200 Subject: [PATCH 02/18] Add ConfidenceValueTests --- Package.swift | 8 +- Sources/Confidence/ConfidenceValue.swift | 4 +- .../ConfidenceValueTests.swift | 113 ++++++++++++++++++ 3 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 Tests/ConfidenceTests/ConfidenceValueTests.swift diff --git a/Package.swift b/Package.swift index 74afe395..545cf790 100644 --- a/Package.swift +++ b/Package.swift @@ -39,6 +39,12 @@ let package = Package( dependencies: [ "ConfidenceProvider", ] - ) + ), + .testTarget( + name: "ConfidenceTests", + dependencies: [ + "Confidence" + ] + ), ] ) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 3ea17ee4..71615bde 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -83,8 +83,8 @@ public enum ConfidenceValue: Equatable, Codable { } public func asDate() -> DateComponents? { - if case let .date(date) = self { - return date + if case let .date(dateComponents) = self { + return dateComponents } return nil diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift new file mode 100644 index 00000000..220b2430 --- /dev/null +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -0,0 +1,113 @@ +import Confidence +import XCTest + +final class ConfidenceConfidenceValueTests: XCTestCase { + func testNull() { + let value = ConfidenceValue.null + XCTAssertTrue(value.isNull()) + } + + func testIntShouldConvertToInt() { + let value: ConfidenceValue = .integer(3) + XCTAssertEqual(value.asInteger(), 3) + } + + func testDoubleShouldConvertToDouble() { + let value: ConfidenceValue = .double(3.14) + XCTAssertEqual(value.asDouble(), 3.14) + } + + func testBoolShouldConvertToBool() { + let value: ConfidenceValue = .boolean(true) + XCTAssertEqual(value.asBoolean(), true) + } + + func testStringShouldConvertToString() { + let value: ConfidenceValue = .string("test") + XCTAssertEqual(value.asString(), "test") + } + + func testListShouldConvertToList() { + let value: ConfidenceValue = .list([.integer(3), .integer(4)]) + XCTAssertEqual(value.asList(), [.integer(3), .integer(4)]) + } + + func testStructShouldConvertToStruct() { + let value: ConfidenceValue = .structure(["field1": .integer(3), "field2": .string("test")]) + XCTAssertEqual(value.asStructure(), ["field1": .integer(3), "field2": .string("test")]) + } + + func testEmptyListAllowed() { + let value: ConfidenceValue = .list([]) + XCTAssertEqual(value.asList(), []) + } + + func testEncodeDecode() throws { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + let dateComponents = DateComponents(year: 2024, month: 1, day: 1) + + let value: ConfidenceValue = .structure([ + "null": .null, + "bool": .boolean(true), + "int": .integer(3), + "double": .double(4.5), + "date": .date(dateComponents), + "timestamp": .timestamp(date), + "list": .list([.boolean(false), .integer(4)]), + "structure": .structure(["int": .integer(5)]), + ]) + + let result = try JSONEncoder().encode(value) + let decodedConfidenceValue = try JSONDecoder().decode(ConfidenceValue.self, from: result) + + XCTAssertEqual(value, decodedConfidenceValue) + } + + func testDecodeConfidenceValue() throws { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + let dateComponents = DateComponents(year: 2024, month: 1, day: 1) + + let value: ConfidenceValue = .structure([ + "null": .null, + "bool": .boolean(true), + "int": .integer(3), + "double": .double(4.5), + "date": .date(dateComponents), + "timestamp": .timestamp(date), + "list": .list([.integer(3), .integer(5)]), + "structure": .structure(["field1": .string("test"), "field2": .integer(12)]), + ]) + let expected = TestConfidenceValue( + bool: true, + int: 3, + double: 4.5, + date: dateComponents, + timestamp: date, + list: [3, 5], + structure: .init(field1: "test", field2: 12)) + + let decodedConfidenceValue: TestConfidenceValue = try value.decode() + + XCTAssertEqual(decodedConfidenceValue, expected) + } + + struct TestConfidenceValue: Codable, Equatable { + var null: Bool? + var bool: Bool + var int: Int64 + var double: Double + var date: DateComponents + var timestamp: Date + var list: [Int64] + var structure: TestSubConfidenceValue + } + + struct TestSubConfidenceValue: Codable, Equatable { + var field1: String + var field2: Int64 + } +} From 97198c4a5f459abe5c56bbb6633276713208a25e Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Tue, 2 Apr 2024 16:32:01 +0200 Subject: [PATCH 03/18] Remove DateComponents --- Sources/Confidence/ConfidenceValue.swift | 20 +++++++------------ .../ConfidenceValueTests.swift | 10 ++++------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 71615bde..5cd0afcc 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -8,7 +8,7 @@ public enum ConfidenceValue: Equatable, Codable { case string(String) case integer(Int64) case double(Double) - case date(DateComponents) + case date(Date) case timestamp(Date) case list([ConfidenceValue]) case structure([String: ConfidenceValue]) @@ -23,7 +23,7 @@ public enum ConfidenceValue: Equatable, Codable { return .integer(value) } else if let value = value as? Double { return .double(value) - } else if let value = value as? DateComponents { + } else if let value = value as? Date { return .date(value) } else if let value = value as? Date { return .timestamp(value) @@ -82,9 +82,9 @@ public enum ConfidenceValue: Equatable, Codable { return nil } - public func asDate() -> DateComponents? { - if case let .date(dateComponents) = self { - return dateComponents + public func asDate() -> Date? { + if case let .date(date) = self { + return date } return nil @@ -164,14 +164,8 @@ extension ConfidenceValue { return int64 case .double(let double): return double - case .date(let dateComponents): - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "dd-MM-yyyy" - if let date = Calendar.current.date(from: dateComponents) { - return dateFormatter.string(from: date) - } else { - throw ConfidenceError.internalError(message: "Error serializing 'date' value") - } + case .date(let date): + return date.timeIntervalSinceReferenceDate case .timestamp(let date): return date.timeIntervalSinceReferenceDate case .list(let list): diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index 220b2430..d6173135 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -46,14 +46,13 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) - let dateComponents = DateComponents(year: 2024, month: 1, day: 1) let value: ConfidenceValue = .structure([ "null": .null, "bool": .boolean(true), "int": .integer(3), "double": .double(4.5), - "date": .date(dateComponents), + "date": .date(date), "timestamp": .timestamp(date), "list": .list([.boolean(false), .integer(4)]), "structure": .structure(["int": .integer(5)]), @@ -69,14 +68,13 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) - let dateComponents = DateComponents(year: 2024, month: 1, day: 1) let value: ConfidenceValue = .structure([ "null": .null, "bool": .boolean(true), "int": .integer(3), "double": .double(4.5), - "date": .date(dateComponents), + "date": .date(date), "timestamp": .timestamp(date), "list": .list([.integer(3), .integer(5)]), "structure": .structure(["field1": .string("test"), "field2": .integer(12)]), @@ -85,7 +83,7 @@ final class ConfidenceConfidenceValueTests: XCTestCase { bool: true, int: 3, double: 4.5, - date: dateComponents, + date: date, timestamp: date, list: [3, 5], structure: .init(field1: "test", field2: 12)) @@ -100,7 +98,7 @@ final class ConfidenceConfidenceValueTests: XCTestCase { var bool: Bool var int: Int64 var double: Double - var date: DateComponents + var date: Date var timestamp: Date var list: [Int64] var structure: TestSubConfidenceValue From 32daa61a0fb39eb53369affa539b40a94f5ad208 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 3 Apr 2024 11:59:48 +0200 Subject: [PATCH 04/18] Simplify and finalize ConfidenceValue --- Sources/Confidence/ConfidenceValue.swift | 93 ++++++---------- .../ConfidenceValueTests.swift | 105 +++++++++--------- 2 files changed, 83 insertions(+), 115 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 5cd0afcc..4f9de3e2 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -8,48 +8,12 @@ public enum ConfidenceValue: Equatable, Codable { case string(String) case integer(Int64) case double(Double) - case date(Date) + case date(DateComponents) case timestamp(Date) case list([ConfidenceValue]) case structure([String: ConfidenceValue]) case null - public static func of(_ value: T) -> ConfidenceValue { - if let value = value as? Bool { - return .boolean(value) - } else if let value = value as? String { - return .string(value) - } else if let value = value as? Int64 { - return .integer(value) - } else if let value = value as? Double { - return .double(value) - } else if let value = value as? Date { - return .date(value) - } else if let value = value as? Date { - return .timestamp(value) - } else { - return .null - } - } - - public func getTyped() -> T? { - if let value = self as? T { - return value - } - - switch self { - case .boolean(let value): return value as? T - case .string(let value): return value as? T - case .integer(let value): return value as? T - case .double(let value): return value as? T - case .date(let value): return value as? T - case .timestamp(let value): return value as? T - case .list(let value): return value as? T - case .structure(let value): return value as? T - case .null: return nil - } - } - public func asBoolean() -> Bool? { if case let .boolean(bool) = self { return bool @@ -82,15 +46,15 @@ public enum ConfidenceValue: Equatable, Codable { return nil } - public func asDate() -> Date? { - if case let .date(date) = self { - return date + public func asDateComponents() -> DateComponents? { + if case let .date(dateComponents) = self { + return dateComponents } return nil } - public func asTimestamp() -> Date? { + public func asDate() -> Date? { if case let .timestamp(date) = self { return date } @@ -149,31 +113,36 @@ extension ConfidenceValue: CustomStringConvertible { } extension ConfidenceValue { - public func decode() throws -> T { - let data = try JSONSerialization.data(withJSONObject: toJson(value: self)) - return try JSONDecoder().decode(T.self, from: data) - } + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() - func toJson(value: ConfidenceValue) throws -> Any { - switch value { - case .boolean(let bool): - return bool - case .string(let string): - return string - case .integer(let int64): - return int64 + switch self { + case .null: + try container.encodeNil() + case .integer(let integer): + try container.encode(integer) case .double(let double): - return double - case .date(let date): - return date.timeIntervalSinceReferenceDate + try container.encode(double) + case .string(let string): + try container.encode(string) + case .boolean(let boolean): + try container.encode(boolean) + case .date(let dateComponents): + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM-yyyy" + if let date = Calendar.current.date(from: dateComponents) { + try container.encode(dateFormatter.string(from: date)) + } else { + throw ConfidenceError.internalError(message: "Could not create date from components") + } case .timestamp(let date): - return date.timeIntervalSinceReferenceDate - case .list(let list): - return try list.map(self.toJson) + let isoFormatter = ISO8601DateFormatter() + let isoString = isoFormatter.string(from: date) + try container.encode(isoString) case .structure(let structure): - return try structure.mapValues(self.toJson) - case .null: - return NSNull() + try container.encode(structure) + case .list(let list): + try container.encode(list) } } } diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index d6173135..559ee05f 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -27,6 +27,20 @@ final class ConfidenceConfidenceValueTests: XCTestCase { XCTAssertEqual(value.asString(), "test") } + func testStringShouldConvertToDate() throws { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + let value: ConfidenceValue = .timestamp(date) + XCTAssertEqual(value.asDate(), date) + } + + func testStringShouldConvertToDateComponents() { + let dateComponents = DateComponents(year: 2024, month: 4, day: 3) + let value: ConfidenceValue = .date(dateComponents) + XCTAssertEqual(value.asDateComponents(), dateComponents) + } + func testListShouldConvertToList() { let value: ConfidenceValue = .list([.integer(3), .integer(4)]) XCTAssertEqual(value.asList(), [.integer(3), .integer(4)]) @@ -42,70 +56,55 @@ final class ConfidenceConfidenceValueTests: XCTestCase { XCTAssertEqual(value.asList(), []) } - func testEncodeDecode() throws { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) - - let value: ConfidenceValue = .structure([ - "null": .null, - "bool": .boolean(true), - "int": .integer(3), - "double": .double(4.5), - "date": .date(date), - "timestamp": .timestamp(date), - "list": .list([.boolean(false), .integer(4)]), - "structure": .structure(["int": .integer(5)]), - ]) - - let result = try JSONEncoder().encode(value) - let decodedConfidenceValue = try JSONDecoder().decode(ConfidenceValue.self, from: result) + func testWrongTypeDoesntThrow() { + let value = ConfidenceValue.null + XCTAssertNil(value.asList()) + XCTAssertNil(value.asDouble()) + XCTAssertNil(value.asString()) + XCTAssertNil(value.asBoolean()) + XCTAssertNil(value.asInteger()) + XCTAssertNil(value.asStructure()) + XCTAssertNil(value.asDate()) + XCTAssertNil(value.asDateComponents()) + } - XCTAssertEqual(value, decodedConfidenceValue) + func testIsNotNull() { + let value = ConfidenceValue.string("Test") + XCTAssertFalse(value.isNull()) } - func testDecodeConfidenceValue() throws { + func testEncodeDecode() throws { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + let dateComponents = DateComponents(year: 2024, month: 4, day: 3) let value: ConfidenceValue = .structure([ - "null": .null, "bool": .boolean(true), - "int": .integer(3), + "date": .date(dateComponents), "double": .double(4.5), - "date": .date(date), + "int": .integer(3), + "list": .list([.boolean(false), .integer(4)]), + "null": .null, + "string": .string("value"), + "structure": .structure(["int": .integer(5)]), "timestamp": .timestamp(date), - "list": .list([.integer(3), .integer(5)]), - "structure": .structure(["field1": .string("test"), "field2": .integer(12)]), ]) - let expected = TestConfidenceValue( - bool: true, - int: 3, - double: 4.5, - date: date, - timestamp: date, - list: [3, 5], - structure: .init(field1: "test", field2: 12)) - - let decodedConfidenceValue: TestConfidenceValue = try value.decode() - - XCTAssertEqual(decodedConfidenceValue, expected) - } - - struct TestConfidenceValue: Codable, Equatable { - var null: Bool? - var bool: Bool - var int: Int64 - var double: Double - var date: Date - var timestamp: Date - var list: [Int64] - var structure: TestSubConfidenceValue - } - - struct TestSubConfidenceValue: Codable, Equatable { - var field1: String - var field2: Int64 + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let resultString = String(data: try encoder.encode(value), encoding: .utf8) + let expectedString = """ + {\"bool\":true, + \"date\":\"03-04-2024\", + \"double\":4.5, + \"int\":3, + \"list\":[false,4], + \"null\":null, + \"string\":\"value\", + \"structure\":{\"int\":5}, + \"timestamp\":\"2022-01-01T11:00:00Z\"} + """.replacingOccurrences(of: "\n", with: "") // Newlines were added for readability + + XCTAssertEqual(resultString, expectedString) } } From 50c860b75ab15386e3723dd672e6631f9b17e8b3 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 3 Apr 2024 14:13:46 +0200 Subject: [PATCH 05/18] Test fix --- Tests/ConfidenceTests/ConfidenceValueTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index 559ee05f..7abdc8d1 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -76,6 +76,7 @@ final class ConfidenceConfidenceValueTests: XCTestCase { func testEncodeDecode() throws { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + formatter.timeZone = TimeZone(abbreviation: "UTC") let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) let dateComponents = DateComponents(year: 2024, month: 4, day: 3) @@ -102,7 +103,7 @@ final class ConfidenceConfidenceValueTests: XCTestCase { \"null\":null, \"string\":\"value\", \"structure\":{\"int\":5}, - \"timestamp\":\"2022-01-01T11:00:00Z\"} + \"timestamp\":\"2022-01-01T12:00:00Z\"} """.replacingOccurrences(of: "\n", with: "") // Newlines were added for readability XCTAssertEqual(resultString, expectedString) From c2f341fb9276e884dccfd3bd71f7040c765b59c0 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 3 Apr 2024 14:33:55 +0200 Subject: [PATCH 06/18] Update demo app --- ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index e84a5d2b..e30d2679 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -36,7 +36,7 @@ extension ConfidenceDemoApp { let ctx = MutableContext(targetingKey: UUID.init().uuidString, structure: MutableStructure()) Task { await OpenFeatureAPI.shared.setProviderAndWait(provider: provider, initialContext: ctx) - confidence.send(eventName: "my_event") + confidence.send(definition: "my_event", payload: ConfidenceStruct()) } } } From e96adf2356b34c750739be973bc0b693f4faad04 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 3 Apr 2024 15:12:50 +0200 Subject: [PATCH 07/18] Variable renaming --- Sources/Confidence/ConfidenceValue.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 4f9de3e2..6aecbb1e 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -137,8 +137,8 @@ extension ConfidenceValue { } case .timestamp(let date): let isoFormatter = ISO8601DateFormatter() - let isoString = isoFormatter.string(from: date) - try container.encode(isoString) + let formattedDate = isoFormatter.string(from: date) + try container.encode(formattedDate) case .structure(let structure): try container.encode(structure) case .list(let list): From 0656931fc227b3d9e69fbf7f553df8821bad1c56 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 3 Apr 2024 16:19:50 +0200 Subject: [PATCH 08/18] ConfidenceValue is only Codable --- Sources/Confidence/ConfidenceValue.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 6aecbb1e..7c6422c6 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -3,7 +3,7 @@ import Foundation public typealias ConfidenceStruct = [String: ConfidenceValue] /// Serializable data structure meant for event sending via Confidence -public enum ConfidenceValue: Equatable, Codable { +public enum ConfidenceValue: Equatable, Encodable { case boolean(Bool) case string(String) case integer(Int64) From 4facf6ffd5c1b46e2ed94d229c3e86113d0de178 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Thu, 4 Apr 2024 11:31:07 +0200 Subject: [PATCH 09/18] Add Confidence-OF converstions --- Sources/Confidence/ConfidenceValue.swift | 2 +- .../Utils/ConfidenceTypeMapper.swift | 36 +++++++++++++ .../ConfidenceTypeMapperTest.swift | 50 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift create mode 100644 Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 7c6422c6..fd44ca90 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -11,7 +11,7 @@ public enum ConfidenceValue: Equatable, Encodable { case date(DateComponents) case timestamp(Date) case list([ConfidenceValue]) - case structure([String: ConfidenceValue]) + case structure(ConfidenceStruct) case null public func asBoolean() -> Bool? { diff --git a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift new file mode 100644 index 00000000..9cf45dee --- /dev/null +++ b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift @@ -0,0 +1,36 @@ +import Foundation +import Confidence +import OpenFeature + +public enum ConfidenceTypeMapper { + static func from(value: Value) -> ConfidenceValue { + return convertValue(value) + } + + static func from(ctx: EvaluationContext) -> ConfidenceStruct { + var ctxMap = ctx.asMap() + ctxMap["targeting_key"] = .string(ctx.getTargetingKey()) + return ctxMap.compactMapValues(convertValue) + } + + static private func convertValue(_ value: Value) -> ConfidenceValue { + switch value { + case .boolean(let value): + return .boolean(value) + case .string(let value): + return .string(value) + case .integer(let value): + return .integer(value) + case .double(let value): + return .double(value) + case .date(let value): + return .timestamp(value) + case .list(let values): + return .list(values.compactMap(convertValue)) + case .structure(let values): + return .structure(values.compactMapValues(convertValue)) + case .null: + return .null + } + } +} diff --git a/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift new file mode 100644 index 00000000..156bf316 --- /dev/null +++ b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift @@ -0,0 +1,50 @@ +import Foundation +import Confidence +import OpenFeature +import XCTest + +@testable import ConfidenceProvider + +class ValueConverterTest: XCTestCase { + func testContextConversion() throws { + let openFeatureCtx = MutableContext( + targetingKey: "userid", + structure: MutableStructure(attributes: (["key": .string("value")]))) + let confidenceStruct = ConfidenceTypeMapper.from(ctx: openFeatureCtx) + let expected = [ + "key": ConfidenceValue.string("value"), + "targeting_key": ConfidenceValue.string("userid") + ] + XCTAssertEqual(confidenceStruct, expected) + } + + func testValueConversion() throws { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + + let openFeatureValue = Value.structure([ + "key": .string("value"), + "null": .null, + "bool": .boolean(true), + "int": .integer(3), + "double": .double(4.5), + "date": .date(date), + "list": .list([.integer(3), .integer(5)]), + "structure": .structure(["field1": .string("test"), "field2": .integer(12)]), + ]) + + let confidenceValue = ConfidenceTypeMapper.from(value: openFeatureValue) + let expected = ConfidenceValue.structure([ + "key": .string("value"), + "null": .null, + "bool": .boolean(true), + "int": .integer(3), + "double": .double(4.5), + "date": .timestamp(date), + "list": .list([.integer(3), .integer(5)]), + "structure": .structure(["field1": .string("test"), "field2": .integer(12)]), + ]) + XCTAssertEqual(confidenceValue, expected) + } +} From 839fd8c7d39f3b581df3fe32521f5727462037f0 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Fri, 5 Apr 2024 09:36:15 +0200 Subject: [PATCH 10/18] refactor: Restrict heterogeneous ConfidenceValue lists (#86) * Change ConfidenceValue constructors for more control * Setup for list restrictions --- Sources/Confidence/ConfidenceValue.swift | 169 +++++++++++++++++- .../Utils/ConfidenceTypeMapper.swift | 16 +- .../ConfidenceTypeMapperTest.swift | 28 +-- .../ConfidenceValueTests.swift | 58 +++--- 4 files changed, 218 insertions(+), 53 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index fd44ca90..e16c5e29 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -2,16 +2,171 @@ import Foundation public typealias ConfidenceStruct = [String: ConfidenceValue] +public class ConfidenceValue: Equatable, Encodable { + private let value: ConfidenceValueInternal + + public init(boolean: Bool) { + self.value = .boolean(boolean) + } + + public init(string: String) { + self.value = .string(string) + } + + public init(integer: Int64) { + self.value = .integer(integer) + } + + public init(double: Double) { + self.value = .double(double) + } + + public init(date: DateComponents) { + self.value = .date(date) + } + + public init(timestamp: Date) { + 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) }) + } + + public init(stringList: [String]) { + self.value = .list(stringList.map { .string($0) }) + } + + + public init(integerList: [Int64]) { + self.value = .list(integerList.map { .integer($0) }) + } + + public init(doubleList: [Double]) { + self.value = .list(doubleList.map { .double($0) }) + } + + + public init(dateComponentList: [DateComponents]) { + self.value = .list(dateComponentList.map { .date($0) }) + } + + public init(dateList: [Date]) { + self.value = .list(dateList.map { .timestamp($0) }) + } + + public init(structure: [String: ConfidenceValue]) { + self.value = .structure(structure.mapValues { $0.value }) + } + + public init(null: ()) { + self.value = .null + } + + private init(valueInternal: ConfidenceValueInternal) { + self.value = valueInternal + } + + public func asBoolean() -> Bool? { + if case let .boolean(bool) = value { + return bool + } + + return nil + } + + public func asString() -> String? { + if case let .string(string) = value { + return string + } + + return nil + } + + public func asInteger() -> Int64? { + if case let .integer(int64) = value { + return int64 + } + + return nil + } + + public func asDouble() -> Double? { + if case let .double(double) = value { + return double + } + + return nil + } + + public func asDateComponents() -> DateComponents? { + if case let .date(dateComponents) = value { + return dateComponents + } + + return nil + } + + public func asDate() -> Date? { + if case let .timestamp(date) = value { + return date + } + + return nil + } + + public func asList() -> [ConfidenceValue]? { + if case let .list(values) = value { + return values.map { i in ConfidenceValue(valueInternal: i) } + } + + return nil + } + + public func asStructure() -> [String: ConfidenceValue]? { + if case let .structure(values) = value { + return values.mapValues { ConfidenceValue(valueInternal: $0) } + } + + return nil + } + + public func isNull() -> Bool { + if case .null = value { + return true + } + + return false + } + + public static func == (lhs: ConfidenceValue, rhs: ConfidenceValue) -> Bool { + lhs.value == rhs.value + } +} + +extension ConfidenceValue { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(value) + } +} + + /// Serializable data structure meant for event sending via Confidence -public enum ConfidenceValue: Equatable, Encodable { +private enum ConfidenceValueInternal: Equatable, Encodable { case boolean(Bool) case string(String) case integer(Int64) case double(Double) case date(DateComponents) case timestamp(Date) - case list([ConfidenceValue]) - case structure(ConfidenceStruct) + case list([ConfidenceValueInternal]) + case structure([String: ConfidenceValueInternal]) case null public func asBoolean() -> Bool? { @@ -62,7 +217,7 @@ public enum ConfidenceValue: Equatable, Encodable { return nil } - public func asList() -> [ConfidenceValue]? { + public func asList() -> [ConfidenceValueInternal]? { if case let .list(values) = self { return values } @@ -70,7 +225,7 @@ public enum ConfidenceValue: Equatable, Encodable { return nil } - public func asStructure() -> [String: ConfidenceValue]? { + public func asStructure() -> [String: ConfidenceValueInternal]? { if case let .structure(values) = self { return values } @@ -87,7 +242,7 @@ public enum ConfidenceValue: Equatable, Encodable { } } -extension ConfidenceValue: CustomStringConvertible { +extension ConfidenceValueInternal: CustomStringConvertible { public var description: String { switch self { case .boolean(let value): @@ -112,7 +267,7 @@ extension ConfidenceValue: CustomStringConvertible { } } -extension ConfidenceValue { +extension ConfidenceValueInternal { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() diff --git a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift index 9cf45dee..d7ff61da 100644 --- a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift +++ b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift @@ -16,21 +16,21 @@ public enum ConfidenceTypeMapper { static private func convertValue(_ value: Value) -> ConfidenceValue { switch value { case .boolean(let value): - return .boolean(value) + return ConfidenceValue(boolean: value) case .string(let value): - return .string(value) + return ConfidenceValue(string: value) case .integer(let value): - return .integer(value) + return ConfidenceValue(integer: value) case .double(let value): - return .double(value) + return ConfidenceValue(double: value) case .date(let value): - return .timestamp(value) + return ConfidenceValue(timestamp: value) case .list(let values): - return .list(values.compactMap(convertValue)) + return ConfidenceValue(valueList: values.compactMap(convertValue)) case .structure(let values): - return .structure(values.compactMapValues(convertValue)) + return ConfidenceValue(structure: values.compactMapValues(convertValue)) case .null: - return .null + return ConfidenceValue(null: ()) } } } diff --git a/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift index 156bf316..c3a0a45f 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift @@ -12,8 +12,8 @@ class ValueConverterTest: XCTestCase { structure: MutableStructure(attributes: (["key": .string("value")]))) let confidenceStruct = ConfidenceTypeMapper.from(ctx: openFeatureCtx) let expected = [ - "key": ConfidenceValue.string("value"), - "targeting_key": ConfidenceValue.string("userid") + "key": ConfidenceValue(string: "value"), + "targeting_key": ConfidenceValue(string: "userid") ] XCTAssertEqual(confidenceStruct, expected) } @@ -35,16 +35,20 @@ class ValueConverterTest: XCTestCase { ]) let confidenceValue = ConfidenceTypeMapper.from(value: openFeatureValue) - let expected = ConfidenceValue.structure([ - "key": .string("value"), - "null": .null, - "bool": .boolean(true), - "int": .integer(3), - "double": .double(4.5), - "date": .timestamp(date), - "list": .list([.integer(3), .integer(5)]), - "structure": .structure(["field1": .string("test"), "field2": .integer(12)]), - ]) + let expected = ConfidenceValue(structure: ([ + "key": ConfidenceValue(string: "value"), + "null": ConfidenceValue(null: ()), + "bool": ConfidenceValue(boolean: true), + "int": ConfidenceValue(integer: 3), + "double": ConfidenceValue(double: 4.5), + "date": ConfidenceValue(timestamp: date), + "list": ConfidenceValue(integerList: [3, 5]), + "structure": ConfidenceValue( + structure: [ + "field1": ConfidenceValue(string: "test"), + "field2": ConfidenceValue(integer: 12) + ]) + ])) XCTAssertEqual(confidenceValue, expected) } } diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index 7abdc8d1..6f9a2d91 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -3,27 +3,27 @@ import XCTest final class ConfidenceConfidenceValueTests: XCTestCase { func testNull() { - let value = ConfidenceValue.null + let value = ConfidenceValue(null: ()) XCTAssertTrue(value.isNull()) } func testIntShouldConvertToInt() { - let value: ConfidenceValue = .integer(3) + let value = ConfidenceValue(integer: 3) XCTAssertEqual(value.asInteger(), 3) } func testDoubleShouldConvertToDouble() { - let value: ConfidenceValue = .double(3.14) + let value = ConfidenceValue(double: 3.14) XCTAssertEqual(value.asDouble(), 3.14) } func testBoolShouldConvertToBool() { - let value: ConfidenceValue = .boolean(true) + let value = ConfidenceValue(boolean: true) XCTAssertEqual(value.asBoolean(), true) } func testStringShouldConvertToString() { - let value: ConfidenceValue = .string("test") + let value = ConfidenceValue(string: "test") XCTAssertEqual(value.asString(), "test") } @@ -31,33 +31,39 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) - let value: ConfidenceValue = .timestamp(date) + let value = ConfidenceValue(timestamp: date) XCTAssertEqual(value.asDate(), date) } func testStringShouldConvertToDateComponents() { let dateComponents = DateComponents(year: 2024, month: 4, day: 3) - let value: ConfidenceValue = .date(dateComponents) + let value = ConfidenceValue(date: dateComponents) XCTAssertEqual(value.asDateComponents(), dateComponents) } func testListShouldConvertToList() { - let value: ConfidenceValue = .list([.integer(3), .integer(4)]) - XCTAssertEqual(value.asList(), [.integer(3), .integer(4)]) + let value = ConfidenceValue(integerList: [3, 4]) + XCTAssertEqual(value.asList(), [ConfidenceValue(integer: 3), ConfidenceValue(integer: 4)]) } func testStructShouldConvertToStruct() { - let value: ConfidenceValue = .structure(["field1": .integer(3), "field2": .string("test")]) - XCTAssertEqual(value.asStructure(), ["field1": .integer(3), "field2": .string("test")]) + let value = ConfidenceValue(structure: [ + "field1": ConfidenceValue(integer: 3), + "field2": ConfidenceValue(string: "test") + ]) + XCTAssertEqual(value.asStructure(), [ + "field1": ConfidenceValue(integer: 3), + "field2": ConfidenceValue(string: "test") + ]) } func testEmptyListAllowed() { - let value: ConfidenceValue = .list([]) + let value = ConfidenceValue(integerList: []) XCTAssertEqual(value.asList(), []) } func testWrongTypeDoesntThrow() { - let value = ConfidenceValue.null + let value = ConfidenceValue(null: ()) XCTAssertNil(value.asList()) XCTAssertNil(value.asDouble()) XCTAssertNil(value.asString()) @@ -69,7 +75,7 @@ final class ConfidenceConfidenceValueTests: XCTestCase { } func testIsNotNull() { - let value = ConfidenceValue.string("Test") + let value = ConfidenceValue(string: "Test") XCTAssertFalse(value.isNull()) } @@ -80,17 +86,17 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) let dateComponents = DateComponents(year: 2024, month: 4, day: 3) - let value: ConfidenceValue = .structure([ - "bool": .boolean(true), - "date": .date(dateComponents), - "double": .double(4.5), - "int": .integer(3), - "list": .list([.boolean(false), .integer(4)]), - "null": .null, - "string": .string("value"), - "structure": .structure(["int": .integer(5)]), - "timestamp": .timestamp(date), - ]) + let value = ConfidenceValue(structure: ([ + "bool": ConfidenceValue(boolean: true), + "date": ConfidenceValue(date: dateComponents), + "double": ConfidenceValue(double: 4.5), + "int": ConfidenceValue(integer: 3), + "list": ConfidenceValue(integerList: [3, 5]), + "null": ConfidenceValue(null: ()), + "string": ConfidenceValue(string: "value"), + "structure": ConfidenceValue(structure: ["int": ConfidenceValue(integer: 5)]), + "timestamp": ConfidenceValue(timestamp: date), + ])) let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys let resultString = String(data: try encoder.encode(value), encoding: .utf8) @@ -99,7 +105,7 @@ final class ConfidenceConfidenceValueTests: XCTestCase { \"date\":\"03-04-2024\", \"double\":4.5, \"int\":3, - \"list\":[false,4], + \"list\":[3,5], \"null\":null, \"string\":\"value\", \"structure\":{\"int\":5}, From 4fc0e9708c2bb5610f2b6b260ab0d836750c78f9 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Fri, 5 Apr 2024 09:42:09 +0200 Subject: [PATCH 11/18] Remove unused converters --- Sources/Confidence/ConfidenceValue.swift | 72 ------------------------ 1 file changed, 72 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index e16c5e29..af6fb272 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -168,78 +168,6 @@ private enum ConfidenceValueInternal: Equatable, Encodable { case list([ConfidenceValueInternal]) case structure([String: ConfidenceValueInternal]) case null - - public func asBoolean() -> Bool? { - if case let .boolean(bool) = self { - return bool - } - - return nil - } - - public func asString() -> String? { - if case let .string(string) = self { - return string - } - - return nil - } - - public func asInteger() -> Int64? { - if case let .integer(int64) = self { - return int64 - } - - return nil - } - - public func asDouble() -> Double? { - if case let .double(double) = self { - return double - } - - return nil - } - - public func asDateComponents() -> DateComponents? { - if case let .date(dateComponents) = self { - return dateComponents - } - - return nil - } - - public func asDate() -> Date? { - if case let .timestamp(date) = self { - return date - } - - return nil - } - - public func asList() -> [ConfidenceValueInternal]? { - if case let .list(values) = self { - return values - } - - return nil - } - - public func asStructure() -> [String: ConfidenceValueInternal]? { - if case let .structure(values) = self { - return values - } - - return nil - } - - public func isNull() -> Bool { - if case .null = self { - return true - } - - return false - } } extension ConfidenceValueInternal: CustomStringConvertible { From cf1a7b0a4f5e1e13a3ba9a189e2b98ac29e6d8cc Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Fri, 5 Apr 2024 09:51:16 +0200 Subject: [PATCH 12/18] Change time-related arg labels --- Sources/Confidence/ConfidenceValue.swift | 8 +++--- .../ConfidenceValueTests.swift | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index af6fb272..08d2e563 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -52,12 +52,12 @@ public class ConfidenceValue: Equatable, Encodable { } - public init(dateComponentList: [DateComponents]) { - self.value = .list(dateComponentList.map { .date($0) }) + public init(dateList: [DateComponents]) { + self.value = .list(dateList.map { .date($0) }) } - public init(dateList: [Date]) { - self.value = .list(dateList.map { .timestamp($0) }) + public init(timestampList: [Date]) { + self.value = .list(timestampList.map { .timestamp($0) }) } public init(structure: [String: ConfidenceValue]) { diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index 6f9a2d91..e6cd2c9c 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -41,9 +41,28 @@ final class ConfidenceConfidenceValueTests: XCTestCase { XCTAssertEqual(value.asDateComponents(), dateComponents) } - func testListShouldConvertToList() { - let value = ConfidenceValue(integerList: [3, 4]) - XCTAssertEqual(value.asList(), [ConfidenceValue(integer: 3), ConfidenceValue(integer: 4)]) + func testListShouldConvertToList() throws { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + formatter.timeZone = TimeZone(abbreviation: "UTC") + let date1 = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + let dateComponents1 = DateComponents(year: 2024, month: 4, day: 3) + let date2 = try XCTUnwrap(formatter.date(from: "2022-01-02 00:00:00")) + let dateComponents2 = DateComponents(year: 2024, month: 4, day: 2) + + let boolListValue = ConfidenceValue(boolList: [true, false]) + let integerListValue = ConfidenceValue(integerList: [3, 4]) + let doubleListValue = ConfidenceValue(doubleList: [3.14, 4.0]) + let stringListValue = ConfidenceValue(stringList: ["val1", "val2"]) + let timestampListValue = ConfidenceValue(timestampList: [date1, date2]) + let dateListValue = ConfidenceValue(dateList: [dateComponents1, dateComponents2]) + + XCTAssertEqual(boolListValue.asList(), [ConfidenceValue(boolean: true), ConfidenceValue(boolean: false)]) + XCTAssertEqual(integerListValue.asList(), [ConfidenceValue(integer: 3), ConfidenceValue(integer: 4)]) + XCTAssertEqual(doubleListValue.asList(), [ConfidenceValue(double: 3.14), ConfidenceValue(double: 4.0)]) + XCTAssertEqual(stringListValue.asList(), [ConfidenceValue(string: "val1"), ConfidenceValue(string: "val2")]) + XCTAssertEqual(timestampListValue.asList(), [ConfidenceValue(timestamp: date1), ConfidenceValue(timestamp: date2)]) + XCTAssertEqual(dateListValue.asList(), [ConfidenceValue(date: dateComponents1), ConfidenceValue(date: dateComponents2)]) } func testStructShouldConvertToStruct() { From a3a6b4d09ad5db8cd86eb9d1efb331fbe3e3d514 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Fri, 5 Apr 2024 14:31:45 +0200 Subject: [PATCH 13/18] Fix date format --- Sources/Confidence/ConfidenceValue.swift | 2 +- Tests/ConfidenceTests/ConfidenceValueTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 08d2e563..afbe00c0 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -212,7 +212,7 @@ extension ConfidenceValueInternal { try container.encode(boolean) case .date(let dateComponents): let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "dd-MM-yyyy" + dateFormatter.dateFormat = "yyyy-MM-dd" if let date = Calendar.current.date(from: dateComponents) { try container.encode(dateFormatter.string(from: date)) } else { diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index e6cd2c9c..f315b577 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -121,7 +121,7 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let resultString = String(data: try encoder.encode(value), encoding: .utf8) let expectedString = """ {\"bool\":true, - \"date\":\"03-04-2024\", + \"date\":\"2024-04-03\", \"double\":4.5, \"int\":3, \"list\":[3,5], From 3120a424b45a13b7d56bd0ecc422deefb9235ccb Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Fri, 5 Apr 2024 15:00:00 +0200 Subject: [PATCH 14/18] Explicit UTC settings and TZ tests --- Sources/Confidence/ConfidenceValue.swift | 18 ++++++++++----- .../ConfidenceValueTests.swift | 22 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index afbe00c0..6ee62128 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -21,10 +21,16 @@ public class ConfidenceValue: Equatable, Encodable { self.value = .double(double) } + /// `date` should have at least precision to the "day". + /// If a custom TimeZone is set for the input DateComponents, the internal serializers + /// will convert the input to the local TimeZone before extracting the calendar day. public init(date: DateComponents) { self.value = .date(date) } + /// If a custom TimeZone is set for the input Date, the internal serializers will convert + /// the input to the local TimeZone (i.e. the local offset information is maintained + /// rather than the one customly set in Date). public init(timestamp: Date) { self.value = .timestamp(timestamp) } @@ -211,17 +217,19 @@ extension ConfidenceValueInternal { case .boolean(let boolean): try container.encode(boolean) case .date(let dateComponents): - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd" + let dateFormatter = ISO8601DateFormatter() + dateFormatter.timeZone = TimeZone.current + dateFormatter.formatOptions = [.withFullDate] if let date = Calendar.current.date(from: dateComponents) { try container.encode(dateFormatter.string(from: date)) } else { throw ConfidenceError.internalError(message: "Could not create date from components") } case .timestamp(let date): - let isoFormatter = ISO8601DateFormatter() - let formattedDate = isoFormatter.string(from: date) - try container.encode(formattedDate) + let timestampFormatter = ISO8601DateFormatter() + timestampFormatter.timeZone = TimeZone.current + let timestamp = timestampFormatter.string(from: date) + try container.encode(timestamp) case .structure(let structure): try container.encode(structure) case .list(let list): diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index f315b577..2fddf30c 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -61,8 +61,14 @@ final class ConfidenceConfidenceValueTests: XCTestCase { XCTAssertEqual(integerListValue.asList(), [ConfidenceValue(integer: 3), ConfidenceValue(integer: 4)]) XCTAssertEqual(doubleListValue.asList(), [ConfidenceValue(double: 3.14), ConfidenceValue(double: 4.0)]) XCTAssertEqual(stringListValue.asList(), [ConfidenceValue(string: "val1"), ConfidenceValue(string: "val2")]) - XCTAssertEqual(timestampListValue.asList(), [ConfidenceValue(timestamp: date1), ConfidenceValue(timestamp: date2)]) - XCTAssertEqual(dateListValue.asList(), [ConfidenceValue(date: dateComponents1), ConfidenceValue(date: dateComponents2)]) + XCTAssertEqual(timestampListValue.asList(), [ + ConfidenceValue(timestamp: date1), + ConfidenceValue(timestamp: date2) + ]) + XCTAssertEqual(dateListValue.asList(), [ + ConfidenceValue(date: dateComponents1), + ConfidenceValue(date: dateComponents2) + ]) } func testStructShouldConvertToStruct() { @@ -101,8 +107,8 @@ final class ConfidenceConfidenceValueTests: XCTestCase { func testEncodeDecode() throws { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - formatter.timeZone = TimeZone(abbreviation: "UTC") - let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + formatter.timeZone = TimeZone(abbreviation: "EDT") // Verify TimeZone conversion + let date = try XCTUnwrap(formatter.date(from: "2024-04-05 16:00:00")) let dateComponents = DateComponents(year: 2024, month: 4, day: 3) let value = ConfidenceValue(structure: ([ @@ -119,6 +125,10 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys let resultString = String(data: try encoder.encode(value), encoding: .utf8) + + let isoFormatter = ISO8601DateFormatter() + isoFormatter.timeZone = TimeZone.current + let expectedSerializedTimestamp = isoFormatter.string(from: date) let expectedString = """ {\"bool\":true, \"date\":\"2024-04-03\", @@ -128,9 +138,11 @@ final class ConfidenceConfidenceValueTests: XCTestCase { \"null\":null, \"string\":\"value\", \"structure\":{\"int\":5}, - \"timestamp\":\"2022-01-01T12:00:00Z\"} + \"timestamp\":\"\(expectedSerializedTimestamp)\"} """.replacingOccurrences(of: "\n", with: "") // Newlines were added for readability + // The "base" timestamp is in UTC, but the local offset is added (e.g. "+002"). + XCTAssertTrue(expectedSerializedTimestamp.starts(with: "2024-04-05T22:00:00")) XCTAssertEqual(resultString, expectedString) } } From 046e74ecd13af3c5ebd2db7b48f41f04a796c66b Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Mon, 8 Apr 2024 17:27:01 +0200 Subject: [PATCH 15/18] Best effort convert OF lists --- Sources/Confidence/ConfidenceValue.swift | 43 +++++++++++++++--- .../Utils/ConfidenceTypeMapper.swift | 33 +++++++++++++- .../ConfidenceTypeMapperTest.swift | 45 +++++++++++++++++++ 3 files changed, 115 insertions(+), 6 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 6ee62128..89c9f2b2 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..a9a8aed7 100644 --- a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift +++ b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift @@ -13,6 +13,7 @@ public enum ConfidenceTypeMapper { return ctxMap.compactMapValues(convertValue) } + // swiftlint:disable:next cyclomatic_complexity static private func convertValue(_ value: Value) -> ConfidenceValue { switch value { case .boolean(let value): @@ -26,7 +27,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..3c3fe1fb 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" From 80b7512bafa3197eabafdaa9311a41722cf7009a Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Mon, 8 Apr 2024 17:27:37 +0200 Subject: [PATCH 16/18] Rename bool to boolean --- Sources/Confidence/ConfidenceValue.swift | 4 ++-- Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift | 2 +- Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift | 4 ++-- Tests/ConfidenceTests/ConfidenceValueTests.swift | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 89c9f2b2..3c398fc6 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -35,8 +35,8 @@ public class ConfidenceValue: Equatable, Encodable { self.value = .timestamp(timestamp) } - public init(boolList: [Bool]) { - self.value = .list(boolList.map { .boolean($0) }) + public init(booleanList: [Bool]) { + self.value = .list(booleanList.map { .boolean($0) }) } public init(stringList: [String]) { diff --git a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift index a9a8aed7..6f1d0edf 100644 --- a/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift +++ b/Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift @@ -33,7 +33,7 @@ public enum ConfidenceTypeMapper { } switch listType { case .boolean: - return ConfidenceValue.init(boolList: values.compactMap { $0.asBoolean() }) + return ConfidenceValue.init(booleanList: values.compactMap { $0.asBoolean() }) case .string: return ConfidenceValue.init(stringList: values.compactMap { $0.asString() }) case .integer: diff --git a/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift index 3c3fe1fb..7fe525c4 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift @@ -28,7 +28,7 @@ class ValueConverterTest: XCTestCase { targetingKey: "userid", structure: MutableStructure(attributes: ([ "stringList": .list([.string("test1"), .string("test2")]), - "boolList": .list([.boolean(true), .boolean(false)]), + "booleanList": .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)]), @@ -39,7 +39,7 @@ class ValueConverterTest: XCTestCase { let confidenceStruct = ConfidenceTypeMapper.from(ctx: openFeatureCtx) let expected = [ "stringList": ConfidenceValue(stringList: ["test1", "test2"]), - "boolList": ConfidenceValue(boolList: [true, false]), + "booleanList": ConfidenceValue(booleanList: [true, false]), "integerList": ConfidenceValue(integerList: [11, 33]), "doubleList": ConfidenceValue(doubleList: [3.14, 1.0]), "dateList": ConfidenceValue(timestampList: [date1, date2]), diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index 2fddf30c..1a312c94 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -50,14 +50,14 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let date2 = try XCTUnwrap(formatter.date(from: "2022-01-02 00:00:00")) let dateComponents2 = DateComponents(year: 2024, month: 4, day: 2) - let boolListValue = ConfidenceValue(boolList: [true, false]) + let booleanListValue = ConfidenceValue(booleanList: [true, false]) let integerListValue = ConfidenceValue(integerList: [3, 4]) let doubleListValue = ConfidenceValue(doubleList: [3.14, 4.0]) let stringListValue = ConfidenceValue(stringList: ["val1", "val2"]) let timestampListValue = ConfidenceValue(timestampList: [date1, date2]) let dateListValue = ConfidenceValue(dateList: [dateComponents1, dateComponents2]) - XCTAssertEqual(boolListValue.asList(), [ConfidenceValue(boolean: true), ConfidenceValue(boolean: false)]) + XCTAssertEqual(booleanListValue.asList(), [ConfidenceValue(boolean: true), ConfidenceValue(boolean: false)]) XCTAssertEqual(integerListValue.asList(), [ConfidenceValue(integer: 3), ConfidenceValue(integer: 4)]) XCTAssertEqual(doubleListValue.asList(), [ConfidenceValue(double: 3.14), ConfidenceValue(double: 4.0)]) XCTAssertEqual(stringListValue.asList(), [ConfidenceValue(string: "val1"), ConfidenceValue(string: "val2")]) From ac9cc885294f4b3166bf5d68526b360c7d1243c9 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Mon, 8 Apr 2024 17:33:08 +0200 Subject: [PATCH 17/18] Rename TZ offset from timestamps --- Sources/Confidence/ConfidenceValue.swift | 2 +- Tests/ConfidenceTests/ConfidenceValueTests.swift | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index 3c398fc6..a6e0e3d0 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -260,7 +260,7 @@ extension ConfidenceValueInternal { } case .timestamp(let date): let timestampFormatter = ISO8601DateFormatter() - timestampFormatter.timeZone = TimeZone.current + timestampFormatter.timeZone = TimeZone.init(identifier: "UTC") let timestamp = timestampFormatter.string(from: date) try container.encode(timestamp) case .structure(let structure): diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index 1a312c94..08617c7d 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -126,9 +126,6 @@ final class ConfidenceConfidenceValueTests: XCTestCase { encoder.outputFormatting = .sortedKeys let resultString = String(data: try encoder.encode(value), encoding: .utf8) - let isoFormatter = ISO8601DateFormatter() - isoFormatter.timeZone = TimeZone.current - let expectedSerializedTimestamp = isoFormatter.string(from: date) let expectedString = """ {\"bool\":true, \"date\":\"2024-04-03\", @@ -138,11 +135,9 @@ final class ConfidenceConfidenceValueTests: XCTestCase { \"null\":null, \"string\":\"value\", \"structure\":{\"int\":5}, - \"timestamp\":\"\(expectedSerializedTimestamp)\"} + \"timestamp\":\"2024-04-05T20:00:00Z"} """.replacingOccurrences(of: "\n", with: "") // Newlines were added for readability - // The "base" timestamp is in UTC, but the local offset is added (e.g. "+002"). - XCTAssertTrue(expectedSerializedTimestamp.starts(with: "2024-04-05T22:00:00")) XCTAssertEqual(resultString, expectedString) } } From 5527f937ac462b69627b4a82bab08af3a38be612 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Mon, 8 Apr 2024 18:56:47 +0200 Subject: [PATCH 18/18] Add swiftpm back --- .gitignore | 3 +- .../xcschemes/Confidence-Package.xcscheme | 123 ++++++++++++++++++ .../xcschemes/Confidence.xcscheme | 66 ++++++++++ .../xcschemes/ConfidenceProvider.xcscheme | 92 +++++++++++++ 4 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/ConfidenceProvider.xcscheme diff --git a/.gitignore b/.gitignore index ad8ce721..f9658ead 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,4 @@ DerivedData/ .netrc .build .mockingbird -project.json -.swiftpm \ No newline at end of file +project.json \ No newline at end of file diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme new file mode 100644 index 00000000..4f466f8b --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme new file mode 100644 index 00000000..0e8ddb01 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ConfidenceProvider.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ConfidenceProvider.xcscheme new file mode 100644 index 00000000..6eaaa02e --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ConfidenceProvider.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +