Releases: mattpolzin/OpenAPIKit
We're seeing double now
NOTE: The following will refer to both OpenAPIKit v3.0.0 (this library's upcoming version) and also OpenAPI v3.0/v3.1 (two versions of the specification this library implements). Notice the Kit suffix is the easiest way to tell if I am talking about this library. It can be confusing to keep these straight, because this library follows semantic versioning (wherein v3.0.0 is a major version bump associated with breaking changes) and the specification itself does not follow semantic versioning (the move from v3.0 to v3.1 of the specification is associated with breaking changes) -- and, what's worse, it is merely coincidental that the specification is on v3.1 and the library is on v3.0.
Here's a table that might help:
OpenAPIKit | OpenAPI v3.0 | OpenAPI v3.1 |
---|---|---|
v1.x | ✅ | ❌ |
v2.x | ✅ | ❌ |
v3.x | ✅ | ✅ |
Release Notes
Version 3.0.0 of OpenAPIKit primarily focuses on supporting the new v3.1 of the OpenAPI specification. It will, however, also make some small breaking changes to support for the OpenAPI v3.0 specification where those changes fix bugs, drastically clean things up, or support really beneficial new features.
The release notes for this first alpha of OpenAPIKit 3.0.0 will not go into details on the differences between OpenAPIKit's implementation of v3.0 and v3.1 of the OpenAPI specification. It will, however, describe the biggest difference going into this major version of the OpenAPIKit library:
Two Modules. The two public modules are OpenAPIKit30
(support for v3.0 of the specification) and OpenAPIKit
(support for v3.1 of the specification).
v3.0.0 of OpenAPIKit splits support for OpenAPI v3.0 and OpenAPI v3.1 into separate modules because there are incompatibilities between the two versions that are most easily handled by either picking which version of the specification to use or explicitly referring to the version being used if both are needed.
Migrating from OpenAPIKit v2.x
V3.0 of the specification
To use the OpenAPI v3.0 specification much like it has been supported in past versions of this library, change your imports
as follows:
// before:
import OpenAPKit
// after:
import OpenAPIKit30
If you are using Swift Package Manager, also change your dependency from the string "OpenAPIKit"
or the product .product(name: "OpenAPIKit", package: "OpenAPIKit")
to .product(name: "OpenAPIKit30", package: "OpenAPIKit")
.
Beyond that, the only change you currently need to look out for is that JSONReferences
inside JSONSchema
has gained a context so that it can support optionality. This means switches that match may need to change as follows:
// before:
case .reference(let ref) ...
// after:
case .reference(let ref, let context) ...
The new context contains the required
boolean. This boolean defaults to true
, but can be made false
if a reference should be optional (which means it will be left out of its parent's required
array on serialization to JSON/YAML).
You can also access this context with the new referenceContext
accessor on JSONSchema
and you can now reliably test whether a reference schema is required with the JSONSchema
required
accessor (boolean for whether or not any particular schema is required).
v3.1 of the specification
As noted above, the release notes for this alpha won't go into details on the differences between OpenAPIKit's support for v3.0 and v3.1 of the OpenAPI specification. Future alpha & beta releases will detail some of these differences; for now, this release serves as a way to test some of those differences for practicality.
To use the new version, you use the OpenAPIKit
module (as opposed to the OpenAPIKit30
module that supports v3.0 of the specification).
Thanks
Thanks goes to @siemensikkema for implementing support of required/optional reference schemas and @mihaelamj for implementing numerous changes required by the OpenAPI v3.1 specification.
A Path To Validation
- Add
lookup()
andunwrapAndLookup()
validation helpers to make it easier to work with JSON references during validation (see Validation Documentation). - Add
filteringPaths()
method toOpenAPI.PathItem.Map
andOpenAPI.Document
to assist in quickly filtering the paths in the documentation down to a subset (producing a new path item map or a new document).
Reference cycles are reference cycles are reference...
JSON Schema (OpenAPI Schema Object) reference cycles were not previously handled by the OpenAPIKit dereferencing logic; this bug fix handles reference cycles by producing an error message instead of crashing after running out of stack space.
Serve Up Some Variables
The URLTemplate
type used for the OpenAPI.Server
urlTemplate
property now exposes information on the variables contained within it in two ways and will fail if the URL used to create it has malformed variables.
You can access an array of variable names within the template via the variables
property or an array of components (variables and constants) via the components
property.
You can now use the optional serverVariablesAreDefined
validation to assert that if a variable exists in the template for a Server Object's URL, then that same Server Object gives the variable a definition (See the OpenAPI Server Variable and the variables
property on the OpenAPIKit Server
type).
You can replace URL template variables with values to turn a template into a valid fully formed URL. The URLTemplate
type exposes a replacing(_:)
method for just this purpose. Once all variables have been replaced by constant values, you can use the url
property of URLTemplate
to attempt to retrieve a valid Foundation URL
if possible.
Other Additions
ValidationErrorCollection
now uses a newline separated list of its errors as its description (you can useString(describing: errors)
to get this description).- The
JSONSchema
type has a series of properties to check if a given schema is of a particular type.isBoolean
,isNumber
,isInteger
, etc. - A new
all(_:)
has been added to the validation helpers. This function accepts any number ofValidations
and produces a function of the type expected by acheck
clause of anotherValidation
. See the Validation Documentation for more.
Everyone gets a default
Schema Objects were missing the JSON Schema default property that allows you to define a default for values. This release adds defaultValue
to the CoreContext
and the JSONSchema
type.
The defaultValue
property is an AnyCodable
so that you are not tied to one Swift way of representing each JSON type when defining default values.
Call Me Back
Adds support for OpenAPI Callback Object.
The Callback Object can be used in Operation Objects and defined inline or in the Components Object.
Adds (optional) pathParametersAreDefined
validation.
Use this new validation by creating a validator and tacking the new validation onto it. See the Validation Documentation for more information on OpenAPIKit's validation system.
let document: OpenAPI.Document = ...
// this will check all the default validations _and_ the one being added on with the following line.
let validator = Validator()
.validating(.pathParametersAreDefined)
try document.validate(using: validator)
Errors, Not Exceptions
Fix a crash that occurred when a Response Object failed to decode from within the Components Object. There was an assumption in the code that Response Objects lived within Operation Objects. That assumption has been removed and now OpenAPIKit successfully produces errors for Response Object decoding failures across the board.
At the same time, a couple of error messages have been made a bit more specific and therefore easier to understand.
Tell Me More
A minor patch to the message output when validation of .schemaComponentsAreDefined
fails.
2.0.0 - Much Love for Schemas
Features & Additions
New schema validation
New optional schema validation (not included in validation by default): .schemaComponentsAreDefined
. Validates that all schemas are at least minimally defined (i.e. none of the JSON Schema definitions are just an empty object).
LocallyDereferenceable
Most OpenAPI
types are LocallyDereferenceable
now, which means they offer a dereferenced(in:)
method that takes an OpenAPI.Components
and results in a new type that is guaranteed to not contain any JSONReference
s. This is the same behavior exposed on OpenAPI.Document
via its locallyDereferenced()
method.
Schema simplification
DereferencedJSONSchema
has gained the simplified()
method which will attempt to simplify compound schemas. The same is exposed via JSONSchema
's simplified(given:)
method which takes OpenAPI.Components
.
Simplification is in its early stages right now and does not support all possible schemas but it works well on all(of:)
schemas already.
Bug Fixes
OpenAPI.Server
'surl
used to not be able to handle server URLs with variables in them. This has been fixed in the newurlTemplate
property (see breaking changes and the migration guide for more information).JSONSchema
can express a number of valid schemas it previously could not -- all of its cases support the core set of properties (see breaking changes and the migratio guide for more information).OpenAPI.Content
'sschema
property has become optional, as indicated by the OpenAPI Specification (under Media Type Object).
Breaking Changes
The following list enumerates the breaking changes but the migration guide serves as a better resource for identifying the changes needed in your codebase (if any).
OpenAPI.Server
loses itsurl
property in favor of a more correcturlTemplate
property that can in turn be asked for a FoundationURL
(but it will only produce one if the URL in question has no variables in it).OpenAPI.Content
'sschema
property has become optional to support valid OpenAPI documentation where the Media Type Object has no schema.JSONSchemaFragment
has been removed -- you can now useJSONSchema
to represent all of the same schema fragments. Relatedly,JSONSchema
'sundefined
case has been renamedfragment
and it has gained a fullCoreContext
.DereferencedJSONSchema
'sunderlyingJSONSchema
property has been renamed tojsonSchema
.- The
JSONSchema
generalContext
property andContext
type have been renamed tocoreContext
andCoreContext
.DereferencedJSONSchema
has also renamed itsContext
toCoreContext
. JSONSchema
'sall(of:)
,any(of:)
, andone(of:)
cases had theirdiscriminator
associated value replaced with a fullCoreContext
(which in turn has adiscriminator
).JSONSchema
'snot
case gained aCoreContext
.- Some additional properties of the
JSONSchema.CoreContext
(and its other contexts) have become optional to support a broader range of valid schemas. In practice, this is mostly non-breaking because the accessors remain non-optional even where the underlying type has changed but the initializers now take optional input to support the new expressivity. JSONSchema
'sdereferencedSchemaObject()
anddereferencedSchemaObject(resolvingIn:)
renamed todereferenced()
anddereferenced(in:)
.OpenAPI.Components
dereferencing and lookup methods have been renamed to better express the differences between "looking a resource up" and "dereferencing a resource." See the migration guide for more information.
Improvements in-depth
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 iteratively for all properties of the given value. That made the use of the word "dereference" in the OpenAPI.Components
type's methods misleading -- these 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 iterative dereferencing behavior exposed by the OpenAPI.Document
locallyDereferenced()
method that was added in OpenAPIKit v1.4.
Simplifying all(of:) schemas
The JSONSchema
all(of:)
case is unique amongst the combination cases because all of the schema fragments under it can often effectively be merged into a new schema. This is what "simplifying" that schema means in OpenAPIKit. The result of simplifying an all(of:)
schema is a DereferencedJSONSchema
.
Simplification is performed by JSONSchema
's simplified(given:)
method (which takes an OpenAPI.Components
value) or the DereferencedJSONSchema
simplified()
method.
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)
.simplified(given: .noComponents)
// results in simplified schema equivalent to:
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 components (i.e. a JSON Schema that does not live within another .object
) having required: false
because there is 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
value 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.
]
)
Version 2.0.0 Release Candidate 1
There are no substantial changes since the last beta and this release candidate. Test coverage has improved, a few gaps in the API have been closed, and the migration guide has been completed.
The v2 release notes (on the upcoming release) will summarize changes and the migration guide will be a resource to help quickly identify what breaking changes require attention.