-
Notifications
You must be signed in to change notification settings - Fork 33
/
URIValueToNodeEncoder.swift
126 lines (102 loc) · 4.55 KB
/
URIValueToNodeEncoder.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
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
/// A type that converts an `Encodable` type into a `URIEncodableNode` value.
final class URIValueToNodeEncoder {
/// An entry in the coding stack for `URIEncoder`.
///
/// This is used to keep track of where we are in the encode.
struct CodingStackEntry {
/// The key at which to write the node.
var key: URICoderCodingKey
/// The node at the key inside its parent.
var storage: URIEncodedNode
}
/// An encoder error.
enum GeneralError: Swift.Error {
/// The encoder set a nil value, which isn't supported.
case nilNotSupported
/// The encoder set a Data value, which isn't supported.
case dataNotSupported
/// The encoder set a value for an index out of range of the container.
case integerOutOfRange
}
/// The stack of nested values within the root node.
private var _codingPath: [CodingStackEntry]
/// The current value, which will be added on top of the stack once
/// finished encoding.
var currentStackEntry: CodingStackEntry
/// Creates a new encoder.
init() {
self._codingPath = []
self.currentStackEntry = CodingStackEntry(key: .init(stringValue: ""), storage: .unset)
}
/// Encodes the provided value into a node.
/// - Parameter value: The value to encode.
/// - Returns: The node with the encoded contents of the value.
/// - Throws: An error if encoding the value into a node fails.
func encodeValue(_ value: some Encodable) throws -> URIEncodedNode {
defer {
_codingPath = []
currentStackEntry = CodingStackEntry(key: .init(stringValue: ""), storage: .unset)
}
// We have to catch the special values early, otherwise we fall
// back to their Codable implementations, which don't give us
// a chance to customize the coding in the containers.
if let date = value as? Date {
var container = singleValueContainer()
try container.encode(date)
} else {
try value.encode(to: self)
}
let encodedValue = currentStackEntry.storage
return encodedValue
}
}
extension URIValueToNodeEncoder {
/// Pushes a new container on top of the current stack, nesting into the
/// value at the provided key.
/// - Parameters:
/// - key: The coding key for the new value on top of the stack.
/// - newStorage: The node to push on top of the stack.
func push(key: URICoderCodingKey, newStorage: URIEncodedNode) {
_codingPath.append(currentStackEntry)
currentStackEntry = .init(key: key, storage: newStorage)
}
/// Pops the top container from the stack and restores the previously top
/// container to be the current top container.
func pop() throws {
// This is called when we've completed the storage in the current container.
// We can pop the value at the base of the stack, then "insert" the current one
// into it, and save the new value as the new current.
let current = currentStackEntry
var newCurrent = _codingPath.removeLast()
try newCurrent.storage.insert(current.storage, atKey: current.key)
currentStackEntry = newCurrent
}
}
extension URIValueToNodeEncoder: Encoder {
var codingPath: [any CodingKey] {
// The coding path meaningful to the types conforming to Codable.
// 1. Omit the root coding path.
// 2. Add the current stack entry's coding path.
(_codingPath.dropFirst().map(\.key) + [currentStackEntry.key]).map { $0 as any CodingKey }
}
var userInfo: [CodingUserInfoKey: Any] { [:] }
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey {
KeyedEncodingContainer(URIKeyedEncodingContainer(encoder: self))
}
func unkeyedContainer() -> any UnkeyedEncodingContainer { URIUnkeyedEncodingContainer(encoder: self) }
func singleValueContainer() -> any SingleValueEncodingContainer { URISingleValueEncodingContainer(encoder: self) }
}