Skip to content

Commit

Permalink
Merge pull request #22 from vapor/cc-fixes
Browse files Browse the repository at this point in the history
conditional conformance fixes
  • Loading branch information
tanner0101 authored Feb 27, 2018
2 parents 25a0435 + a226381 commit 2d93ec3
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 28 deletions.
42 changes: 28 additions & 14 deletions Sources/PostgreSQL/Data/PostgreSQLArrayCustomConvertible.swift
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -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 = []
Expand All @@ -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

Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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`
Expand All @@ -126,3 +126,17 @@ extension Array: PostgreSQLArrayCustomConvertible where Element: Codable, Elemen
return self
}
}

func requirePostgreSQLDataCustomConvertible<T>(_ 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<T>(_ type: T) -> PostgreSQLDataCustomConvertible {
guard let custom = type as? PostgreSQLDataCustomConvertible else {
fatalError("`\(T.self)` does not conform to `PostgreSQLDataCustomConvertible`")
}
return custom
}
15 changes: 7 additions & 8 deletions Sources/PostgreSQL/Data/PostgreSQLData+Optional.swift
Original file line number Diff line number Diff line change
@@ -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
}
}

43 changes: 38 additions & 5 deletions Sources/PostgreSQL/Data/PostgreSQLJSONCustomConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -17,22 +17,55 @@ 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())
}
}

/// 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 { }
2 changes: 1 addition & 1 deletion Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ class PostgreSQLConnectionTests: XCTestCase {
}

func testStruct() throws {
struct Hello: PostgreSQLJSONCustomConvertible {
struct Hello: PostgreSQLJSONCustomConvertible, Codable {
var message: String
}

Expand Down

0 comments on commit 2d93ec3

Please sign in to comment.