Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip duplicate enum values #674

Merged
merged 4 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ let package = Package(

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

// Read OpenAPI documents
.package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "3.3.0"),
Expand All @@ -72,7 +73,9 @@ let package = Package(
.product(name: "OpenAPIKit", package: "OpenAPIKit"),
.product(name: "OpenAPIKit30", package: "OpenAPIKit"),
.product(name: "OpenAPIKitCompat", package: "OpenAPIKit"),
.product(name: "Algorithms", package: "swift-algorithms"), .product(name: "Yams", package: "Yams"),
.product(name: "Algorithms", package: "swift-algorithms"),
.product(name: "OrderedCollections", package: "swift-collections"),
.product(name: "Yams", package: "Yams"),
],
swiftSettings: swiftSettings
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import OpenAPIKit
import OrderedCollections

/// The backing type of a raw enum.
enum RawEnumBackingType {

/// Backed by a `String`.
case string

/// Backed by an `Int`.
case integer
}

/// The extracted enum value's identifier.
private enum EnumCaseID: Hashable, CustomStringConvertible {

/// A string value.
case string(String)

/// An integer value.
case integer(Int)

var description: String {
switch self {
case .string(let value): return "\"\(value)\""
case .integer(let value): return String(value)
}
}
simonjbeaumont marked this conversation as resolved.
Show resolved Hide resolved
}

/// A wrapper for the metadata about the raw enum case.
private struct EnumCase {

/// Used for checking uniqueness.
var id: EnumCaseID

/// The raw Swift-safe name for the case.
var caseName: String

/// The literal value of the enum case.
var literal: LiteralDescription
}

extension EnumCase: Equatable { static func == (lhs: EnumCase, rhs: EnumCase) -> Bool { lhs.id == rhs.id } }

extension EnumCase: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(id) } }

extension FileTranslator {

/// Returns a declaration of the specified raw value-based enum schema.
/// - Parameters:
/// - backingType: The backing type of the enum.
/// - typeName: The name of the type to give to the declared enum.
/// - userDescription: A user-specified description from the OpenAPI
/// document.
/// - isNullable: Whether the enum schema is nullable.
/// - allowedValues: The enumerated allowed values.
/// - Throws: A `GenericError` if a disallowed value is encountered.
/// - Returns: A declaration of the specified raw value-based enum schema.
func translateRawEnum(
backingType: RawEnumBackingType,
typeName: TypeName,
userDescription: String?,
isNullable: Bool,
allowedValues: [AnyCodable]
) throws -> Declaration {
var cases: OrderedSet<EnumCase> = []
func addIfUnique(id: EnumCaseID, caseName: String) throws {
let literal: LiteralDescription
switch id {
case .string(let string): literal = .string(string)
case .integer(let int): literal = .int(int)
}
guard cases.append(.init(id: id, caseName: caseName, literal: literal)).inserted else {
try diagnostics.emit(
.warning(
message: "Duplicate enum value, skipping",
context: ["id": "\(id)", "foundIn": typeName.description]
)
)
return
}
}
for anyValue in allowedValues.map(\.value) {
switch backingType {
case .string:
// In nullable enum schemas, empty strings are parsed as Void.
// This is unlikely to be fixed, so handling that case here.
// https://github.com/apple/swift-openapi-generator/issues/118
if isNullable && anyValue is Void {
try addIfUnique(id: .string(""), caseName: context.asSwiftSafeName(""))
} else {
guard let rawValue = anyValue as? String else {
throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)")
}
let caseName = context.asSwiftSafeName(rawValue)
try addIfUnique(id: .string(rawValue), caseName: caseName)
}
case .integer:
let rawValue: Int
if let intRawValue = anyValue as? Int {
rawValue = intRawValue
} else if let stringRawValue = anyValue as? String, let intRawValue = Int(stringRawValue) {
rawValue = intRawValue
} else {
throw GenericError(message: "Disallowed value for an integer enum '\(typeName)': \(anyValue)")
}
let caseName = rawValue < 0 ? "_n\(abs(rawValue))" : "_\(rawValue)"
try addIfUnique(id: .integer(rawValue), caseName: caseName)
}
}
let baseConformance: String
switch backingType {
case .string: baseConformance = Constants.RawEnum.baseConformanceString
case .integer: baseConformance = Constants.RawEnum.baseConformanceInteger
}
let conformances = [baseConformance] + Constants.RawEnum.conformances
return try translateRawRepresentableEnum(
typeName: typeName,
conformances: conformances,
userDescription: userDescription,
cases: cases.map { ($0.caseName, $0.literal) },
unknownCaseName: nil,
unknownCaseDescription: nil
)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,33 @@ final class SnippetBasedReferenceTests: XCTestCase {
)
}

func testComponentsSchemasStringEnumWithDuplicates() throws {
try self.assertSchemasTranslation(
ignoredDiagnosticMessages: ["Duplicate enum value, skipping"],
"""
schemas:
MyEnum:
type: string
enum:
- one
- two
- three
- two
- four
""",
"""
public enum Schemas {
@frozen public enum MyEnum: String, Codable, Hashable, Sendable, CaseIterable {
case one = "one"
case two = "two"
case three = "three"
case four = "four"
}
}
"""
)
}

func testComponentsSchemasIntEnum() throws {
try self.assertSchemasTranslation(
"""
Expand Down