Skip to content

Commit

Permalink
Merge pull request #80 from mattpolzin/experiment/validator
Browse files Browse the repository at this point in the history
Add extensible specification validation
  • Loading branch information
mattpolzin authored Jun 14, 2020
2 parents c43f197 + 2a2fc8b commit b31e2bc
Show file tree
Hide file tree
Showing 15 changed files with 4,258 additions and 9 deletions.
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
[![sswg:sandbox|94x20](https://img.shields.io/badge/sswg-sandbox-lightgrey.svg)](https://github.com/swift-server/sswg/blob/master/process/incubation.md#sandbox-level) [![Swift 5.1+](http://img.shields.io/badge/Swift-5.1-blue.svg)](https://swift.org) [![Build Status](https://app.bitrise.io/app/2f7379e33723d853/status.svg?token=Jx4X3su3oE59z_rJBRC_og&branch=master)](https://app.bitrise.io/app/2f7379e33723d853)
[![sswg:sandbox|94x20](https://img.shields.io/badge/sswg-sandbox-lightgrey.svg)](https://github.com/swift-server/sswg/blob/master/process/incubation.md#sandbox-level) [![Swift 5.1+](http://img.shields.io/badge/Swift-5.1/5.2-blue.svg)](https://swift.org) [![Build Status](https://app.bitrise.io/app/2f7379e33723d853/status.svg?token=Jx4X3su3oE59z_rJBRC_og&branch=master)](https://app.bitrise.io/app/2f7379e33723d853)

[![MIT license](http://img.shields.io/badge/license-MIT-lightgrey.svg)](http://opensource.org/licenses/MIT)

# OpenAPIKit
# OpenAPIKit <!-- omit in toc -->

A library containing Swift types that encode to- and decode from [OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md) Documents and their components.
A library containing Swift types that encode to- and decode from [OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md) Documents and their components.

- [Usage](#usage)
- [Decoding OpenAPI Documents](#decoding-openapi-documents)
- [Decoding Errors](#decoding-errors)
- [Encoding OpenAPI Documents](#encoding-openapi-documents)
- [Validating OpenAPI Documents](#validating-openapi-documents)
- [A note on dictionary ordering](#a-note-on-dictionary-ordering)
- [Generating OpenAPI Documents](#generating-openapi-documents)
- [OpenAPI Document structure](#openapi-document-structure)
- [Document Root](#document-root)
- [Routes](#routes)
Expand All @@ -22,6 +22,8 @@ A library containing Swift types that encode to- and decode from [OpenAPI](https
- [JSON References](#json-references)
- [Specification Extensions](#specification-extensions)
- [Curated Integrations](#curated-integrations)
- [Generating OpenAPI Documents](#generating-openapi-documents)
- [Semantic Diffing of OpenAPI Documents](#semantic-diffing-of-openapi-documents)
- [Notes](#notes)
- [Project Status](#project-status)
- [OpenAPI Object (`OpenAPI.Document`)](#openapi-object-openapidocument)
Expand All @@ -33,9 +35,9 @@ A library containing Swift types that encode to- and decode from [OpenAPI](https
- [Components Object (`OpenAPI.Components`)](#components-object-openapicomponents)
- [Paths Object (`OpenAPI.PathItem.Map`)](#paths-object-openapipathitemmap)
- [Path Item Object (`OpenAPI.PathItem`)](#path-item-object-openapipathitem)
- [Operation Object (`OpenAPI.Operation`)](#operation-object-openapipathitemoperation)
- [External Document Object (`OpenAPI.ExternalDocumentation`)](#external-document-object-openapiexternaldoc)
- [Parameter Object (`OpenAPI.Parameter`)](#parameter-object-openapipathitemparameter)
- [Operation Object (`OpenAPI.Operation`)](#operation-object-openapioperation)
- [External Document Object (`OpenAPI.ExternalDocumentation`)](#external-document-object-openapiexternaldocumentation)
- [Parameter Object (`OpenAPI.Parameter`)](#parameter-object-openapiparameter)
- [Request Body Object (`OpenAPI.Request`)](#request-body-object-openapirequest)
- [Media Type Object (`OpenAPI.Content`)](#media-type-object-openapicontent)
- [Encoding Object (`OpenAPI.Content.Encoding`)](#encoding-object-openapicontentencoding)
Expand Down Expand Up @@ -85,6 +87,19 @@ let encoder = ... // JSONEncoder() or YAMLEncoder()
let encodedOpenAPIDoc = try encoder.encode(openAPIDoc)
```

### Validating OpenAPI Documents
Thanks to Swift's type system, the vast majority of the OpenAPI Specification is represented by the types of OpenAPIKit -- you cannot create bad OpenAPI docuements in the first place and decoding a document will fail with generally useful errors.

That being said, there are a small number of additional checks that you can perform to really put any concerns to bed.

```swift
let openAPIDoc = ...
// perform additional validations on the document:
try openAPIDoc.validate()
```

You can use this same validation system to dig arbitrarily deep into an OpenAPI Document and assert things that the OpenAPI Specification does not actually mandate. For more on validation, see the [Validation Documentation](./documentation/validation.md).

### 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.

Expand Down
12 changes: 12 additions & 0 deletions Sources/OpenAPIKit/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ extension OpenAPI {
public var tags: [Tag]?
public var externalDocs: ExternalDocumentation?

/// Retrieve an array of all Operation Ids defined by
/// this API. These Ids are guaranteed to be unique by
/// the OpenAPI Specification.
///
/// See [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operation-object) in the specifcation.
///
public var allOperationIds: [String] {
return paths.values
.flatMap { $0.endpoints }
.compactMap { $0.operation.operationId }
}

/// Dictionary of vendor extensions.
///
/// These should be of the form:
Expand Down
5 changes: 5 additions & 0 deletions Sources/OpenAPIKit/Either/Either+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ extension Either where B == OpenAPI.Example {
public static func example(_ example: OpenAPI.Example) -> Self { .b(example) }
}

extension Either where B == OpenAPI.Request {
/// Construct a request value.
public static func request(_ request: OpenAPI.Request) -> Self { .b(request) }
}

extension Either where B == OpenAPI.Response {
/// Construct a response value.
public static func response(_ response: OpenAPI.Response) -> Self { .b(response) }
Expand Down
30 changes: 30 additions & 0 deletions Sources/OpenAPIKit/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,36 @@ extension OpenAPI {
/// where the values are anything codable.
public var vendorExtensions: [String: AnyCodable]

// allowing Request Body reference
public init(
tags: [String]? = nil,
summary: String? = nil,
description: String? = nil,
externalDocs: OpenAPI.ExternalDocumentation? = nil,
operationId: String? = nil,
parameters: Parameter.Array = [],
requestBody: Either<JSONReference<OpenAPI.Request>, OpenAPI.Request>,
responses: OpenAPI.Response.Map,
deprecated: Bool = false,
security: [OpenAPI.SecurityRequirement]? = nil,
servers: [OpenAPI.Server]? = nil,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.tags = tags
self.summary = summary
self.description = description
self.externalDocs = externalDocs
self.operationId = operationId
self.parameters = parameters
self.requestBody = requestBody
self.responses = responses
self.deprecated = deprecated
self.security = security
self.servers = servers
self.vendorExtensions = vendorExtensions
}

// assuming inline request body
public init(
tags: [String]? = nil,
summary: String? = nil,
Expand Down
4 changes: 2 additions & 2 deletions Sources/OpenAPIKit/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ extension OpenAPI {
public init(
description: String,
headers: Header.Map? = nil,
content: Content.Map,
content: Content.Map = [:],
vendorExtensions: [String: AnyCodable] = [:]
) {
self.description = description
Expand Down Expand Up @@ -134,7 +134,7 @@ extension Either where A == JSONReference<OpenAPI.Response>, B == OpenAPI.Respon
public static func response(
description: String,
headers: OpenAPI.Header.Map? = nil,
content: OpenAPI.Content.Map
content: OpenAPI.Content.Map = [:]
) -> Self {
return .b(
.init(
Expand Down
Loading

0 comments on commit b31e2bc

Please sign in to comment.