Sources/TwitterApacheThrift/ThriftDecoder.swift (239 lines of code) (raw):

// Copyright 2020 Twitter, Inc. // Licensed under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 // // ThriftDecoder.swift // TwitterApacheThrift // // Created on 3/24/20. // Copyright © 2020 Twitter, Inc. All rights reserved. // import Foundation /// Provide a default implementation which calls through to `Decodable`. This /// allows `ThriftDecodable` to use the `Decodable` implementation generated by the /// compiler. public extension ThriftDecodable { init(fromThrift decoder: ThriftDecoder) throws { try self.init(from: decoder) } } /// Errors thrown when the thrift is decoded. public enum ThriftDecoderError: Error { /// The key of the field is not the next key in the thrift, possibly because /// of malformed thrift data or model discrepancy case keyOrderMismatch /// Internal buffer is uninitialized should only happen if using methods /// other then `decode<T>(_ type: T.Type, from data: Data)` for decoding case uninitializedDecodingData /// CodingKey is missing the value case codingKeyMissingIntValue(key: CodingKey) /// The type is not encodable, example not conforming to ThriftDecodable case undecodableType(type: Any) /// The string found while decoding is not UTF8 formatted, possibly because /// of malformed thrift or an encoding error. case nonUTF8StringData(Data) /// The buffer overflowed while trying to read a field, possibly because /// of using the wrong model type case readBufferOverflow /// The thrift data has a unsupported type value case unsupportedThriftType /// The decoder unexpectedly found nil while decoding case unexpectedlyFoundNil } ///An object that decodes instances of a data type from Thrift objects. public class ThriftDecoder: Decoder { public var codingPath: [CodingKey] = [] public var userInfo: [CodingUserInfoKey : Any] = [:] fileprivate var binary: ThriftBinary? fileprivate var value: ThriftObject? /// The specification to be used for decoding public var specification: ThriftSpecification = .standard /// Initializes `self` with defaults. public init() {} /// Initializes `self` with defaults. fileprivate init(value: ThriftObject, specification: ThriftSpecification) { self.value = value self.specification = specification } /// Decodes a top-level value of the given type from the given Thrift representation. /// /// - parameter type: The type of the value to decode. /// - parameter data: The data to decode from. /// - returns: A value of the requested type. /// - throws: An `ThriftDecoderError` if any value throws an error during decoding. public func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : ThriftDecodable { self.binary = specification == .compact ? ThriftCompactBinary(data: data) : ThriftBinary(data: data) let thriftType = try ThriftType.decodableType(from: type.self) self.value = try binary?.readThrift(type: thriftType) return try type.init(fromThrift: self) } public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey { return KeyedDecodingContainer(KeyedContainer<Key>(decoder: self)) } public func unkeyedContainer() throws -> UnkeyedDecodingContainer { return UnkeyedContainer(decoder: self) } public func singleValueContainer() throws -> SingleValueDecodingContainer { return UnkeyedContainer(decoder: self) } fileprivate func thriftCollectionContainer() throws -> UnkeyedContainer { return UnkeyedContainer(decoder: self) } private class KeyedContainer<Key: CodingKey>: KeyedDecodingContainerProtocol { var allKeys: [Key] = [] var decoder: ThriftDecoder var codingPath: [CodingKey] { return [] } var previousFieldId: Int = 0 init(decoder: ThriftDecoder) { self.decoder = decoder } func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey { return try decoder.container(keyedBy: type) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { return try decoder.unkeyedContainer() } func superDecoder() -> Decoder { return decoder } func superDecoder(forKey key: Key) throws -> Decoder { return decoder } func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { return try decoder.decode(type, forKey: key, previousFieldId: previousFieldId) } func contains(_ key: Key) -> Bool { return (try? decoder.isNull(forKey: key)) == false } func decodeNil(forKey key: Key) throws -> Bool { return try decoder.isNull(forKey: key) } } fileprivate class UnkeyedContainer: UnkeyedDecodingContainer, SingleValueDecodingContainer { var decoder: ThriftDecoder var codingPath: [CodingKey] { return [] } var count: Int? { switch decoder.value { case .keyedCollection(let collection): return collection.count case .unkeyedCollection(let collection): return collection.count default: return nil } } var currentIndex: Int = 0 var isAtEnd: Bool { return currentIndex == count } init(decoder: ThriftDecoder) { self.decoder = decoder } func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey { return try decoder.container(keyedBy: type) } func nestedUnkeyedContainer() -> UnkeyedDecodingContainer { return self } func superDecoder() -> Decoder { return decoder } func decode<T>(_ type: T.Type) throws -> T where T : Decodable { let value: ThriftObject if case .keyedCollection(let collection) = decoder.value { value = collection.value[currentIndex].value } else if case .unkeyedCollection(let collection) = decoder.value { value = collection.value[currentIndex] } else if let decoderValue = self.decoder.value { return try decoder.decodeType(type: type, value: decoderValue) } else { throw ThriftDecoderError.uninitializedDecodingData } return try decoder.decodeType(type: type, value: value) } func decodeKey<T>(_ type: T.Type) throws -> T where T : Decodable { guard case .keyedCollection(let collection) = decoder.value else { throw ThriftDecoderError.keyOrderMismatch } let key = collection.value[currentIndex].key return try decoder.decodeType(type: type, value: key) } func moveContainer() { currentIndex += 1 } func decodeNil() -> Bool { return true } } } extension ThriftDecoder { private func decode<T: Decodable, Key: CodingKey>(_ type: T.Type, forKey key: Key, previousFieldId: Int) throws -> T { self.codingPath.append(key) defer { self.codingPath.removeLast() } guard let keyId = key.intValue else { throw ThriftDecoderError.codingKeyMissingIntValue(key: key) } guard case .struct(let object) = self.value else { throw ThriftDecoderError.keyOrderMismatch } guard let value = object.fields[keyId] else { throw ThriftDecoderError.unexpectedlyFoundNil } return try decodeType(type: type, value: value.data) } private func isNull<Key: CodingKey>(forKey key: Key) throws -> Bool { guard let keyId = key.intValue else { throw ThriftDecoderError.codingKeyMissingIntValue(key: key) } guard case .struct(let object) = self.value else { throw ThriftDecoderError.keyOrderMismatch } return object.fields[keyId] == nil } private func decodeType<T: Decodable>(type: T.Type, value: ThriftObject) throws -> T { let decoded: T? if let thriftDecodable = type as? ThriftDecodable.Type { decoded = try thriftDecodable.init(fromThrift: ThriftDecoder(value: value, specification: self.specification)) as? T } else if case .data(let data) = value { let binary = self.specification == .compact ? ThriftCompactBinary(data: data) : ThriftBinary(data: data) switch type { case is Bool.Type: decoded = try binary.readBool() as? T case is Data.Type: decoded = data as? T case is Double.Type: decoded = try binary.readDouble() as? T case is UInt8.Type: decoded = try binary.readByte() as? T case is Int16.Type: decoded = try binary.readInt16() as? T case is Int32.Type: decoded = try binary.readInt32() as? T case is Int64.Type: decoded = try binary.readInt64() as? T case is String.Type: guard let string = String(bytes: data, encoding: .utf8) else { throw ThriftDecoderError.nonUTF8StringData(data) } decoded = string as? T default: decoded = nil } } else { decoded = nil } guard let typedDecoded = decoded else { throw ThriftDecoderError.undecodableType(type: type) } return typedDecoded } } extension Dictionary : ThriftDecodable where Key: Decodable, Value: Decodable { public init(fromThrift decoder: ThriftDecoder) throws { let container = try decoder.thriftCollectionContainer() let size = container.count ?? 0 self.init(minimumCapacity: container.count ?? 0) for _ in 0..<size { let key = try container.decodeKey(Key.self) let value = try container.decode(Value.self) self[key] = value container.moveContainer() } } } extension Set: ThriftDecodable where Element: Decodable { public init(fromThrift decoder: ThriftDecoder) throws { let container = try decoder.thriftCollectionContainer() let size = container.count ?? 0 self.init(minimumCapacity: Int(size)) for _ in 0..<size { let value = try container.decode(Element.self) self.insert(value) container.moveContainer() } } } extension Array: ThriftDecodable where Element: Decodable { public init(fromThrift decoder: ThriftDecoder) throws { let container = try decoder.thriftCollectionContainer() let size = container.count ?? 0 self.init() self.reserveCapacity(Int(size)) for _ in 0..<size { let value = try container.decode(Element.self) self.append(value) container.moveContainer() } } } extension PreencodedContainer: ThriftDecodable { public init(fromThrift decoder: ThriftDecoder) throws { try self.init(from: decoder) } public init(from decoder: Decoder) throws { self = .value(try T(from: decoder)) } }