diff --git a/.circleci/config.yml b/.circleci/config.yml index a8879cc87f..f7a516d460 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -97,12 +97,12 @@ commands: command: pod install --verbose name: CocoaPods - Install - run: - working_directory: Tests/CodegenCLITests/pod-install-test/Pods/Apollo/ - command: ./apollo-ios-cli init --schema-name NewTestSchema + working_directory: Tests/CodegenCLITests/pod-install-test/ + command: ./Pods/Apollo/apollo-ios-cli init --schema-name NewTestSchema name: CocoaPods - CLI Test (init) - run: - working_directory: Tests/CodegenCLITests/pod-install-test/Pods/Apollo/ - command: ./apollo-ios-cli generate + working_directory: Tests/CodegenCLITests/pod-install-test/ + command: ./Pods/Apollo/apollo-ios-cli generate name: CocoaPods - CLI Test (generate) swiftpm_plugin_test: steps: diff --git a/Sources/ApolloCodegenLib/ApolloCodegen.swift b/Sources/ApolloCodegenLib/ApolloCodegen.swift index f4451689c0..c838780ea7 100644 --- a/Sources/ApolloCodegenLib/ApolloCodegen.swift +++ b/Sources/ApolloCodegenLib/ApolloCodegen.swift @@ -16,6 +16,8 @@ public class ApolloCodegen { case testMocksInvalidSwiftPackageConfiguration case inputSearchPathInvalid(path: String) case schemaNameConflict(name: String) + case cannotLoadSchema + case cannotLoadOperations public var errorDescription: String? { switch self { @@ -27,6 +29,10 @@ public class ApolloCodegen { return "Input search path '\(path)' is invalid. Input search paths must include a file extension component. (eg. '.graphql')" case let .schemaNameConflict(name): return "Schema name \(name) conflicts with name of a type in your GraphQL schema. Please choose a different schema name. Suggestions: \(name)Schema, \(name)GraphQL, \(name)API" + case .cannotLoadSchema: + return "A GraphQL schema could not be found. Please verify the schema search paths." + case .cannotLoadOperations: + return "No GraphQL operations could be found. Please verify the operation search paths." } } } @@ -56,12 +62,14 @@ public class ApolloCodegen { rootURL: rootURL ) + try validate(config: configContext) + let compilationResult = try compileGraphQLResult( configContext, experimentalFeatures: configuration.experimentalFeatures ) - try validate(config: configContext, compilationResult: compilationResult) + try validate(schemaName: configContext.schemaName, compilationResult: compilationResult) let ir = IR( schemaName: configContext.schemaName, @@ -112,7 +120,7 @@ public class ApolloCodegen { } /// Performs validation against deterministic errors that will cause code generation to fail. - static func validate(config: ConfigurationContext, compilationResult: CompilationResult) throws { + static func validate(config: ConfigurationContext) throws { if case .swiftPackage = config.output.testMocks, config.output.schemaTypes.moduleType != .swiftPackageManager { throw Error.testMocksInvalidSwiftPackageConfiguration @@ -124,8 +132,6 @@ public class ApolloCodegen { for searchPath in config.input.operationSearchPaths { try validate(inputSearchPath: searchPath) } - - try validate(schemaName: config.schemaName, compilationResult: compilationResult) } static private func validate(inputSearchPath: String) throws { @@ -134,7 +140,7 @@ public class ApolloCodegen { } } - static private func validate(schemaName: String, compilationResult: CompilationResult) throws { + static func validate(schemaName: String, compilationResult: CompilationResult) throws { guard !compilationResult.referencedTypes.contains(where: { namedType in namedType.swiftName == schemaName.firstUppercased @@ -153,10 +159,8 @@ public class ApolloCodegen { experimentalFeatures: ApolloCodegenConfiguration.ExperimentalFeatures = .init() ) throws -> CompilationResult { let frontend = try GraphQLJSFrontend() - let graphQLSchema = try createSchema(config, frontend) let operationsDocument = try createOperationsDocument(config, frontend, experimentalFeatures) - let validationOptions = ValidationOptions(config: config) let graphqlErrors = try frontend.validateDocument( @@ -184,6 +188,11 @@ public class ApolloCodegen { _ frontend: GraphQLJSFrontend ) throws -> GraphQLSchema { let matches = try Glob(config.input.schemaSearchPaths, relativeTo: config.rootURL).match() + + guard !matches.isEmpty else { + throw Error.cannotLoadSchema + } + let sources = try matches.map { try frontend.makeSource(from: URL(fileURLWithPath: $0)) } return try frontend.loadSchema(from: sources) } @@ -194,6 +203,11 @@ public class ApolloCodegen { _ experimentalFeatures: ApolloCodegenConfiguration.ExperimentalFeatures ) throws -> GraphQLDocument { let matches = try Glob(config.input.operationSearchPaths, relativeTo: config.rootURL).match() + + guard !matches.isEmpty else { + throw Error.cannotLoadOperations + } + let documents = try matches.map({ path in return try frontend.parseDocument( from: URL(fileURLWithPath: path), diff --git a/Sources/ApolloCodegenLib/Glob.swift b/Sources/ApolloCodegenLib/Glob.swift index d27d775c38..d31d1c6584 100644 --- a/Sources/ApolloCodegenLib/Glob.swift +++ b/Sources/ApolloCodegenLib/Glob.swift @@ -191,7 +191,7 @@ public struct Glob { globfree(&globT) } - CodegenLogger.log("Matching \(pattern)", logLevel: .debug) + CodegenLogger.log("Evaluating \(pattern)", logLevel: .debug) let response = glob(pattern, flags, nil, &globT) diff --git a/Tests/ApolloCodegenTests/ApolloCodegenTests.swift b/Tests/ApolloCodegenTests/ApolloCodegenTests.swift index 0a49f5aa66..c4bc70770d 100644 --- a/Tests/ApolloCodegenTests/ApolloCodegenTests.swift +++ b/Tests/ApolloCodegenTests/ApolloCodegenTests.swift @@ -5,7 +5,6 @@ import Nimble class ApolloCodegenTests: XCTestCase { private var directoryURL: URL! - private var testFileManager: ApolloFileManager! override func setUpWithError() throws { @@ -281,19 +280,6 @@ class ApolloCodegenTests: XCTestCase { }) } - func test_compileResults_givenSchema_withNoOperations_shouldReturnEmpty() throws { - // given - let schemaPath = createFile(containing: schemaData, named: "schema.graphqls") - - let config = ApolloCodegen.ConfigurationContext(config: ApolloCodegenConfiguration.mock(input: .init( - schemaPath: schemaPath, - operationSearchPaths: [directoryURL.appendingPathComponent("*.graphql").path] - )), rootURL: nil) - - // then - expect(try ApolloCodegen.compileGraphQLResult(config).operations).to(beEmpty()) - } - func test_compileResults_givenRelativeSchemaSearchPath_relativeToRootURL_shouldReturnSchemaRelativeToRoot() throws { // given createFile( @@ -521,6 +507,77 @@ class ApolloCodegenTests: XCTestCase { expect(try ApolloCodegen.compileGraphQLResult(config)).to(throwError()) } + func test__compileResults__givenSchemaSearchPath_withNoMatches_throwsError() throws { + // given + let config = ApolloCodegen.ConfigurationContext(config: .mock( + input: .init(schemaPath: directoryURL.appendingPathComponent("file_does_not_exist").path))) + + // then + expect(try ApolloCodegen.compileGraphQLResult(config)) + .to(throwError(ApolloCodegen.Error.cannotLoadSchema)) + } + + func test__compileResults__givenSchemaSearchPaths_withMixedMatches_doesNotThrowError() throws { + // given + let schemaPath = createFile(containing: schemaData, named: "schema.graphqls") + + let operationPath = createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + + let config = ApolloCodegen.ConfigurationContext(config: .mock( + input: .init( + schemaSearchPaths: [ + schemaPath, + directoryURL.appendingPathComponent("file_does_not_exist").path + ], + operationSearchPaths: [operationPath] + ))) + + // then + expect(try ApolloCodegen.compileGraphQLResult(config)) + .notTo(throwError()) + } + + func test__compileResults__givenOperationSearchPath_withNoMatches_throwsError() throws { + // given + let schemaPath = createFile(containing: schemaData, named: "schema.graphqls") + + let config = ApolloCodegen.ConfigurationContext(config: .mock( + input: .init( + schemaPath: schemaPath, + operationSearchPaths: [directoryURL.appendingPathComponent("file_does_not_exist").path]))) + + // then + expect(try ApolloCodegen.compileGraphQLResult(config)) + .to(throwError(ApolloCodegen.Error.cannotLoadOperations)) + } + + func test__compileResults__givenOperationSearchPaths_withMixedMatches_doesNotThrowError() throws { + // given + let schemaPath = createFile(containing: schemaData, named: "schema.graphqls") + + let operationPath = createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + + let config = ApolloCodegen.ConfigurationContext(config: .mock( + input: .init( + schemaPath: schemaPath, + operationSearchPaths: [ + operationPath, + directoryURL.appendingPathComponent("file_does_not_exist").path + ]))) + + // then + expect(try ApolloCodegen.compileGraphQLResult(config)) + .notTo(throwError()) + } + // MARK: File Generator Tests func test_fileGenerators_givenSchemaAndMultipleOperationDocuments_operations_inSchemaModule_shouldGenerateSchemaAndOperationsFiles() throws { @@ -1108,6 +1165,12 @@ class ApolloCodegenTests: XCTestCase { // given createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + let testFile = createFile( filename: "TestGeneratedA.graphql.swift", inDirectory: "SchemaModule" @@ -1147,6 +1210,12 @@ class ApolloCodegenTests: XCTestCase { // given createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + let testFile = createFile( filename: "TestGeneratedA.graphql.swift", inDirectory: "SchemaModule" @@ -1203,6 +1272,12 @@ class ApolloCodegenTests: XCTestCase { let absolutePath = "OperationPath" createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + let testFile = createFile( filename: "TestGeneratedA.graphql.swift", inDirectory: absolutePath @@ -1248,6 +1323,13 @@ class ApolloCodegenTests: XCTestCase { // given createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql", + inDirectory: "code" + ) + let testGeneratedFileInRootPath = createFile( filename: "TestGeneratedA.graphql.swift", inDirectory: "code" @@ -1315,6 +1397,12 @@ class ApolloCodegenTests: XCTestCase { // given createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "code.graphql" + ) + let testGeneratedFileInRootPath = createFile( filename: "TestGeneratedA.graphql.swift", inDirectory: "code" @@ -1484,6 +1572,12 @@ class ApolloCodegenTests: XCTestCase { // given createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "code.graphql" + ) + let testGeneratedFileInRootPath = createFile( filename: "TestGeneratedA.graphql.swift", inDirectory: "code" @@ -1689,6 +1783,12 @@ class ApolloCodegenTests: XCTestCase { let absolutePath = "TestMocksPath" createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + let testFile = createFile( filename: "TestGeneratedA.graphql.swift", inDirectory: absolutePath @@ -1735,6 +1835,12 @@ class ApolloCodegenTests: XCTestCase { // given createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + let testInTestMocksFolderFile = createFile( filename: "TestGeneratedD.graphql.swift", inDirectory: "SchemaModule/TestMocks" @@ -1787,6 +1893,12 @@ class ApolloCodegenTests: XCTestCase { let testMockTargetName = "ApolloTestTarget" createFile(containing: schemaData, named: "schema.graphqls") + createOperationFile( + type: .query, + named: "TestQuery", + filename: "TestQuery.graphql" + ) + let testInTestMocksFolderFile = createFile( filename: "TestGeneratedD.graphql.swift", inDirectory: "SchemaModule/\(testMockTargetName)" @@ -1847,7 +1959,7 @@ class ApolloCodegenTests: XCTestCase { ), rootURL: nil) // then - expect(try ApolloCodegen.validate(config: configContext, compilationResult: .mock())) + expect(try ApolloCodegen.validate(config: configContext)) .to(throwError(ApolloCodegen.Error.testMocksInvalidSwiftPackageConfiguration)) } @@ -1862,7 +1974,7 @@ class ApolloCodegenTests: XCTestCase { ), rootURL: nil) // then - expect(try ApolloCodegen.validate(config: configContext, compilationResult: .mock())) + expect(try ApolloCodegen.validate(config: configContext)) .to(throwError(ApolloCodegen.Error.testMocksInvalidSwiftPackageConfiguration)) } @@ -1873,7 +1985,7 @@ class ApolloCodegenTests: XCTestCase { ), rootURL: nil) // then - expect(try ApolloCodegen.validate(config: configContext, compilationResult: .mock())) + expect(try ApolloCodegen.validate(config: configContext)) .notTo(throwError()) } @@ -1886,7 +1998,7 @@ class ApolloCodegenTests: XCTestCase { ), rootURL: nil) // then - expect(try ApolloCodegen.validate(config: configContext, compilationResult: .mock())) + expect(try ApolloCodegen.validate(config: configContext)) .to(throwError(ApolloCodegen.Error.inputSearchPathInvalid(path: "operations/*"))) } @@ -1897,7 +2009,7 @@ class ApolloCodegenTests: XCTestCase { ), rootURL: nil) // then - expect(try ApolloCodegen.validate(config: configContext, compilationResult: .mock())) + expect(try ApolloCodegen.validate(config: configContext)) .to(throwError(ApolloCodegen.Error.inputSearchPathInvalid(path: "operations/*."))) } @@ -1908,7 +2020,7 @@ class ApolloCodegenTests: XCTestCase { ), rootURL: nil) // then - expect(try ApolloCodegen.validate(config: configContext, compilationResult: .mock())) + expect(try ApolloCodegen.validate(config: configContext)) .to(throwError(ApolloCodegen.Error.inputSearchPathInvalid(path: "schema/*"))) } @@ -1919,7 +2031,7 @@ class ApolloCodegenTests: XCTestCase { ), rootURL: nil) // then - expect(try ApolloCodegen.validate(config: configContext, compilationResult: .mock())) + expect(try ApolloCodegen.validate(config: configContext)) .to(throwError(ApolloCodegen.Error.inputSearchPathInvalid(path: "schema/*."))) } @@ -1940,8 +2052,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: name ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) } } @@ -1958,8 +2072,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: name ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) } } @@ -1976,8 +2092,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: name ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) } } @@ -1994,8 +2112,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: name ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) } } @@ -2012,8 +2132,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: name ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) } } @@ -2030,8 +2152,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: name ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) } } @@ -2050,8 +2174,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: name ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .to(throwError(ApolloCodegen.Error.schemaNameConflict(name: configContext.schemaName))) } } @@ -2083,8 +2209,10 @@ class ApolloCodegenTests: XCTestCase { schemaName: "MySchema" ), rootURL: nil) - expect(try ApolloCodegen.validate(config: configContext, compilationResult: compilationResult)) - .notTo(throwError()) + expect(try ApolloCodegen.validate( + schemaName: configContext.schemaName, + compilationResult: compilationResult)) + .notTo(throwError()) } } diff --git a/Tests/CodegenCLITests/pod-install-test/graphql/operation.graphql b/Tests/CodegenCLITests/pod-install-test/operation.graphql similarity index 100% rename from Tests/CodegenCLITests/pod-install-test/graphql/operation.graphql rename to Tests/CodegenCLITests/pod-install-test/operation.graphql diff --git a/Tests/CodegenCLITests/pod-install-test/graphql/schema.graphqls b/Tests/CodegenCLITests/pod-install-test/schema.graphqls similarity index 100% rename from Tests/CodegenCLITests/pod-install-test/graphql/schema.graphqls rename to Tests/CodegenCLITests/pod-install-test/schema.graphqls