True Forms
This is a big release that closes two longstanding gaps: An inability to dereference an OpenAPI Document and the lack of any canonical representation of the components of the API described by an OpenAPI Document.
Dereferencing
The OpenAPI specification supports the use of JSON References in a number of places throughout the Document. These references allow authors to reuse one component in multiple places or even just make the route definition section of the document more concise by storing some components in the Components Object.
When traversing an OpenAPI document, these references are often more of an annoyance than anything else; Every time you reach a part of the document where a reference is allowed, you must check whether you are working with a reference or not or maybe even reach out to the Components Object and look a reference up.
This release introduces the OpenAPI.Document
locallyDereferenced()
method. This method traverses the whole document resolving all references to components found in the Components Object. The result is a document that has replaced many types with dereferenced variants -- anywhere you would have seen Either<JSONReference<Thing>, Thing>
you will now just see Thing
(or a DereferencedThing
, anyway). The dereferenced variants will always expose the properties of the original OpenAPI
type.
Before:
let document: OpenAPI.Document = ...
let anOperation: OpenAPI.Operation = document
.paths["/hello/world"]!
.get!
let parametersOrReferences = anOperation.parameters
// print the name of all parameters that happen to be inlined:
for parameter in parametersOrReferences.compactMap({ $0.parameterValue }) {
print(parameter.name)
}
// resolve parameters in order to print the name of them all
for parameter in parametersOrReferences.compactMap(document.components.dereference) {
print(parameter.name)
}
Now:
let document: OpenAPI.Document = ...
let anOperation = try document
.locallyDereferenced() // new
.paths["/hello/world"]!
.get!
let parameters = anOperation.parameters
// loop over all parameters and print their names
for parameter in parameters {
print(parameter.name)
}
Resolving (to canonical representations)
The OpenAPI Specification leaves numerous opportunities (including JSON References) for authors to write the same documentation in different ways. Sometimes when analyzing an OpenAPI Document or using it to produce something new (a la code generation) you really just need to know what the API looks like, not how the author of the documentation chose to structure the OpenAPI document.
This release introduces the resolved()
method on the DereferencedDocument
(the result of the locallyDereferenced()
method on an OpenAPI.Document
). A ResolvedDocument
collects information from all over the OpenAPI.Document
to form canonical definitions of the routes and endpoints found within. In a resolved document, you work with ResolvedRoute
(the canonical counterpart to the OpenAPI.PathItem
) and ResolvedEndpoint
(the canonical counterpart to the OpenAPI.Operation
).
To show the power of a resolved type, let's look at the hypothetical need to look at all parameters for a particular endpoint. To achieve this without resolved types, we need to collect parameters on the Path Item Object and combine them with those on the Operation Object. Even worse, to really get this right we would need to let the parameters of the Operation override those of the Path Item if the parameter names & locations were the same.
Before:
let document: OpenAPI.Document = ...
let aPathItem = try document
.locallyDereferenced()
.paths["/hello/world"]!
let anOperation = aPathItem.get!
// going to oversimplify here and not worry
// about collisions between the Path Item and
// Operation parameters.
let parameters = aPathItem.parameters + anOperation.parameters
After:
let document: OpenAPI.Document = ...
let anEndpoint = try document
.locallyDereferenced()
.resolved() // new
.routesByPath["/hello/world"]! // new
.get!
// no oversimplification here, this is going to get
// us the totally correct comprehensive list of
// parameters for the endpoint.
let parameters = anEndpoint.parameters