From 461d092e5835dcfadf43aaa37606305582d1ac59 Mon Sep 17 00:00:00 2001 From: Zach FettersMoore <4425109+BobaFetters@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:05:34 -0500 Subject: [PATCH] feature: @oneOf input object support (apollographql/apollo-ios-dev#537) --- .../ApolloCodegenConfiguration.swift | 8 +-- .../ConfigurationValidation.swift | 10 ++-- .../FileGenerators/FileGenerator.swift | 6 +-- .../InputObjectFileGenerator.swift | 6 ++- .../Templates/InputObjectTemplate.swift | 38 +++++++------- .../Templates/OneOfInputObjectTemplate.swift | 52 +++++++++++++++++++ .../GraphQLType+Rendered.swift | 2 +- .../SwiftPackageManagerModuleTemplate.swift | 2 +- Sources/GraphQLCompiler/GraphQLSchema.swift | 7 ++- 9 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 Sources/ApolloCodegenLib/Templates/OneOfInputObjectTemplate.swift diff --git a/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration.swift b/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration.swift index cb5d9f2d..a482bcd8 100644 --- a/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration.swift +++ b/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration.swift @@ -386,7 +386,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { decoder: decoder ) - url = try values.decode(String.self, forKey: .url) + url = try values.decodeIfPresent(String.self, forKey: .url) ?? "https://github.com/apollographql/apollo-ios" if let version = try? values.decodeIfPresent(SDKVersion.self, forKey: .sdkVersion) { sdkVersion = version @@ -394,11 +394,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { let version = try SDKVersion(fromString: versionString) sdkVersion = version } else { - throw DecodingError.typeMismatch(Self.self, DecodingError.Context.init( - codingPath: values.codingPath, - debugDescription: "No valid 'sdkVersion' provided.", - underlyingError: nil - )) + sdkVersion = .default } } diff --git a/Sources/ApolloCodegenLib/ConfigurationValidation.swift b/Sources/ApolloCodegenLib/ConfigurationValidation.swift index 552c74af..84a625d2 100644 --- a/Sources/ApolloCodegenLib/ConfigurationValidation.swift +++ b/Sources/ApolloCodegenLib/ConfigurationValidation.swift @@ -33,9 +33,13 @@ extension ApolloCodegen.ConfigurationContext { throw ApolloCodegen.Error.schemaNameConflict(name: self.schemaNamespace) } - if case .swiftPackage = self.output.testMocks, - self.output.schemaTypes.moduleType != .swiftPackage() { - throw ApolloCodegen.Error.testMocksInvalidSwiftPackageConfiguration + if case .swiftPackage = self.output.testMocks { + switch self.output.schemaTypes.moduleType { + case .swiftPackage(_), .swiftPackageManager: + break + default: + throw ApolloCodegen.Error.testMocksInvalidSwiftPackageConfiguration + } } if case .swiftPackage = self.output.schemaTypes.moduleType, diff --git a/Sources/ApolloCodegenLib/FileGenerators/FileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/FileGenerator.swift index 0e8f3349..2dbb8c7a 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/FileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/FileGenerator.swift @@ -112,7 +112,7 @@ enum FileTarget: Equatable { forConfig config: ApolloCodegen.ConfigurationContext ) -> String { var moduleSubpath: String = "/" - if config.output.schemaTypes.moduleType == .swiftPackage() { + if case .swiftPackage = config.output.schemaTypes.moduleType { moduleSubpath += "Sources/" } if config.output.operations.isInModule { @@ -132,7 +132,7 @@ enum FileTarget: Equatable { switch config.output.operations { case .inSchemaModule: var url = URL(fileURLWithPath: config.output.schemaTypes.path, relativeTo: config.rootURL) - if config.output.schemaTypes.moduleType == .swiftPackage() { + if case .swiftPackage = config.output.schemaTypes.moduleType { url = url.appendingPathComponent("Sources") } @@ -167,7 +167,7 @@ enum FileTarget: Equatable { switch config.output.operations { case .inSchemaModule: var url = URL(fileURLWithPath: config.output.schemaTypes.path, relativeTo: config.rootURL) - if config.output.schemaTypes.moduleType == .swiftPackage() { + if case .swiftPackage = config.output.schemaTypes.moduleType { url = url.appendingPathComponent("Sources") } if !operation.isLocalCacheMutation { diff --git a/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift index e72433f5..d2bf15b6 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/InputObjectFileGenerator.swift @@ -10,7 +10,11 @@ struct InputObjectFileGenerator: FileGenerator { let config: ApolloCodegen.ConfigurationContext var template: any TemplateRenderer { - InputObjectTemplate(graphqlInputObject: graphqlInputObject, config: config) + if graphqlInputObject.isOneOf { + OneOfInputObjectTemplate(graphqlInputObject: graphqlInputObject, config: config) + } else { + InputObjectTemplate(graphqlInputObject: graphqlInputObject, config: config) + } } var target: FileTarget { .inputObject } var fileName: String { graphqlInputObject.render(as: .filename) } diff --git a/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift b/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift index d5e5c362..637ce990 100644 --- a/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift @@ -15,7 +15,7 @@ struct InputObjectTemplate: TemplateRenderer { func renderBodyTemplate( nonFatalErrorRecorder: ApolloCodegen.NonFatalError.Recorder ) -> TemplateString { - let (validFields, deprecatedFields) = filterFields(graphqlInputObject.fields) + let (validFields, deprecatedFields) = graphqlInputObject.fields.filterFields() let memberAccessControl = accessControlModifier(for: .member) return TemplateString( @@ -63,23 +63,6 @@ struct InputObjectTemplate: TemplateRenderer { config.options.warningsOnDeprecatedUsage == .include } - private func filterFields( - _ fields: GraphQLInputFieldDictionary - ) -> (valid: GraphQLInputFieldDictionary, deprecated: GraphQLInputFieldDictionary) { - var valid: GraphQLInputFieldDictionary = [:] - var deprecated: GraphQLInputFieldDictionary = [:] - - for (key, value) in fields { - if let _ = value.deprecationReason { - deprecated[key] = value - } else { - valid[key] = value - } - } - - return (valid: valid, deprecated: deprecated) - } - private func deprecatedMessage(for fields: GraphQLInputFieldDictionary) -> String { guard !fields.isEmpty else { return "" } @@ -123,3 +106,22 @@ struct InputObjectTemplate: TemplateRenderer { """ } } + +extension GraphQLInputFieldDictionary { + + func filterFields() -> (valid: GraphQLInputFieldDictionary, deprecated: GraphQLInputFieldDictionary) { + var valid: GraphQLInputFieldDictionary = [:] + var deprecated: GraphQLInputFieldDictionary = [:] + + for (key, value) in self { + if let _ = value.deprecationReason { + deprecated[key] = value + } else { + valid[key] = value + } + } + + return (valid: valid, deprecated: deprecated) + } + +} diff --git a/Sources/ApolloCodegenLib/Templates/OneOfInputObjectTemplate.swift b/Sources/ApolloCodegenLib/Templates/OneOfInputObjectTemplate.swift new file mode 100644 index 00000000..9d407aaa --- /dev/null +++ b/Sources/ApolloCodegenLib/Templates/OneOfInputObjectTemplate.swift @@ -0,0 +1,52 @@ +import Foundation +import GraphQLCompiler +import TemplateString + +struct OneOfInputObjectTemplate: TemplateRenderer { + + let graphqlInputObject: GraphQLInputObjectType + + let config: ApolloCodegen.ConfigurationContext + + let target: TemplateTarget = .schemaFile(type: .inputObject) + + func renderBodyTemplate( + nonFatalErrorRecorder: ApolloCodegen.NonFatalError.Recorder + ) -> TemplateString { + let memberAccessControl = accessControlModifier(for: .member) + + return TemplateString( + """ + \(documentation: graphqlInputObject.documentation, config: config) + \(graphqlInputObject.name.typeNameDocumentation) + \(accessControlModifier(for: .parent))\ + enum \(graphqlInputObject.render(as: .typename)): OneOfInputObject { + \(graphqlInputObject.fields.map({ "\(FieldCaseTemplate($1))" }), separator: "\n") + + \(memberAccessControl)var __data: InputDict { + switch self { + \(graphqlInputObject.fields.map({ "\(FieldCaseDataTemplate($1))" }), separator: "\n") + } + } + } + """ + ) + } + + private func FieldCaseTemplate(_ field: GraphQLInputField) -> TemplateString { + """ + \(documentation: field.documentation, config: config) + \(deprecationReason: field.deprecationReason, config: config) + \(field.name.typeNameDocumentation) + case \(field.render(config: config))(\(field.type.renderAsInputValue(inNullable: false, config: config.config))) + """ + } + + private func FieldCaseDataTemplate(_ field: GraphQLInputField) -> TemplateString { + """ + case .\(field.render(config: config))(let value): + return InputDict(["\(field.name.schemaName)": value]) + """ + } + +} diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift index 1e302dc9..388c3436 100644 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift +++ b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/GraphQLType+Rendered.swift @@ -71,7 +71,7 @@ extension GraphQLType { // MARK: Input Value - private func renderAsInputValue( + func renderAsInputValue( inNullable: Bool, config: ApolloCodegenConfiguration ) -> String { diff --git a/Sources/ApolloCodegenLib/Templates/SwiftPackageManagerModuleTemplate.swift b/Sources/ApolloCodegenLib/Templates/SwiftPackageManagerModuleTemplate.swift index c544e1df..6a3d259c 100644 --- a/Sources/ApolloCodegenLib/Templates/SwiftPackageManagerModuleTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/SwiftPackageManagerModuleTemplate.swift @@ -124,7 +124,7 @@ fileprivate extension ApolloCodegenConfiguration.SchemaTypesFileOutput.ModuleTyp """ case .local(let path): return """ - .package(path: "\(path)") + .package(name: "apollo-ios", path: "\(path)") """ } } diff --git a/Sources/GraphQLCompiler/GraphQLSchema.swift b/Sources/GraphQLCompiler/GraphQLSchema.swift index e3e9f804..c6064ee5 100644 --- a/Sources/GraphQLCompiler/GraphQLSchema.swift +++ b/Sources/GraphQLCompiler/GraphQLSchema.swift @@ -154,8 +154,11 @@ public typealias GraphQLInputFieldDictionary = OrderedDictionary