diff --git a/MessagePack.xcodeproj/project.pbxproj b/MessagePack.xcodeproj/project.pbxproj index c8279a5..3a7442b 100644 --- a/MessagePack.xcodeproj/project.pbxproj +++ b/MessagePack.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 261F54B41F83A9470085CAB2 /* MessagePackEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261F54B31F83A9470085CAB2 /* MessagePackEncoder.swift */; }; + 261F54B71F83A9640085CAB2 /* MessagePackEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261F54B31F83A9470085CAB2 /* MessagePackEncoder.swift */; }; + 261F54B81F83A9640085CAB2 /* MessagePackEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261F54B31F83A9470085CAB2 /* MessagePackEncoder.swift */; }; + 261F54B91F83A9650085CAB2 /* MessagePackEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261F54B31F83A9470085CAB2 /* MessagePackEncoder.swift */; }; + 261F54BA1F83A9670085CAB2 /* MessagePackEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261F54B51F83A9570085CAB2 /* MessagePackEncoderTests.swift */; }; + 261F54BB1F83A9670085CAB2 /* MessagePackEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261F54B51F83A9570085CAB2 /* MessagePackEncoderTests.swift */; }; + 261F54BC1F83A9670085CAB2 /* MessagePackEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261F54B51F83A9570085CAB2 /* MessagePackEncoderTests.swift */; }; 825321871EFFC9C500914B55 /* MessagePack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8253217D1EFFC9C400914B55 /* MessagePack.framework */; }; 8253219E1EFFCA0D00914B55 /* ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825321971EFFCA0D00914B55 /* ConvenienceInitializers.swift */; }; 8253219F1EFFCA0D00914B55 /* ConvenienceProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825321981EFFCA0D00914B55 /* ConvenienceProperties.swift */; }; @@ -120,6 +127,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 261F54B31F83A9470085CAB2 /* MessagePackEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackEncoder.swift; sourceTree = ""; }; + 261F54B51F83A9570085CAB2 /* MessagePackEncoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackEncoderTests.swift; sourceTree = ""; }; 8253217D1EFFC9C400914B55 /* MessagePack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessagePack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 825321861EFFC9C500914B55 /* MessagePackTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MessagePackTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 825321971EFFCA0D00914B55 /* ConvenienceInitializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConvenienceInitializers.swift; path = Sources/MessagePack/ConvenienceInitializers.swift; sourceTree = SOURCE_ROOT; }; @@ -244,6 +253,7 @@ 825321981EFFCA0D00914B55 /* ConvenienceProperties.swift */, 825321991EFFCA0D00914B55 /* LiteralConvertibles.swift */, 8253219A1EFFCA0D00914B55 /* MessagePack.swift */, + 261F54B31F83A9470085CAB2 /* MessagePackEncoder.swift */, 8253219B1EFFCA0D00914B55 /* Pack.swift */, 8253219C1EFFCA0D00914B55 /* Subdata.swift */, 8253219D1EFFCA0D00914B55 /* Unpack.swift */, @@ -269,6 +279,7 @@ 825321B01EFFCA1C00914B55 /* HashValueTests.swift */, 825321B11EFFCA1C00914B55 /* IntegerTests.swift */, 825321B21EFFCA1C00914B55 /* MapTests.swift */, + 261F54B51F83A9570085CAB2 /* MessagePackEncoderTests.swift */, 825321B31EFFCA1C00914B55 /* NilTests.swift */, 825321B41EFFCA1C00914B55 /* StringTests.swift */, 825321B51EFFCA1C00914B55 /* SubdataTests.swift */, @@ -578,6 +589,7 @@ 8253219F1EFFCA0D00914B55 /* ConvenienceProperties.swift in Sources */, 8253219E1EFFCA0D00914B55 /* ConvenienceInitializers.swift in Sources */, 825321A21EFFCA0D00914B55 /* Pack.swift in Sources */, + 261F54B41F83A9470085CAB2 /* MessagePackEncoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -587,6 +599,7 @@ files = ( 825321C31EFFCA1C00914B55 /* IntegerTests.swift in Sources */, 825321C21EFFCA1C00914B55 /* HashValueTests.swift in Sources */, + 261F54BA1F83A9670085CAB2 /* MessagePackEncoderTests.swift in Sources */, 825321C11EFFCA1C00914B55 /* FloatTests.swift in Sources */, 825321BB1EFFCA1C00914B55 /* DescriptionTests.swift in Sources */, 825321B91EFFCA1C00914B55 /* ConvenienceInitializersTests.swift in Sources */, @@ -617,6 +630,7 @@ 825322461EFFCBC700914B55 /* ConvenienceProperties.swift in Sources */, 825322451EFFCBC700914B55 /* ConvenienceInitializers.swift in Sources */, 825322491EFFCBC700914B55 /* Pack.swift in Sources */, + 261F54B91F83A9650085CAB2 /* MessagePackEncoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -631,6 +645,7 @@ 8253223F1EFFCBC700914B55 /* ConvenienceProperties.swift in Sources */, 8253223E1EFFCBC700914B55 /* ConvenienceInitializers.swift in Sources */, 825322421EFFCBC700914B55 /* Pack.swift in Sources */, + 261F54B81F83A9640085CAB2 /* MessagePackEncoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -640,6 +655,7 @@ files = ( 825322281EFFCBC400914B55 /* ConveniencePropertiesTests.swift in Sources */, 8253222E1EFFCBC400914B55 /* FalseTests.swift in Sources */, + 261F54BB1F83A9670085CAB2 /* MessagePackEncoderTests.swift in Sources */, 825322291EFFCBC400914B55 /* DescriptionTests.swift in Sources */, 825322361EFFCBC400914B55 /* TrueTests.swift in Sources */, 8253222D1EFFCBC400914B55 /* ExtendedTests.swift in Sources */, @@ -670,6 +686,7 @@ 825322381EFFCBC700914B55 /* ConvenienceProperties.swift in Sources */, 825322371EFFCBC700914B55 /* ConvenienceInitializers.swift in Sources */, 8253223B1EFFCBC700914B55 /* Pack.swift in Sources */, + 261F54B71F83A9640085CAB2 /* MessagePackEncoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -679,6 +696,7 @@ files = ( 825322161EFFCBC400914B55 /* ConveniencePropertiesTests.swift in Sources */, 8253221C1EFFCBC400914B55 /* FalseTests.swift in Sources */, + 261F54BC1F83A9670085CAB2 /* MessagePackEncoderTests.swift in Sources */, 825322171EFFCBC400914B55 /* DescriptionTests.swift in Sources */, 825322241EFFCBC400914B55 /* TrueTests.swift in Sources */, 8253221B1EFFCBC400914B55 /* ExtendedTests.swift in Sources */, diff --git a/Sources/MessagePack/MessagePackEncoder.swift b/Sources/MessagePack/MessagePackEncoder.swift new file mode 100644 index 0000000..f2fef05 --- /dev/null +++ b/Sources/MessagePack/MessagePackEncoder.swift @@ -0,0 +1,1514 @@ +import Foundation + +/* + * The implementation of MessagePackEncoder/Decoder heavily references JSONEncoder.swift and PlistEncoder.swift + * from the Swift foundation library. As the logic required to implement this correctly is non-trivial and + * complicated, I kept the structure pretty much the same as JSONEncoder and PlistEncoder so that it is easy + * for anyone to cross reference. For your info, JSONEncoder is a single file with 2.1k lines of code =x. + * + * Warning for anyone who wants to modify this file, please make sure you understood all the code in JSONEncoder.swift + * and PlistEncoder.swift before doing so. There are reasons for why things are done in a particular way. + * + * Swift repo commit at time of reference: 24821ccb08832e5d6ef5d21d6730b51f93d9d210 + */ + +// MARK: - MessagePackEncoder + +open class MessagePackEncoder { + + open var userInfo: [CodingUserInfoKey : Any] = [:] + + fileprivate struct _Options { + let userInfo: [CodingUserInfoKey : Any] + } + + fileprivate var options: _Options { + return _Options(userInfo: userInfo) + } + + public init() {} + + open func encode(_ value: T) throws -> Data { + let messagePack = try self.messagePack(with: value) + return try encode(messagePack: messagePack) + } + + open func encode(messagePack: MessagePackValue) throws -> Data { + return pack(messagePack) + } + + open func messagePack(with value: T) throws -> MessagePackValue { + + let encoder = _MessagePackEncoder(options: options) + + guard let box = try encoder.box_(value) else { + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values.")) + } + + return box.messagePackValue + } +} + +// MARK: _MessagePackEncoder + +fileprivate class _MessagePackEncoder : Encoder { + + fileprivate var storage: _MessagePackEncodingStorage + + fileprivate let options: MessagePackEncoder._Options + + public var codingPath: [CodingKey] + + public var userInfo: [CodingUserInfoKey : Any] { + return self.options.userInfo + } + + fileprivate init(options: MessagePackEncoder._Options, codingPath: [CodingKey] = []) { + self.options = options + self.storage = _MessagePackEncodingStorage() + self.codingPath = codingPath + } + + fileprivate var canEncodeNewValue: Bool { + return self.storage.count == self.codingPath.count + } + + public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { + + let topContainer: _MessagePackDictionaryBox + if canEncodeNewValue { + topContainer = storage.pushKeyedContainer() + } else { + guard let container = storage.containers.last as? _MessagePackDictionaryBox else { + preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + let container = _MessagePackKeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer) + return KeyedEncodingContainer(container) + } + + public func unkeyedContainer() -> UnkeyedEncodingContainer { + + let topContainer: _MessagePackArrayBox + if canEncodeNewValue { + topContainer = storage.pushUnkeyedContainer() + } else { + guard let container = storage.containers.last as? _MessagePackArrayBox else { + preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + return _MessagePackUnkeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer) + } + + public func singleValueContainer() -> SingleValueEncodingContainer { + return self + } +} + +// MARK: Encoding Storage + +fileprivate struct _MessagePackEncodingStorage { + + private(set) fileprivate var containers: [_MessagePackBox] = [] + + fileprivate init() {} + + fileprivate var count: Int { + return self.containers.count + } + + fileprivate mutating func pushKeyedContainer() -> _MessagePackDictionaryBox { + let dictionary = _MessagePackDictionaryBox() + containers.append(dictionary) + return dictionary + } + + fileprivate mutating func pushUnkeyedContainer() -> _MessagePackArrayBox { + let array = _MessagePackArrayBox() + containers.append(array) + return array + } + + fileprivate mutating func push(container: _MessagePackBox) { + self.containers.append(container) + } + + fileprivate mutating func popContainer() -> _MessagePackBox { + precondition(containers.count > 0, "Empty container stack.") + return containers.popLast()! + } +} + +// MARK: Encoding Containers + +fileprivate struct _MessagePackKeyedEncodingContainer : KeyedEncodingContainerProtocol { + typealias Key = K + + private let encoder: _MessagePackEncoder + + private let container: _MessagePackDictionaryBox + + private(set) public var codingPath: [CodingKey] + + fileprivate init(referencing encoder: _MessagePackEncoder, codingPath: [CodingKey], wrapping container: _MessagePackDictionaryBox) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + public mutating func encodeNil(forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.boxNil() } + public mutating func encode(_ value: Bool, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int8, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int16, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int32, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int64, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt8, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt16, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt32, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt64, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Float, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Double, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: String, forKey key: Key) throws { container.dictionary[key.stringValue] = encoder.box(value) } + + public mutating func encode(_ value: T, forKey key: Key) throws { + encoder.codingPath.append(key) + defer { encoder.codingPath.removeLast() } + + container.dictionary[key.stringValue] = try encoder.box(value) + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { + let dictionary = _MessagePackDictionaryBox() + self.container.dictionary[key.stringValue] = dictionary + + codingPath.append(key) + defer { codingPath.removeLast() } + + let container = _MessagePackKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + let array = _MessagePackArrayBox() + container.dictionary[key.stringValue] = array + + codingPath.append(key) + defer { codingPath.removeLast() } + + return _MessagePackUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _MessagePackReferencingEncoder(referencing: encoder, at: _MessagePackKey.super, wrapping: container) + } + + public mutating func superEncoder(forKey key: Key) -> Encoder { + return _MessagePackReferencingEncoder(referencing: encoder, at: key, wrapping: container) + } +} + +fileprivate struct _MessagePackUnkeyedEncodingContainer : UnkeyedEncodingContainer { + + private let encoder: _MessagePackEncoder + + private let container: _MessagePackArrayBox + + private(set) public var codingPath: [CodingKey] + + public var count: Int { + return container.array.count + } + + fileprivate init(referencing encoder: _MessagePackEncoder, codingPath: [CodingKey], wrapping container: _MessagePackArrayBox) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + public mutating func encodeNil() throws { container.array.append(encoder.boxNil()) } + public mutating func encode(_ value: Bool) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: Int) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: Int8) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: Int16) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: Int32) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: Int64) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: UInt) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: UInt8) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: UInt16) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: UInt32) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: UInt64) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: Float) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: Double) throws { container.array.append(encoder.box(value)) } + public mutating func encode(_ value: String) throws { container.array.append(encoder.box(value)) } + + public mutating func encode(_ value: T) throws { + encoder.codingPath.append(_MessagePackKey(index: count)) + defer { encoder.codingPath.removeLast() } + + container.array.append(try encoder.box(value)) + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { + codingPath.append(_MessagePackKey(index: count)) + defer { codingPath.removeLast() } + + let dictionary = _MessagePackDictionaryBox() + self.container.array.append(dictionary) + + let container = _MessagePackKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + codingPath.append(_MessagePackKey(index: count)) + defer { codingPath.removeLast() } + + let array = _MessagePackArrayBox() + container.array.append(array) + return _MessagePackUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _MessagePackReferencingEncoder(referencing: encoder, at: container.array.count, wrapping: container) + } +} + +extension _MessagePackEncoder : SingleValueEncodingContainer { + + fileprivate func assertCanEncodeNewValue() { + precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") + } + + public func encodeNil() throws { + assertCanEncodeNewValue() + storage.push(container: boxNil()) + } + + public func encode(_ value: Bool) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: String) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Float) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Double) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: T) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } +} + +// MARK: Concrete Value Representations + +extension _MessagePackEncoder { + + fileprivate func boxNil() -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue()) } + fileprivate func box(_ value: Bool) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Int) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Int8) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Int16) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Int32) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Int64) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: UInt) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: UInt8) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: UInt16) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: UInt32) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: UInt64) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Float) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Double) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: String) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + fileprivate func box(_ value: Data) -> _MessagePackBox { return _MessagePackValueBox(MessagePackValue(value)) } + + fileprivate func box(_ value: T) throws -> _MessagePackBox { + return try box_(value) ?? _MessagePackDictionaryBox() + } + + fileprivate func box_(_ value: T) throws -> _MessagePackBox? { + + if T.self == Data.self || T.self == NSData.self { + return box((value as! Data)) + } + + let depth = storage.count + try value.encode(to: self) + + guard storage.count > depth else { + return nil + } + + return storage.popContainer() + } +} + +// MARK: _MessagePackReferencingEncoder + +fileprivate class _MessagePackReferencingEncoder : _MessagePackEncoder { + + private enum Reference { + case array(_MessagePackArrayBox, Int) + case dictionary(_MessagePackDictionaryBox, String) + } + + fileprivate let encoder: _MessagePackEncoder + + private let reference: Reference + + fileprivate init(referencing encoder: _MessagePackEncoder, at index: Int, wrapping array: _MessagePackArrayBox) { + self.encoder = encoder + self.reference = .array(array, index) + super.init(options: encoder.options, codingPath: encoder.codingPath) + + codingPath.append(_MessagePackKey(index: index)) + } + + fileprivate init(referencing encoder: _MessagePackEncoder, at key: CodingKey, wrapping dictionary: _MessagePackDictionaryBox) { + self.encoder = encoder + self.reference = .dictionary(dictionary, key.stringValue) + super.init(options: encoder.options, codingPath: encoder.codingPath) + + codingPath.append(key) + } + + fileprivate override var canEncodeNewValue: Bool { + return storage.count == codingPath.count - encoder.codingPath.count - 1 + } + + deinit { + let value: _MessagePackBox + switch self.storage.count { + case 0: value = _MessagePackDictionaryBox() + case 1: value = storage.popContainer() + default: fatalError("Referencing encoder deallocated with multiple containers on stack.") + } + + switch self.reference { + case .array(let box, let index): + box.array.insert(value, at: index) + + case .dictionary(let box, let key): + box.dictionary[key] = value + } + } +} + +// MARK: - Message Pack Decoder +open class MessagePackDecoder { + + public enum NumberDecodingStrategy { + + /// Follows type of model strictly + case noTypeConversion + + /// Automatically convert between UInt64, Int64, Double, Float + case automaticTypeConversion + } + + open var numberDecodingStrategy: NumberDecodingStrategy = .noTypeConversion + + open var userInfo: [CodingUserInfoKey : Any] = [:] + + fileprivate struct _Options { + let numberDecodingStrategy: NumberDecodingStrategy + let userInfo: [CodingUserInfoKey : Any] + } + + fileprivate var options: _Options { + return _Options(numberDecodingStrategy: numberDecodingStrategy, + userInfo: userInfo) + } + + public init() {} + + open func decode(_ type: T.Type, from data: Data) throws -> T { + let messagePack = try self.messagePack(with: data) + return try decode(type, from: messagePack) + } + + open func decode(_ type: T.Type, from messagePack: MessagePackValue) throws -> T { + + let decoder = _MessagePackDecoder(referencing: messagePack, options: options) + + guard let value = try decoder.unbox(messagePack, as: T.self) else { + throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value.")) + } + + return value + } + + open func messagePack(with data: Data) throws -> MessagePackValue { + + let messagePack: MessagePackValue + do { + messagePack = try unpackFirst(data) + } catch { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid Message Pack.", underlyingError: error)) + } + + return messagePack + } +} + +// MARK: _MessagePackDecoder + +fileprivate class _MessagePackDecoder : Decoder { + + fileprivate var storage: _MessagePackDecodingStorage + + fileprivate let options: MessagePackDecoder._Options + + fileprivate(set) public var codingPath: [CodingKey] + + public var userInfo: [CodingUserInfoKey : Any] { + return options.userInfo + } + + fileprivate init(referencing container: MessagePackValue, at codingPath: [CodingKey] = [], options: MessagePackDecoder._Options) { + self.storage = _MessagePackDecodingStorage() + self.storage.push(container: container) + self.codingPath = codingPath + self.options = options + } + + public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { + + guard let messagePackDictionary = storage.topContainer.dictionaryValue else { + let description = "Expected to decode dictionary but found \(storage.topContainer)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch([MessagePackValue: MessagePackValue].self, context) + } + + var dictionary: [String: MessagePackValue] = [:] + try messagePackDictionary.forEach { (key, value) in + + guard let keyString = key.stringValue else { + let description = "Expected to decode string but found \(key)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch(String.self, context) + } + + dictionary[keyString] = value + } + + let container = _MessagePackKeyedDecodingContainer(referencing: self, wrapping: dictionary) + return KeyedDecodingContainer(container) + } + + public func unkeyedContainer() throws -> UnkeyedDecodingContainer { + + guard let array = storage.topContainer.arrayValue else { + let description = "Expected to decode array but found \(storage.topContainer)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch([MessagePackValue].self, context) + } + + return _MessagePackUnkeyedDecodingContainer(referencing: self, wrapping: array) + } + + public func singleValueContainer() throws -> SingleValueDecodingContainer { + return self + } +} + +// MARK: Decoding Storage + +fileprivate struct _MessagePackDecodingStorage { + + private(set) fileprivate var containers: [MessagePackValue] = [] + + fileprivate init() {} + + fileprivate var count: Int { + return containers.count + } + + fileprivate var topContainer: MessagePackValue { + precondition(containers.count > 0, "Empty container stack.") + return containers.last! + } + + fileprivate mutating func push(container: MessagePackValue) { + containers.append(container) + } + + fileprivate mutating func popContainer() { + precondition(containers.count > 0, "Empty container stack.") + containers.removeLast() + } +} + +// MARK: Decoding Containers + +fileprivate struct _MessagePackKeyedDecodingContainer : KeyedDecodingContainerProtocol { + typealias Key = K + + private let decoder: _MessagePackDecoder + + private let container: [String: MessagePackValue] + + private(set) public var codingPath: [CodingKey] + + fileprivate init(referencing decoder: _MessagePackDecoder, wrapping container: [String: MessagePackValue]) { + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + } + + public var allKeys: [Key] { + return container.keys.flatMap { Key(stringValue: $0) } + } + + public func contains(_ key: Key) -> Bool { + return container[key.stringValue] != nil + } + + public func decodeNil(forKey key: Key) throws -> Bool { + guard let entry = container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + return entry.isNil + } + + public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Bool.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Float.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Double.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: String.Type, forKey key: Key) throws -> String { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: String.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: T.Type, forKey key: Key) throws -> T { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: T.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer { + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get \(KeyedDecodingContainer.self) -- no value found for key \"\(key.stringValue)\"")) + } + + guard let messagePackDictionary = value.dictionaryValue else { + let description = "Expected to decode dictionary but found \(value)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch([MessagePackValue: MessagePackValue].self, context) + } + + var dictionary: [String: MessagePackValue] = [:] + try messagePackDictionary.forEach { (key, value) in + + guard let keyString = key.stringValue else { + let description = "Expected to decode string but found \(key)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch(String.self, context) + } + + dictionary[keyString] = value + } + + let container = _MessagePackKeyedDecodingContainer(referencing: decoder, wrapping: dictionary) + return KeyedDecodingContainer(container) + } + + public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = container[key.stringValue] else { + throw DecodingError.keyNotFound(key, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get UnkeyedDecodingContainer -- no value found for key \"\(key.stringValue)\"")) + } + + guard let array = value.arrayValue else { + let description = "Expected to decode array but found \(value)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch([MessagePackValue].self, context) + } + + return _MessagePackUnkeyedDecodingContainer(referencing: decoder, wrapping: array) + } + + private func _superDecoder(forKey key: CodingKey) throws -> Decoder { + decoder.codingPath.append(key) + defer { decoder.codingPath.removeLast() } + + let value: MessagePackValue = container[key.stringValue] ?? .nil + return _MessagePackDecoder(referencing: value, at: decoder.codingPath, options: decoder.options) + } + + public func superDecoder() throws -> Decoder { + return try _superDecoder(forKey: _MessagePackKey.super) + } + + public func superDecoder(forKey key: Key) throws -> Decoder { + return try _superDecoder(forKey: key) + } +} + +fileprivate struct _MessagePackUnkeyedDecodingContainer : UnkeyedDecodingContainer { + + private let decoder: _MessagePackDecoder + + private let container: [MessagePackValue] + + private(set) public var codingPath: [CodingKey] + + private(set) public var currentIndex: Int + + fileprivate init(referencing decoder: _MessagePackDecoder, wrapping container: [MessagePackValue]) { + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + self.currentIndex = 0 + } + + public var count: Int? { + return container.count + } + + public var isAtEnd: Bool { + return currentIndex >= count! + } + + private func expectNotAtEnd(type: Any.Type) throws { + guard !isAtEnd else { + let path = decoder.codingPath + [_MessagePackKey(index: currentIndex)] + let context = DecodingError.Context(codingPath: path, debugDescription: "Unkeyed container is at end.") + throw DecodingError.valueNotFound(type, context) + } + } + + public mutating func decodeNil() throws -> Bool { + try expectNotAtEnd(type: Any?.self) + + if container[currentIndex].isNil { + self.currentIndex += 1 + return true + } else { + return false + } + } + + public mutating func decode(_ type: Bool.Type) throws -> Bool { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Bool.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int.Type) throws -> Int { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int8.Type) throws -> Int8 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int16.Type) throws -> Int16 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int32.Type) throws -> Int32 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int64.Type) throws -> Int64 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt.Type) throws -> UInt { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt8.Type) throws -> UInt8 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt16.Type) throws -> UInt16 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt32.Type) throws -> UInt32 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt64.Type) throws -> UInt64 { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Float.Type) throws -> Float { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Float.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Double.Type) throws -> Double { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Double.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: String.Type) throws -> String { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: T.Type) throws -> T { + try expectNotAtEnd(type: type) + + self.decoder.codingPath.append(_MessagePackKey(index: currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: T.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_MessagePackKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { + decoder.codingPath.append(_MessagePackKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(KeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get nested keyed container -- unkeyed container is at end.")) + } + + let value = self.container[currentIndex] + + guard let messagePackDictionary = value.dictionaryValue else { + let description = "Cannot get keyed decoding container -- found \(value) instead" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch([MessagePackValue: MessagePackValue].self, context) + } + + var dictionary: [String: MessagePackValue] = [:] + try messagePackDictionary.forEach { (key, value) in + + guard let keyString = key.stringValue else { + let description = "Expected to decode string but found \(key)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch(String.self, context) + } + + dictionary[keyString] = value + } + + self.currentIndex += 1 + let container = _MessagePackKeyedDecodingContainer(referencing: decoder, wrapping: dictionary) + return KeyedDecodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get nested keyed container -- unkeyed container is at end.")) + } + + let value = self.container[self.currentIndex] + guard let array = value.arrayValue else { + let description = "Expected to decode array but found \(value)" + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + throw DecodingError.typeMismatch([MessagePackValue].self, context) + } + + self.currentIndex += 1 + return _MessagePackUnkeyedDecodingContainer(referencing: decoder, wrapping: array) + } + + public mutating func superDecoder() throws -> Decoder { + self.decoder.codingPath.append(_MessagePackKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(Decoder.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get superDecoder() -- unkeyed container is at end.")) + } + + let value = self.container[self.currentIndex] + self.currentIndex += 1 + return _MessagePackDecoder(referencing: value, at: decoder.codingPath, options: decoder.options) + } +} + +extension _MessagePackDecoder : SingleValueDecodingContainer { + + public func decodeNil() -> Bool { + return self.storage.topContainer.isNil + } + + public func decode(_ type: Bool.Type) throws -> Bool { + return try unbox(storage.topContainer, as: Bool.self)! + } + + public func decode(_ type: Int.Type) throws -> Int { + return try unbox(storage.topContainer, as: Int.self)! + } + + public func decode(_ type: Int8.Type) throws -> Int8 { + return try unbox(storage.topContainer, as: Int8.self)! + } + + public func decode(_ type: Int16.Type) throws -> Int16 { + return try unbox(storage.topContainer, as: Int16.self)! + } + + public func decode(_ type: Int32.Type) throws -> Int32 { + return try unbox(storage.topContainer, as: Int32.self)! + } + + public func decode(_ type: Int64.Type) throws -> Int64 { + return try unbox(storage.topContainer, as: Int64.self)! + } + + public func decode(_ type: UInt.Type) throws -> UInt { + return try unbox(storage.topContainer, as: UInt.self)! + } + + public func decode(_ type: UInt8.Type) throws -> UInt8 { + return try unbox(storage.topContainer, as: UInt8.self)! + } + + public func decode(_ type: UInt16.Type) throws -> UInt16 { + return try unbox(storage.topContainer, as: UInt16.self)! + } + + public func decode(_ type: UInt32.Type) throws -> UInt32 { + return try unbox(storage.topContainer, as: UInt32.self)! + } + + public func decode(_ type: UInt64.Type) throws -> UInt64 { + return try unbox(storage.topContainer, as: UInt64.self)! + } + + public func decode(_ type: Float.Type) throws -> Float { + return try unbox(storage.topContainer, as: Float.self)! + } + + public func decode(_ type: Double.Type) throws -> Double { + return try unbox(storage.topContainer, as: Double.self)! + } + + public func decode(_ type: String.Type) throws -> String { + return try unbox(storage.topContainer, as: String.self)! + } + + public func decode(_ type: T.Type) throws -> T { + return try unbox(storage.topContainer, as: T.self)! + } +} + +// MARK: Concrete Value Representations + +extension _MessagePackDecoder { + + fileprivate func unbox(_ value: MessagePackValue, as type: Bool.Type) throws -> Bool? { + return value.boolValue + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Int.Type) throws -> Int? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.integerValue.map { Int(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.intValue + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Int8.Type) throws -> Int8? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.integerValue.map { Int8(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.int8Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Int16.Type) throws -> Int16? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.integerValue.map { Int16(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.int16Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Int32.Type) throws -> Int32? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.integerValue.map { Int32(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.int32Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Int64.Type) throws -> Int64? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.integerValue.map { Int64(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.int64Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: UInt.Type) throws -> UInt? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.unsignedIntegerValue.map { UInt(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.uintValue + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: UInt8.Type) throws -> UInt8? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.unsignedIntegerValue.map { UInt8(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.uint8Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: UInt16.Type) throws -> UInt16? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.unsignedIntegerValue.map { UInt16(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.uint16Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: UInt32.Type) throws -> UInt32? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.unsignedIntegerValue.map { UInt32(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.uint32Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: UInt64.Type) throws -> UInt64? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.unsignedIntegerValue.map { UInt64(truncatingIfNeeded: $0) } + case .automaticTypeConversion: return value.numberValue?.uint64Value + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Float.Type) throws -> Float? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.floatValue + case .automaticTypeConversion: return value.numberValue?.floatValue + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Double.Type) throws -> Double? { + switch options.numberDecodingStrategy { + case .noTypeConversion: return value.doubleValue + case .automaticTypeConversion: return value.numberValue?.doubleValue + } + } + + fileprivate func unbox(_ value: MessagePackValue, as type: String.Type) throws -> String? { + return value.stringValue + } + + fileprivate func unbox(_ value: MessagePackValue, as type: Data.Type) throws -> Data? { + return value.dataValue + } + + fileprivate func unbox(_ value: MessagePackValue, as type: T.Type) throws -> T? { + let decoded: T + if T.self == Data.self || T.self == NSData.self { + guard let data = try unbox(value, as: Data.self) else { return nil } + decoded = data as! T + } else { + storage.push(container: value) + decoded = try T(from: self) + storage.popContainer() + } + + return decoded + } +} + +// MARK: - Shared Key Types + +fileprivate struct _MessagePackKey : CodingKey { + public var stringValue: String + public var intValue: Int? + + public init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + public init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + fileprivate init(index: Int) { + self.stringValue = "Index \(index)" + self.intValue = index + } + + fileprivate static let `super` = _MessagePackKey(stringValue: "super")! +} + + +// MARK: - Box + +fileprivate protocol _MessagePackBox { + var messagePackValue: MessagePackValue { get } +} + +fileprivate class _MessagePackValueBox: _MessagePackBox { + var messagePackValue: MessagePackValue + + init(_ messagePackValue: MessagePackValue) { + self.messagePackValue = messagePackValue + } +} + +fileprivate class _MessagePackDictionaryBox: _MessagePackBox { + var dictionary: [String: _MessagePackBox] = [:] + + var messagePackValue: MessagePackValue { + var valueMap: [MessagePackValue: MessagePackValue] = [:] + dictionary.forEach { valueMap[.string($0)] = $1.messagePackValue } + return .map(valueMap) + } +} + +fileprivate class _MessagePackArrayBox: _MessagePackBox { + var array: [_MessagePackBox] = [] + + var messagePackValue: MessagePackValue { + let valueArray = array.map { $0.messagePackValue } + return .array(valueArray) + } +} + +// MARK: - MessagePackValue + +fileprivate extension MessagePackValue { + + fileprivate var numberValue: NSNumber? { + switch self { + case .int(let value): return NSNumber(value: value) + case .uint(let value): return NSNumber(value: value) + case .double(let value): return NSNumber(value: value) + case .float(let value): return NSNumber(value: value) + default: return nil + } + } +} diff --git a/Tests/MessagePackTests/MessagePackEncoderTests.swift b/Tests/MessagePackTests/MessagePackEncoderTests.swift new file mode 100644 index 0000000..11b353a --- /dev/null +++ b/Tests/MessagePackTests/MessagePackEncoderTests.swift @@ -0,0 +1,686 @@ +import Foundation +import XCTest +@testable import MessagePack + +/** + * The implementation of MessagePackEncoderTests heavily references `TestsJSONEncoder.swift` + * from the Swift foundation library. + * + * - Note: See `MessagePackEncoder.swift` + */ + +class MessagePackEncoderTests : XCTestCase { + // MARK: - Encoding Top-Level Empty Types + func testEncodingTopLevelEmptyStruct() { + let empty = EmptyStruct() + _testRoundTrip(of: empty, expectedData: _messagePackEmptyDictionary) + } + + func testEncodingTopLevelEmptyClass() { + let empty = EmptyClass() + _testRoundTrip(of: empty, expectedData: _messagePackEmptyDictionary) + } + + // MARK: - Encoding Top-Level Single-Value Types + func testEncodingTopLevelSingleValueEnum() { + _testRoundTrip(of: Switch.off) + _testRoundTrip(of: Switch.on) + + _testRoundTrip(of: TopLevelWrapper(Switch.off)) + _testRoundTrip(of: TopLevelWrapper(Switch.on)) + } + + func testEncodingTopLevelSingleValueStruct() { + _testRoundTrip(of: Timestamp(3141592653)) + _testRoundTrip(of: TopLevelWrapper(Timestamp(3141592653))) + } + + func testEncodingTopLevelSingleValueClass() { + _testRoundTrip(of: Counter()) + _testRoundTrip(of: TopLevelWrapper(Counter())) + } + + // MARK: - Encoding Top-Level Structured Types + func testEncodingTopLevelStructuredStruct() { + // Address is a struct type with multiple fields. + let address = Address.testValue + _testRoundTrip(of: address) + } + + func testEncodingTopLevelStructuredClass() { + // Person is a class with multiple fields. + let person = Person.testValue + _testRoundTrip(of: person) + } + + func testEncodingTopLevelStructuredSingleStruct() { + // Numbers is a struct which encodes as an array through a single value container. + let numbers = Numbers.testValue + _testRoundTrip(of: numbers) + } + + func testEncodingTopLevelStructuredSingleClass() { + // Mapping is a class which encodes as a dictionary through a single value container. + let mapping = Mapping.testValue + _testRoundTrip(of: mapping) + } + + func testEncodingTopLevelDeepStructuredType() { + // Company is a type with fields which are Codable themselves. + let company = Company.testValue + _testRoundTrip(of: company) + } + + func testEncodingClassWhichSharesEncoderWithSuper() { + // Employee is a type which shares its encoder & decoder with its superclass, Person. + let employee = Employee.testValue + _testRoundTrip(of: employee) + } + + func testEncodingTopLevelNullableType() { + // EnhancedBool is a type which encodes either as a Bool or as nil. + _testRoundTrip(of: EnhancedBool.true) + _testRoundTrip(of: EnhancedBool.false) + _testRoundTrip(of: EnhancedBool.fileNotFound) + + _testRoundTrip(of: TopLevelWrapper(EnhancedBool.true)) + _testRoundTrip(of: TopLevelWrapper(EnhancedBool.false)) + _testRoundTrip(of: TopLevelWrapper(EnhancedBool.fileNotFound)) + } + + // MARK: - Date Strategy Tests + func testEncodingDate() { + _testRoundTrip(of: Date()) + _testRoundTrip(of: TopLevelWrapper(Date())) + _testRoundTrip(of: OptionalTopLevelWrapper(Date())) + } + + // MARK: - Data Strategy Tests + + func testEncodingData() { + let data = Data(bytes: [0xDE, 0xAD, 0xBE, 0xEF]) + + _testRoundTrip(of: data) + _testRoundTrip(of: TopLevelWrapper(data)) + _testRoundTrip(of: OptionalTopLevelWrapper(data)) + } + + // MARK: - Encoder Features + func testNestedContainerCodingPaths() { + let encoder = MessagePackEncoder() + do { + let _ = try encoder.encode(NestedContainersTestType()) + } catch { + XCTAssert(false, "Caught error during encoding nested container types: \(error)") + } + } + + func testSuperEncoderCodingPaths() { + let encoder = MessagePackEncoder() + do { + let _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) + } catch { + XCTAssert(false, "Caught error during encoding nested container types: \(error)") + } + } + + // MARK: - Helper Functions + private var _messagePackEmptyDictionary: Data { + return Data([0x80]) + } + + private func _testRoundTrip(of value: T, expectedData data: Data? = nil) where T : Codable, T : Equatable { + _testSingleStepRoundTrip(of: value, expectedData: data) + _testMultiStepRoundTrip(of: value, expectedData: data) + } + + private func _testSingleStepRoundTrip(of value: T, expectedData data: Data? = nil) where T : Codable, T : Equatable { + + var payload: Data! = nil + do { + let encoder = MessagePackEncoder() + payload = try encoder.encode(value) + } catch { + XCTAssert(false, "Failed to encode \(T.self) to data: \(error)") + } + + if let expectedData = data { + XCTAssertEqual(expectedData, payload, "Produced data not identical to expected data.") + } + + do { + let decoder = MessagePackDecoder() + let decoded = try decoder.decode(T.self, from: payload) + XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.") + } catch { + XCTAssert(false, "Failed to decode \(T.self) from data: \(error)") + } + } + + private func _testMultiStepRoundTrip(of value: T, expectedData data: Data? = nil) where T : Codable, T : Equatable { + + let encoder = MessagePackEncoder() + let decoder = MessagePackDecoder() + + var messagePack: MessagePackValue! = nil + do { + messagePack = try encoder.messagePack(with: value) + } catch { + XCTAssert(false, "Failed to encode \(T.self) to MessagePack: \(error)") + } + + var payload: Data! = nil + do { + payload = try encoder.encode(messagePack: messagePack) + } catch { + XCTAssert(false, "Failed to encode \(T.self) to data: \(error)") + } + + if let expectedData = data { + XCTAssertEqual(expectedData, payload, "Produced data not identical to expected data.") + } + + var decodedMessagePack: MessagePackValue = nil + do { + decodedMessagePack = try decoder.messagePack(with: payload) + XCTAssertEqual(decodedMessagePack, messagePack, "Conversion from Data to MessagePack did not round-trip to an equal value.") + } catch { + XCTAssert(false, "Failed to decode MessagePack from data: \(error)") + } + + do { + let decoded = try decoder.decode(T.self, from: decodedMessagePack) + XCTAssertEqual(decoded, value, "Conversion from MessagePack to \(T.self) did not round-trip to an equal value.") + } catch { + XCTAssert(false, "Failed to decode \(T.self) from MessagePack: \(error)") + } + } +} + +// MARK: - Helper Global Functions +func XCTAssertEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String) { + if lhs.count != rhs.count { + XCTAssert(false, "\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)") + return + } + + for (key1, key2) in zip(lhs, rhs) { + switch (key1.intValue, key2.intValue) { + case (.none, .none): break + case (.some(let i1), .none): + XCTAssert(false, "\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil") + return + case (.none, .some(let i2)): + XCTAssert(false, "\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))") + return + case (.some(let i1), .some(let i2)): + guard i1 == i2 else { + XCTAssert(false, "\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))") + return + } + + break + } + + XCTAssertEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')") + } +} + +// MARK: - Test Types +/* FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. */ + +// MARK: - Empty Types +fileprivate struct EmptyStruct : Codable, Equatable { + static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool { + return true + } +} + +fileprivate class EmptyClass : Codable, Equatable { + static func ==(_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool { + return true + } +} + +// MARK: - Single-Value Types +/// A simple on-off switch type that encodes as a single Bool value. +fileprivate enum Switch : Codable { + case off + case on + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + switch try container.decode(Bool.self) { + case false: self = .off + case true: self = .on + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .off: try container.encode(false) + case .on: try container.encode(true) + } + } +} + +/// A simple timestamp type that encodes as a single Double value. +fileprivate struct Timestamp : Codable, Equatable { + let value: Double + + init(_ value: Double) { + self.value = value + } + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + value = try container.decode(Double.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.value) + } + + static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool { + return lhs.value == rhs.value + } +} + +/// A simple referential counter type that encodes as a single Int value. +fileprivate final class Counter : Codable, Equatable { + var count: Int = 0 + + init() {} + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + count = try container.decode(Int.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.count) + } + + static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool { + return lhs === rhs || lhs.count == rhs.count + } +} + +// MARK: - Structured Types +/// A simple address type that encodes as a dictionary of values. +fileprivate struct Address : Codable, Equatable { + let street: String + let city: String + let state: String + let zipCode: Int + let country: String + + init(street: String, city: String, state: String, zipCode: Int, country: String) { + self.street = street + self.city = city + self.state = state + self.zipCode = zipCode + self.country = country + } + + static func ==(_ lhs: Address, _ rhs: Address) -> Bool { + return lhs.street == rhs.street && + lhs.city == rhs.city && + lhs.state == rhs.state && + lhs.zipCode == rhs.zipCode && + lhs.country == rhs.country + } + + static var testValue: Address { + return Address(street: "1 Infinite Loop", + city: "Cupertino", + state: "CA", + zipCode: 95014, + country: "United States") + } +} + +/// A simple person class that encodes as a dictionary of values. +fileprivate class Person : Codable, Equatable { + let name: String + let email: String + let website: URL? + + init(name: String, email: String, website: URL? = nil) { + self.name = name + self.email = email + self.website = website + } + + private enum CodingKeys : String, CodingKey { + case name + case email + case website + } + + // FIXME: Remove when subclasses (Employee) are able to override synthesized conformance. + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + email = try container.decode(String.self, forKey: .email) + website = try container.decodeIfPresent(URL.self, forKey: .website) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(email, forKey: .email) + try container.encodeIfPresent(website, forKey: .website) + } + + func isEqual(_ other: Person) -> Bool { + return self.name == other.name && + self.email == other.email && + self.website == other.website + } + + static func ==(_ lhs: Person, _ rhs: Person) -> Bool { + return lhs.isEqual(rhs) + } + + class var testValue: Person { + return Person(name: "Johnny Appleseed", email: "appleseed@apple.com") + } +} + +/// A class which shares its encoder and decoder with its superclass. +fileprivate class Employee : Person { + let id: Int + + init(name: String, email: String, website: URL? = nil, id: Int) { + self.id = id + super.init(name: name, email: email, website: website) + } + + enum CodingKeys : String, CodingKey { + case id + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(Int.self, forKey: .id) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try super.encode(to: encoder) + } + + override func isEqual(_ other: Person) -> Bool { + if let employee = other as? Employee { + guard self.id == employee.id else { return false } + } + + return super.isEqual(other) + } + + override class var testValue: Employee { + return Employee(name: "Johnny Appleseed", email: "appleseed@apple.com", id: 42) + } +} + +/// A simple company struct which encodes as a dictionary of nested values. +fileprivate struct Company : Codable, Equatable { + let address: Address + var employees: [Employee] + + init(address: Address, employees: [Employee]) { + self.address = address + self.employees = employees + } + + static func ==(_ lhs: Company, _ rhs: Company) -> Bool { + return lhs.address == rhs.address && lhs.employees == rhs.employees + } + + static var testValue: Company { + return Company(address: Address.testValue, employees: [Employee.testValue]) + } +} + +/// An enum type which decodes from Bool?. +fileprivate enum EnhancedBool : Codable { + case `true` + case `false` + case fileNotFound + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self = .fileNotFound + } else { + let value = try container.decode(Bool.self) + self = value ? .true : .false + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .true: try container.encode(true) + case .false: try container.encode(false) + case .fileNotFound: try container.encodeNil() + } + } +} + +/// A type which encodes as an array directly through a single value container. +struct Numbers : Codable, Equatable { + let values = [4, 8, 15, 16, 23, 42] + + init() {} + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let decodedValues = try container.decode([Int].self) + guard decodedValues == values else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "The Numbers are wrong!")) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(values) + } + + static func ==(_ lhs: Numbers, _ rhs: Numbers) -> Bool { + return lhs.values == rhs.values + } + + static var testValue: Numbers { + return Numbers() + } +} + +/// A type which encodes as a dictionary directly through a single value container. +fileprivate final class Mapping : Codable, Equatable { + let values: [String : URL] + + init(values: [String : URL]) { + self.values = values + } + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + values = try container.decode([String : URL].self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(values) + } + + static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool { + return lhs === rhs || lhs.values == rhs.values + } + + static var testValue: Mapping { + return Mapping(values: ["Apple": URL(string: "http://apple.com")!, + "localhost": URL(string: "http://127.0.0.1")!]) + } +} + +struct NestedContainersTestType : Encodable { + let testSuperEncoder: Bool + + init(testSuperEncoder: Bool = false) { + self.testSuperEncoder = testSuperEncoder + } + + enum TopLevelCodingKeys : Int, CodingKey { + case a + case b + case c + } + + enum IntermediateCodingKeys : Int, CodingKey { + case one + case two + } + + func encode(to encoder: Encoder) throws { + if self.testSuperEncoder { + var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) + XCTAssertEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.") + + let superEncoder = topLevelContainer.superEncoder(forKey: .a) + XCTAssertEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.") + XCTAssertEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.") + _testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a]) + } else { + _testNestedContainers(in: encoder, baseCodingPath: []) + } + } + + func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey]) { + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.") + + // codingPath should not change upon fetching a non-nested container. + var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.") + + // Nested Keyed Container + do { + // Nested container for key should have a new key pushed on. + var secondLevelContainer = firstLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .a) + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.") + + // Inserting a keyed container should not change existing coding paths. + let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .one) + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") + XCTAssertEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.") + + // Inserting an unkeyed container should not change existing coding paths. + let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two) + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.") + XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") + XCTAssertEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.") + } + + // Nested Unkeyed Container + do { + // Nested container for key should have a new key pushed on. + var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b) + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "New second-level keyed container had unexpected codingPath.") + + // Appending a keyed container should not change existing coding paths. + let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self) + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") + XCTAssertEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)], "New third-level keyed container had unexpected codingPath.") + + // Appending an unkeyed container should not change existing coding paths. + let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer() + XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") + XCTAssertEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)], "New third-level unkeyed container had unexpected codingPath.") + } + } +} + +// MARK: - Helper Types + +/// A key type which can take on any string or integer value. +/// This needs to mirror _MessagePackKey. +fileprivate struct _TestKey : CodingKey { + var stringValue: String + var intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + init(index: Int) { + self.stringValue = "Index \(index)" + self.intValue = index + } +} + +/// Wraps a type T so that it can be encoded at the top level of a payload. +fileprivate struct TopLevelWrapper : Codable, Equatable where T : Codable, T : Equatable { + let value: T + + init(_ value: T) { + self.value = value + } + + static func ==(_ lhs: TopLevelWrapper, _ rhs: TopLevelWrapper) -> Bool { + return lhs.value == rhs.value + } +} + +/// Wraps a type T (as T?) so that it can be encoded at the top level of a payload. +fileprivate struct OptionalTopLevelWrapper : Codable, Equatable where T : Codable, T : Equatable { + let value: T? + + init(_ value: T) { + self.value = value + } + + // Provide an implementation of Codable to encode(forKey:) instead of encodeIfPresent(forKey:). + private enum CodingKeys : String, CodingKey { + case value + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + value = try container.decode(T?.self, forKey: .value) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value, forKey: .value) + } + + static func ==(_ lhs: OptionalTopLevelWrapper, _ rhs: OptionalTopLevelWrapper) -> Bool { + return lhs.value == rhs.value + } +}