Skip to content

Commit

Permalink
Merge pull request #63 from mattpolzin/refactor-context-names
Browse files Browse the repository at this point in the history
1.0.0 Release
  • Loading branch information
mattpolzin authored May 13, 2020
2 parents 58304ce + ed41448 commit 382668c
Show file tree
Hide file tree
Showing 41 changed files with 1,381 additions and 550 deletions.
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2018 Mathew Polzin
Copyright 2020 Mathew Polzin

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
1 change: 0 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,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.PathItem.Operation`)](#operation-object-openapipathitemoperation)
- [Operation Object (`OpenAPI.Operation`)](#operation-object-openapipathitemoperation)
- [External Document Object (`OpenAPI.ExternalDoc`)](#external-document-object-openapiexternaldoc)
- [Parameter Object (`OpenAPI.PathItem.Parameter`)](#parameter-object-openapipathitemparameter)
- [Parameter Object (`OpenAPI.Parameter`)](#parameter-object-openapipathitemparameter)
- [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 @@ -101,10 +101,10 @@ The types used by this library largely mirror the object definitions found in th
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.
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. In addition to accessing endpoints on a path item under the name of the method (`.get`, `.post`, etc.), you can get an array of pairs matching endpoint methods to operations with the `.endpoints` method on `PathItem`.

#### 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.
Each endpoint on a route is defined by an `OpenAPI.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.
Expand Down Expand Up @@ -183,7 +183,7 @@ For example,
let apiDoc: OpenAPI.Document = ...
let addBooksPath = apiDoc.paths["/cloudloading/addBook"]

let addBooksParameters: [OpenAPI.PathItem.Parameter]? = addBooksPath?.parameters.compactMap(apiDoc.components.dereference)
let addBooksParameters: [OpenAPI.Parameter]? = addBooksPath?.parameters.compactMap(apiDoc.components.dereference)
```

## Notes
Expand Down Expand Up @@ -275,7 +275,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d
- [x] trace
- [x] specification extensions (`vendorExtensions`)

### Operation Object (`OpenAPI.PathItem.Operation`)
### Operation Object (`OpenAPI.Operation`)
- [x] tags
- [x] summary
- [x] description
Expand All @@ -295,7 +295,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d
- [x] url
- [ ] specification extensions

### Parameter Object (`OpenAPI.PathItem.Parameter`)
### Parameter Object (`OpenAPI.Parameter`)
- [x] name
- [x] in (`context`)
- [x] description
Expand Down Expand Up @@ -387,7 +387,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d
- [x] local (same file) reference (`internal` case)
- [x] encode
- [x] decode
- [ ] dereference
- [x] dereference
- [x] remote (different file) reference (`external` case)
- [x] encode
- [x] decode
Expand All @@ -396,7 +396,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d
### Schema Object (`JSONSchema`)
- [x] Mostly complete support for JSON Schema inherited keywords
- [x] nullable
- [ ] discriminator
- [x] discriminator
- [x] readOnly (`permissions` `.readOnly` case)
- [x] writeOnly (`permissions` `.writeOnly` case)
- [ ] xml
Expand Down
10 changes: 10 additions & 0 deletions Sources/OpenAPIKit/Component Object/Components+JSONReference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ extension OpenAPI.Components {
}
}

/// Pass a reference to a component.
/// `dereference()` will return the component value if it is found
/// in the Components Object.
///
/// - Important: Dereferencing an external reference (i.e. one that points to another file)
/// is not currently supported by OpenAPIKit and will therefore always result in `nil`.
public func dereference<ReferenceType: ComponentDictionaryLocatable>(_ reference: JSONReference<ReferenceType>) -> ReferenceType? {
return self[reference]
}

/// Create a `JSONReference`.
///
/// - throws: If the given name does not refer to an existing component of the given type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension OpenAPI.Response: ComponentDictionaryLocatable {
public static var openAPIComponentsKeyPath: KeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.responses }
}

extension OpenAPI.PathItem.Parameter: ComponentDictionaryLocatable {
extension OpenAPI.Parameter: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "parameters" }
public static var openAPIComponentsKeyPath: KeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.parameters }
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/OpenAPIKit/Component Object/Components.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension OpenAPI {

public var schemas: ComponentDictionary<JSONSchema>
public var responses: ComponentDictionary<Response>
public var parameters: ComponentDictionary<PathItem.Parameter>
public var parameters: ComponentDictionary<Parameter>
public var examples: ComponentDictionary<Example>
public var requestBodies: ComponentDictionary<Request>
public var headers: ComponentDictionary<Header>
Expand All @@ -35,7 +35,7 @@ extension OpenAPI {

public init(schemas: ComponentDictionary<JSONSchema> = [:],
responses: ComponentDictionary<Response> = [:],
parameters: ComponentDictionary<PathItem.Parameter> = [:],
parameters: ComponentDictionary<Parameter> = [:],
examples: ComponentDictionary<Example> = [:],
requestBodies: ComponentDictionary<Request> = [:],
headers: ComponentDictionary<Header> = [:],
Expand Down Expand Up @@ -163,7 +163,7 @@ extension OpenAPI.Components: Decodable {
responses = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Response>.self, forKey: .responses)
?? [:]

parameters = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.PathItem.Parameter>.self, forKey: .parameters)
parameters = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Parameter>.self, forKey: .parameters)
?? [:]

examples = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Example>.self, forKey: .examples)
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension OpenAPI.Content {
///
/// See [OpenAPI Encoding Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#encoding-object).
public struct Encoding: Equatable {
public typealias Style = OpenAPI.PathItem.Parameter.Schema.Style
public typealias Style = OpenAPI.Parameter.SchemaContext.Style

public let contentType: OpenAPI.ContentType?
public let headers: OpenAPI.Header.Map?
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions Sources/OpenAPIKit/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -286,20 +286,20 @@ internal func decodeSecurityRequirements<CodingKeys: CodingKey>(from container:

internal func validateSecurityRequirements(in paths: OpenAPI.PathItem.Map, against components: OpenAPI.Components) throws {
for (path, pathItem) in paths {
for (verb, operation) in pathItem.endpoints {
if let securityRequirements = operation.security {
for endpoint in pathItem.endpoints {
if let securityRequirements = endpoint.operation.security {
try validate(
securityRequirements: securityRequirements,
at: path,
for: verb,
for: endpoint.method,
against: components
)
}
}
}
}

internal func validate(securityRequirements: [OpenAPI.SecurityRequirement], at path: OpenAPI.Path, for verb: OpenAPI.HttpVerb, against components: OpenAPI.Components) throws {
internal func validate(securityRequirements: [OpenAPI.SecurityRequirement], at path: OpenAPI.Path, for verb: OpenAPI.HttpMethod, against components: OpenAPI.Components) throws {
let securitySchemes = securityRequirements.flatMap { $0.keys }

for securityScheme in securitySchemes {
Expand Down
46 changes: 35 additions & 11 deletions Sources/OpenAPIKit/Either/Either+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,36 @@ extension Either where A == URL {
public var urlValue: A? { a }
}

extension Either where A == OpenAPI.Header.Schema {
/// Retrieve the schema if that is what this property contains.
public var schemaValue: A? { a }
}

extension Either where B == OpenAPI.PathItem.Parameter {
// This extension also covers `OpenAPI.Header.SchemaContext`
// which is a typealias of `OpenAPI.Parameter.SchemaContext`.
extension Either where A == OpenAPI.Parameter.SchemaContext {
/// Retrieve the schema context if that is what this property contains.
public var schemaContextValue: A? { a }

/// Retrieve the schema value if this property contains a schema context.
///
/// If the schema is a `JSONReference` this property will be `nil`
/// but the `schemaReference` property will be `non-nil`.
public var schemaValue: JSONSchema? {
guard case .a(let schemaContext) = self else {
return nil
}
return schemaContext.schema.schemaValue
}

/// Retrieve the schema reference if this property contains a schema context.
///
/// If the schema is a `JSONSchema` this property will be `nil` but the
/// `schemaValue` property will be `non-nil`.
public var schemaReference: JSONReference<JSONSchema>? {
guard case .a(let schemaContext) = self else {
return nil
}
return schemaContext.schema.reference
}
}

extension Either where B == OpenAPI.Parameter {
/// Retrieve the parameter if that is what this property contains.
public var parameterValue: B? { b }
}
Expand Down Expand Up @@ -79,19 +103,19 @@ extension Either where A: _OpenAPIReference {
public static func reference(_ reference: A) -> Self { .a(reference) }
}

extension Either where A == OpenAPI.PathItem.Parameter.Schema {
/// Construct a schema value.
public static func schema(_ schema: OpenAPI.PathItem.Parameter.Schema) -> Self { .a(schema) }
extension Either where A == OpenAPI.Parameter.SchemaContext {
/// Construct a schema context value.
public static func schema(_ schema: OpenAPI.Parameter.SchemaContext) -> Self { .a(schema) }
}

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

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

extension Either where B == OpenAPI.Content.Map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

extension OpenAPI.Error.Decoding {
public struct Operation: OpenAPIError {
public let endpoint: OpenAPI.HttpVerb
public let endpoint: OpenAPI.HttpMethod
public let context: Context
internal let relativeCodingPath: [CodingKey]

Expand Down Expand Up @@ -78,7 +78,7 @@ extension OpenAPI.Error.Decoding.Operation {
internal init(_ error: OpenAPI.Error.Decoding.Request) {
var codingPath = error.codingPath.dropFirst(2)
// this part of the coding path is structurally guaranteed to be an HTTP verb.
let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())!
let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())!

endpoint = verb
context = .request(error)
Expand All @@ -88,7 +88,7 @@ extension OpenAPI.Error.Decoding.Operation {
internal init(_ error: OpenAPI.Error.Decoding.Response) {
var codingPath = error.codingPath.dropFirst(2)
// this part of the coding path is structurally guaranteed to be an HTTP verb.
let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())!
let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())!

endpoint = verb
context = .response(error)
Expand All @@ -98,7 +98,7 @@ extension OpenAPI.Error.Decoding.Operation {
internal init(_ error: InconsistencyError) {
var codingPath = error.codingPath.dropFirst(2)
// this part of the coding path is structurally guaranteed to be an HTTP verb.
let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())!
let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())!

endpoint = verb
context = .inconsistency(error)
Expand All @@ -108,7 +108,7 @@ extension OpenAPI.Error.Decoding.Operation {
internal init(_ error: Swift.DecodingError) {
var codingPath = error.codingPathWithoutSubject.dropFirst(2)
// this part of the coding path is structurally guaranteed to be an HTTP verb.
let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())!
let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())!

endpoint = verb
context = .other(error)
Expand All @@ -123,7 +123,7 @@ extension OpenAPI.Error.Decoding.Operation {

var codingPath = eitherError.codingPath.dropFirst(2)
// this part of the coding path is structurally guaranteed to be an HTTP verb.
let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())!
let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())!

endpoint = verb
context = .neither(eitherError)
Expand Down
18 changes: 9 additions & 9 deletions Sources/OpenAPIKit/Header.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ extension OpenAPI {
///
/// See [OpenAPI Header Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#header-object).
public struct Header: Equatable {
public typealias Schema = PathItem.Parameter.Schema
public typealias SchemaContext = Parameter.SchemaContext

public let description: String?
public let required: Bool
public let deprecated: Bool // default is false
/// OpenAPI Spec "schema" or "content", which are mutually exclusive.
public let schemaOrContent: Either<Schema, OpenAPI.Content.Map>
public let schemaOrContent: Either<SchemaContext, OpenAPI.Content.Map>

public typealias Map = OrderedDictionary<String, Either<JSONReference<Header>, Header>>

public init(schemaOrContent: Either<Schema, OpenAPI.Content.Map>,
public init(schemaOrContent: Either<SchemaContext, OpenAPI.Content.Map>,
description: String? = nil,
required: Bool = false,
deprecated: Bool = false) {
Expand All @@ -32,7 +32,7 @@ extension OpenAPI {
self.deprecated = deprecated
}

public init(schema: Schema,
public init(schema: SchemaContext,
description: String? = nil,
required: Bool = false,
deprecated: Bool = false) {
Expand All @@ -46,7 +46,7 @@ extension OpenAPI {
description: String? = nil,
required: Bool = false,
deprecated: Bool = false) {
self.schemaOrContent = .init(Schema(schema, style: .default(for: .header)))
self.schemaOrContent = .init(SchemaContext(schema, style: .default(for: .header)))
self.description = description
self.required = required
self.deprecated = deprecated
Expand All @@ -56,7 +56,7 @@ extension OpenAPI {
description: String? = nil,
required: Bool = false,
deprecated: Bool = false) {
self.schemaOrContent = .init(Schema(schemaReference: schemaReference, style: .default(for: .header)))
self.schemaOrContent = .init(SchemaContext(schemaReference: schemaReference, style: .default(for: .header)))
self.description = description
self.required = required
self.deprecated = deprecated
Expand All @@ -75,7 +75,7 @@ extension OpenAPI {
}

// MARK: - Header Convenience
extension OpenAPI.PathItem.Parameter.Schema {
extension OpenAPI.Parameter.SchemaContext {
public static func header(_ schema: JSONSchema,
allowReserved: Bool = false,
example: AnyCodable? = nil) -> Self {
Expand Down Expand Up @@ -166,9 +166,9 @@ extension OpenAPI.Header: Decodable {

let maybeContent = try container.decodeIfPresent(OpenAPI.Content.Map.self, forKey: .content)

let maybeSchema: Schema?
let maybeSchema: SchemaContext?
if container.contains(.schema) {
maybeSchema = try Schema(from: decoder, for: .header)
maybeSchema = try SchemaContext(from: decoder, for: .header)
} else {
maybeSchema = nil
}
Expand Down
25 changes: 25 additions & 0 deletions Sources/OpenAPIKit/HttpMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// HttpMethod.swift
//
//
// Created by Mathew Polzin on 12/29/19.
//

extension OpenAPI {
/// Represents the HTTP methods supported by the
/// OpenAPI Specification.
///
/// See [OpenAPI Path Item Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#path-item-object) because the supported
/// HTTP methods are enumerated as properties on that
/// object.
public enum HttpMethod: String, CaseIterable {
case get = "GET"
case post = "POST"
case patch = "PATCH"
case put = "PUT"
case delete = "DELETE"
case head = "HEAD"
case options = "OPTIONS"
case trace = "TRACE"
}
}
Loading

0 comments on commit 382668c

Please sign in to comment.