Skip to content

Commit 7453383

Browse files
authored
Merge pull request #86 from mattpolzin/get-all-servers
Get all servers
2 parents 1b12200 + 4b0e073 commit 7453383

File tree

3 files changed

+291
-12
lines changed

3 files changed

+291
-12
lines changed

Sources/OpenAPIKit/Document.swift

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ extension OpenAPI {
3939
/// If the servers property is not provided, or is an
4040
/// empty array, the default value is a Server Object with a url value of
4141
/// "/".
42+
///
43+
/// - Important: If you want to get all servers mentioned anywhere in
44+
/// the whole document (including servers that appear in path items
45+
/// or operations but not at the root document level), use the
46+
/// `allServers` property instead.
4247
public var servers: [Server]
4348

4449
/// All routes supported by this API. This property maps the path of each
@@ -92,18 +97,6 @@ extension OpenAPI {
9297
/// Additional external documentation.
9398
public var externalDocs: ExternalDocumentation?
9499

95-
/// Retrieve an array of all Operation Ids defined by
96-
/// this API. These Ids are guaranteed to be unique by
97-
/// the OpenAPI Specification.
98-
///
99-
/// See [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operation-object) in the specifcation.
100-
///
101-
public var allOperationIds: [String] {
102-
return paths.values
103-
.flatMap { $0.endpoints }
104-
.compactMap { $0.operation.operationId }
105-
}
106-
107100
/// Dictionary of vendor extensions.
108101
///
109102
/// These should be of the form:
@@ -158,6 +151,92 @@ extension OpenAPI.Document {
158151
public var routes: [Route] {
159152
return paths.map { (path, pathItem) in .init(path: path, pathItem: pathItem) }
160153
}
154+
155+
/// Retrieve an array of all Operation Ids defined by
156+
/// this API. These Ids are guaranteed to be unique by
157+
/// the OpenAPI Specification.
158+
///
159+
/// The ordering is not necessarily significant, but it will
160+
/// be the order in which each operation is occurred within
161+
/// each path, traversed in the order the paths appear in
162+
/// the document.
163+
///
164+
/// See [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operation-object) in the specifcation.
165+
///
166+
public var allOperationIds: [String] {
167+
return paths.values
168+
.flatMap { $0.endpoints }
169+
.compactMap { $0.operation.operationId }
170+
}
171+
172+
/// All servers referenced anywhere in the whole document.
173+
///
174+
/// This property contains all servers defined at any level the document
175+
/// and therefore may or may not contain servers not found in the
176+
/// root servers array.
177+
///
178+
/// The `servers` property on `OpenAPI.Document`, by contrast, contains
179+
/// servers that are applicable to all paths and operations that
180+
/// do not define their own `serves` array to override the root array.
181+
///
182+
/// - Important: For the purposes of returning one of each `Server`,
183+
/// two servers are considered identical if they have the same `url`
184+
/// and `variables`. Differing `description` properties for
185+
/// otherwise identical servers are considered to be two ways to
186+
/// describe the same server. `vendorExtensions` are also
187+
/// ignored when determining server uniqueness.
188+
///
189+
/// The first `Server` encountered will be used, so if the only
190+
/// difference between a server at the root document level and
191+
/// one in an `Operation`'s override of the servers array is the
192+
/// description, the description of the `Server` returned by this
193+
/// property will be that of the root document definition.
194+
///
195+
public var allServers: [OpenAPI.Server] {
196+
// We hash `Variable` without its
197+
// `description` or `vendorExtensions`.
198+
func hash(variable: OpenAPI.Server.Variable, into hasher: inout Hasher) {
199+
hasher.combine(variable.enum)
200+
hasher.combine(variable.default)
201+
}
202+
203+
// We hash `Server` without its `description` or
204+
// `vendorExtensions`.
205+
func hash(server: OpenAPI.Server, into hasher: inout Hasher) {
206+
hasher.combine(server.url)
207+
for (key, value) in server.variables {
208+
hasher.combine(key)
209+
hash(variable: value, into: &hasher)
210+
}
211+
}
212+
213+
func hash(for server: OpenAPI.Server) -> Int {
214+
var hasher = Hasher()
215+
hash(server: server, into: &hasher)
216+
return hasher.finalize()
217+
}
218+
219+
var collectedServers = servers
220+
var seenHashes = Set(servers.map(hash(for:)))
221+
222+
func insertUniquely(server: OpenAPI.Server) {
223+
let serverHash = hash(for: server)
224+
if !seenHashes.contains(serverHash) {
225+
seenHashes.insert(serverHash)
226+
collectedServers.append(server)
227+
}
228+
}
229+
230+
for pathItem in paths.values {
231+
let pathItemServers = pathItem.servers ?? []
232+
pathItemServers.forEach(insertUniquely)
233+
234+
let endpointServers = pathItem.endpoints.flatMap { $0.operation.servers ?? [] }
235+
endpointServers.forEach(insertUniquely)
236+
}
237+
238+
return collectedServers
239+
}
161240
}
162241

163242
extension OpenAPI {

Sources/OpenAPIKit/Server.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extension OpenAPI {
1111
/// OpenAPI Spec "Server Object"
1212
///
1313
/// See [OpenAPI Server Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#server-object).
14+
///
1415
public struct Server: Equatable, CodableVendorExtendable {
1516
public let url: URL
1617
public let description: String?
@@ -41,6 +42,7 @@ extension OpenAPI.Server {
4142
/// OpenAPI Spec "Server Variable Object"
4243
///
4344
/// See [OpenAPI Server Variable Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#server-variable-object).
45+
///
4446
public struct Variable: Equatable, CodableVendorExtendable {
4547
public var `enum`: [String]
4648
public var `default`: String

Tests/OpenAPIKitTests/DocumentTests.swift

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,204 @@ final class DocumentTests: XCTestCase {
144144
XCTAssertEqual(t4.allOperationIds, ["two"])
145145
}
146146

147+
func test_allServersEmpty() {
148+
let t = OpenAPI.Document(
149+
info: .init(title: "test", version: "1.0"),
150+
servers: [],
151+
paths: [
152+
"/hello/world": .init(
153+
servers: [],
154+
get: .init(
155+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
156+
servers: []
157+
)
158+
)
159+
],
160+
components: .noComponents
161+
)
162+
163+
XCTAssertEqual(t.allServers, [])
164+
}
165+
166+
func test_allServers_onlyRoot() {
167+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
168+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
169+
170+
let t = OpenAPI.Document(
171+
info: .init(title: "test", version: "1.0"),
172+
servers: [s1, s2],
173+
paths: [
174+
"/hello/world": .init(
175+
servers: [],
176+
get: .init(
177+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
178+
servers: []
179+
)
180+
)
181+
],
182+
components: .noComponents
183+
)
184+
185+
XCTAssertEqual(t.allServers, [s1, s2])
186+
}
187+
188+
func test_allServers_onlyPathItem() {
189+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
190+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
191+
192+
let t = OpenAPI.Document(
193+
info: .init(title: "test", version: "1.0"),
194+
servers: [],
195+
paths: [
196+
"/hello/world": .init(
197+
servers: [s1, s2],
198+
get: .init(
199+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
200+
servers: []
201+
)
202+
)
203+
],
204+
components: .noComponents
205+
)
206+
207+
XCTAssertEqual(t.allServers, [s1, s2])
208+
}
209+
210+
func test_allServers_onlyOperation() {
211+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
212+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
213+
214+
let t = OpenAPI.Document(
215+
info: .init(title: "test", version: "1.0"),
216+
servers: [],
217+
paths: [
218+
"/hello/world": .init(
219+
servers: [],
220+
get: .init(
221+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
222+
servers: [s1, s2]
223+
)
224+
)
225+
],
226+
components: .noComponents
227+
)
228+
229+
XCTAssertEqual(t.allServers, [s1, s2])
230+
}
231+
232+
func test_allServers_allDuplicates() {
233+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
234+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
235+
236+
let t = OpenAPI.Document(
237+
info: .init(title: "test", version: "1.0"),
238+
servers: [s1, s2],
239+
paths: [
240+
"/hello/world": .init(
241+
servers: [s1, s2],
242+
get: .init(
243+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
244+
servers: [s1, s2]
245+
)
246+
)
247+
],
248+
components: .noComponents
249+
)
250+
251+
XCTAssertEqual(t.allServers, [s1, s2])
252+
}
253+
254+
func test_allServers_distributedThroughout() {
255+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
256+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
257+
let s3 = OpenAPI.Server(url: URL(string: "https://website3.com")!)
258+
259+
let t = OpenAPI.Document(
260+
info: .init(title: "test", version: "1.0"),
261+
servers: [s1],
262+
paths: [
263+
"/hello/world": .init(
264+
servers: [s2],
265+
get: .init(
266+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
267+
servers: [s3]
268+
)
269+
)
270+
],
271+
components: .noComponents
272+
)
273+
274+
XCTAssertEqual(t.allServers, [s1, s2, s3])
275+
}
276+
277+
func test_allServers_descriptionDoesNotFactorIn() {
278+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
279+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
280+
let s3 = OpenAPI.Server(url: URL(string: "https://website.com")!, description: "test")
281+
let s4 = OpenAPI.Server(url: URL(string: "https://website2.com")!, description: "test2")
282+
283+
let t = OpenAPI.Document(
284+
info: .init(title: "test", version: "1.0"),
285+
servers: [s1],
286+
paths: [
287+
"/hello/world": .init(
288+
servers: [s2, s4],
289+
get: .init(
290+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
291+
servers: [s3]
292+
)
293+
)
294+
],
295+
components: .noComponents
296+
)
297+
298+
XCTAssertEqual(t.allServers, [s1, s2])
299+
}
300+
301+
func test_allServers_variablesDoFactorIn() {
302+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
303+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
304+
let s3 = OpenAPI.Server(url: URL(string: "https://website.com")!, variables: ["hi": .init(default: "there")])
305+
let s4 = OpenAPI.Server(url: URL(string: "https://website.com")!, variables: ["hi": .init(default: "again")])
306+
307+
let t = OpenAPI.Document(
308+
info: .init(title: "test", version: "1.0"),
309+
servers: [s1],
310+
paths: [
311+
"/hello/world": .init(
312+
servers: [s2, s4],
313+
get: .init(
314+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])],
315+
servers: [s3]
316+
)
317+
)
318+
],
319+
components: .noComponents
320+
)
321+
322+
XCTAssertEqual(t.allServers, [s1, s2, s4, s3])
323+
}
324+
325+
func test_allServers_nilIsEmptyServers() {
326+
let s1 = OpenAPI.Server(url: URL(string: "https://website.com")!)
327+
let s2 = OpenAPI.Server(url: URL(string: "https://website2.com")!)
328+
329+
let t = OpenAPI.Document(
330+
info: .init(title: "test", version: "1.0"),
331+
servers: [s1, s2],
332+
paths: [
333+
"/hello/world": .init(
334+
get: .init(
335+
responses: [.default: .response(description: "test", content: [.json: .init(schema: .string)])]
336+
)
337+
)
338+
],
339+
components: .noComponents
340+
)
341+
342+
XCTAssertEqual(t.allServers, [s1, s2])
343+
}
344+
147345
func test_existingSecuritySchemeSuccess() {
148346
let docData =
149347
"""

0 commit comments

Comments
 (0)