Skip to content

Commit 81f8743

Browse files
Update to OpenAPIKit 3.0.0-alpha.9 (#147)
### Motivation Investigation into #128 led to a new release of OpenAPIKit, which supports path references. Supporting path references isn't something we want to do right now because: 1. OpenAPI 3.0.3 only supports external path references (cf. 3.1, which supports internal references too). 2. Swift OpenAPI Generator currently only supports OpenAPI 3.0.x. 3. Swift OpenAPI Generator currently doesn't support external references. The OpenAPIKit update comes with a new API, because the paths are now an `Either` to support being a `PathItem` or a reference to a `PathItem`. For now we'd like to be keeping up to date with OpenAPIKit so that, when we support OpenAPI 3.1 and/or external references, we have the APIs we need in the upstream package. ### Modifications - Update to OpenAPIKit 3.0.0-alpha.9. - Attempt to resolve the path item during translation. ### Result An error will be thrown when using an OpenAPI document with a path reference. ### Test Plan Added reference tests. --------- Signed-off-by: Si Beaumont <beaumont@apple.com>
1 parent 16420df commit 81f8743

File tree

7 files changed

+184
-18
lines changed

7 files changed

+184
-18
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ let package = Package(
6464
// Read OpenAPI documents
6565
.package(
6666
url: "https://github.com/mattpolzin/OpenAPIKit.git",
67-
exact: "3.0.0-alpha.7"
67+
exact: "3.0.0-alpha.9"
6868
),
6969
.package(
7070
url: "https://github.com/jpsim/Yams.git",

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ extension FileTranslator {
5555
switch type {
5656
case .allOf:
5757
// AllOf uses all required properties.
58-
propertyType = rawPropertyType
58+
propertyType = rawPropertyType.withOptional(false)
5959
case .anyOf:
6060
// AnyOf uses all optional properties.
61-
propertyType = rawPropertyType.asOptional
61+
propertyType = rawPropertyType.withOptional(true)
6262
}
6363
let comment: Comment? = .property(
6464
originalName: key,

Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,20 @@ extension OperationDescription {
5959
/// - components: The components from the OpenAPI document.
6060
/// - asSwiftSafeName: A converted function from user-provided strings
6161
/// to strings safe to be used as a Swift identifier.
62+
/// - Throws: if `map` contains any references; see discussion for details.
63+
///
64+
/// This function will throw an error if `map` contains any references, because:
65+
/// 1. OpenAPI 3.0.3 only supports external path references (cf. 3.1, which supports internal references too)
66+
/// 2. Swift OpenAPI Generator currently only supports OpenAPI 3.0.x.
67+
/// 3. Swift OpenAPI Generator currently doesn't support external references.
6268
static func all(
6369
from map: OpenAPI.PathItem.Map,
6470
in components: OpenAPI.Components,
6571
asSwiftSafeName: @escaping (String) -> String
66-
) -> [OperationDescription] {
67-
map.flatMap { path, value in
68-
value.endpoints.map { endpoint in
72+
) throws -> [OperationDescription] {
73+
try map.flatMap { path, value in
74+
let value = try value.resolve(in: components)
75+
return value.endpoints.map { endpoint in
6976
OperationDescription(
7077
path: path,
7178
endpoint: endpoint,

Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,17 @@ struct TypesFileTranslator: FileTranslator {
4141
+ config.additionalImports
4242
.map { ImportDescription(moduleName: $0) }
4343

44-
let apiProtocol = translateAPIProtocol(doc.paths)
44+
let apiProtocol = try translateAPIProtocol(doc.paths)
4545

4646
let serversDecl = translateServers(doc.servers)
4747

4848
let components = try translateComponents(doc.components)
4949

50-
let operationDescriptions =
51-
OperationDescription
52-
.all(
53-
from: parsedOpenAPI.paths,
54-
in: doc.components,
55-
asSwiftSafeName: swiftSafeName
56-
)
50+
let operationDescriptions = try OperationDescription.all(
51+
from: parsedOpenAPI.paths,
52+
in: doc.components,
53+
asSwiftSafeName: swiftSafeName
54+
)
5755
let operations = try translateOperations(operationDescriptions)
5856

5957
let typesFile = FileDescription(

Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ extension TypesFileTranslator {
1919
/// per HTTP operation defined in the OpenAPI document.
2020
/// - Parameter paths: The paths object from the OpenAPI document.
2121
/// - Returns: A protocol declaration.
22-
func translateAPIProtocol(_ paths: OpenAPI.PathItem.Map) -> Declaration {
22+
/// - Throws: If `paths` contains any references.
23+
func translateAPIProtocol(_ paths: OpenAPI.PathItem.Map) throws -> Declaration {
2324

24-
let operations = OperationDescription.all(
25+
let operations = try OperationDescription.all(
2526
from: paths,
2627
in: components,
2728
asSwiftSafeName: swiftSafeName

Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,12 @@ final class Test_YamsParser: Test_Core {
118118
description: Success
119119
"""
120120

121-
let expected =
122-
"/foo.yaml: error: Expected to find `responses` key for the **GET** endpoint under `/system` but it is missing."
121+
let expected = """
122+
/foo.yaml: error: Found neither a $ref nor a PathItem in Document.paths['/system'].
123+
124+
PathItem could not be decoded because:
125+
Expected to find `responses` key for the **GET** endpoint under `/system` but it is missing..
126+
"""
123127
assertThrownError(try _test(yaml), expectedDiagnostic: expected)
124128
}
125129

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,103 @@ final class SnippetBasedReferenceTests: XCTestCase {
347347
)
348348
}
349349

350+
func testComponentsSchemasAllOfOneStringRef() throws {
351+
try self.assertSchemasTranslation(
352+
"""
353+
schemas:
354+
A:
355+
type: string
356+
MyAllOf:
357+
allOf:
358+
- $ref: '#/components/schemas/A'
359+
""",
360+
"""
361+
public enum Schemas {
362+
public typealias A = Swift.String
363+
public struct MyAllOf: Codable, Equatable, Hashable, Sendable {
364+
public var value1: Components.Schemas.A
365+
public init(value1: Components.Schemas.A) {
366+
self.value1 = value1
367+
}
368+
public init(from decoder: any Decoder) throws {
369+
value1 = try .init(from: decoder)
370+
}
371+
public func encode(to encoder: any Encoder) throws {
372+
try value1.encode(to: encoder)
373+
}
374+
}
375+
}
376+
"""
377+
)
378+
}
379+
380+
func testComponentsSchemasObjectWithRequiredAllOfOneStringRefProperty() throws {
381+
try self.assertSchemasTranslation(
382+
"""
383+
schemas:
384+
A:
385+
type: string
386+
B:
387+
type: object
388+
required:
389+
- c
390+
properties:
391+
c:
392+
allOf:
393+
- $ref: "#/components/schemas/A"
394+
""",
395+
"""
396+
public enum Schemas {
397+
public typealias A = Swift.String
398+
public struct B: Codable, Equatable, Hashable, Sendable {
399+
public struct cPayload: Codable, Equatable, Hashable, Sendable {
400+
public var value1: Components.Schemas.A
401+
public init(value1: Components.Schemas.A) { self.value1 = value1 }
402+
public init(from decoder: any Decoder) throws { value1 = try .init(from: decoder) }
403+
public func encode(to encoder: any Encoder) throws { try value1.encode(to: encoder) }
404+
}
405+
public var c: Components.Schemas.B.cPayload
406+
public init(c: Components.Schemas.B.cPayload) { self.c = c }
407+
public enum CodingKeys: String, CodingKey { case c }
408+
}
409+
}
410+
"""
411+
)
412+
}
413+
414+
func testComponentsSchemasObjectWithOptionalAllOfOneStringRefProperty() throws {
415+
try self.assertSchemasTranslation(
416+
"""
417+
schemas:
418+
A:
419+
type: string
420+
B:
421+
type: object
422+
required: []
423+
properties:
424+
c:
425+
allOf:
426+
- $ref: "#/components/schemas/A"
427+
""",
428+
"""
429+
public enum Schemas {
430+
public typealias A = Swift.String
431+
public struct B: Codable, Equatable, Hashable, Sendable {
432+
public struct cPayload: Codable, Equatable, Hashable, Sendable {
433+
public var value1: Components.Schemas.A
434+
public init(value1: Components.Schemas.A) { self.value1 = value1 }
435+
public init(from decoder: any Decoder) throws { value1 = try .init(from: decoder) }
436+
public func encode(to encoder: any Encoder) throws { try value1.encode(to: encoder) }
437+
}
438+
public var c: Components.Schemas.B.cPayload?
439+
public init(c: Components.Schemas.B.cPayload? = nil) { self.c = c }
440+
public enum CodingKeys: String, CodingKey { case c }
441+
}
442+
}
443+
"""
444+
)
445+
}
446+
350447
func testComponentsSchemasEnum() throws {
351448
try self.assertSchemasTranslation(
352449
"""
@@ -629,6 +726,52 @@ final class SnippetBasedReferenceTests: XCTestCase {
629726
"""
630727
)
631728
}
729+
730+
func testPathsSimplestCase() throws {
731+
try self.assertPathsTranslation(
732+
"""
733+
/health:
734+
get:
735+
operationId: getHealth
736+
responses:
737+
'200':
738+
description: A success response with a greeting.
739+
content:
740+
text/plain:
741+
schema:
742+
type: string
743+
""",
744+
"""
745+
public protocol APIProtocol: Sendable {
746+
func getHealth(_ input: Operations.getHealth.Input) async throws -> Operations.getHealth.Output
747+
}
748+
"""
749+
)
750+
}
751+
752+
func testPathWithPathItemReference() throws {
753+
XCTAssertThrowsError(
754+
try self.assertPathsTranslation(
755+
"""
756+
/health:
757+
get:
758+
operationId: getHealth
759+
responses:
760+
'200':
761+
description: A success response with a greeting.
762+
content:
763+
text/plain:
764+
schema:
765+
type: string
766+
/health2:
767+
$ref: "#/paths/~1health"
768+
""",
769+
"""
770+
unused: This test throws an error.
771+
"""
772+
)
773+
)
774+
}
632775
}
633776

634777
extension SnippetBasedReferenceTests {
@@ -720,6 +863,19 @@ extension SnippetBasedReferenceTests {
720863
let translation = try translator.translateComponentRequestBodies(translator.components.requestBodies)
721864
try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line)
722865
}
866+
867+
func assertPathsTranslation(
868+
_ pathsYAML: String,
869+
componentsYAML: String = "{}",
870+
_ expectedSwift: String,
871+
file: StaticString = #filePath,
872+
line: UInt = #line
873+
) throws {
874+
let translator = try makeTypesTranslator(componentsYAML: componentsYAML)
875+
let paths = try YAMLDecoder().decode(OpenAPI.PathItem.Map.self, from: pathsYAML)
876+
let translation = try translator.translateAPIProtocol(paths)
877+
try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line)
878+
}
723879
}
724880

725881
private func XCTAssertEqualWithDiff(

0 commit comments

Comments
 (0)