Skip to content

Commit 4cf4891

Browse files
authored
Nullable enums with an empty string fail to get generated (#176)
Nullable enums with an empty string fail to get generated ### Motivation Fixes #118. The issue is that when a `type: string, enum: ...` schema is marked as `nullable: true`, _empty cases are parsed as Void_ instead of _nil_. See the issue for the investigation and identifying the root cause, but the TL;DR is that we need a workaround in the generator in the short term, and it's uncertain if the issue will ever be addressed in our dependency Yams (might be unlikely, as it'd probably be a breaking change for them at this point). ### Modifications This PR adds code that catches this case and converts the `Void` into an empty string, which is what the OpenAPI document author actually put into their document in the first place. ### Result Now `nullable: true` string enums with an empty case can be generated correctly. One example of such use is the [GitHub API](https://github.com/github/rest-api-description). Note that this PR isn't really adding support for nullable schemas in general, that's tracked by #82. We're just working around one specific scenario in which a relatively common input trips up the generator. ### Test Plan Added a unit test for each of: non-nullable and nullable variants. Reviewed by: gjcairo, simonjbeaumont Builds: ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. #176
1 parent 0ae57b9 commit 4cf4891

File tree

3 files changed

+72
-0
lines changed

3 files changed

+72
-0
lines changed

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateSchema.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ extension FileTranslator {
115115
let enumDecl = try translateStringEnum(
116116
typeName: typeName,
117117
userDescription: overrides.userDescription ?? coreContext.description,
118+
isNullable: coreContext.nullable,
118119
allowedValues: allowedValues
119120
)
120121
return [enumDecl]

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,23 @@ extension FileTranslator {
2020
/// - typeName: The name of the type to give to the declared enum.
2121
/// - openAPIDescription: A user-specified description from the OpenAPI
2222
/// document.
23+
/// - isNullable: Whether the enum schema is nullable.
2324
/// - allowedValues: The enumerated allowed values.
2425
func translateStringEnum(
2526
typeName: TypeName,
2627
userDescription: String?,
28+
isNullable: Bool,
2729
allowedValues: [AnyCodable]
2830
) throws -> Declaration {
2931

3032
let rawValues = try allowedValues.map(\.value)
3133
.map { anyValue in
34+
// In nullable enum schemas, empty strings are parsed as Void.
35+
// This is unlikely to be fixed, so handling that case here.
36+
// https://github.com/apple/swift-openapi-generator/issues/118
37+
if isNullable && anyValue is Void {
38+
return ""
39+
}
3240
guard let string = anyValue as? String else {
3341
throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)")
3442
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import XCTest
15+
import OpenAPIKit30
16+
@testable import _OpenAPIGeneratorCore
17+
18+
final class Test_translateStringEnum: Test_Core {
19+
20+
func testCaseValues() throws {
21+
let names = try _caseValues(
22+
.string(
23+
allowedValues: "a",
24+
""
25+
)
26+
)
27+
XCTAssertEqual(names, ["a", "_empty", "undocumented"])
28+
}
29+
30+
func testCaseValuesForNullableSchema() throws {
31+
let names = try _caseValues(
32+
.string(
33+
nullable: true,
34+
allowedValues: "a",
35+
nil
36+
)
37+
)
38+
XCTAssertEqual(names, ["a", "_empty", "undocumented"])
39+
}
40+
41+
func _caseValues(_ schema: JSONSchema) throws -> [String] {
42+
self.continueAfterFailure = false
43+
let translator = makeTypesTranslator()
44+
let decls = try translator.translateSchema(
45+
typeName: .init(swiftKeyPath: ["FooEnum"]),
46+
schema: schema,
47+
overrides: .none
48+
)
49+
XCTAssertEqual(decls.count, 1)
50+
let decl = decls[0]
51+
guard case .enum(let enumDesc) = decl.strippingTopComment else {
52+
throw UnexpectedDeclError(actual: decl.info.kind, expected: .enum)
53+
}
54+
XCTAssertEqual(enumDesc.name, "FooEnum")
55+
let names: [String] = enumDesc.members.compactMap { memberDecl in
56+
guard case .enumCase(let caseDesc) = memberDecl.strippingTopComment else {
57+
return nil
58+
}
59+
return caseDesc.name
60+
}
61+
return names
62+
}
63+
}

0 commit comments

Comments
 (0)