-
Notifications
You must be signed in to change notification settings - Fork 33
/
CodableExtensions.swift
157 lines (139 loc) · 7.09 KB
/
CodableExtensions.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
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
@_spi(Generated) extension Decoder {
// MARK: - Coding SPI
/// Validates that no undocumented keys are present.
///
/// - Throws: When at least one undocumented key is found.
/// - Parameter knownKeys: A set of known and already decoded keys.
public func ensureNoAdditionalProperties(knownKeys: Set<String>) throws {
let (unknownKeys, container) = try unknownKeysAndContainer(knownKeys: knownKeys)
guard unknownKeys.isEmpty else {
let key = unknownKeys.sorted().first!
throw DecodingError.dataCorruptedError(
forKey: key,
in: container,
debugDescription:
"Additional properties are disabled, but found \(unknownKeys.count) unknown keys during decoding"
)
}
}
/// Returns decoded additional properties.
///
/// The included properties are those still present in the decoder but
/// not already decoded and passed in as known keys.
/// - Parameter knownKeys: Known and already decoded keys.
/// - Returns: A container with the decoded undocumented properties.
/// - Throws: An error if decoding additional properties fails.
public func decodeAdditionalProperties(knownKeys: Set<String>) throws -> OpenAPIObjectContainer {
let (unknownKeys, container) = try unknownKeysAndContainer(knownKeys: knownKeys)
guard !unknownKeys.isEmpty else { return .init() }
let keyValuePairs: [(String, (any Sendable)?)] = try unknownKeys.map { key in
(key.stringValue, try container.decode(OpenAPIValueContainer.self, forKey: key).value)
}
return .init(validatedValue: Dictionary(uniqueKeysWithValues: keyValuePairs))
}
/// Returns decoded additional properties.
///
/// The included properties are those still present in the decoder but
/// not already decoded and passed in as known keys.
/// - Parameter knownKeys: Known and already decoded keys.
/// - Returns: A container with the decoded undocumented properties.
/// - Throws: An error if there are issues with decoding the additional properties.
public func decodeAdditionalProperties<T: Decodable>(knownKeys: Set<String>) throws -> [String: T] {
let (unknownKeys, container) = try unknownKeysAndContainer(knownKeys: knownKeys)
guard !unknownKeys.isEmpty else { return .init() }
let keyValuePairs: [(String, T)] = try unknownKeys.compactMap { key in
(key.stringValue, try container.decode(T.self, forKey: key))
}
return .init(uniqueKeysWithValues: keyValuePairs)
}
/// Returns the decoded value by using a single value container.
/// - Parameter type: The type to decode.
/// - Returns: The decoded value.
/// - Throws: An error if there are issues with decoding the value from the single value container.
public func decodeFromSingleValueContainer<T: Decodable>(_ type: T.Type = T.self) throws -> T {
let container = try singleValueContainer()
return try container.decode(T.self)
}
// MARK: - Private
/// Returns the keys in the given decoder that are not present
/// in the `knownKeys` set.
///
/// This is used to implement the `additionalProperties` feature.
/// - Parameter knownKeys: A set of known keys that have already been decoded.
/// - Returns: A tuple containing two values: a set of unknown keys and a keyed decoding container
/// for further decoding of the unknown properties.
/// - Throws: An error if there are issues with creating the decoding container or identifying
/// the unknown keys.
private func unknownKeysAndContainer(knownKeys: Set<String>) throws -> (
Set<StringKey>, KeyedDecodingContainer<StringKey>
) {
let container = try container(keyedBy: StringKey.self)
let unknownKeys = Set(container.allKeys).subtracting(knownKeys.map(StringKey.init(_:)))
return (unknownKeys, container)
}
}
@_spi(Generated) extension Encoder {
/// Encodes additional properties into the encoder.
///
/// The properties are encoded directly into the encoder, rather that
/// into a nested container.
/// - Parameter additionalProperties: A container of additional properties.
/// - Throws: An error if there are issues with encoding the additional properties.
public func encodeAdditionalProperties(_ additionalProperties: OpenAPIObjectContainer) throws {
var container = container(keyedBy: StringKey.self)
for (key, value) in additionalProperties.value {
try container.encode(OpenAPIValueContainer(unvalidatedValue: value), forKey: .init(key))
}
}
/// Encodes additional properties into the encoder.
///
/// The properties are encoded directly into the encoder, rather that
/// into a nested container.
/// - Parameter additionalProperties: A container of additional properties.
/// - Throws: An error if there are issues with encoding the additional properties.
public func encodeAdditionalProperties<T: Encodable>(_ additionalProperties: [String: T]) throws {
var container = container(keyedBy: StringKey.self)
for (key, value) in additionalProperties { try container.encode(value, forKey: .init(key)) }
}
/// Encodes the value into the encoder using a single value container.
/// - Parameter value: The value to encode.
/// - Throws: An error if there are issues with encoding the value.
public func encodeToSingleValueContainer<T: Encodable>(_ value: T) throws {
var container = singleValueContainer()
try container.encode(value)
}
/// Encodes the first non-nil value from the provided array into
/// the encoder using a single value container.
/// - Parameter values: An array of optional values.
/// - Throws: An error if there are issues with encoding the value.
public func encodeFirstNonNilValueToSingleValueContainer(_ values: [(any Encodable)?]) throws {
for value in values {
if let value {
try encodeToSingleValueContainer(value)
return
}
}
}
}
/// A freeform String coding key for decoding undocumented values.
private struct StringKey: CodingKey, Hashable, Comparable {
var stringValue: String
var intValue: Int? { Int(stringValue) }
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { self.stringValue = String(intValue) }
static func < (lhs: StringKey, rhs: StringKey) -> Bool { lhs.stringValue < rhs.stringValue }
}