-
Notifications
You must be signed in to change notification settings - Fork 148
/
Copy pathDecoder.swift
383 lines (311 loc) · 14.6 KB
/
Decoder.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
//
// Decoder.swift
// Yams
//
// Created by Norio Nomura on 5/6/17.
// Copyright (c) 2017 Yams. All rights reserved.
//
import Foundation
/// `Codable`-style `Decoder` that can be used to decode a `Decodable` type from a given `String` and optional
/// user info mapping. Similar to `Foundation.JSONDecoder`.
public class YAMLDecoder {
/// Creates a `YAMLDecoder` instance.
///
/// - parameter encoding: Encoding, `.default` if omitted.
public init(encoding: Parser.Encoding = .default) {
self.encoding = encoding
}
/// Decode a `Decodable` type from a given `Node` and optional user info mapping.
///
/// - parameter type: `Decodable` type to decode.
/// - parameter node: YAML Node to decode.
/// - parameter userInfo: Additional key/values which can be used when looking up keys to decode.
///
/// - returns: Returns the decoded type `T`.
///
/// - throws: `DecodingError` or `YamlError` if something went wrong while decoding.
public func decode<T>(_ type: T.Type = T.self,
from node: Node,
userInfo: [CodingUserInfoKey: Any] = [:]) throws -> T where T: Swift.Decodable {
let decoder = _Decoder(referencing: node, userInfo: userInfo)
let container = try decoder.singleValueContainer()
return try container.decode(type)
}
/// Decode a `Decodable` type from a given `String` and optional user info mapping.
///
/// - parameter type: `Decodable` type to decode.
/// - parameter yaml: YAML string to decode.
/// - parameter userInfo: Additional key/values which can be used when looking up keys to decode.
///
/// - returns: Returns the decoded type `T`.
///
/// - throws: `DecodingError` or `YamlError` if something went wrong while decoding.
public func decode<T>(_ type: T.Type = T.self,
from yaml: String,
userInfo: [CodingUserInfoKey: Any] = [:]) throws -> T where T: Swift.Decodable {
do {
let node = try Parser(yaml: yaml, resolver: Resolver([.merge]), encoding: encoding).singleRoot() ?? ""
return try self.decode(type, from: node, userInfo: userInfo)
} catch let error as DecodingError {
throw error
} catch {
throw DecodingError.dataCorrupted(.init(codingPath: [],
debugDescription: "The given data was not valid YAML.",
underlyingError: error))
}
}
/// Decode a `Decodable` type from a given `Data` and optional user info mapping.
///
/// - parameter type: `Decodable` type to decode.
/// - parameter yaml: YAML data to decode.
/// - parameter userInfo: Additional key/values which can be used when looking up keys to decode.
///
/// - returns: Returns the decoded type `T`.
///
/// - throws: `DecodingError` or `YamlError` if something went wrong while decoding.
public func decode<T>(_ type: T.Type = T.self,
from yamlData: Data,
userInfo: [CodingUserInfoKey: Any] = [:]) throws -> T where T: Swift.Decodable {
guard let yamlString = String(data: yamlData, encoding: encoding.swiftStringEncoding) else {
throw YamlError.dataCouldNotBeDecoded(encoding: encoding.swiftStringEncoding)
}
return try decode(type, from: yamlString, userInfo: userInfo)
}
/// Encoding
public var encoding: Parser.Encoding
}
private struct _Decoder: Decoder {
fileprivate let node: Node
init(referencing node: Node, userInfo: [CodingUserInfoKey: Any], codingPath: [CodingKey] = []) {
self.node = node
self.userInfo = userInfo
self.codingPath = codingPath
}
// MARK: - Swift.Decoder Methods
let codingPath: [CodingKey]
let userInfo: [CodingUserInfoKey: Any]
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
guard let mapping = node.mapping?.flatten() else {
throw _typeMismatch(at: codingPath, expectation: Node.Mapping.self, reality: node)
}
return .init(_KeyedDecodingContainer<Key>(decoder: self, wrapping: mapping))
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
guard let sequence = node.sequence else {
throw _typeMismatch(at: codingPath, expectation: Node.Sequence.self, reality: node)
}
return _UnkeyedDecodingContainer(decoder: self, wrapping: sequence)
}
func singleValueContainer() throws -> SingleValueDecodingContainer { return self }
// MARK: -
/// create a new `_Decoder` instance referencing `node` as `key` inheriting `userInfo`
func decoder(referencing node: Node, `as` key: CodingKey) -> _Decoder {
return .init(referencing: node, userInfo: userInfo, codingPath: codingPath + [key])
}
/// returns `Node.Scalar` or throws `DecodingError.typeMismatch`
private func scalar() throws -> Node.Scalar {
switch node {
case .scalar(let scalar):
return scalar
case .mapping(let mapping):
throw _typeMismatch(at: codingPath, expectation: Node.Scalar.self, reality: mapping)
case .sequence(let sequence):
throw _typeMismatch(at: codingPath, expectation: Node.Scalar.self, reality: sequence)
}
}
}
private struct _KeyedDecodingContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
private let decoder: _Decoder
private let mapping: Node.Mapping
init(decoder: _Decoder, wrapping mapping: Node.Mapping) {
self.decoder = decoder
self.mapping = mapping
}
// MARK: - Swift.KeyedDecodingContainerProtocol Methods
var codingPath: [CodingKey] { return decoder.codingPath }
var allKeys: [Key] { return mapping.keys.compactMap { $0.string.flatMap(Key.init(stringValue:)) } }
func contains(_ key: Key) -> Bool { return mapping[key.stringValue] != nil }
func decodeNil(forKey key: Key) throws -> Bool {
return try decoder(for: key).decodeNil()
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable & ScalarConstructible {
return try decoder(for: key).decode(type)
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
return try decoder(for: key).decode(type)
}
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type,
forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
return try decoder(for: key).container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
return try decoder(for: key).unkeyedContainer()
}
func superDecoder() throws -> Decoder { return try decoder(for: _YAMLCodingKey.super) }
func superDecoder(forKey key: Key) throws -> Decoder { return try decoder(for: key) }
// MARK: -
private func node(for key: CodingKey) throws -> Node {
guard let node = mapping[key.stringValue] else {
throw _keyNotFound(at: codingPath, key, "No value associated with key \(key) (\"\(key.stringValue)\").")
}
return node
}
private func decoder(for key: CodingKey) throws -> _Decoder {
return decoder.decoder(referencing: try node(for: key), as: key)
}
}
private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer {
private let decoder: _Decoder
private let sequence: Node.Sequence
init(decoder: _Decoder, wrapping sequence: Node.Sequence) {
self.decoder = decoder
self.sequence = sequence
self.currentIndex = 0
}
// MARK: - Swift.UnkeyedDecodingContainer Methods
var codingPath: [CodingKey] { return decoder.codingPath }
var count: Int? { return sequence.count }
var isAtEnd: Bool { return currentIndex >= sequence.count }
var currentIndex: Int
mutating func decodeNil() throws -> Bool {
try throwErrorIfAtEnd(Any?.self)
return try currentDecoder { $0.decodeNil() }
}
mutating func decode<T>(_ type: T.Type) throws -> T where T: Decodable & ScalarConstructible {
return try currentDecoder { try $0.decode(type) }
}
mutating func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
return try currentDecoder { try $0.decode(type) }
}
mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> {
return try currentDecoder { try $0.container(keyedBy: type) }
}
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
return try currentDecoder { try $0.unkeyedContainer() }
}
mutating func superDecoder() throws -> Decoder { return try currentDecoder { $0 } }
// MARK: -
private var currentKey: CodingKey { return _YAMLCodingKey(index: currentIndex) }
private var currentNode: Node { return sequence[currentIndex] }
private func throwErrorIfAtEnd<T>(_ type: T.Type) throws {
if isAtEnd { throw _valueNotFound(at: codingPath + [currentKey], type, "Unkeyed container is at end.") }
}
private mutating func currentDecoder<T>(closure: (_Decoder) throws -> T) throws -> T {
try throwErrorIfAtEnd(T.self)
let decoded: T = try closure(decoder.decoder(referencing: currentNode, as: currentKey))
currentIndex += 1
return decoded
}
}
extension _Decoder: SingleValueDecodingContainer {
// MARK: - Swift.SingleValueDecodingContainer Methods
func decodeNil() -> Bool { return node.null == NSNull() }
func decode<T>(_ type: T.Type) throws -> T where T: Decodable & ScalarConstructible { return try construct(type) }
func decode<T>(_ type: T.Type) throws -> T where T: Decodable {return try construct(type) ?? type.init(from: self) }
// MARK: -
/// constuct `T` from `node`
private func construct<T: ScalarConstructible>(_ type: T.Type) throws -> T {
let scalar = try self.scalar()
guard let constructed = type.construct(from: scalar) else {
throw _typeMismatch(at: codingPath, expectation: type, reality: scalar)
}
return constructed
}
private func construct<T>(_ type: T.Type) throws -> T? {
guard let constructibleType = type as? ScalarConstructible.Type else {
return nil
}
let scalar = try self.scalar()
guard let value = constructibleType.construct(from: scalar) else {
throw _valueNotFound(at: codingPath, type, "Expected \(type) value but found \(scalar) instead.")
}
return value as? T
}
}
// MARK: - DecodingError helpers
private func _keyNotFound(at codingPath: [CodingKey], _ key: CodingKey, _ description: String) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: description)
return.keyNotFound(key, context)
}
private func _valueNotFound(at codingPath: [CodingKey], _ type: Any.Type, _ description: String) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: description)
return .valueNotFound(type, context)
}
private func _typeMismatch(at codingPath: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError {
let description = "Expected to decode \(expectation) but found \(type(of: reality)) instead."
let context = DecodingError.Context(codingPath: codingPath, debugDescription: description)
return .typeMismatch(expectation, context)
}
// MARK: - ScalarConstructible FixedWidthInteger & SignedInteger Conformance
extension FixedWidthInteger where Self: SignedInteger {
/// Construct an instance of `Self`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Self`, if possible.
///
/// - returns: An instance of `Self`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Self? {
return Int64.construct(from: scalar).flatMap(Self.init(exactly:))
}
}
// MARK: - ScalarConstructible FixedWidthInteger & UnsignedInteger Conformance
extension FixedWidthInteger where Self: UnsignedInteger {
/// Construct an instance of `Self`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Self`, if possible.
///
/// - returns: An instance of `Self`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Self? {
return UInt64.construct(from: scalar).flatMap(Self.init(exactly:))
}
}
// MARK: - ScalarConstructible Int8 Conformance
extension Int8: ScalarConstructible {}
// MARK: - ScalarConstructible Int16 Conformance
extension Int16: ScalarConstructible {}
// MARK: - ScalarConstructible Int32 Conformance
extension Int32: ScalarConstructible {}
// MARK: - ScalarConstructible UInt8 Conformance
extension UInt8: ScalarConstructible {}
// MARK: - ScalarConstructible UInt16 Conformance
extension UInt16: ScalarConstructible {}
// MARK: - ScalarConstructible UInt32 Conformance
extension UInt32: ScalarConstructible {}
// MARK: - ScalarConstructible Decimal Conformance
extension Decimal: ScalarConstructible {
/// Construct an instance of `Decimal`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Decimal`, if possible.
///
/// - returns: An instance of `Decimal`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Decimal? {
return Decimal(string: scalar.string)
}
}
// MARK: - ScalarConstructible URL Conformance
extension URL: ScalarConstructible {
/// Construct an instance of `URL`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `URL`, if possible.
///
/// - returns: An instance of `URL`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> URL? {
return URL(string: scalar.string)
}
}
// MARK: Decoder.mark
extension Decoder {
/// The `Mark` for the underlying `Node` that has been decoded.
public var mark: Mark? {
return (self as? _Decoder)?.node.mark
}
}
// MARK: TopLevelDecoder
#if canImport(Combine)
import protocol Combine.TopLevelDecoder
extension YAMLDecoder: TopLevelDecoder {
public typealias Input = Data
public func decode<T>(_ type: T.Type, from: Data) throws -> T where T: Decodable {
try decode(type, from: from, userInfo: [:])
}
}
#endif