Skip to content

Commit

Permalink
Merge branch 'main' into openapikit-3
Browse files Browse the repository at this point in the history
  • Loading branch information
mattpolzin authored Sep 24, 2023
2 parents 7407481 + d243491 commit 109e1a2
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ jobs:
image:
- swift:5.8-bionic
- swift:5.8-focal
- swift:5.8-jammy
- swift:5.8-amazonlinux2
- swift:5.9-bionic
- swift:5.9-focal
- swift:5.9-jammy
- swift:5.9-amazonlinux2
container: ${{ matrix.image }}
steps:
Expand All @@ -29,7 +30,7 @@ jobs:
runs-on: macOS-latest
steps:
- name: Select latest available Xcode
uses: maxim-lobanov/setup-xcode@1.0
uses: maxim-lobanov/setup-xcode@v1
with: { 'xcode-version': 'latest' }
- name: Checkout code
uses: actions/checkout@v3
Expand Down
34 changes: 31 additions & 3 deletions Sources/OpenAPIReflection/Sampleable+OpenAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ public func genericOpenAPISchemaGuess<T>(for value: T, using encoder: JSONEncode

return node
}
// short circuit for optionals
if let optional = value as? _Optional {
// we don't want to accidentally not take advantage of user-defined support
// so we try for a schema guess right off the bat
if let schemaGuess = try openAPISchemaGuess(for: type(of: optional), using: encoder) {
return schemaGuess
}

// otherwise, we dig into optional by hand to avoid the below code considering .some and .none to be
// "children"
switch optional.value {
case .some(let wrappedValue):
return try genericOpenAPISchemaGuess(for: wrappedValue, using: encoder)
.optionalSchemaObject()
case .none:
return .object(required: false)
}
}

let mirror = Mirror(reflecting: value)
let properties: [(String, JSONSchema)] = try mirror.children.compactMap { child in
Expand Down Expand Up @@ -114,7 +132,6 @@ internal func openAPISchemaGuess(for type: Any.Type, using encoder: JSONEncoder)
} else {
return nil
}

default:
return nil
}
Expand Down Expand Up @@ -178,5 +195,16 @@ private struct PrimitiveWrapper<Wrapped: Encodable>: Encodable {
let primitive: Wrapped
}

private protocol _Optional {}
extension Optional: _Optional {}
private protocol _Optional {
static var wrapped: Any.Type { get }
var value: Any? { get }
}
extension Optional: _Optional {
static var wrapped: Any.Type {
Wrapped.self
}

var value: Any? {
return self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ final class GenericOpenAPISchemaInternalTests: XCTestCase {
XCTAssertEqual(try openAPISchemaGuess(for: Double.self, using: testEncoder), .number(format: .double))
XCTAssertEqual(try openAPISchemaGuess(for: Bool.self, using: testEncoder), .boolean)
XCTAssertEqual(try openAPISchemaGuess(for: TestEnum.self, using: testEncoder), .string)
XCTAssertEqual(try openAPISchemaGuess(for: Optional<String>.self, using: testEncoder), .string(required: false))
}

func test_openAPINodeGuessForValue() {
Expand All @@ -33,6 +34,7 @@ final class GenericOpenAPISchemaInternalTests: XCTestCase {
XCTAssertEqual(try openAPISchemaGuess(for: 11.5, using: testEncoder), .number(format: .double))
XCTAssertEqual(try openAPISchemaGuess(for: true, using: testEncoder), .boolean)
XCTAssertEqual(try openAPISchemaGuess(for: TestEnum.one, using: testEncoder), .string)
XCTAssertEqual(try openAPISchemaGuess(for: Optional("hello") as Any, using: testEncoder), .string(required: false))
}
}

Expand Down
38 changes: 36 additions & 2 deletions Tests/OpenAPIReflectionTests/GenericOpenAPISchemaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,29 @@ final class GenericOpenAPISchemaTests: XCTestCase {
"int": .integer,
"double": .number(format: .double),
"float": .number(format: .float),
"bool": .boolean
"bool": .boolean,
"optionalString": .string(required: false)
]
)
)
}

func test_opaqueStruct() throws {
// "opaque" in that the `OnlyCodable` type does not itself define an OpenAPI schema and so we must
// encode it to have any chance at guessing its structure.
let node = try OpaqueStructs.genericOpenAPISchemaGuess(using: JSONEncoder())

XCTAssertEqual(
node,
JSONSchema.object(
properties: [
"opaque": .object(properties: ["value": .string]),
"optionalOpaque": .object(required: false, properties: ["value": .string])
]
)
)
}

func test_dateType() throws {
let node = try DateType.genericOpenAPISchemaGuess(using: JSONEncoder())

Expand Down Expand Up @@ -283,8 +300,25 @@ extension GenericOpenAPISchemaTests {
let double: Double
let float: Float
let bool: Bool
let optionalString: String?

static let sample: BasicTypes = .init(string: "hello", int: 10, double: 2.3, float: 1.1, bool: true, optionalString: "world")
}

static let sample: BasicTypes = .init(string: "hello", int: 10, double: 2.3, float: 1.1, bool: true)
struct OnlyCodable: Codable {
let value: String
}

struct OpaqueStructs: Codable, Sampleable {
// "opaque" in that the `OnlyCodable` type does not itself define an OpenAPI schema and so we must
// encode it to have any chance at guessing its structure.
let opaque: OnlyCodable
let optionalOpaque: OnlyCodable?

static let sample: OpaqueStructs = .init(
opaque: .init(value: "hello"),
optionalOpaque: .init(value: "world")
)
}

struct DateType: Codable, Sampleable {
Expand Down

0 comments on commit 109e1a2

Please sign in to comment.