Sources/TwitterApacheThrift/ThriftEncoder.swift (197 lines of code) (raw):
// Copyright 2020 Twitter, Inc.
// Licensed under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// ThriftEncoder.swift
// TwitterApacheThrift
//
// Created on 3/23/20.
// Copyright © 2020 Twitter, Inc. All rights reserved.
//
import Foundation
public extension ThriftEncodable {
func thriftEncode(to encoder: ThriftEncoder) throws {
try self.encode(to: encoder)
encoder.binary?.writeFieldStop()
}
static func thriftType() throws -> ThriftType {
return .structure
}
func validate() throws {}
}
/// Errors thrown when the thrift is encoded.
public enum ThriftEncoderError: Error {
/// Internal buffer is uninitialized
case uninitializedEncodingData
/// CodingKey is missing the value
case codingKeyMissingIntValue(key: CodingKey)
/// The type is not encodable, example not conforming to ThriftEncodable
case unencodableType(type: Any)
/// The validate method returned an error
case validationFailure(type: Any)
}
///An object that encodes instances of a data type to Thrift objects.
public class ThriftEncoder: Encoder {
fileprivate var binary: MutableThriftBinary?
public var codingPath: [CodingKey] = []
public var userInfo: [CodingUserInfoKey : Any] = [:]
fileprivate var previousId: Int = 0
/// The specification to be used for encoding
public var specification: ThriftSpecification = .standard
/// Initializes `self` with defaults.
public init() {}
fileprivate init(binary: MutableThriftBinary?, specification: ThriftSpecification) {
self.binary = binary
self.specification = specification
}
/// Encodes the given top-level value and returns its Thrift representation.
///
/// - parameter value: The value to encode.
/// - returns: A new `Data` value containing the encoded Thrift data.
/// - throws: `ThriftEncoderError` An error if any value throws an error during encoding.
public func encode<T>(_ value: T) throws -> Data where T: ThriftEncodable {
let binary = specification == .compact ? MutableThriftCompactBinary() : MutableThriftBinary()
self.binary = binary
try value.thriftEncode(to: self)
self.previousId = 0
return binary.getBuffer()
}
public func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
return KeyedEncodingContainer(KeyedContainer<Key>(encoder: self))
}
public func unkeyedContainer() -> UnkeyedEncodingContainer {
return UnkeyedContainer(encoder: self)
}
public func singleValueContainer() -> SingleValueEncodingContainer {
return UnkeyedContainer(encoder: self)
}
private struct KeyedContainer<Key: CodingKey>: KeyedEncodingContainerProtocol {
var encoder: ThriftEncoder
var codingPath: [CodingKey] { return [] }
func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
try encoder.encode(value, forKey: key)
}
func encodeNil(forKey key: Key) throws {}
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
return encoder.container(keyedBy: keyType)
}
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
return encoder.unkeyedContainer()
}
func superEncoder() -> Encoder {
return encoder
}
func superEncoder(forKey key: Key) -> Encoder {
return encoder
}
}
fileprivate struct UnkeyedContainer: UnkeyedEncodingContainer, SingleValueEncodingContainer {
var encoder: ThriftEncoder
var codingPath: [CodingKey] { return [] }
var count: Int { return 0 }
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
return encoder.container(keyedBy: keyType)
}
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
return self
}
func superEncoder() -> Encoder {
return encoder
}
func encodeNil() throws {}
func encode<T>(_ value: T) throws where T : Encodable {
try encoder.encodeValue(value)
}
}
}
extension ThriftEncoder {
private func encode<T, Key>(_ value: T, forKey key: Key) throws where T : Encodable, Key: CodingKey {
self.codingPath.append(key)
defer {
self.codingPath.removeLast()
}
guard let binary = self.binary else {
throw ThriftEncoderError.uninitializedEncodingData
}
guard let keyId = key.intValue else {
throw ThriftEncoderError.codingKeyMissingIntValue(key: key)
}
let thriftType: ThriftType
//Bools are encoded as part of the type
if let boolValue = value as? Bool, specification == .compact {
thriftType = boolValue ? .void : .bool
} else {
thriftType = try ThriftType.type(from: value)
}
binary.writeFieldBegin(fieldType: thriftType, fieldID: keyId, previousId: self.previousId)
if (specification == .compact && thriftType != .bool && thriftType != .void) || specification != .compact {
try encodeValue(value)
}
previousId = keyId
}
private func encodeValue<T>(_ value: T) throws where T : Encodable {
guard let binary = self.binary else {
throw ThriftEncoderError.uninitializedEncodingData
}
switch value {
case let v as Bool:
binary.write(v)
case let v as Data:
binary.write(v)
case let v as Double:
binary.write(v)
case let v as UInt8:
binary.write(v)
case let v as Int16:
binary.write(v)
case let v as Int32:
binary.write(v)
case let v as Int64:
binary.write(v)
case let v as String:
binary.write(v)
case let v as ThriftEncodable:
try v.thriftEncode(to: ThriftEncoder(binary: self.binary, specification: self.specification))
default:
throw ThriftEncoderError.unencodableType(type: value.self)
}
}
}
extension Dictionary : ThriftEncodable where Key : Encodable, Value : Encodable {
public static func thriftType() -> ThriftType {
return .map
}
public func thriftEncode(to encoder: ThriftEncoder) throws {
var container = encoder.unkeyedContainer()
let keyType = try ThriftType.type(from: Key.self)
let valueType = try ThriftType.type(from: Value.self)
encoder.binary?.writeMapMetadata(keyType: keyType, valueType: valueType, count: self.count)
for item in self {
try container.encode(item.key)
try container.encode(item.value)
}
}
}
extension Set: ThriftEncodable where Element: Encodable {
public static func thriftType() -> ThriftType {
return .set
}
public func thriftEncode(to encoder: ThriftEncoder) throws {
var container = encoder.unkeyedContainer()
let keyType = try ThriftType.type(from: Element.self)
encoder.binary?.writeSetMetadata(elementType: keyType, count: self.count)
for item in self {
try container.encode(item)
}
}
}
extension Array: ThriftEncodable where Element: Encodable {
public static func thriftType() -> ThriftType {
return .list
}
public func thriftEncode(to encoder: ThriftEncoder) throws {
var container = encoder.unkeyedContainer()
let arrayType = try ThriftType.type(from: Element.self)
encoder.binary?.writeListMetadata(elementType: arrayType, count: self.count)
for item in self {
try container.encode(item)
}
}
}
extension PreencodedContainer: ThriftEncodable {
public static func thriftType() throws -> ThriftType {
return try ThriftType.type(from: T.self)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .encodedData(let encodedData):
if let thriftContainer = container as? ThriftEncoder.UnkeyedContainer {
thriftContainer.encoder.binary?.insert(data: encodedData)
} else {
try container.encode(encodedData)
}
case .value(let value):
try container.encode(value)
}
}
public func thriftEncode(to encoder: ThriftEncoder) throws {
try self.encode(to: encoder)
}
}