Update the CBOREncodable protocol to require that a toCBOR function be
implemented. The `encode` function can then rely on a default implementation that will call `toCBOR()` and then encode the resulting `CBOR` object.
This commit is contained in:
+107
-22
@@ -3,7 +3,17 @@ import Foundation
|
||||
#endif
|
||||
|
||||
public protocol CBOREncodable {
|
||||
/// Optional function that can potentially serve as an opportunity to optimize encoding.
|
||||
func encode(options: CBOROptions) -> [UInt8]
|
||||
|
||||
/// Required function that returns the appropriate `CBOR` variant for a `CBOREncodable`-conforming value.
|
||||
func toCBOR(options: CBOROptions) -> CBOR
|
||||
}
|
||||
|
||||
extension CBOREncodable {
|
||||
func encode(options: CBOROptions) -> [UInt8] {
|
||||
self.toCBOR(options: options).encode(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension CBOR: CBOREncodable {
|
||||
@@ -30,45 +40,49 @@ extension CBOR: CBOREncodable {
|
||||
case .break: return CBOR.encodeBreak()
|
||||
}
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
if (self < 0) {
|
||||
return CBOR.encodeNegativeInt(Int64(self))
|
||||
} else {
|
||||
return CBOR.encodeVarUInt(UInt64(self))
|
||||
}
|
||||
return Int64(self).encode(options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return Int64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int8: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
if (self < 0) {
|
||||
return CBOR.encodeNegativeInt(Int64(self))
|
||||
} else {
|
||||
return CBOR.encodeUInt8(UInt8(self))
|
||||
}
|
||||
return Int64(self).encode(options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return Int64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int16: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
if (self < 0) {
|
||||
return CBOR.encodeNegativeInt(Int64(self))
|
||||
} else {
|
||||
return CBOR.encodeUInt16(UInt16(self))
|
||||
}
|
||||
return Int64(self).encode(options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return Int64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int32: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
if (self < 0) {
|
||||
return CBOR.encodeNegativeInt(Int64(self))
|
||||
} else {
|
||||
return CBOR.encodeUInt32(UInt32(self))
|
||||
}
|
||||
return Int64(self).encode(options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return Int64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,14 +91,26 @@ extension Int64: CBOREncodable {
|
||||
if (self < 0) {
|
||||
return CBOR.encodeNegativeInt(self)
|
||||
} else {
|
||||
return CBOR.encodeUInt64(UInt64(self))
|
||||
return CBOR.encodeVarUInt(UInt64(self))
|
||||
}
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
if self < 0 {
|
||||
return CBOR.negativeInt(~UInt64(bitPattern: self))
|
||||
} else {
|
||||
return CBOR.unsignedInt(UInt64(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeVarUInt(UInt64(self))
|
||||
return UInt64(self).encode(options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return UInt64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,60 +118,100 @@ extension UInt8: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeUInt8(self)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return UInt64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt16: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeUInt16(self)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return UInt64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt32: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeUInt32(self)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return UInt64(self).toCBOR(options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt64: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeUInt64(self)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.unsignedInt(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension String: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeString(self, options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.utf8String(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Float: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeFloat(self)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.float(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Double: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeDouble(self)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.double(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Bool: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeBool(self)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.boolean(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeArray(self, options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.array(self.map { $0.toCBOR(options: options) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary where Key: CBOREncodable, Value: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeMap(self, options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.map(Dictionary<CBOR, CBOR>(uniqueKeysWithValues: self.map { ($0.key.toCBOR(options: options), $0.value.toCBOR(options: options)) }))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: CBOREncodable {
|
||||
@@ -155,12 +221,23 @@ extension Optional where Wrapped: CBOREncodable {
|
||||
case .none: return CBOR.encodeNull()
|
||||
}
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
switch self {
|
||||
case .some(let wrapped): return wrapped.toCBOR(options: options)
|
||||
case .none: return CBOR.null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSNull: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeNull()
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.null
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(Foundation)
|
||||
@@ -168,11 +245,19 @@ extension Date: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeDate(self, options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.date(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Data: CBOREncodable {
|
||||
public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] {
|
||||
return CBOR.encodeByteString(self.map{ $0 }, options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR.byteString(self.map { $0 })
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -297,6 +297,14 @@ extension CBOR {
|
||||
switch any {
|
||||
case is Int:
|
||||
return (any as! Int).encode()
|
||||
case is Int8:
|
||||
return (any as! Int8).encode()
|
||||
case is Int16:
|
||||
return (any as! Int16).encode()
|
||||
case is Int32:
|
||||
return (any as! Int32).encode()
|
||||
case is Int64:
|
||||
return (any as! Int64).encode()
|
||||
case is UInt:
|
||||
return (any as! UInt).encode()
|
||||
case is UInt8:
|
||||
@@ -356,6 +364,84 @@ extension CBOR {
|
||||
}
|
||||
}
|
||||
|
||||
internal static func cborFromAny(_ any: Any?, options: CBOROptions = CBOROptions()) throws -> CBOR {
|
||||
switch any {
|
||||
case is Int:
|
||||
return cborFromInt64(Int64(any as! Int))
|
||||
case is Int8:
|
||||
return cborFromInt64(Int64(any as! Int8))
|
||||
case is Int16:
|
||||
return cborFromInt64(Int64(any as! Int16))
|
||||
case is Int32:
|
||||
return cborFromInt64(Int64(any as! Int32))
|
||||
case is Int64:
|
||||
return cborFromInt64(any as! Int64)
|
||||
case is UInt:
|
||||
return CBOR.unsignedInt(UInt64(any as! UInt))
|
||||
case is UInt8:
|
||||
return CBOR.unsignedInt(UInt64(any as! UInt8))
|
||||
case is UInt16:
|
||||
return CBOR.unsignedInt(UInt64(any as! UInt16))
|
||||
case is UInt32:
|
||||
return CBOR.unsignedInt(UInt64(any as! UInt32))
|
||||
case is UInt64:
|
||||
return CBOR.unsignedInt(any as! UInt64)
|
||||
case is String:
|
||||
return CBOR.utf8String(any as! String)
|
||||
case is Float:
|
||||
return CBOR.float(any as! Float)
|
||||
case is Double:
|
||||
return CBOR.double(any as! Double)
|
||||
case is Bool:
|
||||
return CBOR.boolean(any as! Bool)
|
||||
case is [UInt8]:
|
||||
return CBOR.byteString(any as! [UInt8])
|
||||
#if canImport(Foundation)
|
||||
case is Data:
|
||||
return CBOR.byteString((any as! Data).map { $0 })
|
||||
case is Date:
|
||||
return CBOR.date(any as! Date)
|
||||
case is NSNull:
|
||||
return CBOR.null
|
||||
#endif
|
||||
case is [Any?]:
|
||||
let anyArr = any as! [Any?]
|
||||
return try CBOR.array(anyArr.map { try cborFromAny($0) })
|
||||
case is [String: Any?]:
|
||||
let anyMap = any as! [String: Any?]
|
||||
return try CBOR.map(Dictionary(
|
||||
uniqueKeysWithValues: anyMap.map { try (cborFromAny($0.key), cborFromAny($0.value)) }
|
||||
))
|
||||
case is Void:
|
||||
return CBOR.undefined
|
||||
case nil:
|
||||
return CBOR.null
|
||||
default:
|
||||
if let encodable = any as? CBOREncodable {
|
||||
return encodable.toCBOR(options: options)
|
||||
} else if let encodable = any as? Codable {
|
||||
// This is very much a slow path - we fully encode and then
|
||||
// decode the value to get it as a `CBOR`
|
||||
let encoder = CodableCBOREncoder()
|
||||
encoder.setOptions(options.toCodableEncoderOptions())
|
||||
let encoded = try [UInt8](encoder.encode(encodable))
|
||||
guard let decoded = try CBOR.decode(encoded) else {
|
||||
throw CBOREncoderError.invalidType
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
throw CBOREncoderError.invalidType
|
||||
}
|
||||
}
|
||||
|
||||
private static func cborFromInt64(_ i: Int64) -> CBOR {
|
||||
if i < 0 {
|
||||
return CBOR.negativeInt(~UInt64(bitPattern: i))
|
||||
} else {
|
||||
return CBOR.unsignedInt(UInt64(i))
|
||||
}
|
||||
}
|
||||
|
||||
private static func encodeMap<A: CBOREncodable>(_ map: [A: Any?], into res: inout [UInt8], options: CBOROptions = CBOROptions()) throws {
|
||||
if options.forbidNonStringMapKeys {
|
||||
try ensureStringKey(A.self)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import XCTest
|
||||
@testable import SwiftCBOR
|
||||
|
||||
class CBOREncodableTests: XCTestCase {
|
||||
func testToCBOR() {
|
||||
XCTAssertEqual(CBOR.unsignedInt(0), 0.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(1), 1.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(20), 20.toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64(Int8.max)), Int8.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(127, Int8.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64(Int16.max)), Int16.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(32_767, Int16.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64(Int32.max)), Int32.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(2_147_483_647, Int32.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64(Int64.max)), Int64.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(9_223_372_036_854_775_807, Int64.max.toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64(UInt8.max)), UInt8.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(255, UInt8.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64(UInt16.max)), UInt16.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(65_535, UInt16.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64(UInt32.max)), UInt32.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(4_294_967_295, UInt32.max.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.unsignedInt(UInt64.max), UInt64.max.toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.negativeInt(~UInt64(bitPattern: Int64(Int8.min))), Int8.min.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(-128, Int8.min.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.negativeInt(~UInt64(bitPattern: Int64(Int16.min))), Int16.min.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(-32_768, Int16.min.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.negativeInt(~UInt64(bitPattern: Int64(Int32.min))), Int32.min.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(-2_147_483_648, Int32.min.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.negativeInt(~UInt64(bitPattern: Int64(Int64.min))), Int64.min.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(-9_223_372_036_854_775_808, Int64.min.toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.double(Double.greatestFiniteMagnitude), Double.greatestFiniteMagnitude.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.double(Double.leastNonzeroMagnitude), Double.leastNonzeroMagnitude.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.double(Double.pi), Double.pi.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.double(0.123456789), 0.123456789.toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.float(Float.greatestFiniteMagnitude), Float.greatestFiniteMagnitude.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.float(Float.leastNonzeroMagnitude), Float.leastNonzeroMagnitude.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.float(Float.pi), Float.pi.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.float(0.123456789), Float(0.123456789).toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.boolean(true), true.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(true, true.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(CBOR.boolean(false), false.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(false, false.toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.utf8String("test"), "test".toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual("test", "test".toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.null, Optional<String>.none.toCBOR(options: CBOROptions()))
|
||||
XCTAssertEqual(nil, Optional<String>.none.toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.byteString([1, 2, 3]), Data([1, 2, 3]).toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(CBOR.array([CBOR.unsignedInt(1), CBOR.unsignedInt(2)]), [1, 2].toCBOR(options: CBOROptions()))
|
||||
|
||||
XCTAssertEqual(
|
||||
CBOR.map([CBOR.utf8String("a"): CBOR.unsignedInt(1), CBOR.utf8String("b"): CBOR.unsignedInt(2)]),
|
||||
["a": 1, "b": 2].toCBOR(options: CBOROptions())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -250,6 +250,10 @@ class CBOREncoderTests: XCTestCase {
|
||||
]
|
||||
return cborWrapper.encode(options: options)
|
||||
}
|
||||
|
||||
public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR {
|
||||
return CBOR(dictionaryLiteral:("x", CBOR(integerLiteral: self.x)), ("y", .utf8String(self.y)))
|
||||
}
|
||||
}
|
||||
|
||||
let encoded = MyStruct(x: 42, y: "words").encode()
|
||||
|
||||
Reference in New Issue
Block a user