Add SE-0166 support

This commit is contained in:
Norio Nomura 2017-05-07 21:36:35 +09:00
parent 57ac824a76
commit d06d6e03e6
No known key found for this signature in database
GPG Key ID: D4A7318EB7F7138D
4 changed files with 1079 additions and 0 deletions

454
Sources/Yams/Decoder.swift Normal file
View File

@ -0,0 +1,454 @@
//
// Decoder.swift
// Yams
//
// Created by Norio Nomura on 5/6/17.
// Copyright (c) 2017 Yams. All rights reserved.
//
#if swift(>=4.0)
import Foundation
public class YAMLDecoder {
public init() {}
public func decode<T: Swift.Decodable>(_ type: T.Type, from data: Data) throws -> T {
// TODO: Detect string encoding
let yaml = String(data: data, encoding: .utf8)! // swiftlint:disable:this force_unwrapping
let node = try Yams.compose(yaml: yaml) ?? ""
let decoder = _YAMLDecoder(referencing: node)
return try T(from: decoder)
}
}
fileprivate class _YAMLDecoder: Decoder {
let node: Node
init(referencing node: Node, codingPath: [CodingKey?] = []) {
self.node = node
self.codingPath = codingPath
}
// MARK: - Swift.Decoder Methods
/// The path to the current point in encoding.
var codingPath: [CodingKey?]
/// Contextual user-provided information for use during encoding.
var userInfo: [CodingUserInfoKey : Any] = [:]
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
guard let mapping = node.mapping else {
// FIXME: Should throw type mismatch error
throw DecodingError.valueNotFound(
KeyedDecodingContainer<Key>.self,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get keyed decoding container -- found null value instead."
)
)
}
let wrapper = _YAMLKeyedDecodingContainer<Key>(decoder: self, wrapping: mapping)
return KeyedDecodingContainer(wrapper)
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
guard let sequence = node.sequence else {
// FIXME: Should throw type mismatch error
throw DecodingError.valueNotFound(
UnkeyedDecodingContainer.self,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get unkeyed decoding container -- found null value instead."
)
)
}
return _YAMLUnkeyedDecodingContainer(decoder: self, wrapping: sequence)
}
func singleValueContainer() throws -> SingleValueDecodingContainer {
guard node.isScalar else {
// FIXME: Should throw type mismatch error
throw DecodingError.valueNotFound(
UnkeyedDecodingContainer.self,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get unkeyed decoding container -- found null value instead."
)
)
}
return self
}
// MARK: Utility
/// Performs the given closure with the given key pushed onto the end of the current coding path.
///
/// - parameter key: The key to push. May be nil for unkeyed containers.
/// - parameter work: The work to perform with the key in the path.
func with<T>(pushedKey key: CodingKey?, _ work: () throws -> T) rethrows -> T {
self.codingPath.append(key)
let ret: T = try work()
self.codingPath.removeLast()
return ret
}
}
fileprivate struct _YAMLKeyedDecodingContainer<K: CodingKey> : KeyedDecodingContainerProtocol {
typealias Key = K
let decoder: _YAMLDecoder
let mapping: Node.Mapping
init(decoder: _YAMLDecoder, wrapping mapping: Node.Mapping) {
self.decoder = decoder
self.mapping = mapping
}
// MARK: - KeyedDecodingContainerProtocol
var codingPath: [CodingKey?] {
return decoder.codingPath
}
var allKeys: [Key] {
return mapping.keys.flatMap { $0.string.flatMap(Key.init(stringValue:)) }
}
func contains(_ key: K) -> Bool {
if mapping[key.stringValue] != nil {
return true
}
return false
}
func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? { return try construct(for: key) }
func decodeIfPresent(_ type: Int.Type, forKey key: Key) throws -> Int? { return try construct(for: key) }
func decodeIfPresent(_ type: Int8.Type, forKey key: Key) throws -> Int8? { return try construct(for: key) }
func decodeIfPresent(_ type: Int16.Type, forKey key: Key) throws -> Int16? { return try construct(for: key) }
func decodeIfPresent(_ type: Int32.Type, forKey key: Key) throws -> Int32? { return try construct(for: key) }
func decodeIfPresent(_ type: Int64.Type, forKey key: Key) throws -> Int64? { return try construct(for: key) }
func decodeIfPresent(_ type: UInt.Type, forKey key: Key) throws -> UInt? { return try construct(for: key) }
func decodeIfPresent(_ type: UInt8.Type, forKey key: Key) throws -> UInt8? { return try construct(for: key) }
func decodeIfPresent(_ type: UInt16.Type, forKey key: Key) throws -> UInt16? { return try construct(for: key) }
func decodeIfPresent(_ type: UInt32.Type, forKey key: Key) throws -> UInt32? { return try construct(for: key) }
func decodeIfPresent(_ type: UInt64.Type, forKey key: Key) throws -> UInt64? { return try construct(for: key) }
func decodeIfPresent(_ type: Float.Type, forKey key: Key) throws -> Float? { return try construct(for: key) }
func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> Double? { return try construct(for: key) }
func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> String? { return try construct(for: key) }
func decodeIfPresent<T>(_ type: T.Type, forKey key: Key) throws -> T? where T : Decodable {
return try decoder.with(pushedKey: key) {
guard let node = mapping[key.stringValue] else { return nil }
if T.self == Data.self {
return Data.construct(from: node) as? T
} else if T.self == Date.self {
return Date.construct(from: node) as? T
}
let decoder = _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
return try T(from: decoder)
}
}
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type,
forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
return try decoder.with(pushedKey: key) {
guard let node = mapping[key.stringValue] else {
// FIXME: Should throw type mismatch error
throw DecodingError.keyNotFound(
key,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get \(KeyedDecodingContainer<NestedKey>.self) -- no value found for key \"\(key.stringValue)\""
)
)
}
guard let mapping = node.mapping else {
fatalError("should throw type mismatch error")
}
let decoder = _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
let wrapping = _YAMLKeyedDecodingContainer<NestedKey>(decoder: decoder, wrapping: mapping)
return KeyedDecodingContainer(wrapping)
}
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
return try decoder.with(pushedKey: key) {
guard let node = mapping[key.stringValue], let sequence = node.sequence else {
// FIXME: Should throw type mismatch error
throw DecodingError.keyNotFound(
key,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get UnkeyedDecodingContainer -- no value found for key \"\(key.stringValue)\""
)
)
}
let decoder = _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
return _YAMLUnkeyedDecodingContainer(decoder: decoder, wrapping: sequence)
}
}
private func _superDecoder(forKey key: CodingKey) throws -> Decoder {
return try self.decoder.with(pushedKey: key) {
guard let node = mapping[key.stringValue] else {
throw DecodingError.keyNotFound(
key,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get superDecoder() -- no value found for key \"\(key.stringValue)\""
)
)
}
return _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
}
}
func superDecoder() throws -> Decoder {
return try _superDecoder(forKey: _YAMLDecodingSuperKey())
}
func superDecoder(forKey key: Key) throws -> Decoder {
return try _superDecoder(forKey: key)
}
// MARK: Utility
/// Encode ScalarConstructible
func construct<T: ScalarConstructible>(for key: Key) throws -> T? {
return decoder.with(pushedKey: key) {
guard let node = mapping[key.stringValue] else { return nil }
return T.construct(from: node)
}
}
}
fileprivate struct _YAMLDecodingSuperKey: CodingKey {
init() {}
var stringValue: String { return "super" }
init?(stringValue: String) {
guard stringValue == "super" else { return nil }
}
var intValue: Int? { return nil }
init?(intValue: Int) {
return nil
}
}
fileprivate struct _YAMLUnkeyedDecodingContainer: UnkeyedDecodingContainer {
let decoder: _YAMLDecoder
let sequence: Node.Sequence
/// The index of the element we're about to decode.
var currentIndex: Int
init(decoder: _YAMLDecoder, wrapping sequence: Node.Sequence) {
self.decoder = decoder
self.sequence = sequence
self.currentIndex = 0
}
// MARK: - UnkeyedDecodingContainer
var codingPath: [CodingKey?] {
return decoder.codingPath
}
var count: Int? {
return sequence.count
}
var isAtEnd: Bool {
return self.currentIndex >= sequence.count
}
mutating func decodeIfPresent(_ type: Bool.Type) throws -> Bool? { return try construct() }
mutating func decodeIfPresent(_ type: Int.Type) throws -> Int? { return try construct() }
mutating func decodeIfPresent(_ type: Int8.Type) throws -> Int8? { return try construct() }
mutating func decodeIfPresent(_ type: Int16.Type) throws -> Int16? { return try construct() }
mutating func decodeIfPresent(_ type: Int32.Type) throws -> Int32? { return try construct() }
mutating func decodeIfPresent(_ type: Int64.Type) throws -> Int64? { return try construct() }
mutating func decodeIfPresent(_ type: UInt.Type) throws -> UInt? { return try construct() }
mutating func decodeIfPresent(_ type: UInt8.Type) throws -> UInt8? { return try construct() }
mutating func decodeIfPresent(_ type: UInt16.Type) throws -> UInt16? { return try construct() }
mutating func decodeIfPresent(_ type: UInt32.Type) throws -> UInt32? { return try construct() }
mutating func decodeIfPresent(_ type: UInt64.Type) throws -> UInt64? { return try construct() }
mutating func decodeIfPresent(_ type: Float.Type) throws -> Float? { return try construct() }
mutating func decodeIfPresent(_ type: Double.Type) throws -> Double? { return try construct() }
mutating func decodeIfPresent(_ type: String.Type) throws -> String? { return try construct() }
mutating func decodeIfPresent<T>(_ type: T.Type) throws -> T? where T : Decodable {
guard !self.isAtEnd else { return nil }
let decoded: T? = try decoder.with(pushedKey: nil) {
let node = sequence[currentIndex]
if T.self == Data.self {
return Data.construct(from: node) as? T
} else if T.self == Date.self {
return Date.construct(from: node) as? T
}
let decoder = _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
return try T(from: decoder)
}
currentIndex += 1
return decoded
}
mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> {
return try decoder.with(pushedKey: nil) {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(
KeyedDecodingContainer<NestedKey>.self,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get nested keyed container -- unkeyed container is at end."
)
)
}
let node = sequence[currentIndex]
guard let mapping = node.mapping else {
// FIXME: Should throw type mismatch error
throw DecodingError.valueNotFound(
KeyedDecodingContainer<NestedKey>.self,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get \(KeyedDecodingContainer<NestedKey>.self) -- no value found at index \"\(currentIndex)\""
)
)
}
currentIndex += 1
let decoder = _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
let wrapping = _YAMLKeyedDecodingContainer<NestedKey>(decoder: decoder, wrapping: mapping)
return KeyedDecodingContainer(wrapping)
}
}
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
return try decoder.with(pushedKey: nil) {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(
UnkeyedDecodingContainer.self,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get UnkeyedDecodingContainer -- unkeyed container is at end."
)
)
}
let node = sequence[currentIndex]
guard let sequence = node.sequence else {
// FIXME: Should throw type mismatch error
throw DecodingError.typeMismatch(
type(of: node),
DecodingError.Context(
codingPath: codingPath,
debugDescription: "Cannot get UnkeyedDecodingContainer -- no value found at index \"\(currentIndex)\""
)
)
}
let decoder = _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
return _YAMLUnkeyedDecodingContainer(decoder: decoder, wrapping: sequence)
}
}
mutating func superDecoder() throws -> Decoder {
return try decoder.with(pushedKey: nil) {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(
Decoder.self,
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Cannot get superDecoder() -- unkeyed container is at end."
)
)
}
let node = sequence[currentIndex]
self.currentIndex += 1
return _YAMLDecoder(referencing: node, codingPath: self.decoder.codingPath)
}
}
// MARK: Utility
/// Encode ScalarConstructible
mutating func construct<T: ScalarConstructible>() throws -> T? {
guard !self.isAtEnd else { return nil }
return decoder.with(pushedKey: nil) {
let node = sequence[currentIndex]
currentIndex += 1
return T.construct(from: node)
}
}
}
extension _YAMLDecoder : SingleValueDecodingContainer {
// MARK: SingleValueDecodingContainer Methods
func decode(_ type: Bool.Type) throws -> Bool { return try construct() }
func decode(_ type: Int.Type) throws -> Int { return try construct() }
func decode(_ type: Int8.Type) throws -> Int8 { return try construct() }
func decode(_ type: Int16.Type) throws -> Int16 { return try construct() }
func decode(_ type: Int32.Type) throws -> Int32 { return try construct() }
func decode(_ type: Int64.Type) throws -> Int64 { return try construct() }
func decode(_ type: UInt.Type) throws -> UInt { return try construct() }
func decode(_ type: UInt8.Type) throws -> UInt8 { return try construct() }
func decode(_ type: UInt16.Type) throws -> UInt16 { return try construct() }
func decode(_ type: UInt32.Type) throws -> UInt32 { return try construct() }
func decode(_ type: UInt64.Type) throws -> UInt64 { return try construct() }
func decode(_ type: Float.Type) throws -> Float { return try construct() }
func decode(_ type: Double.Type) throws -> Double { return try construct() }
func decode(_ type: String.Type) throws -> String { return try construct() }
func decode(_ type: Data.Type) throws -> Data { return try construct() }
// MARK: Utility
/// Encode ScalarConstructible
func construct<T: ScalarConstructible>() throws -> T {
return try with(pushedKey: nil) {
guard let decoded = T.construct(from: node) else {
// FIXME: Should throw type mismatch error
throw DecodingError.typeMismatch(
T.self,
DecodingError.Context(
codingPath: codingPath, debugDescription: ""
)
)
}
return decoded
}
}
}
extension BinaryInteger {
public static func construct(from node: Node) -> Self? {
return Int.construct(from: node) as? Self
}
}
extension Int16: ScalarConstructible {}
extension Int32: ScalarConstructible {}
extension Int64: ScalarConstructible {}
extension Int8: ScalarConstructible {}
extension UInt: ScalarConstructible {}
extension UInt16: ScalarConstructible {}
extension UInt32: ScalarConstructible {}
extension UInt64: ScalarConstructible {}
extension UInt8: ScalarConstructible {}
extension Float: ScalarConstructible {
public static func construct(from node: Node) -> Float? {
return Double.construct(from: node) as? Float
}
}
#endif // swiftlint:disable:this file_length

324
Sources/Yams/Encoder.swift Normal file
View File

@ -0,0 +1,324 @@
//
// Encoder.swift
// Yams
//
// Created by Norio Nomura on 5/2/17.
// Copyright (c) 2017 Yams. All rights reserved.
//
#if swift(>=4.0)
import Foundation
public class YAMLEncoder {
public init() {}
public func encode<T: Swift.Encodable>(_ value: T) throws -> Data {
let encoder = _YAMLEncoder()
try value.encode(to: encoder)
return try serialize(node: encoder.node).data(using: .utf8, allowLossyConversion: false) ?? Data()
}
}
fileprivate class _YAMLEncoder: Swift.Encoder {
var node: Node = ""
init(codingPath: [CodingKey?] = []) {
self.codingPath = codingPath
}
// MARK: - Swift.Encoder Methods
/// The path to the current point in encoding.
var codingPath: [CodingKey?]
/// Contextual user-provided information for use during encoding.
var userInfo: [CodingUserInfoKey : Any] = [:]
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
assertCanRequestNewContainer()
node = [:]
let wrapper = _KeyedEncodingContainer<Key>(referencing: self)
return KeyedEncodingContainer(wrapper)
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
assertCanRequestNewContainer()
node = []
return _UnkeyedEncodingContainer(referencing: self)
}
func singleValueContainer() -> SingleValueEncodingContainer {
assertCanRequestNewContainer()
return self
}
// MARK: Utility
/// Performs the given closure with the given key pushed onto the end of the current coding path.
///
/// - parameter key: The key to push. May be nil for unkeyed containers.
/// - parameter work: The work to perform with the key in the path.
func with(pushedKey key: CodingKey?, _ work: () throws -> Void) rethrows {
self.codingPath.append(key)
try work()
self.codingPath.removeLast()
}
/// Asserts that a new container can be requested at this coding path.
/// `preconditionFailure()`s if one cannot be requested.
func assertCanRequestNewContainer() {
guard node == "" else {
let previousContainerType: String
switch node {
case .mapping:
previousContainerType = "keyed"
case .sequence:
previousContainerType = "unkeyed"
case .scalar:
previousContainerType = "single value"
}
preconditionFailure(
"Attempt to encode with new container when already encoded with \(previousContainerType) container."
)
}
}
}
fileprivate class _YAMLReferencingEncoder: _YAMLEncoder {
enum Reference {
case sequence(Int)
case mapping(String)
}
let encoder: _YAMLEncoder
let reference: Reference
init(referencing encoder: _YAMLEncoder, at index: Int) {
self.encoder = encoder
reference = .sequence(index)
super.init(codingPath: encoder.codingPath)
}
init(referencing encoder: _YAMLEncoder, key: String) {
self.encoder = encoder
reference = .mapping(key)
super.init(codingPath: encoder.codingPath)
}
deinit {
switch reference {
case .sequence(let index):
encoder.node[index] = node
case .mapping(let key):
encoder.node[key] = node
}
}
}
fileprivate struct _KeyedEncodingContainer<K: CodingKey> : KeyedEncodingContainerProtocol {
typealias Key = K
let encoder: _YAMLEncoder
init(referencing encoder: _YAMLEncoder) {
self.encoder = encoder
}
// MARK: - KeyedEncodingContainerProtocol
var codingPath: [CodingKey?] {
return encoder.codingPath
}
// assumes following methods never throws
func encode(_ value: Bool?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: Int?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: Int8?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: Int16?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: Int32?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: Int64?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: UInt?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: UInt8?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: UInt16?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: UInt32?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: UInt64?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: Float?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: Double?, forKey key: Key) throws { try represent(value, for: key) }
func encode(_ value: String?, forKey key: Key) throws { try represent(value, for: key) }
func encode<T>(_ value: T?, forKey key: Key) throws where T : Encodable {
try encoder.with(pushedKey: key) {
if let value = value {
if let data = value as? Data {
encoder.node.sequence?.append(try data.represented())
} else if let date = value as? Date {
encoder.node.sequence?.append(try date.represented())
} else {
try value.encode(to: referencingEncoder(for: key.stringValue))
}
} else {
encoder.node.sequence?.append(Node("null", Tag(.null)))
}
}
}
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type,
forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
let wrapper = _KeyedEncodingContainer<NestedKey>(referencing: referencingEncoder(for: key.stringValue))
return KeyedEncodingContainer(wrapper)
}
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
return _UnkeyedEncodingContainer(referencing: referencingEncoder(for: key.stringValue))
}
func superEncoder() -> Encoder {
return referencingEncoder(for: "super")
}
func superEncoder(forKey key: Key) -> Encoder {
return referencingEncoder(for: key.stringValue)
}
// MARK: Utility
/// Encode NodeRepresentable
private func represent<T: NodeRepresentable>(_ value: T, for key: Key) throws {
// assumes this function is used for types that never throws.
encoder.node.mapping?[key.stringValue] = try Node(value)
}
private func referencingEncoder(for key: String) -> _YAMLReferencingEncoder {
return _YAMLReferencingEncoder(referencing: self.encoder, key: key)
}
}
fileprivate struct _UnkeyedEncodingContainer: UnkeyedEncodingContainer {
let encoder: _YAMLEncoder
init(referencing encoder: _YAMLEncoder) {
self.encoder = encoder
}
// MARK: - UnkeyedEncodingContainer
var codingPath: [CodingKey?] {
return encoder.codingPath
}
func encode(_ value: Bool?) throws { try represent(value) }
func encode(_ value: Int?) throws { try represent(value) }
func encode(_ value: Int8?) throws { try represent(value) }
func encode(_ value: Int16?) throws { try represent(value) }
func encode(_ value: Int32?) throws { try represent(value) }
func encode(_ value: Int64?) throws { try represent(value) }
func encode(_ value: UInt?) throws { try represent(value) }
func encode(_ value: UInt8?) throws { try represent(value) }
func encode(_ value: UInt16?) throws { try represent(value) }
func encode(_ value: UInt32?) throws { try represent(value) }
func encode(_ value: UInt64?) throws { try represent(value) }
func encode(_ value: Float?) throws { try represent(value) }
func encode(_ value: Double?) throws { try represent(value) }
func encode(_ value: String?) throws { try represent(value) }
func encode<T>(_ value: T?) throws where T : Encodable {
// Since generic types may throw, the coding path needs to contain this key.
try encoder.with(pushedKey: nil) {
if let value = value {
if let data = value as? Data {
encoder.node.sequence?.append(try data.represented())
} else if let date = value as? Date {
encoder.node.sequence?.append(try date.represented())
} else {
try value.encode(to: referencingEncoder())
}
} else {
encoder.node.sequence?.append(Node("null", Tag(.null)))
}
}
}
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
let wrapper = _KeyedEncodingContainer<NestedKey>(referencing: referencingEncoder())
return KeyedEncodingContainer(wrapper)
}
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
return _UnkeyedEncodingContainer(referencing: referencingEncoder())
}
func superEncoder() -> Encoder {
return referencingEncoder()
}
// MARK: Utility
/// Encode NodeRepresentable
private func represent<T: NodeRepresentable>(_ value: T) throws {
// assumes this function is used for types that never throws.
encoder.node.sequence?.append(try Node(value))
}
private func referencingEncoder() -> _YAMLReferencingEncoder {
let index: Int = encoder.node.sequence?.count ?? 0
encoder.node.sequence?.append("")
return _YAMLReferencingEncoder(referencing: self.encoder, at: index)
}
}
extension _YAMLEncoder: SingleValueEncodingContainer {
// MARK: - SingleValueEncodingContainer Methods
func encode(_ value: Bool) throws { try represent(value) }
func encode(_ value: Int) throws { try represent(value) }
func encode(_ value: Int8) throws { try represent(value) }
func encode(_ value: Int16) throws { try represent(value) }
func encode(_ value: Int32) throws { try represent(value) }
func encode(_ value: Int64) throws { try represent(value) }
func encode(_ value: UInt) throws { try represent(value) }
func encode(_ value: UInt8) throws { try represent(value) }
func encode(_ value: UInt16) throws { try represent(value) }
func encode(_ value: UInt32) throws { try represent(value) }
func encode(_ value: UInt64) throws { try represent(value) }
func encode(_ value: Float) throws { try represent(value) }
func encode(_ value: Double) throws { try represent(value) }
func encode(_ value: String) throws {
assertCanEncodeSingleValue()
node = Node(value)
}
// MARK: Utility
/// Asserts that a single value can be encoded at the current coding path
/// (i.e. that one has not already been encoded through this container).
/// `preconditionFailure()`s if one cannot be encoded.
///
/// This is similar to assertCanRequestNewContainer above.
func assertCanEncodeSingleValue() {
guard node == "" else {
let previousContainerType: String
switch node {
case .mapping:
previousContainerType = "keyed"
case .sequence:
previousContainerType = "unkeyed"
case .scalar:
preconditionFailure("Attempt to encode multiple values in a single value container.")
}
preconditionFailure(
"Attempt to encode with new container when already encoded with \(previousContainerType) container."
)
}
}
/// Encode NodeRepresentable
func represent<T: NodeRepresentable>(_ value: T) throws {
assertCanEncodeSingleValue()
node = try Node(value)
}
}
#endif

View File

@ -0,0 +1,289 @@
//
// EncoderTests.swift
// Yams
//
// Created by Norio Nomura on 5/2/17.
// Copyright (c) 2017 Yams. All rights reserved.
//
import XCTest
import Yams
#if swift(>=4.0)
/// Tests are copied from https://github.com/apple/swift/blob/master/test/stdlib/TestJSONEncoder.swift
class EncoderTests: XCTestCase {
// MARK: - Encoding Top-Level Empty Types
func testEncodingTopLevelEmptyStruct() {
let empty = EmptyStruct()
_testRoundTrip(of: empty, expectedYAML: "{}\n")
}
func testEncodingTopLevelEmptyClass() {
let empty = EmptyClass()
_testRoundTrip(of: empty, expectedYAML: "{}\n")
}
// MARK: - Encoding Top-Level Single-Value Types
func testEncodingTopLevelSingleValueEnum() {
_testRoundTrip(of: Switch.off, expectedYAML: "false\n...\n")
_testRoundTrip(of: Switch.on, expectedYAML: "true\n...\n")
}
func testEncodingTopLevelSingleValueStruct() {
_testRoundTrip(of: Timestamp(3141592653), expectedYAML: "3.141592653e+9\n...\n")
}
func testEncodingTopLevelSingleValueClass() {
_testRoundTrip(of: 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 testEncodingTopLevelDeepStructuredType() {
// Company is a type with fields which are Codable themselves.
let company = Company.testValue
_testRoundTrip(of: company)
}
// MARK: - Date Strategy Tests
func testEncodingDate() {
// We can't encode a top-level Date, so it'll be wrapped in an array.
_testRoundTrip(of: TopLevelWrapper(Date()))
}
func testEncodingDateMillisecondsSince1970() {
// Cannot encode an arbitrary number of seconds since we've lost precision since 1970.
let seconds = 1000.0
let expectedYAML = "- 1970-01-01T00:16:40Z\n"
// We can't encode a top-level Date, so it'll be wrapped in an array.
_testRoundTrip(of: TopLevelWrapper(Date(timeIntervalSince1970: seconds)),
expectedYAML: expectedYAML)
}
// MARK: - Data Tests
func testEncodingBase64Data() {
let data = Data(bytes: [0xDE, 0xAD, 0xBE, 0xEF])
// We can't encode a top-level Data, so it'll be wrapped in an array.
let expectedYAML = "- 3q2+7w==\n"
_testRoundTrip(of: TopLevelWrapper(data), expectedYAML: expectedYAML)
}
// MARK: - Helper Functions
private func _testRoundTrip<T>(of value: T,
expectedYAML yamlString: String? = nil) where T : Codable, T : Equatable {
let yaml = yamlString?.data(using: .utf8)! // swiftlint:disable:this force_unwrapping
var payload: Data! = nil
do {
let encoder = YAMLEncoder()
payload = try encoder.encode(value)
} catch {
XCTFail("Failed to encode \(T.self) to YAML.")
}
if let expectedYAML = yaml {
XCTAssertEqual(expectedYAML, payload, "Produced YAML not identical to expected YAML.")
}
do {
let decoder = YAMLDecoder()
let decoded = try decoder.decode(T.self, from: payload)
XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
} catch {
XCTFail("Failed to decode \(T.self) from YAML by error: \(error)")
}
}
}
// 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 // swiftlint:disable:this identifier_name
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.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
init(name: String, email: String) {
self.name = name
self.email = email
}
static func == (_ lhs: Person, _ rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.email == rhs.email
}
static var testValue: Person {
return Person(name: "Johnny Appleseed", email: "appleseed@apple.com")
}
}
/// A simple company struct which encodes as a dictionary of nested values.
fileprivate struct Company: Codable, Equatable {
let address: Address
var employees: [Person]
init(address: Address, employees: [Person]) {
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: [Person.testValue])
}
}
// MARK: - Helper Types
/// Wraps a type T so that it can be encoded at the top level of a payload.
fileprivate struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable {
let value: T
init(_ value: T) {
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(value)
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
value = try container.decode(T.self)
assert(container.isAtEnd)
}
static func == (_ lhs: TopLevelWrapper<T>, _ rhs: TopLevelWrapper<T>) -> Bool {
return lhs.value == rhs.value
}
}
#endif

View File

@ -17,12 +17,15 @@
6C3C90B91E0FFB6B009DEFE8 /* NodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3C90B81E0FFB6B009DEFE8 /* NodeTests.swift */; };
6C4A22071DF8553C002A0206 /* String+Yams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4A22061DF8553C002A0206 /* String+Yams.swift */; };
6C4A22091DF855BB002A0206 /* StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4A22081DF855BB002A0206 /* StringTests.swift */; };
6C4AF3201EBE1705008775BC /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4AF31E1EBE14A1008775BC /* Decoder.swift */; };
6C6834C81E0281880047B4D1 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6834C71E0281880047B4D1 /* Node.swift */; };
6C6834CC1E0283980047B4D1 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6834CB1E0283980047B4D1 /* Parser.swift */; };
6C6834CD1E02847D0047B4D1 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6834C91E0281D90047B4D1 /* Tag.swift */; };
6C6834D11E0297390047B4D1 /* SpecTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6834D01E0297390047B4D1 /* SpecTests.swift */; };
6C6834D31E02B9760047B4D1 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6834D21E02B9760047B4D1 /* Resolver.swift */; };
6C6834D51E02BC1F0047B4D1 /* ResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6834D41E02BC1F0047B4D1 /* ResolverTests.swift */; };
6C788A011EB87232005386F0 /* EncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C788A001EB87232005386F0 /* EncoderTests.swift */; };
6C788A041EB89162005386F0 /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C788A021EB876C4005386F0 /* Encoder.swift */; };
6C78C5651E29B27D0096215F /* RepresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C78C5631E29B1CE0096215F /* RepresenterTests.swift */; };
6CBAEE1A1E3839500021BF87 /* Yams.h in Headers */ = {isa = PBXBuildFile; fileRef = 6CBAEE191E3839500021BF87 /* Yams.h */; settings = {ATTRIBUTES = (Public, ); }; };
6CC2E33F1E22347B00F62269 /* Representer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC2E33E1E22347B00F62269 /* Representer.swift */; };
@ -66,12 +69,15 @@
6C4A22061DF8553C002A0206 /* String+Yams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Yams.swift"; sourceTree = "<group>"; };
6C4A22081DF855BB002A0206 /* StringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringTests.swift; sourceTree = "<group>"; };
6C4A220A1DF85793002A0206 /* LinuxMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinuxMain.swift; sourceTree = "<group>"; };
6C4AF31E1EBE14A1008775BC /* Decoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decoder.swift; sourceTree = "<group>"; };
6C6834C71E0281880047B4D1 /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = "<group>"; };
6C6834C91E0281D90047B4D1 /* Tag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
6C6834CB1E0283980047B4D1 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
6C6834D01E0297390047B4D1 /* SpecTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpecTests.swift; sourceTree = "<group>"; };
6C6834D21E02B9760047B4D1 /* Resolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
6C6834D41E02BC1F0047B4D1 /* ResolverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolverTests.swift; sourceTree = "<group>"; };
6C788A001EB87232005386F0 /* EncoderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncoderTests.swift; sourceTree = "<group>"; };
6C788A021EB876C4005386F0 /* Encoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Encoder.swift; sourceTree = "<group>"; };
6C78C5631E29B1CE0096215F /* RepresenterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepresenterTests.swift; sourceTree = "<group>"; };
6CBAEE191E3839500021BF87 /* Yams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Yams.h; sourceTree = "<group>"; };
6CC2E33E1E22347B00F62269 /* Representer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Representer.swift; sourceTree = "<group>"; };
@ -127,7 +133,9 @@
isa = PBXGroup;
children = (
6C0D2A351E0A934B00C45545 /* Constructor.swift */,
6C4AF31E1EBE14A1008775BC /* Decoder.swift */,
6CE603971E13502E00A13D8D /* Emitter.swift */,
6C788A021EB876C4005386F0 /* Encoder.swift */,
6CF025371E9CF4380061FB47 /* Mark.swift */,
6C6834C71E0281880047B4D1 /* Node.swift */,
6C0409AB1E607E9900C95D83 /* Node.Scalar.swift */,
@ -160,6 +168,7 @@
6CF6CE071E0E3A5900CB87D4 /* Fixtures */,
6C0488ED1E0CBD56006F9F80 /* ConstructorTests.swift */,
6C0A00D41E152D6200222704 /* EmitterTests.swift */,
6C788A001EB87232005386F0 /* EncoderTests.swift */,
6CF025391E9D12680061FB47 /* MarkTests.swift */,
6C3C90B81E0FFB6B009DEFE8 /* NodeTests.swift */,
6CF6CE081E0E3B1000CB87D4 /* PerformanceTests.swift */,
@ -347,12 +356,14 @@
E8EDB8881DE2181B0062268D /* loader.c in Sources */,
E8EDB88C1DE2181B0062268D /* writer.c in Sources */,
E8EDB88B1DE2181B0062268D /* scanner.c in Sources */,
6C4AF3201EBE1705008775BC /* Decoder.swift in Sources */,
6C0409AC1E607E9900C95D83 /* Node.Scalar.swift in Sources */,
6C4A22071DF8553C002A0206 /* String+Yams.swift in Sources */,
E8EDB8891DE2181B0062268D /* parser.c in Sources */,
6CF025381E9CF4380061FB47 /* Mark.swift in Sources */,
OBJ_50 /* YamlError.swift in Sources */,
E8EDB88A1DE2181B0062268D /* reader.c in Sources */,
6C788A041EB89162005386F0 /* Encoder.swift in Sources */,
6C6834CD1E02847D0047B4D1 /* Tag.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -361,6 +372,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 0;
files = (
6C788A011EB87232005386F0 /* EncoderTests.swift in Sources */,
6CF6CE091E0E3B1000CB87D4 /* PerformanceTests.swift in Sources */,
6C4A22091DF855BB002A0206 /* StringTests.swift in Sources */,
6C0488EC1E0BE113006F9F80 /* TestHelper.swift in Sources */,