Skip to content

Commit 7706507

Browse files
authored
Merge pull request #405 from ayushshrivastv/Add-vendor-extension-support-to-Content.Encoding-type
2 parents dcc9f0e + c0f4396 commit 7706507

File tree

4 files changed

+212
-14
lines changed

4 files changed

+212
-14
lines changed

Sources/OpenAPIKit/Content/ContentEncoding.swift

+64-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// ContentEncoding.swift
3-
//
3+
//
44
//
55
// Created by Mathew Polzin on 12/29/19.
66
//
@@ -9,9 +9,9 @@ import OpenAPIKitCore
99

1010
extension OpenAPI.Content {
1111
/// OpenAPI Spec "Encoding Object"
12-
///
12+
///
1313
/// See [OpenAPI Encoding Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#encoding-object).
14-
public struct Encoding: Equatable {
14+
public struct Encoding: Equatable, CodableVendorExtendable {
1515
public typealias Style = OpenAPI.Parameter.SchemaContext.Style
1616

1717
/// If an encoding object only contains 1 content type, it will be populated here.
@@ -35,20 +35,29 @@ extension OpenAPI.Content {
3535
public let explode: Bool
3636
public let allowReserved: Bool
3737

38+
/// Dictionary of vendor extensions.
39+
///
40+
/// These should be of the form:
41+
/// `[ "x-extensionKey": <anything>]`
42+
/// where the values are anything codable.
43+
public var vendorExtensions: [String: AnyCodable]
44+
3845
/// The singular `contentType` argument is only provided for backwards compatibility and
3946
/// using the plural `contentTypes` argument should be preferred.
4047
public init(
4148
contentType: OpenAPI.ContentType? = nil,
4249
contentTypes: [OpenAPI.ContentType] = [],
4350
headers: OpenAPI.Header.Map? = nil,
4451
style: Style = Self.defaultStyle,
45-
allowReserved: Bool = false
52+
allowReserved: Bool = false,
53+
vendorExtensions: [String: AnyCodable] = [:]
4654
) {
4755
self.contentTypes = contentTypes + [contentType].compactMap { $0 }
4856
self.headers = headers
4957
self.style = style
5058
self.explode = style.defaultExplode
5159
self.allowReserved = allowReserved
60+
self.vendorExtensions = vendorExtensions
5261
}
5362

5463
/// The singular `contentType` argument is only provided for backwards compatibility and
@@ -59,13 +68,15 @@ extension OpenAPI.Content {
5968
headers: OpenAPI.Header.Map? = nil,
6069
style: Style = Self.defaultStyle,
6170
explode: Bool,
62-
allowReserved: Bool = false
71+
allowReserved: Bool = false,
72+
vendorExtensions: [String: AnyCodable] = [:]
6373
) {
6474
self.contentTypes = contentTypes + [contentType].compactMap { $0 }
6575
self.headers = headers
6676
self.style = style
6777
self.explode = explode
6878
self.allowReserved = allowReserved
79+
self.vendorExtensions = vendorExtensions
6980
}
7081

7182
public static let defaultStyle: Style = .default(for: .query)
@@ -96,6 +107,8 @@ extension OpenAPI.Content.Encoding: Encodable {
96107
if allowReserved != false {
97108
try container.encode(allowReserved, forKey: .allowReserved)
98109
}
110+
111+
try encodeExtensions(to: &container)
99112
}
100113
}
101114

@@ -122,16 +135,61 @@ extension OpenAPI.Content.Encoding: Decodable {
122135
explode = try container.decodeIfPresent(Bool.self, forKey: .explode) ?? style.defaultExplode
123136

124137
allowReserved = try container.decodeIfPresent(Bool.self, forKey: .allowReserved) ?? false
138+
139+
vendorExtensions = try Self.extensions(from: decoder)
125140
}
126141
}
127142

128143
extension OpenAPI.Content.Encoding {
129-
private enum CodingKeys: String, CodingKey {
144+
internal enum CodingKeys: ExtendableCodingKey {
130145
case contentType
131146
case headers
132147
case style
133148
case explode
134149
case allowReserved
150+
case extended(String)
151+
152+
static var allBuiltinKeys: [CodingKeys] {
153+
return [.contentType, .headers, .style, .explode, .allowReserved]
154+
}
155+
156+
static func extendedKey(for value: String) -> CodingKeys {
157+
return .extended(value)
158+
}
159+
160+
init?(stringValue: String) {
161+
switch stringValue {
162+
case "contentType":
163+
self = .contentType
164+
case "headers":
165+
self = .headers
166+
case "style":
167+
self = .style
168+
case "explode":
169+
self = .explode
170+
case "allowReserved":
171+
self = .allowReserved
172+
default:
173+
self = .extendedKey(for: stringValue)
174+
}
175+
}
176+
177+
var stringValue: String {
178+
switch self {
179+
case .contentType:
180+
return "contentType"
181+
case .headers:
182+
return "headers"
183+
case .style:
184+
return "style"
185+
case .explode:
186+
return "explode"
187+
case .allowReserved:
188+
return "allowReserved"
189+
case .extended(let key):
190+
return key
191+
}
192+
}
135193
}
136194
}
137195

Sources/OpenAPIKit30/Content/ContentEncoding.swift

+62-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extension OpenAPI.Content {
1111
/// OpenAPI Spec "Encoding Object"
1212
///
1313
/// See [OpenAPI Encoding Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#encoding-object).
14-
public struct Encoding: Equatable {
14+
public struct Encoding: Equatable, CodableVendorExtendable {
1515
public typealias Style = OpenAPI.Parameter.SchemaContext.Style
1616

1717
public let contentType: OpenAPI.ContentType?
@@ -20,31 +20,42 @@ extension OpenAPI.Content {
2020
public let explode: Bool
2121
public let allowReserved: Bool
2222

23+
/// Dictionary of vendor extensions.
24+
///
25+
/// These should be of the form:
26+
/// `[ "x-extensionKey": <anything>]`
27+
/// where the values are anything codable.
28+
public var vendorExtensions: [String: AnyCodable]
29+
2330
public init(
2431
contentType: OpenAPI.ContentType? = nil,
2532
headers: OpenAPI.Header.Map? = nil,
2633
style: Style = Self.defaultStyle,
27-
allowReserved: Bool = false
34+
allowReserved: Bool = false,
35+
vendorExtensions: [String: AnyCodable] = [:]
2836
) {
2937
self.contentType = contentType
3038
self.headers = headers
3139
self.style = style
3240
self.explode = style.defaultExplode
3341
self.allowReserved = allowReserved
42+
self.vendorExtensions = vendorExtensions
3443
}
3544

3645
public init(
3746
contentType: OpenAPI.ContentType? = nil,
3847
headers: OpenAPI.Header.Map? = nil,
3948
style: Style = Self.defaultStyle,
4049
explode: Bool,
41-
allowReserved: Bool = false
50+
allowReserved: Bool = false,
51+
vendorExtensions: [String: AnyCodable] = [:]
4252
) {
4353
self.contentType = contentType
4454
self.headers = headers
4555
self.style = style
4656
self.explode = explode
4757
self.allowReserved = allowReserved
58+
self.vendorExtensions = vendorExtensions
4859
}
4960

5061
public static let defaultStyle: Style = .default(for: .query)
@@ -70,6 +81,8 @@ extension OpenAPI.Content.Encoding: Encodable {
7081
if allowReserved != false {
7182
try container.encode(allowReserved, forKey: .allowReserved)
7283
}
84+
85+
try encodeExtensions(to: &container)
7386
}
7487
}
7588

@@ -87,16 +100,61 @@ extension OpenAPI.Content.Encoding: Decodable {
87100
explode = try container.decodeIfPresent(Bool.self, forKey: .explode) ?? style.defaultExplode
88101

89102
allowReserved = try container.decodeIfPresent(Bool.self, forKey: .allowReserved) ?? false
103+
104+
vendorExtensions = try Self.extensions(from: decoder)
90105
}
91106
}
92107

93108
extension OpenAPI.Content.Encoding {
94-
private enum CodingKeys: String, CodingKey {
109+
internal enum CodingKeys: ExtendableCodingKey {
95110
case contentType
96111
case headers
97112
case style
98113
case explode
99114
case allowReserved
115+
case extended(String)
116+
117+
static var allBuiltinKeys: [CodingKeys] {
118+
return [.contentType, .headers, .style, .explode, .allowReserved]
119+
}
120+
121+
static func extendedKey(for value: String) -> CodingKeys {
122+
return .extended(value)
123+
}
124+
125+
init?(stringValue: String) {
126+
switch stringValue {
127+
case "contentType":
128+
self = .contentType
129+
case "headers":
130+
self = .headers
131+
case "style":
132+
self = .style
133+
case "explode":
134+
self = .explode
135+
case "allowReserved":
136+
self = .allowReserved
137+
default:
138+
self = .extendedKey(for: stringValue)
139+
}
140+
}
141+
142+
var stringValue: String {
143+
switch self {
144+
case .contentType:
145+
return "contentType"
146+
case .headers:
147+
return "headers"
148+
case .style:
149+
return "style"
150+
case .explode:
151+
return "explode"
152+
case .allowReserved:
153+
return "allowReserved"
154+
case .extended(let key):
155+
return key
156+
}
157+
}
100158
}
101159
}
102160

Tests/OpenAPIKit30Tests/Content/ContentTests.swift

+43-2
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,7 @@ extension ContentTests {
515515
style: .form,
516516
explode: true)
517517
}
518-
}
519518

520-
extension ContentTests {
521519
func test_encoding_minimal_encode() throws {
522520
let encoding = OpenAPI.Content.Encoding()
523521

@@ -707,4 +705,47 @@ extension ContentTests {
707705
OpenAPI.Content.Encoding(allowReserved: true)
708706
)
709707
}
708+
709+
func test_encoding_vendorExtensions_encode() throws {
710+
let encoding = OpenAPI.Content.Encoding(
711+
contentType: .json,
712+
vendorExtensions: [
713+
"x-custom": "value",
714+
"x-nested": ["key": 123]
715+
]
716+
)
717+
718+
let encodedEncoding = try orderUnstableTestStringFromEncoding(of: encoding)
719+
720+
assertJSONEquivalent(
721+
encodedEncoding,
722+
"""
723+
{
724+
"contentType" : "application\\/json",
725+
"x-custom" : "value",
726+
"x-nested" : {
727+
"key" : 123
728+
}
729+
}
730+
"""
731+
)
732+
}
733+
734+
func test_encoding_vendorExtensions_decode() throws {
735+
let encodingData =
736+
"""
737+
{
738+
"contentType": "application/json",
739+
"x-custom": "value",
740+
"x-nested": {
741+
"key": 123
742+
}
743+
}
744+
""".data(using: .utf8)!
745+
let encoding = try orderUnstableDecode(OpenAPI.Content.Encoding.self, from: encodingData)
746+
747+
XCTAssertEqual(encoding.contentType, .json)
748+
XCTAssertEqual(encoding.vendorExtensions["x-custom"]?.value as? String, "value")
749+
XCTAssertEqual((encoding.vendorExtensions["x-nested"]?.value as? [String: Int])?["key"], 123)
750+
}
710751
}

Tests/OpenAPIKitTests/Content/ContentTests.swift

+43-2
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,7 @@ extension ContentTests {
515515
style: .form,
516516
explode: true)
517517
}
518-
}
519518

520-
extension ContentTests {
521519
func test_encoding_minimal_encode() throws {
522520
let encoding = OpenAPI.Content.Encoding()
523521

@@ -734,4 +732,47 @@ extension ContentTests {
734732
OpenAPI.Content.Encoding(allowReserved: true)
735733
)
736734
}
735+
736+
func test_encoding_vendorExtensions_encode() throws {
737+
let encoding = OpenAPI.Content.Encoding(
738+
contentType: .json,
739+
vendorExtensions: [
740+
"x-custom": "value",
741+
"x-nested": ["key": 123]
742+
]
743+
)
744+
745+
let encodedEncoding = try orderUnstableTestStringFromEncoding(of: encoding)
746+
747+
assertJSONEquivalent(
748+
encodedEncoding,
749+
"""
750+
{
751+
"contentType" : "application\\/json",
752+
"x-custom" : "value",
753+
"x-nested" : {
754+
"key" : 123
755+
}
756+
}
757+
"""
758+
)
759+
}
760+
761+
func test_encoding_vendorExtensions_decode() throws {
762+
let encodingData =
763+
"""
764+
{
765+
"contentType": "application/json",
766+
"x-custom": "value",
767+
"x-nested": {
768+
"key": 123
769+
}
770+
}
771+
""".data(using: .utf8)!
772+
let encoding = try orderUnstableDecode(OpenAPI.Content.Encoding.self, from: encodingData)
773+
774+
XCTAssertEqual(encoding.contentType, .json)
775+
XCTAssertEqual(encoding.vendorExtensions["x-custom"]?.value as? String, "value")
776+
XCTAssertEqual((encoding.vendorExtensions["x-nested"]?.value as? [String: Int])?["key"], 123)
777+
}
737778
}

0 commit comments

Comments
 (0)