Skip to content

Releases: mattpolzin/OpenAPIKit

Version 2.0.0 Release Candidate 1

22 Sep 03:15
7170489
Compare
Choose a tag to compare
Pre-release

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.

Version 2.0.0 Beta 2

12 Sep 21:53
17521f8
Compare
Choose a tag to compare
Version 2.0.0 Beta 2 Pre-release
Pre-release

Adds a new optional validation: All schemas are at least minimally defined (i.e. none of the JSON Schema definitions are just an empty object).

let document: OpenAPI.Document = ...
let validator = Validator.blank.validating(.schemaComponentsAreDefined)
try document.validate(using: validator)

Version 2.0.0 Beta 1

12 Sep 19:27
Compare
Choose a tag to compare
Version 2.0.0 Beta 1 Pre-release
Pre-release

The first beta signals a relatively stable API as far as major breaking changes go.

A Migration guide has been added and will be filled out over the coming weeks, although it is incomplete for now and developers should continue to use the release notes for the 4 alpha releases to determine what changes need to be made to begin working with OpenAPIKit v2.

The only breaking-ish change between the last Alpha and the first Beta release is the Yams dependency (used only for test targets in OpenAPIKit) was bumped by a major version.

Version 2.0.0 Alpha 4

06 Sep 02:00
d7a08cb
Compare
Choose a tag to compare
Version 2.0.0 Alpha 4 Pre-release
Pre-release

Reintroduced support for all(of:) in DereferencedJSONSchema. Whereas dereferencing a JSONSchema performed a step aimed at simplifying away (and therefore removing) all(of:), alpha 4 moves that process into a separate method that can be called on-demand. A few bugs related to simplification have also been fixed.

⚠️ Breaking Changes ⚠️

  • The behavior previously attached to dereferencing a JSONSchema has been moved to JSONSchema's simplified(given:) and DereferencedJSONSchema's simplified() methods.
  • DereferencedJSONSchema now has an all(of:) case.

Version 2.0.0 Alpha 3

29 Aug 19:03
bbbbe5f
Compare
Choose a tag to compare
Version 2.0.0 Alpha 3 Pre-release
Pre-release

Closes #143.

The all(of:), any(of:), one(of:), and not JSON Schema cases in OpenAPIKit used to only support an optional discriminator as metadata directly on the schema component containing the compound property. Now they all support the full CoreContext (with any format) so that title, description, etc. can be specified on them. This version also closes a gap in representability of required/nullable/optional compound schemas.

This kind of thing was not previously representable in OpenAPIKit:

{
  "title": "a compound thing",
  "oneOf": [
    ...
  ]
}

⚠️ Breaking Changes ⚠️

This is a breaking change for the aforementioned cases on JSONSchema.

If you previously matched on .not(let schema), you now must match on .not(let schema, core: let coreContext).

If you previously matched on .any(of: let schemas, discriminator: let discriminator) (or the similar patterns for all or one), you now must match on .any(of: let schemas, core: let coreContext); you will find the discriminator at coreContext.discriminator.

If you previously created a schema with .any(of: [...], discriminator: nil) (or the similar patterns for all or one), you can now just create the default empty CoreContext: .any(of: [...], core: .init()).

Version 2.0.0 Alpha 2

23 Aug 15:27
6130f42
Compare
Choose a tag to compare
Version 2.0.0 Alpha 2 Pre-release
Pre-release

This release unifies JSONSchema and JSONSchemaFragment. The two types were growing closer and closer together and realistically the differences were weaknesses in both types.

The big difference here is that the JSONSchema undefined case has changed to a fragment case. Whereas undefined schemas could only have descriptions, the fragment case can store all of the various information needed by an unspecialized schema (i.e. one that has no properties that could be used to identify its type).

The JSONSchemaFragment type has been removed entirely.

Version 2.0.0 Alpha 1

16 Aug 01:16
5fdfa37
Compare
Choose a tag to compare
Version 2.0.0 Alpha 1 Pre-release
Pre-release

Version 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 a dereferenced(in:) method that takes OpenAPI.Components as its argument and performs a recursive deference operation on self -- the same behavior exposed on OpenAPI.Document via its locallyDereferenced() method.
  • Arrays of schema fragments ([JSONSchemaFragment]) like those found under the JSONSchema all(of:) case can be resolved into a JSONSchema representation using the array extension resolved(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 the dereferenced(in:) method of the originating OpenAPI type. For example, to get a DereferencedPathItem, you use the OpenAPI.PathItem dereferenced(in:) method.
  • DereferencedJSONSchema no longer has an all(of:) case. Instead, part of dereferencing a schema is now the process of resolving an all(of:) schema. More on this below.

Changes

  • OpenAPI.Components forceDereference(_:) was renamed to lookup(_:). More on this below.
  • DereferencedJSONSchema underlyingJSONSchema property was renamed to jsonSchema to avoid the misconception that this property stores the JSONSchema that produced the given DereferencedJSONSchema. In reality, this property will build the JSONSchema representing the given DereferencedJSONSchema.
  • JSONSchema.Context, DereferencedJSONSchema.Context, and JSONSchemaFragment.GeneralContext have been renamed to JSONSchema.CoreContext, DereferencedJSONSchema.CoreContext, and JSONSchemaFragment.CoreContext to align and settle on a better name for "the context shared by most cases." With that, the JSONSchema generalContext property has been renamed to coreContext.
  • 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 the required 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, the Validatable 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 that URL is encoded as a string for compatibility with the broadest range of encoders and therefore URL is not Validatable. You can still use String as a validation subject or write validations in terms of the OpenAPIKit types that contain URL properties. See the full Validation Documentation for more.
  • The OpenAPI.Server type's url property was renamed urlTemplate and its type changed from a Foundation URL to a URLTemplate. 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 Foundation URL from a URLTemplate via the url property but only urls without any variable placeholders can be turned into Foundation URLs directly.
  • The OpenAPI.Content type's schema 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.
    ]
)

Enum-be-gone

15 Aug 16:38
9c08102
Compare
Choose a tag to compare

The OpenAPI.Server.Variable type incorrectly required that the enum property be defined (on decoding). This has now been fixed.

Statusfied

03 Aug 15:22
4b5f646
Compare
Choose a tag to compare

Adds a subscript overload to OrderedDictionary when the Key is an OpenAPI.Respose.StatusCode so that elements can be accessed using the status keyword.

Because OrderedDictionary is both a dictionary and a collection, it has always offered subscript access via both Key and Index (Int). OpenAPI.Response.StatusCode is ExpressibleByIntegerLiteral so accessing ordered dictionaries of OpenAPI.Response from the OpenAPI.Operation responses property by Int is ambiguous:

operation.responses[200] // <- is this `StatusCode` 200 or the 200th index of the collection?

Now, code accessing the dictionary with a status key can be written:

operation.responses[status: 200]

Of course, you can still use any other method of constructing a status code as well:

operation.responses[.status(code: 200)] // <- equivalent to above
operation.responses[.range(.success)]   // <- means the OpenAPI status code string "2XX"
operation.responses[.default]

Operation: Callback

01 Aug 01:25
e0b5e64
Compare
Choose a tag to compare

Don't fail to decode documents that have callbacks properties in their Operation Objects (although callbacks are not yet supported by OpenAPIKit yet).