Version 2.0.0 Alpha 1
Pre-releaseVersion 2 of OpenAPIKit adds a few features, fixes some bugs requiring breaking changes, lays groundwork for future work, and renames some types that had started to feel inconsistent with other parts of OpenAPIKit.
As with any alpha release, do not expect the changes in this release to represent a finished stable API. That said, most of the breaking changes intended for this major version are introduced with this first alpha.
Additions
- Most types are now
LocallyDereferenceable
which means they offer adereferenced(in:)
method that takesOpenAPI.Components
as its argument and performs a recursive deference operation onself
-- the same behavior exposed onOpenAPI.Document
via itslocallyDereferenced()
method. - Arrays of schema fragments (
[JSONSchemaFragment]
) like those found under theJSONSchema
all(of:)
case can be resolved into aJSONSchema
representation using the array extensionresolved(against:)
. - A new
URLTemplate
type supports templated URLs like Server Objects use. It will be the basis for future additions like variable replacement and template URL resolution.
Removals
OpenAPI.Components
dereference(_:)
was removed in favor of the subscript with the same signature.- The various
Dereferenced
versions of types lose their public initializer. These types should only be constructed using thedereferenced(in:)
method of the originatingOpenAPI
type. For example, to get aDereferencedPathItem
, you use theOpenAPI.PathItem
dereferenced(in:)
method. DereferencedJSONSchema
no longer has anall(of:)
case. Instead, part of dereferencing a schema is now the process of resolving anall(of:)
schema. More on this below.
Changes
OpenAPI.Components
forceDereference(_:)
was renamed tolookup(_:)
. More on this below.DereferencedJSONSchema
underlyingJSONSchema
property was renamed tojsonSchema
to avoid the misconception that this property stores theJSONSchema
that produced the givenDereferencedJSONSchema
. In reality, this property will build theJSONSchema
representing the givenDereferencedJSONSchema
.JSONSchema.Context
,DereferencedJSONSchema.Context
, andJSONSchemaFragment.GeneralContext
have been renamed toJSONSchema.CoreContext
,DereferencedJSONSchema.CoreContext
, andJSONSchemaFragment.CoreContext
to align and settle on a better name for "the context shared by most cases." With that, theJSONSchema
generalContext
property has been renamed tocoreContext
.- The default optionality of
JSONSchema
values when decoded has been swapped from optional to required. This has no effect on encoding/decoding (perhaps counter-intuitively) because in any JSON Schema structure, the optionality of a property on an object is actually determined by therequired
array on that object and this fact remains true despite the default changing. More on this below. - Instead of allowing
Validation
to use any type as a subject, theValidatable
protocol has been introduced with all types that will work for validation contexts getting conformance. This allows the type checker to steer you away from writing validations that will never be run. NOTE thatURL
is encoded as a string for compatibility with the broadest range of encoders and thereforeURL
is notValidatable
. You can still useString
as a validation subject or write validations in terms of the OpenAPIKit types that containURL
properties. See the full Validation Documentation for more. - The
OpenAPI.Server
type'surl
property was renamedurlTemplate
and its type changed from a FoundationURL
to aURLTemplate
. This change fixes the bug that OpenAPIKit did not used to be able to decode or represent OpenAPI Template URLs (those with variables in them). You can get a FoundationURL
from aURLTemplate
via theurl
property but only urls without any variable placeholders can be turned into FoundationURL
s directly. - The
OpenAPI.Content
type'sschema
property has become optional. Requiring the schema property was not compliant with the OpenAPI specification (see the Media Item Object definition).
Details
Dereferencing
In OpenAPIKit v1, the OpenAPI.Components
type offered the methods dereference(_:)
and forceDereference(_:)
to perform lookup of components by their references. These were overloaded to allow looking up Either
types representing either a reference to a component or the component itself.
In OpenAPIKit v1.4, true dereferencing was introduced. True dereferencing does not just turn a reference into the value it refers to, it removes references recursively for all properties of the given value. That made the use of the word dereference in the OpenAPI.Components
type's methods misleading -- this methods "looked up" values but did not "dereference" them.
OpenAPIKit v2 fixes this confusing naming by supporting component lookup via subscript
(non-throwing) and lookup(_:)
(throwing) methods and not offering any methods that truly dereference types. At the same time, OpenAPIKit v2 adds the dereferenced(in:)
method to most OpenAPI types. This new method takes an OpenAPI.Components
value and returns a fully dereferenced version of self
. The dereferenced(in:)
method offers the same recursive dereferencing behavior exposed by the OpenAPI.Document
locallyDereferenced()
method that was added in OpenAPIKit v1.4.
Resolving all(of:)
schemas
The JSONSchema
all(of:)
case is unique amongst the combination cases because all of the schema fragments under it can effectively be merged into a new schema. This is what "resolving" that schema means in OpenAPIKit. The result of resolving an all(of:)
schema is a DereferencedJSONSchema
.
For the most part, resolution will just happen as part of dereferencing or resolving anything that contains a JSONSchema
that happens to have all(of:)
cases but you do also have the ability to take an array of JSONSchemaFragment
and resolve it directly with the array extension resolved(against:)
which takes an OpenAPI.Components
to resolve the array of schema fragments against.
let schemaData = """
{
"allOf": [
{
"type": "object",
"description": "A person",
"required": [ "name" ],
"properties": {
"name": { "type": "string" }
}
},
{
"type": "object",
"properties": {
"favoriteColor": {
"type": "string",
"enum": [ "red", "green", "blue" ]
}
}
}
]
}
""".data(using: .utf8)!
let personSchema = try JSONDecoder().decode(JSONSchema .self, from: schemaData)
.dereferenced(in: .noComponents)
// results in:
let personSchemaInCode = JSONSchema.object(
description: "A person",
properties: [
"name": .string,
"favoriteColor": try JSONSchema.string(required: false, allowedValues: "red", "green", "blue")
]
)
Default schema optionality
In OpenAPIKit v1, schemas created in-code defaulted to required:true
. While decoding, however, they would default to required:false
and then if a JSON Schema .object
had a required
array containing a certain property, that property's required
boolean
would get flipped to true
. This is somewhat intuitive at face value, but it has the unintuitive side effect of all root schema nodes (i.e. a JSON Schema that does not live within another .object
) will have required: false
because there was no parent required
array to cause OpenAPIKit to flip its required
boolean
.
Again, this has no effect on the accuracy of encoding/decoding because the required
boolean
of a JSONSchema
is only encoded as part of a parent schema's required
array.
OpenAPIKit v2 swaps this decoding default to required: true
and instead flips the boolean
to false
for all properties not found in a parent object's required
array. This approach has the same effect upon encoding/decoding except for root schemas having required: true
which both aligns better with the default in-code required
boolean
and also makes more intuitive sense.
let schemaData = """
{
"type": "object",
"required": [
"test"
],
"properties": {
"test": {
"type": "string"
}
}
}
""".data(using: .utf8)!
//
// OpenAPIKit v1
//
let schema1 = try JSONDecoder().decode(JSONSchema.self, from: schemaData)
// results in:
let schema1InCode = JSONSchema.object(
required: false, // <-- note that this is unintuitive even though it has no effect on the correctness of the schema when encoded.
properties: [
"test": .string(required: true) // <-- note that this `required: true` could be omitted, it is the default for in-code construction.
]
)
//
// OpenAPIKit v2
//
let schema2 = try JSONDecoder().decode(JSONSchema.self, from: schemaData)
// results in:
let schema2InCode = JSONSchema.object(
required: true, // <-- note that this `required: true` could be omitted, it is the default for in-code construction.
properties: [
"test": .string(required: true) // <-- note that this `required: true` could be omitted, it is the default for in-code construction.
]
)