Skip to content

Commit f87213f

Browse files
authored
Merge pull request #473 from mattpolzin/feature/448/server-object-name
OAS 3.2.0 Server Object changes
2 parents 528e0dc + 0f2500f commit f87213f

2 files changed

Lines changed: 167 additions & 14 deletions

File tree

Sources/OpenAPIKit/Server.swift

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ extension OpenAPI {
1313
///
1414
/// See [OpenAPI Server Object](https://spec.openapis.org/oas/v3.1.1.html#server-object).
1515
///
16-
public struct Server: Equatable, CodableVendorExtendable, Sendable {
16+
public struct Server: HasConditionalWarnings, CodableVendorExtendable, Sendable {
1717
/// OpenAPI Server URLs can have variable placeholders in them.
1818
/// The `urlTemplate` can be asked for a well-formed Foundation
1919
/// `URL` if all variables in it have been replaced by constant values.
2020
public let urlTemplate: URLTemplate
21+
/// An optional unique string to refer to the host designated by the URL.
22+
public let name: String?
2123
public let description: String?
2224
/// A map from the names of variables found in the `urlTemplate` to
2325
/// descriptions, allowed values, and defaults.
@@ -30,36 +32,68 @@ extension OpenAPI {
3032
/// where the values are anything codable.
3133
public var vendorExtensions: [String: AnyCodable]
3234

35+
public let conditionalWarnings: [(any Condition, OpenAPI.Warning)]
36+
3337
/// Create an OpenAPI Server Object.
3438
public init(
3539
url: URL,
40+
name: String? = nil,
3641
description: String? = nil,
3742
variables: OrderedDictionary<String, Variable> = [:],
3843
vendorExtensions: [String: AnyCodable] = [:]
3944
) {
4045
self.urlTemplate = URLTemplate(url: url)
46+
self.name = name
4147
self.description = description
4248
self.variables = variables
4349
self.vendorExtensions = vendorExtensions
50+
51+
self.conditionalWarnings = [
52+
nonNilVersionWarning(fieldName: "name", value: name, minimumVersion: .v3_2_0)
53+
].compactMap { $0 }
4454
}
4555

4656
/// Create an OpenAPI Server Object with a URL containing
4757
/// variables that can change depending on the context in
4858
/// which the API is invoked,
4959
public init(
5060
urlTemplate: URLTemplate,
61+
name: String? = nil,
5162
description: String? = nil,
5263
variables: OrderedDictionary<String, Variable> = [:],
5364
vendorExtensions: [String: AnyCodable] = [:]
5465
) {
5566
self.urlTemplate = urlTemplate
67+
self.name = name
5668
self.description = description
5769
self.variables = variables
5870
self.vendorExtensions = vendorExtensions
71+
72+
self.conditionalWarnings = [
73+
nonNilVersionWarning(fieldName: "name", value: name, minimumVersion: .v3_2_0)
74+
].compactMap { $0 }
5975
}
6076
}
6177
}
6278

79+
extension OpenAPI.Server: Equatable {
80+
public static func == (lhs: Self, rhs: Self) -> Bool {
81+
lhs.urlTemplate == rhs.urlTemplate
82+
&& lhs.description == rhs.description
83+
&& lhs.variables == rhs.variables
84+
&& lhs.vendorExtensions == rhs.vendorExtensions
85+
}
86+
}
87+
88+
fileprivate func nonNilVersionWarning<Subject>(fieldName: String, value: Subject?, minimumVersion: OpenAPI.Document.Version) -> (any Condition, OpenAPI.Warning)? {
89+
value.map { _ in
90+
OpenAPI.Document.ConditionalWarnings.version(
91+
lessThan: minimumVersion,
92+
doesNotSupport: "The Server \(fieldName) field"
93+
)
94+
}
95+
}
96+
6397
extension OpenAPI.Server {
6498
/// OpenAPI Spec "Server Variable Object"
6599
///
@@ -112,6 +146,7 @@ extension OpenAPI.Server: Encodable {
112146

113147
try container.encode(urlTemplate, forKey: .url)
114148

149+
try container.encodeIfPresent(name, forKey: .name)
115150
try container.encodeIfPresent(description, forKey: .description)
116151

117152
if variables.count > 0 {
@@ -129,16 +164,22 @@ extension OpenAPI.Server: Decodable {
129164
let container = try decoder.container(keyedBy: CodingKeys.self)
130165

131166
urlTemplate = try container.decode(URLTemplate.self, forKey: .url)
167+
name = try container.decodeIfPresent(String.self, forKey: .name)
132168
description = try container.decodeIfPresent(String.self, forKey: .description)
133169
variables = try container.decodeIfPresent(OrderedDictionary<String, Variable>.self, forKey: .variables) ?? [:]
134170

135171
vendorExtensions = try Self.extensions(from: decoder)
172+
173+
conditionalWarnings = [
174+
nonNilVersionWarning(fieldName: "name", value: name, minimumVersion: .v3_2_0)
175+
].compactMap { $0 }
136176
}
137177
}
138178

139179
extension OpenAPI.Server {
140180
internal enum CodingKeys: ExtendableCodingKey {
141181
case url
182+
case name
142183
case description
143184
case variables
144185

@@ -147,6 +188,7 @@ extension OpenAPI.Server {
147188
static var allBuiltinKeys: [CodingKeys] {
148189
return [
149190
.url,
191+
.name,
150192
.description,
151193
.variables
152194
]
@@ -160,6 +202,8 @@ extension OpenAPI.Server {
160202
switch stringValue {
161203
case "url":
162204
self = .url
205+
case "name":
206+
self = .name
163207
case "description":
164208
self = .description
165209
case "variables":
@@ -173,6 +217,8 @@ extension OpenAPI.Server {
173217
switch self {
174218
case .url:
175219
return "url"
220+
case .name:
221+
return "name"
176222
case .description:
177223
return "description"
178224
case .variables:

Tests/OpenAPIKitTests/ServerTests.swift

Lines changed: 120 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ class ServerTests: XCTestCase {
4848
XCTAssertEqual(s2.urlTemplate, URLTemplate(rawValue: "https://hello.com")!)
4949
XCTAssertEqual(s2.description, "hello world")
5050
XCTAssertEqual(s2.variables, ["hello": variable])
51+
XCTAssertEqual(s2.conditionalWarnings.count, 0)
52+
53+
let s3 = Server(
54+
url: URL(string: "https://hello.com")!,
55+
name: "name",
56+
description: "hello world",
57+
variables: [
58+
"hello": variable
59+
]
60+
)
61+
62+
XCTAssertEqual(s3.urlTemplate, URLTemplate(rawValue: "https://hello.com")!)
63+
XCTAssertEqual(s3.description, "hello world")
64+
XCTAssertEqual(s3.variables, ["hello": variable])
65+
XCTAssertEqual(s3.conditionalWarnings.count, 1)
5166
}
5267
}
5368

@@ -157,22 +172,25 @@ extension ServerTests {
157172

158173
let serverDecoded = try orderUnstableDecode(Server.self, from: serverData)
159174

175+
let expectedServer = Server(
176+
url: URL(string: "https://hello.com")!,
177+
description: "hello world",
178+
variables: [
179+
"hello": .init(
180+
enum: ["hello"],
181+
default: "hello",
182+
description: "hello again",
183+
vendorExtensions: [ "x-otherThing": 1234 ]
184+
)
185+
],
186+
vendorExtensions: ["x-specialFeature": .init(["hello", "world"])]
187+
)
188+
160189
XCTAssertEqual(
161190
serverDecoded,
162-
Server(
163-
url: URL(string: "https://hello.com")!,
164-
description: "hello world",
165-
variables: [
166-
"hello": .init(
167-
enum: ["hello"],
168-
default: "hello",
169-
description: "hello again",
170-
vendorExtensions: [ "x-otherThing": 1234 ]
171-
)
172-
],
173-
vendorExtensions: ["x-specialFeature": .init(["hello", "world"])]
174-
)
191+
expectedServer
175192
)
193+
XCTAssertEqual(expectedServer.conditionalWarnings.count, 0)
176194
}
177195

178196
func test_maximalServer_encode() throws {
@@ -215,4 +233,93 @@ extension ServerTests {
215233
"""
216234
)
217235
}
236+
237+
func test_maximalServerPlusName_decode() throws {
238+
let serverData =
239+
"""
240+
{
241+
"url": "https://hello.com",
242+
"name": "name",
243+
"description": "hello world",
244+
"variables": {
245+
"hello": {
246+
"enum": ["hello"],
247+
"default": "hello",
248+
"description": "hello again",
249+
"x-otherThing": 1234
250+
}
251+
},
252+
"x-specialFeature": [
253+
"hello",
254+
"world"
255+
]
256+
}
257+
""".data(using: .utf8)!
258+
259+
let serverDecoded = try orderUnstableDecode(Server.self, from: serverData)
260+
261+
let expectedServer = Server(
262+
url: URL(string: "https://hello.com")!,
263+
name: "name",
264+
description: "hello world",
265+
variables: [
266+
"hello": .init(
267+
enum: ["hello"],
268+
default: "hello",
269+
description: "hello again",
270+
vendorExtensions: [ "x-otherThing": 1234 ]
271+
)
272+
],
273+
vendorExtensions: ["x-specialFeature": .init(["hello", "world"])]
274+
)
275+
276+
XCTAssertEqual(
277+
serverDecoded,
278+
expectedServer
279+
)
280+
XCTAssertEqual(expectedServer.conditionalWarnings.count, 1)
281+
}
282+
283+
func test_maximalServerPlusName_encode() throws {
284+
let server = Server(
285+
url: URL(string: "https://hello.com")!,
286+
name: "name",
287+
description: "hello world",
288+
variables: [
289+
"hello": .init(
290+
enum: ["hello"],
291+
default: "hello",
292+
description: "hello again",
293+
vendorExtensions: [ "x-otherThing": 1234 ]
294+
)
295+
],
296+
vendorExtensions: ["x-specialFeature": .init(["hello", "world"])]
297+
)
298+
let encodedServer = try orderUnstableTestStringFromEncoding(of: server)
299+
300+
assertJSONEquivalent(
301+
encodedServer,
302+
"""
303+
{
304+
"description" : "hello world",
305+
"name" : "name",
306+
"url" : "https:\\/\\/hello.com",
307+
"variables" : {
308+
"hello" : {
309+
"default" : "hello",
310+
"description" : "hello again",
311+
"enum" : [
312+
"hello"
313+
],
314+
"x-otherThing" : 1234
315+
}
316+
},
317+
"x-specialFeature" : [
318+
"hello",
319+
"world"
320+
]
321+
}
322+
"""
323+
)
324+
}
218325
}

0 commit comments

Comments
 (0)