From a22638136094fbf59bf01a732798a45e75424749 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Tue, 27 Feb 2018 16:25:52 -0500 Subject: [PATCH] conditional conformance fixes --- .../PostgreSQLArrayCustomConvertible.swift | 42 ++++++++++++------ .../Data/PostgreSQLData+Optional.swift | 15 +++---- .../PostgreSQLJSONCustomConvertible.swift | 43 ++++++++++++++++--- .../PostgreSQLConnectionTests.swift | 2 +- 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/Sources/PostgreSQL/Data/PostgreSQLArrayCustomConvertible.swift b/Sources/PostgreSQL/Data/PostgreSQLArrayCustomConvertible.swift index 1bd8ec24..69a6d592 100644 --- a/Sources/PostgreSQL/Data/PostgreSQLArrayCustomConvertible.swift +++ b/Sources/PostgreSQL/Data/PostgreSQLArrayCustomConvertible.swift @@ -1,9 +1,9 @@ import Foundation /// Representable by a `T[]` column on the PostgreSQL database. -public protocol PostgreSQLArrayCustomConvertible: PostgreSQLDataCustomConvertible, Codable { +public protocol PostgreSQLArrayCustomConvertible: PostgreSQLDataCustomConvertible { /// The associated array element type - associatedtype PostgreSQLArrayElement: PostgreSQLDataCustomConvertible + associatedtype PostgreSQLArrayElement // : PostgreSQLDataCustomConvertible /// Convert an array of elements to self. static func convertFromPostgreSQLArray(_ data: [PostgreSQLArrayElement]) -> Self @@ -13,11 +13,6 @@ public protocol PostgreSQLArrayCustomConvertible: PostgreSQLDataCustomConvertibl } extension PostgreSQLArrayCustomConvertible { - /// See `PostgreSQLDataCustomConvertible.postgreSQLDataType` - public static var postgreSQLDataType: PostgreSQLDataType { - return PostgreSQLArrayElement.postgreSQLDataArrayType - } - /// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)` public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self { guard var value = data.data else { @@ -35,8 +30,8 @@ extension PostgreSQLArrayCustomConvertible { let count = Int(value.extract(Int32.self).bigEndian) let subValue = value.extract(count: count) let psqlData = PostgreSQLData(type: metadata.type, format: data.format, data: subValue) - let element = try PostgreSQLArrayElement.convertFromPostgreSQLData(psqlData) - array.append(element) + let element = try requirePostgreSQLDataCustomConvertible(PostgreSQLArrayElement.self).convertFromPostgreSQLData(psqlData) + array.append(element as! PostgreSQLArrayElement) } } else { array = [] @@ -48,13 +43,13 @@ extension PostgreSQLArrayCustomConvertible { /// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()` public func convertToPostgreSQLData() throws -> PostgreSQLData { let elements = try convertToPostgreSQLArray().map { - try $0.convertToPostgreSQLData() + try requirePostgreSQLDataCustomConvertible($0).convertToPostgreSQLData() } var data = Data() data += Int32(1).data // non-null data += Int32(0).data // b - data += PostgreSQLArrayElement.postgreSQLDataType.raw.data // type + data += requirePostgreSQLDataCustomConvertible(PostgreSQLArrayElement.self).postgreSQLDataType.raw.data // type data += Int32(elements.count).data // length data += Int32(1).data // dimensions @@ -67,7 +62,7 @@ extension PostgreSQLArrayCustomConvertible { } } - return PostgreSQLData(type: PostgreSQLArrayElement.postgreSQLDataArrayType, format: .binary, data: data) + return PostgreSQLData(type: requirePostgreSQLDataCustomConvertible(PostgreSQLArrayElement.self).postgreSQLDataArrayType, format: .binary, data: data) } } @@ -107,10 +102,15 @@ extension PostgreSQLArrayMetadata: CustomStringConvertible { } } -extension Array: PostgreSQLArrayCustomConvertible where Element: Codable, Element: PostgreSQLDataCustomConvertible { +extension Array: PostgreSQLArrayCustomConvertible { /// See `PostgreSQLArrayCustomConvertible.postgreSQLDataArrayType` public static var postgreSQLDataArrayType: PostgreSQLDataType { - return Element.postgreSQLDataArrayType + fatalError("Multi-dimensional arrays are not yet supported.") + } + + /// See `PostgreSQLDataCustomConvertible.postgreSQLDataType` + public static var postgreSQLDataType: PostgreSQLDataType { + return requirePostgreSQLDataCustomConvertible(Element.self).postgreSQLDataArrayType } /// See `PostgreSQLArrayCustomConvertible.PostgreSQLArrayElement` @@ -126,3 +126,17 @@ extension Array: PostgreSQLArrayCustomConvertible where Element: Codable, Elemen return self } } + +func requirePostgreSQLDataCustomConvertible(_ type: T.Type) -> PostgreSQLDataCustomConvertible.Type { + guard let custom = T.self as? PostgreSQLDataCustomConvertible.Type else { + fatalError("`\(T.self)` does not conform to `PostgreSQLDataCustomConvertible`") + } + return custom +} + +func requirePostgreSQLDataCustomConvertible(_ type: T) -> PostgreSQLDataCustomConvertible { + guard let custom = type as? PostgreSQLDataCustomConvertible else { + fatalError("`\(T.self)` does not conform to `PostgreSQLDataCustomConvertible`") + } + return custom +} diff --git a/Sources/PostgreSQL/Data/PostgreSQLData+Optional.swift b/Sources/PostgreSQL/Data/PostgreSQLData+Optional.swift index 90f0fe56..32fd95f9 100644 --- a/Sources/PostgreSQL/Data/PostgreSQLData+Optional.swift +++ b/Sources/PostgreSQL/Data/PostgreSQLData+Optional.swift @@ -1,33 +1,32 @@ import Async import Foundation -extension OptionalType where Self.WrappedType: PostgreSQLDataCustomConvertible { +extension OptionalType { /// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)` public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self { - let wrapped = try WrappedType.convertFromPostgreSQLData(data) - return Self.makeOptionalType(wrapped) + let wrapped = try requirePostgreSQLDataCustomConvertible(WrappedType.self).convertFromPostgreSQLData(data) + return Self.makeOptionalType(wrapped as? WrappedType) } /// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()` public func convertToPostgreSQLData() throws -> PostgreSQLData { if let wrapped = self.wrapped { - return try wrapped.convertToPostgreSQLData() + return try requirePostgreSQLDataCustomConvertible(wrapped).convertToPostgreSQLData() } else { return PostgreSQLData(type: .void, format: .binary, data: nil) } } } -extension Optional: PostgreSQLDataCustomConvertible where Wrapped: PostgreSQLDataCustomConvertible { +extension Optional: PostgreSQLDataCustomConvertible { /// See `PostgreSQLDataCustomConvertible.postgreSQLDataType` public static var postgreSQLDataType: PostgreSQLDataType { - return Wrapped.postgreSQLDataType + return requirePostgreSQLDataCustomConvertible(Wrapped.self).postgreSQLDataType } /// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType` public static var postgreSQLDataArrayType: PostgreSQLDataType { - return Wrapped.postgreSQLDataArrayType + return requirePostgreSQLDataCustomConvertible(Wrapped.self).postgreSQLDataArrayType } } - diff --git a/Sources/PostgreSQL/Data/PostgreSQLJSONCustomConvertible.swift b/Sources/PostgreSQL/Data/PostgreSQLJSONCustomConvertible.swift index 6c1a3dae..d305931e 100644 --- a/Sources/PostgreSQL/Data/PostgreSQLJSONCustomConvertible.swift +++ b/Sources/PostgreSQL/Data/PostgreSQLJSONCustomConvertible.swift @@ -2,7 +2,7 @@ import COperatingSystem import Foundation /// Representable by a `JSONB` column on the PostgreSQL database. -public protocol PostgreSQLJSONCustomConvertible: PostgreSQLDataCustomConvertible, Codable { } +public protocol PostgreSQLJSONCustomConvertible: PostgreSQLDataCustomConvertible { } extension PostgreSQLJSONCustomConvertible { /// See `PostgreSQLDataCustomConvertible.postgreSQLDataType` @@ -17,13 +17,21 @@ extension PostgreSQLJSONCustomConvertible { throw PostgreSQLError(identifier: "data", reason: "Unable to decode PostgreSQL JSON from `null` data.", source: .capture()) } + + guard let decodable = Self.self as? Decodable.Type else { + fatalError("`\(Self.self)` is not `Decodable`.") + } + switch data.type { case .jsonb: switch data.format { - case .text: return try JSONDecoder().decode(Self.self, from: value) + case .text: + let decoder = try JSONDecoder().decode(DecoderUnwrapper.self, from: value).decoder + return try decodable.init(from: decoder) as! Self case .binary: assert(value[0] == 0x01) - return try JSONDecoder().decode(Self.self, from: value[1...]) + let decoder = try JSONDecoder().decode(DecoderUnwrapper.self, from: value[1...]).decoder + return try decodable.init(from: decoder) as! Self } default: throw PostgreSQLError(identifier: "json", reason: "Could not decode \(Self.self) from data type: \(data.type).", source: .capture()) } @@ -31,8 +39,33 @@ extension PostgreSQLJSONCustomConvertible { /// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()` public func convertToPostgreSQLData() throws -> PostgreSQLData { - return try PostgreSQLData(type: .jsonb, format: .text, data: JSONEncoder().encode(self)) + guard let encodable = self as? Encodable else { + fatalError("`\(Self.self)` is not `Encodable`.") + } + return try PostgreSQLData( + type: .jsonb, + format: .text, + data: JSONEncoder().encode(EncoderWrapper(encodable)) + ) + } +} + +extension Dictionary: PostgreSQLJSONCustomConvertible { } + +fileprivate struct DecoderUnwrapper: Decodable { + let decoder: Decoder + init(from decoder: Decoder) { + self.decoder = decoder + } +} + +fileprivate struct EncoderWrapper: Encodable { + var encodable: Encodable + init(_ encodable: Encodable) { + self.encodable = encodable + } + func encode(to encoder: Encoder) throws { + try encodable.encode(to: encoder) } } -extension Dictionary: PostgreSQLJSONCustomConvertible where Key: Codable, Value: Codable { } diff --git a/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift b/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift index 8f98521a..c47951bf 100644 --- a/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift @@ -247,7 +247,7 @@ class PostgreSQLConnectionTests: XCTestCase { } func testStruct() throws { - struct Hello: PostgreSQLJSONCustomConvertible { + struct Hello: PostgreSQLJSONCustomConvertible, Codable { var message: String }