From 32daa61a0fb39eb53369affa539b40a94f5ad208 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 3 Apr 2024 11:59:48 +0200 Subject: [PATCH] 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) } }