Swiftpack.co - Package - mattpolzin/OpenAPIKit

MIT license Swift 5.1 Build Status

OpenAPIKit

A library containing Swift types that encode to- and decode from OpenAPI Documents and their components.

Usage

Decoding OpenAPI Documents

You can decode a JSON OpenAPI document (i.e. using the JSONDecoder from Foundation library) or a YAML OpenAPI document (i.e. using the YAMLDecoder from the Yams library) with the following code:

let decoder = ... // JSONDecoder() or YAMLDecoder()
let openAPIDoc = try decoder.decode(OpenAPI.Document, from: ...)

Encoding OpenAPI Documents

You can encode a JSON OpenAPI document (i.e. using the JSONEncoder from the Foundation library) or a YAML OpenAPI document (i.e. using the YAMLEncoder from the Yams library) with the following code:

let openAPIDoc = ...
let encoder = ... // JSONEncoder() or YAMLEncoder()
let encodedOpenAPIDoc = try encoder.encode(openAPIDoc)

A note on dictionary ordering

The Foundation library's JSONEncoder and JSONDecoder do not make any guarantees about the ordering of keyed containers. This means decoding a JSON OpenAPI Document and then encoding again might result in the document's various hashed structures being in a totally different order.

If retaining order is important for your use-case, I recommend the Yams and FineJSON libraries for YAML and JSON respectively.

Generating OpenAPI Documents

See VaporOpenAPI / VaporOpenAPIExample for an example of generating OpenAPI from a Vapor application's routes.

See JSONAPI+OpenAPI for an example of generating OpenAPI response schemas from JSON:API response documents.

OpenAPI Document structure

The types used by this library largely mirror the object definitions found in the OpenAPI specification version 3.0.2. The Project Status lists each object defined by the spec and the name of the respective type in this library.

Document Root

At the root there is an OpenAPI.Document. In addition to some information that applies to the entire API, the document contains OpenAPI.Components (essentially a dictionary of reusable components that can be referenced with JSONReferences) and an OpenAPI.PathItem.Map (a dictionary of routes your API defines).

Routes

Each route is an entry in the document's OpenAPI.PathItem.Map. The keys of this dictionary are the paths for each route (i.e. /widgets). The values of this dictionary are OpenAPI.PathItems which define any combination of endpoints (i.e. GET, POST, PATCH, etc.) that the given route supports.

Endpoints

Each endpoint on a route is defined by an OpenAPI.PathItem.Operation. Among other things, this operation can specify the parameters (path, query, header, etc.), request body, and response bodies/codes supported by the given endpoint.

Request/Response Bodies

Request and response bodies can be defined in great detail using OpenAPI's derivative of the JSON Schema specification. This library uses the JSONSchema type for such schema definitions.

Schemas

Fundamental types are specified as JSONSchema.integer, JSONSchema.string, JSONSchema.boolean, etc.

Properties are given as arguments to static constructors. By default, types are non-nullable, required, and generic.

A type can be made optional (i.e. it can be omitted) with JSONSchema.integer(required: false) or JSONSchema.integer.optionalSchemaObject(). A type can be made nullable with JSONSchema.number(nullable: true) or JSONSchema.number.nullableSchemaObject().

A type's format can be further specified, for example JSONSchema.number(format: .double) or JSONSchema.string(format: .dateTime).

You can specify a schema's allowed values (e.g. for an enumerated type) with JSONSchema.string(allowedValues: "hello", "world").

Each type has its own additional set of properties that can be specified. For example, integers can have a minimum value: JSONSchema.integer(minimum: (0, exclusive: true)) (where exclusive means the number must be greater than 0, not greater-than-or-equal-to 0).

Compound objects can be built with JSONSchema.array, JSONSchema.object, JSONSchema.all(of:), etc.

For example, perhaps a person is represented by the schema:

JSONSchema.object(
  title: "Person",
  properties: [
    "first_name": .string(minLength: 2),
    "last_name": .string(nullable: true),
    "age": .integer,
    "favorite_color": .string(allowedValues: "red", "green", "blue")
  ]
)
Generating Schemas

Some schemas can be easily generated from Swift types. Many of the fundamental Swift types support schema representations out-of-box.

For example, the following are true

String.openAPINode() == JSONSchema.string

Bool.openAPINode() == JSONSchema.boolean

Double.openAPINode() == JSONSchema.number(format: .double)

Float.openAPINode() == JSONSchema.number(format: .float)
...

Array and Optional are supported out-of-box. For example, the following are true

[String].openAPINode() == .array(items: .string)

[Int].openAPINode() == .array(items: .integer)

Int32?.openAPINode() == .integer(format: .int32, required: false)

[String?].openAPINode() == .array(items: .string(required: false))
...
AnyCodable

A subset of supported Swift types require a JSONEncoder either to make an educated guess at the JSONSchema for the type or in order to turn arbitrary types into AnyCodable for use as schema examples or allowed values.

Swift enums produce schemas with allowed values specified as long as they conform to CaseIterable, Encodable, and AnyJSONCaseIterable (the last of which is free given the former two).

enum CodableEnum: String, CaseIterable, AnyJSONCaseIterable, Codable {
    case one
    case two
}

let schema = CodableEnum.caseIterableOpenAPISchemaGuess(using: JSONEncoder())
// ^ equivalent, although not equatable, to:
let sameSchema = JSONSchema.string(
  allowedValues: "one", "two"
)

Swift structs produce a best-guess schema as long as they conform to Sampleable and Encodable

struct Nested: Encodable, Sampleable {
  let string: String
  let array: [Int]

  // `Sampleable` just enables mirroring, although you could use it to produce
  // OpenAPI examples as well.
  static let sample: Self = .init(
    string: "",
    array: []
  )
}

let schema = Nested.genericOpenAPISchemaGuess(using: JSONEncoder())
// ^ equivalent and indeed equatable to:
let sameSchema = JSONSchema.object(
  properties: [
    "string": .string,
    "array": .array(items: .integer)
  ]
)

Notes

This library does not currently support file reading at all muchless following $refs to other files and loading them in.

This library is opinionated about a few defaults when you use the Swift types, however encoding and decoding stays true to the spec. Some key things to note:

  1. Within schemas, required is specified on the property rather than being specified on the parent object (encoding/decoding still follows the OpenAPI spec).
    • ex JSONSchema.object(properties: [ "val": .string(required: true)]) is an "object" type with a required "string" type property.
  2. Within schemas, required defaults to true on initialization (again, encoding/decoding still follows the OpenAPI spec).
    • ex. JSONSchema.string is a required "string" type.
    • ex. JSONSchema.string(required: false) is an optional "string" type.

See A note on dictionary ordering before deciding on an encoder/decoder to use with this library.

Project Status

OpenAPI Object (OpenAPI.Document)

  • ☑ openapi (openAPIVersion)
  • ☑ info
  • ☑ servers
  • ☑ paths
  • ☑ components
  • ☑ security
  • ☑ tags
  • ☑ externalDocs
  • ☐ specification extensions

Info Object (OpenAPI.Document.Info)

  • ☑ title
  • ☑ description
  • ☑ termsOfService
  • ☑ contact
  • ☑ license
  • ☑ version
  • ☐ specification extensions

Contact Object (OpenAPI.Document.Info.Contact)

  • ☑ name
  • ☑ url
  • ☑ email
  • ☐ specification extensions

License Object (OpenAPI.Document.Info.License)

  • ☑ name
  • ☑ url
  • ☐ specification extensions

Server Object (OpenAPI.Server)

  • ☑ url
  • ☑ description
  • ☑ variables
  • ☐ specification extensions

Server Variable Object (OpenAPI.Server.Variable)

  • ☑ enum
  • ☑ default
  • ☑ description
  • ☐ specification extensions

Components Object (OpenAPI.Components)

  • ☑ schemas
  • ☑ responses
  • ☑ parameters
  • ☑ examples
  • ☑ requestBodies
  • ☑ headers
  • ☑ securitySchemes
  • ☐ links
  • ☐ callbacks
  • ☐ specification extensions

Paths Object (OpenAPI.PathItem.Map)

  • dictionary
  • ~[ ] specification extensions~ (not a planned addition)

Path Item Object (OpenAPI.PathItem)

  • ☑ summary
  • ☑ description
  • ☑ servers
  • ☑ parameters
  • ☑ get
  • ☑ put
  • ☑ post
  • ☑ delete
  • ☑ options
  • ☑ head
  • ☑ patch
  • ☑ trace
  • ☐ specification extensions

Operation Object (OpenAPI.PathItem.Operation)

  • ☑ tags
  • ☑ summary
  • ☑ description
  • ☑ externalDocs
  • ☑ operationId
  • ☑ parameters
  • ☑ requestBody
  • ☑ responses
  • ☐ callbacks
  • ☑ deprecated
  • ☑ security
  • ☑ servers
  • ☐ specification extensions

External Document Object (OpenAPI.ExternalDoc)

  • ☑ description
  • ☑ url
  • ☐ specification extensions

Parameter Object (OpenAPI.PathItem.Parameter)

  • ☑ name
  • ☑ in (parameterLocation)
  • ☑ description
  • ☑ required (part of parameterLocation)
  • ☑ deprecated
  • ☑ allowEmptyValue (part of parameterLocation)
  • ☑ content (schemaOrContent)
  • ☑ schema (schemaOrContent)
    • ☑ style
    • ☑ explode
    • ☑ allowReserved
    • ☑ example
    • ☑ examples
  • ☐ specification extensions

Request Body Object (OpenAPI.Request)

  • ☑ description
  • ☑ content
  • ☑ required
  • ☐ specification extensions

Media Type Object (OpenAPI.Content)

  • ☑ schema
  • ☑ example
  • ☑ examples
  • ☑ encoding
  • ☑ specification extensions (vendorExtensions)

Encoding Object (OpenAPI.Content.Encoding)

  • ☑ contentType
  • ☑ headers
  • ☑ style
  • ☑ explode
  • ☑ allowReserved
  • ☐ specification extensions

Responses Object (OpenAPI.Response.Map)

  • dictionary
  • ~[ ] specification extensions~ (not a planned addition)

Response Object (OpenAPI.Response)

  • ☑ description
  • ☑ headers
  • ☑ content
  • ☐ links
  • ☐ specification extensions

Callback Object

  • {expression}
  • ☐ specification extensions

Example Object (OpenAPI.Example)

  • ☑ summary
  • ☑ description
  • ☑ value
  • ☑ externalValue (part of value)
  • ☑ specification extensions (vendorExtensions)

Link Object

  • ☐ operationRef
  • ☐ operationId
  • ☐ parameters
  • ☐ requestBody
  • ☐ description
  • ☐ server
  • ☐ specification extensions

Header Object (OpenAPI.Header)

  • ☑ description
  • ☑ required
  • ☑ deprecated
  • ☑ content
  • ☑ schema
    • ☑ style
    • ☑ explode
    • ☑ allowReserved
    • ☑ example
    • ☑ examples
  • ☐ specification extensions

Tag Object (OpenAPI.Tag)

  • ☑ name
  • ☑ description
  • ☑ externalDocs
  • ☐ specification extensions

Reference Object (JSONReference)

  • ☑ $ref
    • ☑ local (same file) reference (node case)
      • ☑ encode
      • ☐ decode
      • ☐ dereference
    • ☑ remote (different file) reference (file case)
      • ☑ encode
      • ☑ decode
      • ☐ dereference

Schema Object (JSONSchema)

  • ☑ Mostly complete support for JSON Schema inherited keywords
  • ☑ nullable
  • ☐ discriminator
  • ☑ readOnly (permissions .readOnly case)
  • ☑ writeOnly (permissions .writeOnly case)
  • ☐ xml
  • ☑ externalDocs
  • ☑ example
  • ☑ deprecated
  • ☐ specification extensions

Discriminator Object (OpenAPI.Discriminator)

  • ☑ propertyName
  • ☑ mapping

XML Object (OpenAPI.XML)

  • ☑ name
  • ☑ namespace
  • ☑ prefix
  • ☑ attribute
  • ☑ wrapped
  • ☐ specification extensions

Security Scheme Object (OpenAPI.SecurityScheme)

  • ☑ type
  • ☑ description
  • ☑ name (SecurityType .apiKey case)
  • ☑ in (location in SecurityType .apiKey case)
  • ☑ scheme (SecurityType .http case)
  • ☑ bearerFormat (SecurityType .http case)
  • ☑ flows (SecurityType .oauth2 case)
  • ☑ openIdConnectUrl (SecurityType .openIdConnect case)
  • ☐ specification extensions

OAuth Flows Object (OpenAPI.OauthFlows)

  • ☑ implicit
  • ☑ password
  • ☑ clientCredentials
  • ☑ authorizationCode
  • ☐ specification extensions

OAuth Flow Object (OpenAPI.OauthFlows.*)

  • OpenAPI.OauthFlows.Implicit
  • OpenAPI.OauthFlows.Password
  • OpenAPI.OauthFlows.ClientCredentials
  • OpenAPI.OauthFlows.AuthorizationCode
  • ☑ authorizationUrl
  • ☑ tokenUrl
  • ☑ refreshUrl
  • ☑ scopes
  • ☐ specification extensions

Security Requirement Object (OpenAPI.Document.SecurityRequirement)

  • {name} (using JSONReferences instead of a stringy API)

Github

link
Stars: 26

Used By

Total: 0

Releases

Fix bug with parsing schema examples that are not wrapped in strings. - 2020-02-18 06:46:34

Add compatibility suite and fix a couple bugs - 2020-02-18 04:18:38

  • Add compatibility suite with Google Books API description.
  • Fix Operation.requestBody to support a reference in addition to inline def.
  • Support schemas with no type as a new .undefined case of JSONSchema.
  • Support arbitrary format strings for all JSONSchema types instead of failing for non-standard formats.

Additional Header Schema support, additional JSON Schema support - 2020-02-17 23:47:55

  • Finished adding Header Schema properties.
  • Added readOnly, writeOnly, and deprecated to JSONSchema.
  • Improved test coverage.

Test coverage and a small bug fix - 2020-01-31 03:24:35

While writing tests, discovered a small bug where PathItem.Map was allowed to have references as values even though OpenAPI does not allow this and there is no entry for Path Items in the Components Object anyway.

⚠️ Breaking Changes ⚠️

  • Fixed bug allowing JSON reference for value in PathItem.Map which is a breaking change for declarative initialization or accessing those values from code.

Better support for transformation use-cases. - 2020-01-27 07:14:10

Move to vars in a number of places and add some convenience mutating methods to PathItem.

Add public vendor extendable existentail support. - 2020-01-27 04:31:12

Add OAuthFlows Security Scheme support - 2020-01-26 02:34:47

Add `OrderedDictionary` - 2020-01-23 06:11:36

⚠️ Breaking Changes ⚠️ Many of the Dictionary types have been replaced by a new OrderedDictionary. This means that encoding and decoding can retain the ordering of JSON/YAML objects. This ability is still contingent upon the specific encoder or decoder being used retaining that information in the course of its duties.

The Foundation library JSONEncoder retains ordering on OS X, but does not on Linux. The Foundation JSONDecoder does not retain ordering on any operating system. There is, however, currently an alternative that does retain ordering on all operating systems and seems worth trying: FineJSON.

The most popular YAML decoder/encoder, Yams, does retain ordering.

Additions and consistent `Either` specialization ordering - 2020-01-16 06:37:48

  • Added XML Object
  • Added security to Operations Object

⚠️ Breaking Changes ⚠️

  • Swapped order of generic specializations on Either types in a few places so that JSONReference is always the first specialization parameter when present.

Big schema generation changes - 2020-01-12 20:59:45

⚠️ Breaking Changes ⚠️ Big changes to the protocols and functions responsible for generating OpenAPI schemas from Swift types. NOTE this specifically refers to generating JSONSchemas.

Protocols were renamed or removed, functions were renamed, removed, and added. Bugs were fixed. It all functions in roughly the same way but things are much less thrown together now that I've had time to revisit the code.

Refinements - 2019-12-30 06:30:11

  • Omit some things that are not required when they have their default values.
  • Add some Content Types.
  • Some file restructuring and test coverage.

Better OpenAPI node generation for arbitrary Sampleable types - 2019-12-16 00:17:28

Brought a generation function over from JSONAPI+OpenAPI that really belongs here; that function generates OpenAPI nodes from any type that conforms to Sampleable. This means many types of JSON data can get out of box documentation.

⚠️ Breaking Changes ⚠️ Renamed a few functions and similar small changes, did a lot of work on node generation that could break downstream stuff in JSONAPI+OpenAPI dependents.

Additions, Tests, Fixes - 2019-11-04 01:45:38

  • Add style and explode to OpenAPI.Content.Encoding
  • Move vendor extensions encoding into shared protocol extension.
  • Add a bunch of custom encoding implementations to fix a bug where null was being written (from a small subset of types) when a property should simply be omitted from the encoded value.
  • Add some tests.
  • Better error checking around vendor extension decoding.

⚠️ Breaking Changes ⚠️ The change to omit certain values instead of encoding null is both a fix and a breaking change.

Add remaining `Parameter` properties - 2019-10-29 15:56:04

Added remaining Parameter properties. These were added under a new Parameter.Schema type because they are not applicable if a Content.Map is used instead of a JSONSchema.

⚠️ Breaking Changes ⚠️ Some restructuring was done when nesting Parameter's JSONSchema under a new Parameter.Schema type.

Ease-of-use improvements - 2019-10-29 01:34:39

Focused on making declarative OpenAPI documents easier to write in Swift.

⚠️ Breaking Changes ⚠️ A number of changes to function/initializer arguments or argument names were made. Some of these are breaking and others are not but all of them make declarative document definitions cleaner.

Add convenience constructors for `Parameter.schemaOrContent` - 2019-10-21 00:04:35

Add `trace` HttpVerb case - 2019-10-20 03:19:33

Add missing HttpVerb.trace case.

Add checks for which case a `Parameter.Location` is in - 2019-10-09 04:46:07

Add `required` to `Parameter` - 2019-10-09 04:08:10

Numerous Additions - 2019-10-06 22:25:46

Added support for Example Object and with it examples on Components Object and Media Type Object.

Rounded off test coverage of Media Type Object.

Added a number of other small things.

⚠️ Breaking Changes ⚠️ Requires Swift 5.1

Add vendor extensions to Content - 2019-09-19 07:14:04

Content gets vendor extensions (specification extensions) via a dict that can hold arbitrary additional properties.

Tons of additions, improvements, test coverage, and breaking changes. - 2019-09-07 20:29:45

⚠️ Breaking Changes ⚠️

  • PathItem Parameter
  • JSONReference
  • JSONSchema

Fix annoying warnings and add integer expressibility to status codes. - 2019-08-25 02:10:54

  • Fix annoying warnings from passing nil to string interpolation.
  • Add integer expressibility for response status codes and add unit tests for Response file.

Make package available without OS X 10.13 - 2019-07-26 13:56:39

OS X 10.13 is required for some conveniences I used for testing but the package works fine without it.

- 2019-07-26 13:38:45