Skip to content

Commit

Permalink
Merge pull request #7 from mattpolzin/openapikit-3
Browse files Browse the repository at this point in the history
OpenAPIKit v3.0.0 transition
  • Loading branch information
mattpolzin authored Nov 6, 2023
2 parents d243491 + 2d93277 commit aa9d56c
Show file tree
Hide file tree
Showing 21 changed files with 1,314 additions and 51 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ on: [pull_request]
jobs:
codecov:
container:
image: swift:5.3-bionic
image: swift:5.8-focal
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: swift test --enable-test-discovery --enable-code-coverage
- uses: mattpolzin/swift-codecov-action@0.6.0
- uses: mattpolzin/swift-codecov-action@0.7.5
with:
MINIMUM_COVERAGE: 85
INCLUDE_TESTS: 'true'
19 changes: 7 additions & 12 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,13 @@ jobs:
fail-fast: false
matrix:
image:
- swift:5.2-xenial
- swift:5.2-bionic
- swift:5.2-focal
- swift:5.2-centos8
- swift:5.2-amazonlinux2
- swift:5.3-xenial
- swift:5.3-bionic
- swift:5.3-focal
- swift:5.3-centos8
- swift:5.3-amazonlinux2
- swift:5.6-focal
- swift:5.7-jammy
- swift:5.8-bionic
- swift:5.8-focal
- swift:5.8-jammy
- swift:5.8-amazonlinux2
- swift:5.9-focal
- swift:5.9-jammy
- swift:5.9-amazonlinux2
container: ${{ matrix.image }}
steps:
- name: Checkout code
Expand Down
24 changes: 3 additions & 21 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
{
"object": {
"pins": [
{
"package": "FineJSON",
"repositoryURL": "https://github.com/omochi/FineJSON.git",
"state": {
"branch": null,
"revision": "05101709243cb66d80c92e645210a3b80cf4e17f",
"version": "1.14.0"
}
},
{
"package": "OpenAPIKit",
"repositoryURL": "https://github.com/mattpolzin/OpenAPIKit.git",
"state": {
"branch": null,
"revision": "dea99e8bb5d8241c5c9f815d0fb0ad487b9491b0",
"version": "2.0.0"
}
},
{
"package": "RichJSONParser",
"repositoryURL": "https://github.com/omochi/RichJSONParser.git",
"state": {
"branch": null,
"revision": "263e2ecfe88d0500fa99e4cbc8c948529d335534",
"revision": "ae98338a8e660ae547b058ebb69c010e70b64e31",
"version": "3.0.0"
}
},
Expand All @@ -42,8 +24,8 @@
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "88caa2e6fffdbef2e91c2022d038576062042907",
"version": "4.0.0"
"revision": "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
"version": "5.0.6"
}
}
]
Expand Down
13 changes: 11 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,24 @@ let package = Package(
.library(
name: "OpenAPIReflection",
targets: ["OpenAPIReflection"]),
.library(
name: "OpenAPIReflection30",
targets: ["OpenAPIReflection30"]),
],
dependencies: [
.package(url: "https://github.com/mattpolzin/OpenAPIKit.git", from: "2.0.0"),
.package(url: "https://github.com/mattpolzin/OpenAPIKit.git", from: "3.0.0"),
.package(url: "https://github.com/mattpolzin/Sampleable.git", from: "2.1.0")
],
targets: [
.target(
name: "OpenAPIReflection30",
dependencies: [.product(name: "OpenAPIKit30", package: "OpenAPIKit"), "Sampleable"]),
.testTarget(
name: "OpenAPIReflection30Tests",
dependencies: ["OpenAPIReflection30"]),
.target(
name: "OpenAPIReflection",
dependencies: ["OpenAPIKit", "Sampleable"]),
dependencies: [.product(name: "OpenAPIKit", package: "OpenAPIKit"), "Sampleable"]),
.testTarget(
name: "OpenAPIReflectionTests",
dependencies: ["OpenAPIReflection"]),
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
[![Swift 5.1+](http://img.shields.io/badge/Swift-5.2+-blue.svg)](https://swift.org)
[![Swift 5.8+](http://img.shields.io/badge/Swift-5.8+-blue.svg)](https://swift.org)

[![MIT license](http://img.shields.io/badge/license-MIT-lightgrey.svg)](http://opensource.org/licenses/MIT) ![Tests](https://github.com/mattpolzin/OpenAPIReflection/workflows/Tests/badge.svg)

# OpenAPI support

See parent library at https://github.com/mattpolzin/OpenAPIKit
See parent library at https://github.com/mattpolzin/OpenAPIKit.

To generate OpenAPI 3.1.x types, use the `OpenAPIReflection` module. To generate OpenAPI 3.0.x types, use the `OpenAPIReflection30` module.

# OpenAPIReflection

Expand Down
4 changes: 2 additions & 2 deletions Sources/OpenAPIReflection/Sampleable+OpenAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public func genericOpenAPISchemaGuess<T>(for value: T, using encoder: JSONEncode
// There should not be any duplication of keys since these are
// property names, but rather than risk runtime exception, we just
// fail to the newer value arbitrarily
let propertiesDict = Dictionary(properties) { _, value2 in value2 }
let propertiesDict = OrderedDictionary(properties) { _, value2 in value2 }

return .object(required: true, properties: propertiesDict)
}
Expand Down Expand Up @@ -172,7 +172,7 @@ internal func openAPISchemaGuess(for value: Any, using encoder: JSONEncoder) thr

case is Data:
return .string(
format: .binary
contentEncoding: .binary
)

case is DateOpenAPISchemaType:
Expand Down
75 changes: 75 additions & 0 deletions Sources/OpenAPIReflection30/AnyJSONCaseIterable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// AnyJSONCaseIterable.swift
// OpenAPI
//
// Created by Mathew Polzin on 6/22/19.
//

import Foundation
import OpenAPIKit30

public protocol AnyRawRepresentable {
/// The `RawValue` type of this type.
static var rawValueType: Any.Type { get }
}

extension AnyRawRepresentable where Self: RawRepresentable {
/// The default `rawValueType` of a `RawRepresentable` is just the
/// type of `Self.RawValue`.
public static var rawValueType: Any.Type { return Self.RawValue.self }
}

/// Anything conforming to `AnyJSONCaseIterable` can provide a
/// list of its possible values.
public protocol AnyJSONCaseIterable: AnyRawRepresentable {
static func allCases(using encoder: JSONEncoder) -> [AnyCodable]
}

extension AnyJSONCaseIterable where Self: RawRepresentable {
/// The default `rawValueType` of a `RawRepresentable` is just the
/// type of `Self.RawValue`.
public static var rawValueType: Any.Type { return Self.RawValue.self }
}

public extension AnyJSONCaseIterable {
/// Given an array of Codable values, retrieve an array of AnyCodables.
static func allCases<T: Encodable>(from input: [T], using encoder: JSONEncoder) throws -> [AnyCodable] {
return try OpenAPIReflection30.allCases(from: input, using: encoder)
}
}

public extension AnyJSONCaseIterable where Self: CaseIterable, Self: Codable {
static func caseIterableOpenAPISchemaGuess(using encoder: JSONEncoder) throws -> JSONSchema {
guard let first = allCases.first else {
throw OpenAPI.EncodableError.exampleNotCodable
}
let itemSchema = try OpenAPIReflection30.nestedGenericOpenAPISchemaGuess(for: first, using: encoder)

return itemSchema.with(allowedValues: allCases.map { AnyCodable($0) })
}
}

extension CaseIterable where Self: Encodable {
public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] {
return (try? OpenAPIReflection30.allCases(from: Array(Self.allCases), using: encoder)) ?? []
}
}

fileprivate func allCases<T: Encodable>(from input: [T], using encoder: JSONEncoder) throws -> [AnyCodable] {
if let alreadyGoodToGo = input as? [AnyCodable] {
return alreadyGoodToGo
}

// The following is messy, but it does get us the intended result:
// Given any array of things that can be encoded, we want
// to map to an array of AnyCodable so we can store later. We need to
// muck with JSONSerialization because something like an `enum` may
// very well be encoded as a string, and therefore representable
// by AnyCodable, but AnyCodable wants it to actually BE a String
// upon initialization.

guard let arrayOfCodables = try JSONSerialization.jsonObject(with: encoder.encode(input), options: []) as? [Any] else {
throw OpenAPI.EncodableError.allCasesArrayNotCodable
}
return arrayOfCodables.map(AnyCodable.init)
}
48 changes: 48 additions & 0 deletions Sources/OpenAPIReflection30/Date+OpenAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Date+OpenAPI.swift
// OpenAPI
//
// Created by Mathew Polzin on 1/24/19.
//

import Foundation
import OpenAPIKit30

extension Date: DateOpenAPISchemaType {
public static func dateOpenAPISchemaGuess(using encoder: JSONEncoder) -> JSONSchema? {

switch encoder.dateEncodingStrategy {
case .deferredToDate, .custom:
// I don't know if we can say anything about this case without
// encoding the Date and looking at it, which is what `primitiveGuess()`
// does.
return nil

case .secondsSince1970,
.millisecondsSince1970:
return .number(format: .double)

case .iso8601:
return .string(format: .dateTime)

case .formatted(let formatter):
let hasTime = formatter.timeStyle != .none
let format: JSONTypeFormat.StringFormat = hasTime ? .dateTime : .date

return .string(format: format)

@unknown default:
return nil
}
}
}

extension Date: OpenAPIEncodedSchemaType {
public static func openAPISchema(using encoder: JSONEncoder) throws -> JSONSchema {
guard let dateSchema: JSONSchema = try openAPISchemaGuess(for: Date(), using: encoder) else {
throw OpenAPI.TypeError.unknownSchemaType(type(of: self))
}

return dateSchema
}
}
32 changes: 32 additions & 0 deletions Sources/OpenAPIReflection30/OpenAPI+Errors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// OpenAPI+Errors.swift
//
//
// Created by Mathew Polzin on 4/21/20.
//

import Foundation
import OpenAPIKit30

extension OpenAPI {
public enum TypeError: Swift.Error, CustomDebugStringConvertible {
case invalidSchema
case unknownSchemaType(Any.Type)

public var debugDescription: String {
switch self {
case .invalidSchema:
return "Invalid Schema"
case .unknownSchemaType(let type):
return "Could not determine OpenAPI schema type of \(String(describing: type))"
}
}
}

public enum EncodableError: Swift.Error, Equatable {
case allCasesArrayNotCodable
case exampleNotCodable
case primitiveGuessFailed
case exampleNotSupported(String)
}
}
13 changes: 13 additions & 0 deletions Sources/OpenAPIReflection30/Optional+ZipWith.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Optional+ZipWith.swift
// OpenAPIKit
//
// Created by Mathew Polzin on 1/19/19.
//

/// Zip two optionals together with the given operation performed on
/// the unwrapped contents. If either optional is nil, the zip
/// yields nil.
func zip<X, Y, Z>(_ left: X?, _ right: Y?, with fn: (X, Y) -> Z) -> Z? {
return left.flatMap { lft in right.map { rght in fn(lft, rght) }}
}
Loading

0 comments on commit aa9d56c

Please sign in to comment.