Skip to content

Commit 42d6259

Browse files
authored
Merge branch 'main' into error_handling
2 parents 55bd604 + 31fa50a commit 42d6259

File tree

9 files changed

+205
-124
lines changed

9 files changed

+205
-124
lines changed

.github/workflows/main.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Main
2+
3+
on:
4+
push:
5+
branches: [main]
6+
schedule:
7+
- cron: "0 8,20 * * *"
8+
9+
jobs:
10+
unit-tests:
11+
name: Unit tests
12+
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
13+
with:
14+
linux_5_9_arguments_override: "--explicit-target-dependency-import-check error"
15+
linux_5_10_arguments_override: "--explicit-target-dependency-import-check error"
16+
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error"
17+
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error"
18+
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
19+
20+
integration-test:
21+
name: Integration test
22+
uses: apple/swift-nio/.github/workflows/swift_matrix.yml@main
23+
with:
24+
name: "Integration test"
25+
matrix_linux_command: "apt-get update -yq && apt-get install -yq jq && SWIFT_OPENAPI_GENERATOR_REPO_URL=file://${GITHUB_WORKSPACE} ./scripts/run-integration-test.sh"
26+
27+
example-packages:
28+
name: Example packages
29+
uses: apple/swift-nio/.github/workflows/swift_matrix.yml@main
30+
with:
31+
name: "Example packages"
32+
matrix_linux_command: "./scripts/test-examples.sh"

.github/workflows/pull_request.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,3 @@ jobs:
5757
name: "Example packages"
5858
matrix_linux_command: "./scripts/test-examples.sh"
5959
matrix_linux_nightly_main_enabled: false
60-
61-
swift-6-language-mode:
62-
name: Swift 6 Language Mode
63-
uses: apple/swift-nio/.github/workflows/swift_6_language_mode.yml@main
64-
if: false # Disabled for now.

.github/workflows/scheduled.yml

Lines changed: 0 additions & 30 deletions
This file was deleted.

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ let package = Package(
4949

5050
// General algorithms
5151
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
52+
.package(url: "https://github.com/apple/swift-collections", from: "1.1.4"),
5253

5354
// Read OpenAPI documents
5455
.package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "3.3.0"),
@@ -72,7 +73,9 @@ let package = Package(
7273
.product(name: "OpenAPIKit", package: "OpenAPIKit"),
7374
.product(name: "OpenAPIKit30", package: "OpenAPIKit"),
7475
.product(name: "OpenAPIKitCompat", package: "OpenAPIKit"),
75-
.product(name: "Algorithms", package: "swift-algorithms"), .product(name: "Yams", package: "Yams"),
76+
.product(name: "Algorithms", package: "swift-algorithms"),
77+
.product(name: "OrderedCollections", package: "swift-collections"),
78+
.product(name: "Yams", package: "Yams"),
7679
],
7780
swiftSettings: swiftSettings
7881
),

Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ extension Diagnostic {
139139
static func openAPIMissingVersionError(location: Location) -> Diagnostic {
140140
error(
141141
message:
142-
"No openapi key found, please provide a valid OpenAPI document with OpenAPI versions in the 3.0.x or 3.1.x sets.",
142+
"No key named openapi found. Please provide a valid OpenAPI document with OpenAPI versions in the 3.0.x or 3.1.x sets.",
143143
location: location
144144
)
145145
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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 OpenAPIKit
15+
import OrderedCollections
16+
17+
/// The backing type of a raw enum.
18+
enum RawEnumBackingType {
19+
20+
/// Backed by a `String`.
21+
case string
22+
23+
/// Backed by an `Int`.
24+
case integer
25+
}
26+
27+
/// The extracted enum value's identifier.
28+
private enum EnumCaseID: Hashable, CustomStringConvertible {
29+
30+
/// A string value.
31+
case string(String)
32+
33+
/// An integer value.
34+
case integer(Int)
35+
36+
var description: String {
37+
switch self {
38+
case .string(let value): return "\"\(value)\""
39+
case .integer(let value): return String(value)
40+
}
41+
}
42+
}
43+
44+
/// A wrapper for the metadata about the raw enum case.
45+
private struct EnumCase {
46+
47+
/// Used for checking uniqueness.
48+
var id: EnumCaseID
49+
50+
/// The raw Swift-safe name for the case.
51+
var caseName: String
52+
53+
/// The literal value of the enum case.
54+
var literal: LiteralDescription
55+
}
56+
57+
extension EnumCase: Equatable { static func == (lhs: EnumCase, rhs: EnumCase) -> Bool { lhs.id == rhs.id } }
58+
59+
extension EnumCase: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(id) } }
60+
61+
extension FileTranslator {
62+
63+
/// Returns a declaration of the specified raw value-based enum schema.
64+
/// - Parameters:
65+
/// - backingType: The backing type of the enum.
66+
/// - typeName: The name of the type to give to the declared enum.
67+
/// - userDescription: A user-specified description from the OpenAPI
68+
/// document.
69+
/// - isNullable: Whether the enum schema is nullable.
70+
/// - allowedValues: The enumerated allowed values.
71+
/// - Throws: A `GenericError` if a disallowed value is encountered.
72+
/// - Returns: A declaration of the specified raw value-based enum schema.
73+
func translateRawEnum(
74+
backingType: RawEnumBackingType,
75+
typeName: TypeName,
76+
userDescription: String?,
77+
isNullable: Bool,
78+
allowedValues: [AnyCodable]
79+
) throws -> Declaration {
80+
var cases: OrderedSet<EnumCase> = []
81+
func addIfUnique(id: EnumCaseID, caseName: String) throws {
82+
let literal: LiteralDescription
83+
switch id {
84+
case .string(let string): literal = .string(string)
85+
case .integer(let int): literal = .int(int)
86+
}
87+
guard cases.append(.init(id: id, caseName: caseName, literal: literal)).inserted else {
88+
try diagnostics.emit(
89+
.warning(
90+
message: "Duplicate enum value, skipping",
91+
context: ["id": "\(id)", "foundIn": typeName.description]
92+
)
93+
)
94+
return
95+
}
96+
}
97+
for anyValue in allowedValues.map(\.value) {
98+
switch backingType {
99+
case .string:
100+
// In nullable enum schemas, empty strings are parsed as Void.
101+
// This is unlikely to be fixed, so handling that case here.
102+
// https://github.com/apple/swift-openapi-generator/issues/118
103+
if isNullable && anyValue is Void {
104+
try addIfUnique(id: .string(""), caseName: context.asSwiftSafeName(""))
105+
} else {
106+
guard let rawValue = anyValue as? String else {
107+
throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)")
108+
}
109+
let caseName = context.asSwiftSafeName(rawValue)
110+
try addIfUnique(id: .string(rawValue), caseName: caseName)
111+
}
112+
case .integer:
113+
let rawValue: Int
114+
if let intRawValue = anyValue as? Int {
115+
rawValue = intRawValue
116+
} else if let stringRawValue = anyValue as? String, let intRawValue = Int(stringRawValue) {
117+
rawValue = intRawValue
118+
} else {
119+
throw GenericError(message: "Disallowed value for an integer enum '\(typeName)': \(anyValue)")
120+
}
121+
let caseName = rawValue < 0 ? "_n\(abs(rawValue))" : "_\(rawValue)"
122+
try addIfUnique(id: .integer(rawValue), caseName: caseName)
123+
}
124+
}
125+
let baseConformance: String
126+
switch backingType {
127+
case .string: baseConformance = Constants.RawEnum.baseConformanceString
128+
case .integer: baseConformance = Constants.RawEnum.baseConformanceInteger
129+
}
130+
let conformances = [baseConformance] + Constants.RawEnum.conformances
131+
return try translateRawRepresentableEnum(
132+
typeName: typeName,
133+
conformances: conformances,
134+
userDescription: userDescription,
135+
cases: cases.map { ($0.caseName, $0.literal) },
136+
unknownCaseName: nil,
137+
unknownCaseDescription: nil
138+
)
139+
}
140+
}

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift

Lines changed: 0 additions & 86 deletions
This file was deleted.

Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ final class Test_YamsParser: Test_Core {
5656
"""
5757

5858
let expected =
59-
"/foo.yaml: error: No openapi key found, please provide a valid OpenAPI document with OpenAPI versions in the 3.0.x or 3.1.x sets."
59+
"/foo.yaml: error: No key named openapi found. Please provide a valid OpenAPI document with OpenAPI versions in the 3.0.x or 3.1.x sets."
6060
assertThrownError(try _test(yaml), expectedDiagnostic: expected)
6161
}
6262

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,33 @@ final class SnippetBasedReferenceTests: XCTestCase {
13101310
)
13111311
}
13121312

1313+
func testComponentsSchemasStringEnumWithDuplicates() throws {
1314+
try self.assertSchemasTranslation(
1315+
ignoredDiagnosticMessages: ["Duplicate enum value, skipping"],
1316+
"""
1317+
schemas:
1318+
MyEnum:
1319+
type: string
1320+
enum:
1321+
- one
1322+
- two
1323+
- three
1324+
- two
1325+
- four
1326+
""",
1327+
"""
1328+
public enum Schemas {
1329+
@frozen public enum MyEnum: String, Codable, Hashable, Sendable, CaseIterable {
1330+
case one = "one"
1331+
case two = "two"
1332+
case three = "three"
1333+
case four = "four"
1334+
}
1335+
}
1336+
"""
1337+
)
1338+
}
1339+
13131340
func testComponentsSchemasIntEnum() throws {
13141341
try self.assertSchemasTranslation(
13151342
"""

0 commit comments

Comments
 (0)