Skip to content
Open
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
6 changes: 5 additions & 1 deletion Sources/SwiftGraphQLCLI/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ struct SwiftGraphQLCLI: ParsableCommand {
files = try generator.generate(
schema: schema,
generateStaticFields: config.generateStaticFields != false,
singleFile: singleFileOutput
singleFile: singleFileOutput,
enumFallbackCase: config.enumFallbackCase
)
generateCodeSpinner.success("API generated successfully!")
} catch CodegenError.formatting(let err) {
Expand Down Expand Up @@ -197,6 +198,9 @@ struct Config: Codable, Equatable {
/// Whether to generate static lookups for object fields
var generateStaticFields: Bool?

/// An extra fallback case for all enums if decoding fails. "unknown": is a recommended value. If the case already exists in the enum it will use that, otherwise generate a new case
var enumFallbackCase: String?

// MARK: - Initializers

/// Creates an empty configuration instance.
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftGraphQLCodegen/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public struct GraphQLCodegen {
/// - generateStaticFields: Whether to generate static selections for fields on objects
/// - singleFile: Whether to return all the swift code in a single file
/// - Returns: A list of generated files
public func generate(schema: Schema, generateStaticFields: Bool, singleFile: Bool = false) throws -> [GeneratedFile] {
public func generate(schema: Schema, generateStaticFields: Bool, singleFile: Bool = false, enumFallbackCase: String? = nil) throws -> [GeneratedFile] {
let context = Context(schema: schema, scalars: self.scalars)

let subscription = schema.operations.first { $0.isSubscription }?.type.name
Expand Down Expand Up @@ -87,7 +87,7 @@ public struct GraphQLCodegen {
}

for enumSchema in schema.enums {
try addFile(name: "Enums/\(enumSchema.name)", contents: enumSchema.declaration)
try addFile(name: "Enums/\(enumSchema.name)", contents: enumSchema.declaration(fallbackCase: enumFallbackCase))
}

for interface in schema.interfaces {
Expand Down
25 changes: 18 additions & 7 deletions Sources/SwiftGraphQLCodegen/Generator/Enum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import SwiftGraphQLUtils
extension EnumType {

/// Represents the enum structure.
var declaration: String {
func declaration(fallbackCase: String? = nil) -> String {
"""
extension Enums {
\(docs)
public enum \(name.pascalCase): String, CaseIterable, Codable {
\(values)
\(values(fallbackCase: fallbackCase))
}
}

extension Enums.\(name.pascalCase): GraphQLScalar {
\(decode)
\(decode(fallbackCase: fallbackCase))

\(mock)
}
Expand All @@ -29,21 +29,32 @@ extension EnumType {
}

/// Represents possible enum cases.
private var values: String {
enumValues.map { $0.declaration }.joined(separator: "\n")
private func values(fallbackCase: String?) -> String {
var cases = enumValues.map { $0.declaration }
if let fallbackCase, !enumValues.contains(where: { $0.name == fallbackCase }) {
let fallbackEnumValue = EnumValue(
name: fallbackCase,
description: "Fallback in case decoding fails.",
isDeprecated: false,
deprecationReason: nil
)
cases.append("")
cases.append(fallbackEnumValue.declaration)
}
return cases.joined(separator: "\n")
}

// MARK: - GraphQL Scalar

private var decode: String {
private func decode(fallbackCase: String?) -> String {
return """
public init(from data: AnyCodable) throws {
switch data.value {
case let string as String:
if let value = Enums.\(self.name.pascalCase)(rawValue: string) {
self = value
} else {
throw ScalarDecodingError.unknownEnumCase(value: string)
\(fallbackCase != nil ? "self = .\(fallbackCase!.camelCasePreservingSurroundingUnderscores.normalize)" : "throw ScalarDecodingError.unknownEnumCase(value: string)")
}
default:
throw ScalarDecodingError.unexpectedScalarType(
Expand Down
111 changes: 110 additions & 1 deletion Tests/SwiftGraphQLCodegenTests/Generator/EnumTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final class EnumTests: XCTestCase {
]
)

let generated = try type.declaration.format()
let generated = try type.declaration().format()

generated.assertInlineSnapshot(matching: """
extension Enums {
Expand Down Expand Up @@ -78,4 +78,113 @@ final class EnumTests: XCTestCase {
}
""")
}

func testFallbackCase() throws {

let type = EnumType(
name: "Episodes",
description: "Collection of all StarWars episodes.\nEarliest trilogy.",
enumValues: [
EnumValue(
name: "NEWHOPE",
description: "Released in 1977.",
isDeprecated: false,
deprecationReason: nil
),
]
)

let generated = try type.declaration(fallbackCase: "unknown").format()

generated.assertInlineSnapshot(matching: """
extension Enums {
/// Collection of all StarWars episodes.
/// Earliest trilogy.
public enum Episodes: String, CaseIterable, Codable {
/// Released in 1977.
case newhope = "NEWHOPE"

/// Fallback in case decoding fails.
case unknown = "unknown"
}
}

extension Enums.Episodes: GraphQLScalar {
public init(from data: AnyCodable) throws {
switch data.value {
case let string as String:
if let value = Enums.Episodes(rawValue: string) {
self = value
} else {
self = .unknown
}
default:
throw ScalarDecodingError.unexpectedScalarType(
expected: "Episodes",
received: data.value
)
}
}

public static var mockValue = Self.newhope
}
""")
}

func testMatchingFallbackCase() throws {

let type = EnumType(
name: "Episodes",
description: "Collection of all StarWars episodes.\nEarliest trilogy.",
enumValues: [
EnumValue(
name: "NEWHOPE",
description: "Released in 1977.",
isDeprecated: false,
deprecationReason: nil
),
EnumValue(
name: "unknown",
description: "An unknown episode",
isDeprecated: false,
deprecationReason: nil
),
]
)

let generated = try type.declaration(fallbackCase: "unknown").format()

generated.assertInlineSnapshot(matching: """
extension Enums {
/// Collection of all StarWars episodes.
/// Earliest trilogy.
public enum Episodes: String, CaseIterable, Codable {
/// Released in 1977.
case newhope = "NEWHOPE"
/// An unknown episode
case unknown = "unknown"
}
}

extension Enums.Episodes: GraphQLScalar {
public init(from data: AnyCodable) throws {
switch data.value {
case let string as String:
if let value = Enums.Episodes(rawValue: string) {
self = value
} else {
self = .unknown
}
default:
throw ScalarDecodingError.unexpectedScalarType(
expected: "Episodes",
received: data.value
)
}
}

public static var mockValue = Self.newhope
}
""")
}
}
Loading