From 53ca0632eff2bbc89bc3634079ec0c08827f8b2e Mon Sep 17 00:00:00 2001 From: gh-action-runner Date: Thu, 13 Jun 2024 17:00:12 +0000 Subject: [PATCH] Squashed 'apollo-ios-codegen/' changes from 54e956a2..d4eaa545 d4eaa545 Feature: Schema Type Renaming (apollographql/apollo-ios-dev#388) git-subtree-dir: apollo-ios-codegen git-subtree-split: d4eaa5457e0b3f0f4b42671d16efe1f649003398 --- Sources/ApolloCodegenLib/ApolloCodegen.swift | 97 ++++++-- .../ApolloCodegenConfiguration.swift | 14 ++ ...genConfiguration+SchemaCustomization.swift | 233 ++++++++++++++++++ .../ConfigurationValidation.swift | 2 +- .../CustomScalarFileGenerator.swift | 2 +- .../FileGenerators/EnumFileGenerator.swift | 2 +- .../InputObjectFileGenerator.swift | 2 +- .../InterfaceFileGenerator.swift | 2 +- .../MockInterfacesFileGenerator.swift | 6 +- .../MockObjectFileGenerator.swift | 2 +- .../MockUnionsFileGenerator.swift | 8 +- .../FileGenerators/ObjectFileGenerator.swift | 2 +- .../FileGenerators/UnionFileGenerator.swift | 2 +- .../Templates/CustomScalarTemplate.swift | 3 +- .../Templates/EnumTemplate.swift | 8 +- .../Templates/InputObjectTemplate.swift | 16 +- .../Templates/InterfaceTemplate.swift | 3 +- .../Templates/MockInterfacesTemplate.swift | 6 +- .../Templates/MockObjectTemplate.swift | 4 +- .../Templates/MockUnionsTemplate.swift | 6 +- .../Templates/ObjectTemplate.swift | 7 +- .../GraphQLEnumValue+Rendered.swift | 28 --- .../GraphQLName+RenderingHelper.swift | 161 ++++++++++++ .../GraphQLNamedType+NameFormatting.swift | 89 ------- .../GraphQLType+Rendered.swift | 15 +- .../InputVariableRenderable.swift | 11 +- .../String+SwiftNameEscaping.swift | 20 +- .../Templates/SchemaMetadataTemplate.swift | 2 +- .../Templates/SelectionSetTemplate.swift | 6 +- .../Templates/UnionTemplate.swift | 7 +- Sources/GraphQLCompiler/GraphQLName.swift | 51 ++++ Sources/GraphQLCompiler/GraphQLSchema.swift | 54 ++-- Sources/GraphQLCompiler/GraphQLType.swift | 2 +- 33 files changed, 636 insertions(+), 237 deletions(-) create mode 100644 Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration+SchemaCustomization.swift delete mode 100644 Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLEnumValue+Rendered.swift create mode 100644 Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLName+RenderingHelper.swift delete mode 100644 Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLNamedType+NameFormatting.swift create mode 100644 Sources/GraphQLCompiler/GraphQLName.swift diff --git a/Sources/ApolloCodegenLib/ApolloCodegen.swift b/Sources/ApolloCodegenLib/ApolloCodegen.swift index 58ef7a526..d34b527fd 100644 --- a/Sources/ApolloCodegenLib/ApolloCodegen.swift +++ b/Sources/ApolloCodegenLib/ApolloCodegen.swift @@ -118,10 +118,11 @@ public class ApolloCodegen { try config.validateConfigValues() let compilationResult = try await compileGraphQLResult() - try config.validate(compilationResult) let ir = IRBuilder(compilationResult: compilationResult) + + processSchemaCustomizations(ir: ir) try await withThrowingTaskGroup(of: Void.self) { group in if itemsToGenerate.contains(.operationManifest) { @@ -321,6 +322,72 @@ public class ApolloCodegen { } } } + + func processSchemaCustomizations(ir: IRBuilder) { + for (name, customization) in config.options.schemaCustomization.customTypeNames { + if let type = ir.schema.referencedTypes.allTypes.first(where: { $0.name.schemaName == name }) { + if type is GraphQLObjectType || + type is GraphQLInterfaceType || + type is GraphQLUnionType { + switch customization { + case .type(let name): + type.name.customName = name + default: + break + } + } else if let scalarType = type as? GraphQLScalarType { + guard scalarType.isCustomScalar else { + return + } + + switch customization { + case .type(let name): + type.name.customName = name + default: + break + } + } else if let enumType = type as? GraphQLEnumType { + switch customization { + case .type(let name): + enumType.name.customName = name + break + case .enum(let name, let cases): + enumType.name.customName = name + + if let cases = cases { + for value in enumType.values { + if let caseName = cases[value.name.schemaName] { + value.name.customName = caseName + } + } + } + break + default: + break + } + } else if let inputObjectType = type as? GraphQLInputObjectType { + switch customization { + case .type(let name): + inputObjectType.name.customName = name + break + case .inputObject(let name, let fields): + inputObjectType.name.customName = name + + if let fields = fields { + for (_, field) in inputObjectType.fields { + if let fieldName = fields[field.name.schemaName] { + field.name.customName = fieldName + } + } + } + break + default: + break + } + } + } + } + } /// Generates the schema types and schema metadata files for the `ir`'s compiled schema. private func generateSchemaFiles( @@ -333,10 +400,10 @@ public class ApolloCodegen { nonFatalErrors.merge( try await nonFatalErrorCollectingTaskGroup() { group in - for graphQLObject in ir.schema.referencedTypes.objects { + for graphqlObject in ir.schema.referencedTypes.objects { addFileGenerationTask( for: ObjectFileGenerator( - graphqlObject: graphQLObject, + graphqlObject: graphqlObject, config: config ), to: &group, @@ -344,10 +411,10 @@ public class ApolloCodegen { ) if config.output.testMocks != .none { - let fields = await ir.fieldCollector.collectedFields(for: graphQLObject) + let fields = await ir.fieldCollector.collectedFields(for: graphqlObject) addFileGenerationTask( for: MockObjectFileGenerator( - graphqlObject: graphQLObject, + graphqlObject: graphqlObject, fields: fields, ir: ir, config: config @@ -362,10 +429,10 @@ public class ApolloCodegen { nonFatalErrors.merge( try await nonFatalErrorCollectingTaskGroup() { group in - for graphQLEnum in ir.schema.referencedTypes.enums { + for graphqlEnum in ir.schema.referencedTypes.enums { addFileGenerationTask( for: EnumFileGenerator( - graphqlEnum: graphQLEnum, + graphqlEnum: graphqlEnum, config: config ), to: &group, @@ -378,10 +445,10 @@ public class ApolloCodegen { nonFatalErrors.merge( try await nonFatalErrorCollectingTaskGroup() { group in - for graphQLInterface in ir.schema.referencedTypes.interfaces { + for graphqlInterface in ir.schema.referencedTypes.interfaces { addFileGenerationTask( for: InterfaceFileGenerator( - graphqlInterface: graphQLInterface, + graphqlInterface: graphqlInterface, config: config ), to: &group, @@ -393,10 +460,10 @@ public class ApolloCodegen { nonFatalErrors.merge( try await nonFatalErrorCollectingTaskGroup() { group in - for graphQLUnion in ir.schema.referencedTypes.unions { + for graphqlUnion in ir.schema.referencedTypes.unions { addFileGenerationTask( for: UnionFileGenerator( - graphqlUnion: graphQLUnion, + graphqlUnion: graphqlUnion, config: config ), to: &group, @@ -408,10 +475,10 @@ public class ApolloCodegen { nonFatalErrors.merge( try await nonFatalErrorCollectingTaskGroup() { group in - for graphQLInputObject in ir.schema.referencedTypes.inputObjects { + for graphqlInputObject in ir.schema.referencedTypes.inputObjects { addFileGenerationTask( for: InputObjectFileGenerator( - graphqlInputObject: graphQLInputObject, + graphqlInputObject: graphqlInputObject, config: config ), to: &group, @@ -423,10 +490,10 @@ public class ApolloCodegen { nonFatalErrors.merge( try await nonFatalErrorCollectingTaskGroup() { group in - for graphQLScalar in ir.schema.referencedTypes.customScalars { + for graphqlScalar in ir.schema.referencedTypes.customScalars { addFileGenerationTask( for: CustomScalarFileGenerator( - graphqlScalar: graphQLScalar, + graphqlScalar: graphqlScalar, config: config ), to: &group, fileManager: fileManager diff --git a/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift b/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift index 7b88933c5..1d667a3ad 100644 --- a/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift +++ b/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift @@ -468,6 +468,8 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { public let selectionSetInitializers: SelectionSetInitializers /// How to generate the operation documents for your generated operations. public let operationDocumentFormat: OperationDocumentFormat + /// Customization options to be applie to the schema during code generation. + public let schemaCustomization: SchemaCustomization /// Generate import statements that are compatible with including `Apollo` via Cocoapods. /// /// Cocoapods bundles all files from subspecs into the main target for a pod. This means that @@ -514,6 +516,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { public static let schemaDocumentation: Composition = .include public static let selectionSetInitializers: SelectionSetInitializers = [.localCacheMutations] public static let operationDocumentFormat: OperationDocumentFormat = .definition + public static let schemaCustomization: SchemaCustomization = .init() public static let cocoapodsCompatibleImportStatements: Bool = false public static let warningsOnDeprecatedUsage: Composition = .include public static let conversionStrategies: ConversionStrategies = .init() @@ -546,6 +549,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { schemaDocumentation: Composition = Default.schemaDocumentation, selectionSetInitializers: SelectionSetInitializers = Default.selectionSetInitializers, operationDocumentFormat: OperationDocumentFormat = Default.operationDocumentFormat, + schemaCustomization: SchemaCustomization = Default.schemaCustomization, cocoapodsCompatibleImportStatements: Bool = Default.cocoapodsCompatibleImportStatements, warningsOnDeprecatedUsage: Composition = Default.warningsOnDeprecatedUsage, conversionStrategies: ConversionStrategies = Default.conversionStrategies, @@ -557,6 +561,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { self.schemaDocumentation = schemaDocumentation self.selectionSetInitializers = selectionSetInitializers self.operationDocumentFormat = operationDocumentFormat + self.schemaCustomization = schemaCustomization self.cocoapodsCompatibleImportStatements = cocoapodsCompatibleImportStatements self.warningsOnDeprecatedUsage = warningsOnDeprecatedUsage self.conversionStrategies = conversionStrategies @@ -574,6 +579,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { case selectionSetInitializers case apqs case operationDocumentFormat + case schemaCustomization case cocoapodsCompatibleImportStatements case warningsOnDeprecatedUsage case conversionStrategies @@ -614,6 +620,11 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { forKey: .apqs )?.operationDocumentFormat ?? Default.operationDocumentFormat + + schemaCustomization = try values.decodeIfPresent( + SchemaCustomization.self, + forKey: .schemaCustomization + ) ?? Default.schemaCustomization cocoapodsCompatibleImportStatements = try values.decodeIfPresent( Bool.self, @@ -649,6 +660,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { try container.encode(self.schemaDocumentation, forKey: .schemaDocumentation) try container.encode(self.selectionSetInitializers, forKey: .selectionSetInitializers) try container.encode(self.operationDocumentFormat, forKey: .operationDocumentFormat) + try container.encode(self.schemaCustomization, forKey: .schemaCustomization) try container.encode(self.cocoapodsCompatibleImportStatements, forKey: .cocoapodsCompatibleImportStatements) try container.encode(self.warningsOnDeprecatedUsage, forKey: .warningsOnDeprecatedUsage) try container.encode(self.conversionStrategies, forKey: .conversionStrategies) @@ -1368,6 +1380,7 @@ extension ApolloCodegenConfiguration.OutputOptions { self.conversionStrategies = conversionStrategies self.pruneGeneratedFiles = pruneGeneratedFiles self.markOperationDefinitionsAsFinal = markOperationDefinitionsAsFinal + self.schemaCustomization = Default.schemaCustomization } /// Deprecated initializer. @@ -1417,6 +1430,7 @@ extension ApolloCodegenConfiguration.OutputOptions { self.conversionStrategies = conversionStrategies self.pruneGeneratedFiles = pruneGeneratedFiles self.markOperationDefinitionsAsFinal = markOperationDefinitionsAsFinal + self.schemaCustomization = Default.schemaCustomization } /// Whether the generated operations should use Automatic Persisted Queries. diff --git a/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration+SchemaCustomization.swift b/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration+SchemaCustomization.swift new file mode 100644 index 000000000..a6c4883c3 --- /dev/null +++ b/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration+SchemaCustomization.swift @@ -0,0 +1,233 @@ +import Foundation + +extension ApolloCodegenConfiguration { + + public struct SchemaCustomization: Codable, Equatable { + + // MARK: - Properties + + /// Dictionary with Keys representing the types being renamed/customized, and + public let customTypeNames: [String: CustomSchemaTypeName] + + /// Default property values + public struct Default { + public static let customTypeNames: [String: CustomSchemaTypeName] = [:] + } + + // MARK: - Initialization + + /// Designated initializer + /// + /// - Parameters: + /// - customTypeNames: Dictionary repsenting the types to be renamed and how to rename them. + public init( + customTypeNames: [String: CustomSchemaTypeName] = Default.customTypeNames + ) { + self.customTypeNames = customTypeNames + } + + // MARK: - Codable + + enum CodingKeys: CodingKey, CaseIterable { + case customTypeNames + } + + public init(from decoder: any Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + try throwIfContainsUnexpectedKey( + container: values, + type: Self.self, + decoder: decoder + ) + + customTypeNames = try values.decode( + [String: CustomSchemaTypeName].self, + forKey: .customTypeNames + ) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.customTypeNames, forKey: .customTypeNames) + } + + // MARK: - Enums + + public enum CustomSchemaTypeName: Codable, ExpressibleByStringLiteral, Equatable { + case type(name: String) + case `enum`(name: String?, cases: [String: String]?) + case inputObject(name: String?, fields: [String: String]?) + + public init(stringLiteral value: String) { + self = .type(name: value) + } + + enum CodingKeys: CodingKey, CaseIterable { + case type + case `enum` + case inputObject + } + + enum TypeCodingKeys: CodingKey, CaseIterable { + case name + } + + enum EnumCodingKeys: CodingKey, CaseIterable { + case name + case cases + } + + enum InputObjectCodingKeys: CodingKey, CaseIterable { + case name + case fields + } + + public init(from decoder: any Decoder) throws { + guard let originalTypeName = decoder.codingPath.last?.stringValue else { + preconditionFailure("Unable to get original type name value from JSON during decoding.") + } + var customTypeName: CustomSchemaTypeName? + + if let container = try? decoder.container(keyedBy: CodingKeys.self) { + switch container.allKeys.first { + case .type: + let subContainer = try container.nestedContainer(keyedBy: TypeCodingKeys.self, forKey: .type) + let name = try subContainer.decodeIfPresentOrEmpty(type: String.self, key: .name) + guard let name = name else { + throw Error.emptyCustomization(type: originalTypeName) + } + customTypeName = .type(name: name) + break + case .enum: + let subContainer = try container.nestedContainer(keyedBy: EnumCodingKeys.self, forKey: .enum) + let name = try subContainer.decodeIfPresentOrEmpty(type: String.self, key: .name) + let cases = try subContainer.decodeIfPresentOrEmpty(type: [String: String].self, key: .cases) + + guard name != nil || cases != nil else { + throw Error.emptyCustomization(type: originalTypeName) + } + + if let name = name, cases == nil { + customTypeName = .type(name: name) + } else { + customTypeName = .enum(name: name, cases: cases) + } + break + case .inputObject: + let subContainer = try container.nestedContainer(keyedBy: InputObjectCodingKeys.self, forKey: .inputObject) + let name = try subContainer.decodeIfPresentOrEmpty(type: String.self, key: .name) + let fields = try subContainer.decodeIfPresentOrEmpty(type: [String: String].self, key: .fields) + + guard name != nil || fields != nil else { + throw Error.emptyCustomization(type: originalTypeName) + } + + if let name = name, fields == nil { + customTypeName = .type(name: name) + } else { + customTypeName = .inputObject(name: name, fields: fields) + } + break + case .none: + break + } + } else if let container = try? decoder.singleValueContainer() { + let name = try container.decode(String.self) + guard !name.isEmpty else { + throw Error.emptyCustomization(type: originalTypeName) + } + customTypeName = .type(name: name) + } + + if let customTypeName = customTypeName { + self = customTypeName + } else { + throw Error.decodingFailure(type: originalTypeName) + } + } + + public func encode(to encoder: any Encoder) throws { + guard let originalTypeName = encoder.codingPath.last?.stringValue else { + preconditionFailure("Unable to get original type name value from type during decoding.") + } + switch self { + case .type(let name): + guard !name.isEmpty else { + throw Error.emptyCustomization(type: originalTypeName) + } + var container = encoder.singleValueContainer() + try container.encode(name) + case .enum(let name, let cases): + guard (name != nil && !(name ?? "").isEmpty) || (cases != nil && !(cases ?? [:]).isEmpty) else { + throw Error.emptyCustomization(type: originalTypeName) + } + + if cases == nil { + var container = encoder.singleValueContainer() + try container.encode(name) + } else { + var container = encoder.container(keyedBy: CodingKeys.self) + var subContainer = container.nestedContainer(keyedBy: EnumCodingKeys.self, forKey: .enum) + try subContainer.encodeIfPresent(name, forKey: .name) + try subContainer.encodeIfPresent(cases, forKey: .cases) + } + case .inputObject(let name, let fields): + guard (name != nil && !(name ?? "").isEmpty) || (fields != nil && !(fields ?? [:]).isEmpty) else { + throw Error.emptyCustomization(type: originalTypeName) + } + + if name != nil, fields == nil { + var container = encoder.singleValueContainer() + try container.encode(name) + } else { + var container = encoder.container(keyedBy: CodingKeys.self) + var subContainer = container.nestedContainer(keyedBy: InputObjectCodingKeys.self, forKey: .inputObject) + try subContainer.encodeIfPresent(name, forKey: .name) + try subContainer.encodeIfPresent(fields, forKey: .fields) + } + } + } + + } + + public enum Error: Swift.Error, LocalizedError { + case decodingFailure(type: String) + case emptyCustomization(type: String) + + public var errorDescription: String? { + switch self { + case let .decodingFailure(type): + return """ + Unable to decode type '\(type)' when processing custom schema + type names. + """ + case let .emptyCustomization(type): + return """ + No customization data was provided for type '\(type)', customization + will be ignored. + """ + } + } + } + + } + +} + +extension KeyedDecodingContainer { + fileprivate func decodeIfPresentOrEmpty(type: String.Type, key: KeyedDecodingContainer.Key) throws -> String? { + if let value = try decodeIfPresent(type, forKey: key) { + return value.isEmpty ? nil : value + } + return nil + } + + fileprivate func decodeIfPresentOrEmpty(type: [String: String].Type, key: KeyedDecodingContainer.Key) throws -> [String: String]? { + if let value = try decodeIfPresent(type, forKey: key) { + return value.isEmpty ? nil : value + } + return nil + } +} + diff --git a/Sources/ApolloCodegenLib/ConfigurationValidation.swift b/Sources/ApolloCodegenLib/ConfigurationValidation.swift index 00281f825..c2abebe7f 100644 --- a/Sources/ApolloCodegenLib/ConfigurationValidation.swift +++ b/Sources/ApolloCodegenLib/ConfigurationValidation.swift @@ -67,7 +67,7 @@ extension ApolloCodegen.ConfigurationContext { func validate(_ compilationResult: CompilationResult) throws { guard !compilationResult.referencedTypes.contains(where: { namedType in - namedType.swiftName == self.schemaNamespace.firstUppercased + namedType.name.swiftName == self.schemaNamespace.firstUppercased }), !compilationResult.fragments.contains(where: { fragmentDefinition in fragmentDefinition.name == self.schemaNamespace.firstUppercased diff --git a/Sources/ApolloCodegenLib/FileGenerators/CustomScalarFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/CustomScalarFileGenerator.swift index 6c096aa00..a7adb5ff9 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/CustomScalarFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/CustomScalarFileGenerator.swift @@ -13,6 +13,6 @@ struct CustomScalarFileGenerator: FileGenerator { } var target: FileTarget { .customScalar } - var fileName: String { graphqlScalar.name } + var fileName: String { graphqlScalar.render(as: .filename) } var overwrite: Bool { false } } diff --git a/Sources/ApolloCodegenLib/FileGenerators/EnumFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/EnumFileGenerator.swift index e8e156c23..4456f3578 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/EnumFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/EnumFileGenerator.swift @@ -13,5 +13,5 @@ struct EnumFileGenerator: FileGenerator { } var target: FileTarget { .enum } - var fileName: String { graphqlEnum.name } + var fileName: String { graphqlEnum.render(as: .filename) } } diff --git a/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift index 1dddd3c34..e72433f58 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift @@ -13,5 +13,5 @@ struct InputObjectFileGenerator: FileGenerator { InputObjectTemplate(graphqlInputObject: graphqlInputObject, config: config) } var target: FileTarget { .inputObject } - var fileName: String { graphqlInputObject.name } + var fileName: String { graphqlInputObject.render(as: .filename) } } diff --git a/Sources/ApolloCodegenLib/FileGenerators/InterfaceFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/InterfaceFileGenerator.swift index 871b569c7..f4c0af572 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/InterfaceFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/InterfaceFileGenerator.swift @@ -13,5 +13,5 @@ struct InterfaceFileGenerator: FileGenerator { } var target: FileTarget { .interface } - var fileName: String { graphqlInterface.name } + var fileName: String { graphqlInterface.render(as: .filename) } } diff --git a/Sources/ApolloCodegenLib/FileGenerators/MockInterfacesFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/MockInterfacesFileGenerator.swift index 0d7445630..f994e31ed 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/MockInterfacesFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/MockInterfacesFileGenerator.swift @@ -7,20 +7,20 @@ import GraphQLCompiler /// for testing purposes. struct MockInterfacesFileGenerator: FileGenerator { - let graphQLInterfaces: OrderedSet + let graphqlInterfaces: OrderedSet let config: ApolloCodegen.ConfigurationContext init?(ir: IRBuilder, config: ApolloCodegen.ConfigurationContext) { let interfaces = ir.schema.referencedTypes.interfaces guard !interfaces.isEmpty else { return nil } - self.graphQLInterfaces = interfaces + self.graphqlInterfaces = interfaces self.config = config } var template: any TemplateRenderer { MockInterfacesTemplate( - graphQLInterfaces: graphQLInterfaces, + graphqlInterfaces: graphqlInterfaces, config: config ) } diff --git a/Sources/ApolloCodegenLib/FileGenerators/MockObjectFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/MockObjectFileGenerator.swift index 9403d9f70..936a9e582 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/MockObjectFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/MockObjectFileGenerator.swift @@ -23,5 +23,5 @@ struct MockObjectFileGenerator: FileGenerator { } var target: FileTarget { .testMock } - var fileName: String { "\(graphqlObject.name)+Mock" } + var fileName: String { "\(graphqlObject.render(as: .filename))+Mock" } } diff --git a/Sources/ApolloCodegenLib/FileGenerators/MockUnionsFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/MockUnionsFileGenerator.swift index 577f68e63..990632be1 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/MockUnionsFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/MockUnionsFileGenerator.swift @@ -1,26 +1,26 @@ import Foundation import IR -import GraphQLCompiler import OrderedCollections +import GraphQLCompiler /// Generates a file providing the ability to mock the GraphQLUnionTypes in a schema /// for testing purposes. struct MockUnionsFileGenerator: FileGenerator { - let graphQLUnions: OrderedSet + let graphqlUnions: OrderedSet let config: ApolloCodegen.ConfigurationContext init?(ir: IRBuilder, config: ApolloCodegen.ConfigurationContext) { let unions = ir.schema.referencedTypes.unions guard !unions.isEmpty else { return nil } - self.graphQLUnions = unions + self.graphqlUnions = unions self.config = config } var template: any TemplateRenderer { MockUnionsTemplate( - graphQLUnions: graphQLUnions, + graphqlUnions: graphqlUnions, config: config ) } diff --git a/Sources/ApolloCodegenLib/FileGenerators/ObjectFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/ObjectFileGenerator.swift index 40eccabc8..b7d2c244d 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/ObjectFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/ObjectFileGenerator.swift @@ -14,5 +14,5 @@ struct ObjectFileGenerator: FileGenerator { } var target: FileTarget { .object } - var fileName: String { graphqlObject.name } + var fileName: String { graphqlObject.render(as: .filename) } } diff --git a/Sources/ApolloCodegenLib/FileGenerators/UnionFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/UnionFileGenerator.swift index e42d3507b..fff0bab58 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/UnionFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/UnionFileGenerator.swift @@ -13,5 +13,5 @@ struct UnionFileGenerator: FileGenerator { config: config ) } var target: FileTarget { .union } - var fileName: String { graphqlUnion.name } + var fileName: String { graphqlUnion.render(as: .filename) } } diff --git a/Sources/ApolloCodegenLib/Templates/CustomScalarTemplate.swift b/Sources/ApolloCodegenLib/Templates/CustomScalarTemplate.swift index c665a47c8..c3b5d9853 100644 --- a/Sources/ApolloCodegenLib/Templates/CustomScalarTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/CustomScalarTemplate.swift @@ -26,8 +26,9 @@ struct CustomScalarTemplate: TemplateRenderer { TemplateString( """ \(documentation: documentationTemplate, config: config) + \(graphqlScalar.name.typeNameDocumentation) \(accessControlModifier(for: .parent))\ - typealias \(graphqlScalar.formattedName) = String + typealias \(graphqlScalar.render(as: .typename)) = String """ ) diff --git a/Sources/ApolloCodegenLib/Templates/EnumTemplate.swift b/Sources/ApolloCodegenLib/Templates/EnumTemplate.swift index a3ae8c3a0..cd4614a4b 100644 --- a/Sources/ApolloCodegenLib/Templates/EnumTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/EnumTemplate.swift @@ -18,8 +18,9 @@ struct EnumTemplate: TemplateRenderer { TemplateString( """ \(documentation: graphqlEnum.documentation, config: config) + \(graphqlEnum.name.typeNameDocumentation) \(accessControlModifier(for: .parent))\ - enum \(graphqlEnum.formattedName): String, EnumType { + enum \(graphqlEnum.render(as: .typename)): String, EnumType { \(graphqlEnum.values.compactMap({ enumCase(for: $0) }), separator: "\n") @@ -43,15 +44,16 @@ struct EnumTemplate: TemplateRenderer { \(if: shouldRenderDocumentation, "///") \(documentation: "**Deprecated**: \($0.escapedSwiftStringSpecialCharacters())") """ }) + \(graphqlEnumValue.name.typeNameDocumentation) \(caseDefinition(for: graphqlEnumValue)) """ } private func caseDefinition(for graphqlEnumValue: GraphQLEnumValue) -> TemplateString { """ - case \(graphqlEnumValue.name.rendered(as: .swiftEnumCase, config: config.config))\ + case \(graphqlEnumValue.render(as: .enumCase, config: config))\ \(if: config.options.conversionStrategies.enumCases != .none, """ - = "\(graphqlEnumValue.name.rendered(as: .rawValue, config: config.config))" + = "\(graphqlEnumValue.render(as: .enumRawValue, config: config))" """) """ } diff --git a/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift b/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift index 02c2e0b22..d5e5c3622 100644 --- a/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift @@ -21,8 +21,9 @@ struct InputObjectTemplate: TemplateRenderer { return TemplateString( """ \(documentation: graphqlInputObject.documentation, config: config) + \(graphqlInputObject.name.typeNameDocumentation) \(accessControlModifier(for: .parent))\ - struct \(graphqlInputObject.formattedName): InputObject { + struct \(graphqlInputObject.render(as: .typename)): InputObject { \(memberAccessControl)private(set) var __data: InputDict \(memberAccessControl)init(_ data: InputDict) { @@ -82,7 +83,7 @@ struct InputObjectTemplate: TemplateRenderer { private func deprecatedMessage(for fields: GraphQLInputFieldDictionary) -> String { guard !fields.isEmpty else { return "" } - let names: String = fields.values.map({ $0.name }).joined(separator: ", ") + let names: String = fields.values.map({ $0.render(config: config) }).joined(separator: ", ") if fields.count > 1 { return "Arguments '\(names)' are deprecated." @@ -96,7 +97,7 @@ struct InputObjectTemplate: TemplateRenderer { ) -> TemplateString { TemplateString(""" \(fields.map({ - "\($1.name.renderAsInputObjectName(config: config.config)): \($1.renderInputValueType(includeDefault: true, config: config.config))" + "\($1.render(config: config)): \($1.renderInputValueType(includeDefault: true, config: config.config))" }), separator: ",\n") """) } @@ -105,7 +106,7 @@ struct InputObjectTemplate: TemplateRenderer { _ fields: GraphQLInputFieldDictionary ) -> TemplateString { TemplateString(""" - \(fields.map({ "\"\($1.name)\": \($1.name.renderAsInputObjectName(config: config.config))" }), separator: ",\n") + \(fields.map({ "\"\($1.name.schemaName)\": \($1.render(config: config))" }), separator: ",\n") """) } @@ -113,10 +114,11 @@ struct InputObjectTemplate: TemplateRenderer { """ \(documentation: field.documentation, config: config) \(deprecationReason: field.deprecationReason, config: config) + \(field.name.typeNameDocumentation) \(accessControlModifier(for: .member))\ - var \(field.name.renderAsInputObjectName(config: config.config)): \(field.renderInputValueType(config: config.config)) { - get { __data["\(field.name)"] } - set { __data["\(field.name)"] = newValue } + var \(field.render(config: config)): \(field.renderInputValueType(config: config.config)) { + get { __data["\(field.name.schemaName)"] } + set { __data["\(field.name.schemaName)"] = newValue } } """ } diff --git a/Sources/ApolloCodegenLib/Templates/InterfaceTemplate.swift b/Sources/ApolloCodegenLib/Templates/InterfaceTemplate.swift index e8598b416..52644799f 100644 --- a/Sources/ApolloCodegenLib/Templates/InterfaceTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/InterfaceTemplate.swift @@ -17,7 +17,8 @@ struct InterfaceTemplate: TemplateRenderer { ) -> TemplateString { """ \(documentation: graphqlInterface.documentation, config: config) - static let \(graphqlInterface.formattedName) = \(config.ApolloAPITargetName).Interface(name: "\(graphqlInterface.name)") + \(graphqlInterface.name.typeNameDocumentation) + static let \(graphqlInterface.render(as: .typename)) = \(config.ApolloAPITargetName).Interface(name: "\(graphqlInterface.name.schemaName)") """ } } diff --git a/Sources/ApolloCodegenLib/Templates/MockInterfacesTemplate.swift b/Sources/ApolloCodegenLib/Templates/MockInterfacesTemplate.swift index 2b9327910..a6e9bbad0 100644 --- a/Sources/ApolloCodegenLib/Templates/MockInterfacesTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/MockInterfacesTemplate.swift @@ -5,7 +5,7 @@ import TemplateString struct MockInterfacesTemplate: TemplateRenderer { - let graphQLInterfaces: OrderedSet + let graphqlInterfaces: OrderedSet let config: ApolloCodegen.ConfigurationContext @@ -16,8 +16,8 @@ struct MockInterfacesTemplate: TemplateRenderer { ) -> TemplateString { TemplateString(""" \(accessControlModifier(for: .parent))extension MockObject { - \(graphQLInterfaces.map { - "typealias \($0.formattedName) = Interface" + \(graphqlInterfaces.map { + "typealias \($0.render(as: .typename)) = Interface" }, separator: "\n") } diff --git a/Sources/ApolloCodegenLib/Templates/MockObjectTemplate.swift b/Sources/ApolloCodegenLib/Templates/MockObjectTemplate.swift index 35807e9a1..eda7d8914 100644 --- a/Sources/ApolloCodegenLib/Templates/MockObjectTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/MockObjectTemplate.swift @@ -27,7 +27,7 @@ struct MockObjectTemplate: TemplateRenderer { func renderBodyTemplate( nonFatalErrorRecorder: ApolloCodegen.NonFatalError.Recorder ) -> TemplateString { - let objectName = graphqlObject.formattedName + let objectName = graphqlObject.render(as: .typename) let fields: [TemplateField] = fields .sorted { $0.0 < $1.0 } .map { @@ -128,7 +128,7 @@ struct MockObjectTemplate: TemplateRenderer { case is GraphQLInterfaceType, is GraphQLUnionType: mockType = "(any AnyMock)" default: - mockType = "Mock<\(graphQLCompositeType.formattedName)>" + mockType = "Mock<\(graphQLCompositeType.render(as: .typename))>" } return TemplateString("\(mockType)\(if: !forceNonNull, "?")").description case .scalar, diff --git a/Sources/ApolloCodegenLib/Templates/MockUnionsTemplate.swift b/Sources/ApolloCodegenLib/Templates/MockUnionsTemplate.swift index bc97c30dd..cb876086e 100644 --- a/Sources/ApolloCodegenLib/Templates/MockUnionsTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/MockUnionsTemplate.swift @@ -5,7 +5,7 @@ import TemplateString struct MockUnionsTemplate: TemplateRenderer { - let graphQLUnions: OrderedSet + let graphqlUnions: OrderedSet let config: ApolloCodegen.ConfigurationContext @@ -16,8 +16,8 @@ struct MockUnionsTemplate: TemplateRenderer { ) -> TemplateString { TemplateString(""" \(accessControlModifier(for: .parent))extension MockObject { - \(graphQLUnions.map { - "typealias \($0.formattedName) = Union" + \(graphqlUnions.map { + "typealias \($0.render(as: .typename)) = Union" }, separator: "\n") } diff --git a/Sources/ApolloCodegenLib/Templates/ObjectTemplate.swift b/Sources/ApolloCodegenLib/Templates/ObjectTemplate.swift index 63eb1dd5d..38c0c0d5e 100644 --- a/Sources/ApolloCodegenLib/Templates/ObjectTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/ObjectTemplate.swift @@ -17,8 +17,9 @@ struct ObjectTemplate: TemplateRenderer { ) -> TemplateString { """ \(documentation: graphqlObject.documentation, config: config) - static let \(graphqlObject.formattedName) = \(config.ApolloAPITargetName).Object( - typename: "\(graphqlObject.name)\", + \(graphqlObject.name.typeNameDocumentation) + static let \(graphqlObject.render(as: .typename)) = \(config.ApolloAPITargetName).Object( + typename: "\(graphqlObject.name.schemaName)\", implementedInterfaces: \(ImplementedInterfacesTemplate()) ) """ @@ -29,7 +30,7 @@ struct ObjectTemplate: TemplateRenderer { [\(list: graphqlObject.interfaces.map({ interface in TemplateString(""" \(if: !config.output.schemaTypes.isInModule, "\(config.schemaNamespace.firstUppercased).")\ - Interfaces.\(interface.formattedName).self + Interfaces.\(interface.render(as: .typename)).self """) }))] """ diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLEnumValue+Rendered.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLEnumValue+Rendered.swift deleted file mode 100644 index 50bb20a29..000000000 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLEnumValue+Rendered.swift +++ /dev/null @@ -1,28 +0,0 @@ -import GraphQLCompiler - -extension GraphQLEnumValue.Name { - - enum RenderContext { - /// Renders the value as a case in a generated Swift enum. - case swiftEnumCase - /// Renders the value as the rawValue for the enum case. - case rawValue - } - - func rendered( - as context: RenderContext, - config: ApolloCodegenConfiguration - ) -> String { - switch (context, config.options.conversionStrategies.enumCases) { - case (.rawValue, _): - return value - - case (.swiftEnumCase, .none): - return value.asEnumCaseName - - case (.swiftEnumCase, .camelCase): - return value.convertToCamelCase().asEnumCaseName - } - } - -} diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLName+RenderingHelper.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLName+RenderingHelper.swift new file mode 100644 index 000000000..3a4e8d25e --- /dev/null +++ b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLName+RenderingHelper.swift @@ -0,0 +1,161 @@ +import Foundation +import GraphQLCompiler + +extension GraphQLNamedType { + + enum RenderContext { + case filename + case typename + } + + func render( + as context: RenderContext + ) -> String { + //If the name has been customized return it unchanged + if let customName = name.customName { + return customName + } + + switch context { + case .filename: + return self.name.schemaName + case .typename: + return renderTypeName() + } + } + + private func renderTypeName() -> String { + switch self { + case let type as GraphQLScalarType: + if !type.isCustomScalar || self.name.schemaName == "ID" { + return self.name.swiftName + } + fallthrough + case is GraphQLAbstractType: fallthrough + case is GraphQLCompositeType: fallthrough + case is GraphQLEnumType: fallthrough + case is GraphQLInputObjectType: fallthrough + case is GraphQLInterfaceType: fallthrough + case is GraphQLUnionType: fallthrough + case is GraphQLObjectType: + let uppercasedName = self.name.swiftName.firstUppercased + return SwiftKeywords.TypeNamesToSuffix.contains(uppercasedName) ? + "\(uppercasedName)\(typenameSuffix)" : uppercasedName + default: + break + } + + return self.name.swiftName + } + + private var typenameSuffix: String { + switch self { + case is GraphQLEnumType: + return "_Enum" + case is GraphQLInputObjectType: + return "_InputObject" + case is GraphQLInterfaceType: + return "_Interface" + case is GraphQLObjectType: + return "_Object" + case is GraphQLScalarType: + return "_Scalar" + case is GraphQLUnionType: + return "_Union" + default: + return "_GraphQL" + } + } + +} + +extension GraphQLEnumValue { + + enum RenderContext { + case enumCase + case enumRawValue + } + + func render( + as context: RenderContext, + config: ApolloCodegen.ConfigurationContext + ) -> String { + render(as: context, config: config.config) + } + + func render( + as context: RenderContext, + config: ApolloCodegenConfiguration + ) -> String { + //If the name has been customized and its not for .enumRawValue, return it unchanged + if let customName = name.customName, context != .enumRawValue { + return customName + } + + switch context { + case .enumCase: + return renderEnumCase(config) + case .enumRawValue: + return name.schemaName + } + } + + private func renderEnumCase( + _ config: ApolloCodegenConfiguration + ) -> String { + switch config.options.conversionStrategies.enumCases { + case .none: + return self.name.schemaName.asEnumCaseName + case .camelCase: + return self.name.schemaName.convertToCamelCase().asEnumCaseName + } + } +} + +extension GraphQLInputField { + + func render( + config: ApolloCodegen.ConfigurationContext + ) -> String { + render(config: config.config) + } + + func render( + config: ApolloCodegenConfiguration + ) -> String { + //If the name has been customized return it unchanged + if let customName = name.customName { + return customName + } + + return renderInputField(config) + } + + private func renderInputField( + _ config: ApolloCodegenConfiguration + ) -> String { + var typename = name.schemaName + switch config.options.conversionStrategies.inputObjects { + case .none: + break + case .camelCase: + typename = typename.convertToCamelCase() + break + } + + return typename.escapeIf(in: SwiftKeywords.FieldAccessorNamesToEscape) + } + +} + +extension GraphQLScalarType { + + var isSwiftType: Bool { + switch name.schemaName { + case "String", "Int", "Float", "Boolean": + return true + default: + return false + } + } +} diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLNamedType+NameFormatting.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLNamedType+NameFormatting.swift deleted file mode 100644 index 521809a65..000000000 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLNamedType+NameFormatting.swift +++ /dev/null @@ -1,89 +0,0 @@ -import GraphQLCompiler -import Foundation - -extension GraphQLNamedType { - /// Provides a Swift type name for GraphQL-specific type names that are not compatible with Swift. - var swiftName: String { - switch name { - case "Boolean": return "Bool" - case "Float": return "Double" - default: return name - } - } - - @objc var formattedName: String { swiftName } -} - -extension GraphQLScalarType { - - var isSwiftType: Bool { - switch name { - case "String", "Int", "Float", "Boolean": - return true - default: - return false - } - } - - override var formattedName: String { - // ID should be suffixed if it's used as the name for any type other than built-in scalar ID. - if !isCustomScalar || name == "ID" { - return swiftName - } - - let uppercasedName = swiftName.firstUppercased - return SwiftKeywords.TypeNamesToSuffix.contains(uppercasedName) ? - "\(uppercasedName)_Scalar" : uppercasedName - } - -} - -extension GraphQLEnumType { - - override var formattedName: String { - let uppercasedName = swiftName.firstUppercased - return SwiftKeywords.TypeNamesToSuffix.contains(uppercasedName) ? - "\(uppercasedName)_Enum" : uppercasedName - } - -} - -extension GraphQLInputObjectType { - - override var formattedName: String { - let uppercasedName = swiftName.firstUppercased - return SwiftKeywords.TypeNamesToSuffix.contains(uppercasedName) ? - "\(uppercasedName)_InputObject" : uppercasedName - } - -} - -extension GraphQLObjectType { - - override var formattedName: String { - let uppercasedName = swiftName.firstUppercased - return SwiftKeywords.TypeNamesToSuffix.contains(uppercasedName) ? - "\(uppercasedName)_Object" : uppercasedName - } - -} - -extension GraphQLInterfaceType { - - override var formattedName: String { - let uppercasedName = swiftName.firstUppercased - return SwiftKeywords.TypeNamesToSuffix.contains(uppercasedName) ? - "\(uppercasedName)_Interface" : uppercasedName - } - -} - -extension GraphQLUnionType { - - override var formattedName: String { - let uppercasedName = swiftName.firstUppercased - return SwiftKeywords.TypeNamesToSuffix.contains(uppercasedName) ? - "\(uppercasedName)_Union" : uppercasedName - } - -} diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift index f9c30533d..1e302dc94 100644 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift +++ b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift @@ -137,13 +137,16 @@ extension GraphQLType { extension GraphQLNamedType { - var testMockFieldTypeName: String { - if SwiftKeywords.TestMockFieldAbstractTypeNamesToNamespace.contains(name) && + func testMockFieldTypeName( + _ config: ApolloCodegenConfiguration + ) -> String { + let typename = render(as: .typename) + if SwiftKeywords.TestMockFieldAbstractTypeNamesToNamespace.contains(typename) && self is GraphQLAbstractType { - return "MockObject.\(formattedName)" + return "MockObject.\(typename)" } - return formattedName + return typename } fileprivate func qualifiedRootTypeName( @@ -154,9 +157,9 @@ extension GraphQLNamedType { let typeName: String = { if case .testMockField = context { - return newTypeName ?? testMockFieldTypeName.firstUppercased + return newTypeName ?? testMockFieldTypeName(config) } else { - return newTypeName ?? self.formattedName + return newTypeName ?? self.render(as: .typename) } }() diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/InputVariableRenderable.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/InputVariableRenderable.swift index 664865914..c22471a4a 100644 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/InputVariableRenderable.swift +++ b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/InputVariableRenderable.swift @@ -31,8 +31,11 @@ extension InputVariableRenderable { case let .int(int): return TemplateString(int.description) case let .float(float): return TemplateString(float.description) case let .enum(enumValue): - let name = GraphQLEnumValue.Name(value: enumValue) - return ".init(.\(name.rendered(as: .swiftEnumCase, config: config)))" + let enumCase = GraphQLEnumValue( + name: GraphQLName(schemaName: enumValue), + documentation: nil, + deprecationReason: nil) + return ".init(.\(enumCase.render(as: .enumCase, config: config)))" case let .list(list): switch type { case let .nonNull(.list(listInnerType)), @@ -81,11 +84,11 @@ fileprivate extension GraphQLInputObjectType { let variable = InputVariable(type: field.type, defaultValue: entry.value) - return "\(entry.0): " + variable.renderVariableDefaultValue(config: config) + return "\(field.render(config: config)): " + variable.renderVariableDefaultValue(config: config) } return """ - \(if: !config.output.operations.isInModule, "\(config.schemaNamespace.firstUppercased).")\(name)(\(list: entries)) + \(if: !config.output.operations.isInModule, "\(config.schemaNamespace.firstUppercased).")\(render(as: .typename))(\(list: entries)) """ } } diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift index 2c6b557e2..e0120cecb 100644 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift +++ b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift @@ -31,7 +31,7 @@ extension String { SwiftKeywords.TestMockConflictingFieldNames.contains(self) } - private func escapeIf(in set: Set) -> String { + func escapeIf(in set: Set) -> String { set.contains(self) ? "`\(self)`" : self } @@ -86,22 +86,6 @@ extension String { return propertyName } - - func renderAsInputObjectName( - config: ApolloCodegenConfiguration - ) -> String { - var propertyName = self - - switch config.options.conversionStrategies.inputObjects { - case .none: - break - case .camelCase: - propertyName = propertyName.convertToCamelCase() - break - } - - return propertyName.escapeIf(in: SwiftKeywords.FieldAccessorNamesToEscape) - } /// Convert to `camelCase` from a number of different `snake_case` variants. /// @@ -181,7 +165,7 @@ enum SwiftKeywords { "hash" ] - fileprivate static let FieldAccessorNamesToEscape: Set = [ + static let FieldAccessorNamesToEscape: Set = [ "associatedtype", "class", "deinit", diff --git a/Sources/ApolloCodegenLib/Templates/SchemaMetadataTemplate.swift b/Sources/ApolloCodegenLib/Templates/SchemaMetadataTemplate.swift index f91b66f14..765ce27fc 100644 --- a/Sources/ApolloCodegenLib/Templates/SchemaMetadataTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/SchemaMetadataTemplate.swift @@ -56,7 +56,7 @@ struct SchemaMetadataTemplate: TemplateRenderer { static func objectType(forTypename typename: String) -> \(config.ApolloAPITargetName).Object? { switch typename { \(schema.referencedTypes.objects.map { - "case \"\($0.name)\": return \(schemaNamespace).Objects.\($0.formattedName)" + "case \"\($0.name.schemaName)\": return \(schemaNamespace).Objects.\($0.render(as: .typename))" }, separator: "\n") default: return nil } diff --git a/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift b/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift index b43909540..686c22a0f 100644 --- a/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift @@ -156,7 +156,7 @@ struct SelectionSetTemplate { pluralizer: config.pluralizer)) \(if: config.options.schemaDocumentation == .include, """ /// - /// Parent Type: `\(selectionSet.typeInfo.parentType.formattedName)` + /// Parent Type: `\(selectionSet.typeInfo.parentType.render(as: .typename))` """) """ } @@ -259,7 +259,7 @@ struct SelectionSetTemplate { } private func GeneratedSchemaTypeReference(_ type: GraphQLCompositeType) -> TemplateString { - "\(config.schemaNamespace.firstUppercased).\(type.schemaTypesNamespace).\(type.formattedName)" + "\(config.schemaNamespace.firstUppercased).\(type.schemaTypesNamespace).\(type.render(as: .typename))" } // MARK: - Selections @@ -1076,7 +1076,7 @@ extension IR.ScopeCondition { } else { return TemplateString( """ - \(ifLet: type, { "As\($0.formattedName)" })\ + \(ifLet: type, { "As\($0.render(as: .typename))" })\ \(ifLet: conditions, { "If\($0.typeNameComponents)"}) """ ).description diff --git a/Sources/ApolloCodegenLib/Templates/UnionTemplate.swift b/Sources/ApolloCodegenLib/Templates/UnionTemplate.swift index d49b6f001..5817c8770 100644 --- a/Sources/ApolloCodegenLib/Templates/UnionTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/UnionTemplate.swift @@ -18,8 +18,9 @@ struct UnionTemplate: TemplateRenderer { TemplateString( """ \(documentation: graphqlUnion.documentation, config: config) - static let \(graphqlUnion.formattedName) = Union( - name: "\(graphqlUnion.name)", + \(graphqlUnion.name.typeNameDocumentation) + static let \(graphqlUnion.render(as: .typename)) = Union( + name: "\(graphqlUnion.name.schemaName)", possibleTypes: \(PossibleTypesTemplate()) ) """ @@ -35,7 +36,7 @@ struct UnionTemplate: TemplateRenderer { ) -> TemplateString { """ \(if: !config.output.schemaTypes.isInModule, "\(config.schemaNamespace.firstUppercased).")\ - Objects.\(type.formattedName).self + Objects.\(type.render(as: .typename)).self """ } diff --git a/Sources/GraphQLCompiler/GraphQLName.swift b/Sources/GraphQLCompiler/GraphQLName.swift new file mode 100644 index 000000000..732879ced --- /dev/null +++ b/Sources/GraphQLCompiler/GraphQLName.swift @@ -0,0 +1,51 @@ +import Foundation +import TemplateString + +public protocol GraphQLNamedItem { + var name: GraphQLName { get } +} + +public class GraphQLName: Hashable { + public let schemaName: String + + public var customName: String? + + public var swiftName: String { + switch schemaName { + case "Boolean": return "Bool" + case "Float": return "Double" + default: return schemaName + } + } + + private var shouldRenderDocumentation: Bool { + if let customName, !customName.isEmpty { + return true + } + return false + } + + public var typeNameDocumentation: TemplateString? { + guard shouldRenderDocumentation else { return nil } + return """ + // Renamed from GraphQL schema value: '\(schemaName)' + """ + } + + public init( + schemaName: String + ) { + self.schemaName = schemaName + } + + // MARK: - Hashable + + public func hash(into hasher: inout Hasher) { + hasher.combine(schemaName) + } + + public static func == (lhs: GraphQLName, rhs: GraphQLName) -> Bool { + return lhs.schemaName == rhs.schemaName + } + +} diff --git a/Sources/GraphQLCompiler/GraphQLSchema.swift b/Sources/GraphQLCompiler/GraphQLSchema.swift index fc2e53664..e3e9f804b 100644 --- a/Sources/GraphQLCompiler/GraphQLSchema.swift +++ b/Sources/GraphQLCompiler/GraphQLSchema.swift @@ -20,8 +20,8 @@ protocol GraphQLSchemaType: JavaScriptReferencedObject { } public class GraphQLNamedType: - JavaScriptReferencedObject, @unchecked Sendable, Hashable, CustomDebugStringConvertible { - public let name: String + JavaScriptReferencedObject, @unchecked Sendable, Hashable, CustomDebugStringConvertible, GraphQLNamedItem { + public let name: GraphQLName public let documentation: String? @@ -29,7 +29,7 @@ public class GraphQLNamedType: _ jsValue: JSValue, bridge: isolated JavaScriptBridge ) { - self.name = jsValue["name"] + self.name = .init(schemaName: jsValue["name"]) self.documentation = jsValue["description"] } @@ -37,7 +37,7 @@ public class GraphQLNamedType: /// Initializer to be used for creating mock objects in tests only. init( - name: String, + name: GraphQLName, documentation: String? ) { self.name = name @@ -60,7 +60,7 @@ public class GraphQLNamedType: } public var debugDescription: String { - name + name.schemaName } } @@ -71,7 +71,7 @@ public final class GraphQLScalarType: GraphQLNamedType { public var isCustomScalar: Bool { guard self.specifiedByURL == nil else { return true } - switch name { + switch name.schemaName { case "String", "Int", "Float", "Boolean": return false default: @@ -86,7 +86,7 @@ public final class GraphQLScalarType: GraphQLNamedType { /// Initializer to be used for creating mock objects in tests only. init( - name: String, + name: GraphQLName, documentation: String?, specifiedByURL: String? ) { @@ -109,7 +109,7 @@ public final class GraphQLEnumType: GraphQLNamedType { /// Initializer to be used for creating mock objects in tests only. init( - name: String, + name: GraphQLName, documentation: String?, values: [GraphQLEnumValue] ) { @@ -118,17 +118,9 @@ public final class GraphQLEnumType: GraphQLNamedType { } } -public struct GraphQLEnumValue: JavaScriptObjectDecodable { - - public struct Name { - public let value: String +public struct GraphQLEnumValue: JavaScriptObjectDecodable, GraphQLNamedItem { - public init(value: String) { - self.value = value - } - } - - public let name: Name + public let name: GraphQLName public let documentation: String? @@ -136,8 +128,8 @@ public struct GraphQLEnumValue: JavaScriptObjectDecodable { public var isDeprecated: Bool { deprecationReason != nil } - init( - name: Name, + public init( + name: GraphQLName, documentation: String?, deprecationReason: String? ) { @@ -151,7 +143,7 @@ public struct GraphQLEnumValue: JavaScriptObjectDecodable { bridge: isolated JavaScriptBridge ) -> GraphQLEnumValue { self.init( - name: .init(value: jsValue["name"]), + name: .init(schemaName: jsValue["name"]), documentation: jsValue["description"], deprecationReason: jsValue["deprecationReason"] ) @@ -173,7 +165,7 @@ public final class GraphQLInputObjectType: GraphQLNamedType { /// Initializer to be used for creating mock objects in tests only. init( - name: String, + name: GraphQLName, documentation: String?, fields: GraphQLInputFieldDictionary ) { @@ -182,8 +174,8 @@ public final class GraphQLInputObjectType: GraphQLNamedType { } } -public struct GraphQLInputField: JavaScriptObjectDecodable { - public let name: String +public class GraphQLInputField: JavaScriptObjectDecodable, GraphQLNamedItem { + public let name: GraphQLName public let type: GraphQLType @@ -196,9 +188,9 @@ public struct GraphQLInputField: JavaScriptObjectDecodable { static func fromJSValue( _ jsValue: JSValue, bridge: isolated JavaScriptBridge - ) -> GraphQLInputField { + ) -> Self { self.init( - name: jsValue["name"], + name: .init(schemaName: jsValue["name"]), type: GraphQLType.fromJSValue(jsValue["type"], bridge: bridge), documentation: jsValue["description"], deprecationReason: jsValue["deprecationReason"], @@ -206,8 +198,8 @@ public struct GraphQLInputField: JavaScriptObjectDecodable { ) } - init( - name: String, + required init( + name: GraphQLName, type: GraphQLType, documentation: String?, deprecationReason: String?, @@ -245,7 +237,7 @@ public final class GraphQLObjectType: GraphQLCompositeType, GraphQLInterfaceImpl /// Initializer to be used for creating mock objects in tests only. init( - name: String, + name: GraphQLName, documentation: String?, fields: [String: GraphQLField], interfaces: [GraphQLInterfaceType] @@ -280,7 +272,7 @@ public final class GraphQLInterfaceType: GraphQLAbstractType, GraphQLInterfaceIm /// Initializer to be used for creating mock objects in tests only. init( - name: String, + name: GraphQLName, documentation: String?, fields: [String: GraphQLField], interfaces: [GraphQLInterfaceType] @@ -314,7 +306,7 @@ public final class GraphQLUnionType: GraphQLAbstractType { /// Initializer to be used for creating mock objects in tests only. init( - name: String, + name: GraphQLName, documentation: String?, types: [GraphQLObjectType] ) { diff --git a/Sources/GraphQLCompiler/GraphQLType.swift b/Sources/GraphQLCompiler/GraphQLType.swift index 770accf39..f890abc94 100644 --- a/Sources/GraphQLCompiler/GraphQLType.swift +++ b/Sources/GraphQLCompiler/GraphQLType.swift @@ -15,7 +15,7 @@ public indirect enum GraphQLType: Sendable, Hashable { let .scalar(type as GraphQLNamedType), let .enum(type as GraphQLNamedType), let .inputObject(type as GraphQLNamedType): - return type.name + return type.name.schemaName case let .nonNull(ofType): return "\(ofType.typeReference)!"