Skip to content

Commit e9ff131

Browse files
Merge pull request #1580 from apollographql/fix/file_vs_folder
Add and test validation of multiple vs. single file urls
2 parents ffc3bb4 + 49905bc commit e9ff131

5 files changed

Lines changed: 147 additions & 16 deletions

File tree

Sources/ApolloCodegenLib/ApolloCodegen.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ public class ApolloCodegen {
99
/// Errors which can happen with code generation
1010
public enum CodegenError: Error, LocalizedError {
1111
case folderDoesNotExist(_ url: URL)
12+
case multipleFilesButNotDirectoryURL(_ url: URL)
13+
case singleFileButNotSwiftFileURL(_ url: URL)
1214

1315
public var errorDescription: String? {
1416
switch self {
1517
case .folderDoesNotExist(let url):
1618
return "Can't run codegen trying to run the command from \(url) because there is no folder there! This should be the folder which, at some depth, contains all your `.graphql` files."
19+
case .multipleFilesButNotDirectoryURL(let url):
20+
return "Codegen is requesting multiple file generation, but the URL passed in (\(url)) is not a directory URL. Please check your URL and try again."
21+
case .singleFileButNotSwiftFileURL(let url):
22+
return "Codegen is requesting single file generation, but the URL passed in (\(url)) is a not a Swift file URL. Please check your URL and try again."
1723
}
1824
}
1925
}
@@ -35,8 +41,17 @@ public class ApolloCodegen {
3541

3642
switch options.outputFormat {
3743
case .multipleFiles(let folderURL):
44+
/// We have to try to create the folder first because the stuff
45+
/// underlying `isDirectoryURL` only works on actual directories
3846
try FileManager.default.apollo.createFolderIfNeeded(at: folderURL)
47+
guard folderURL.apollo.isDirectoryURL else {
48+
throw CodegenError.multipleFilesButNotDirectoryURL(folderURL)
49+
}
3950
case .singleFile(let fileURL):
51+
guard fileURL.apollo.isSwiftFileURL else {
52+
throw CodegenError.singleFileButNotSwiftFileURL(fileURL)
53+
}
54+
4055
try FileManager.default.apollo.createContainingFolderIfNeeded(for: fileURL)
4156
}
4257

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,42 @@
11
import Foundation
22

33
public struct FileFinder {
4-
4+
55
#if compiler(>=5.3)
66
/// Version that works if you're using the 5.3 compiler or above
77
/// - Parameter filePath: The full file path of the file to find. Defaults to the `#filePath` of the caller.
88
/// - Returns: The file URL for the parent folder.
9-
public static func findParentFolder(from filePath: StaticString = #filePath) -> URL {
10-
self.findParentFolder(from: filePath.apollo.toString)
11-
}
9+
public static func findParentFolder(from filePath: StaticString = #filePath) -> URL {
10+
self.findParentFolder(from: filePath.apollo.toString)
11+
}
12+
13+
/// The URL of a file at a given path
14+
/// - Parameter filePath: The full file path of the file to find
15+
/// - Returns: The file's URL
16+
public static func fileURL(from filePath: StaticString = #filePath) -> URL {
17+
URL(fileURLWithPath: filePath.apollo.toString)
18+
}
1219
#else
13-
/// Version that works if you're using the 5.2 compiler or below
14-
/// - Parameter file: The full file path of the file to find. Defaults to the `#file` of the caller.
15-
/// - Returns: The file URL for the parent folder.
16-
public static func findParentFolder(from filePath: StaticString = #file) -> URL {
17-
self.findParentFolder(from: filePath.apollo.toString)
18-
}
20+
/// Version that works if you're using the 5.2 compiler or below
21+
/// - Parameter file: The full file path of the file to find. Defaults to the `#file` of the caller.
22+
/// - Returns: The file URL for the parent folder.
23+
public static func findParentFolder(from filePath: StaticString = #file) -> URL {
24+
self.findParentFolder(from: filePath.apollo.toString)
25+
}
26+
27+
/// The URL of a file at a given path
28+
/// - Parameter filePath: The full file path of the file to find
29+
/// - Returns: The file's URL
30+
public static func fileURL(from filePath: StaticString = #file) -> URL {
31+
URL(fileURLWithPath: filePath.apollo.toString)
32+
}
1933
#endif
20-
34+
2135
/// Finds the parent folder from a given file path.
2236
/// - Parameter filePath: The full file path, as a string
2337
/// - Returns: The file URL for the parent folder.
24-
public static func findParentFolder(from filePath: String) -> URL {
25-
let url = URL(fileURLWithPath: filePath)
26-
return url.deletingLastPathComponent()
27-
}
38+
public static func findParentFolder(from filePath: String) -> URL {
39+
let url = URL(fileURLWithPath: filePath)
40+
return url.deletingLastPathComponent()
41+
}
2842
}

Sources/ApolloCodegenLib/URL+Apollo.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ public enum ApolloURLError: Error, LocalizedError {
1818

1919
extension ApolloExtension where Base == URL {
2020

21+
/// Determines if the URL passed in is a directory URL.
22+
///
23+
/// NOTE: Only works if something at the URL already exists.
24+
///
25+
/// - Returns: True if the URL is a directory URL, false if it isn't.
26+
var isDirectoryURL: Bool {
27+
guard
28+
let resourceValues = try? base.resourceValues(forKeys: [.isDirectoryKey]),
29+
let isDirectory = resourceValues.isDirectory else {
30+
return false
31+
}
32+
33+
return isDirectory
34+
}
35+
36+
var isSwiftFileURL: Bool {
37+
base.pathExtension == "swift"
38+
}
39+
2140
/// - Returns: the URL to the parent folder of the current URL.
2241
public func parentFolderURL() -> URL {
2342
base.deletingLastPathComponent()

Tests/ApolloCodegenTests/ApolloCodegenTests.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,54 @@ class ApolloCodegenTests: XCTestCase {
110110
])
111111
}
112112

113+
func testTryingToUseAFileURLToOutputMultipleFilesFails() {
114+
let scriptFolderURL = CodegenTestHelper.cliFolderURL()
115+
let starWarsFolderURL = CodegenTestHelper.starWarsFolderURL()
116+
let starWarsSchemaFileURL = CodegenTestHelper.starWarsSchemaFileURL()
117+
let outputFolder = CodegenTestHelper.outputFolderURL()
118+
let outputFile = outputFolder.appendingPathComponent("API.swift")
119+
120+
let options = ApolloCodegenOptions(outputFormat: .multipleFiles(inFolderAtURL: outputFile),
121+
urlToSchemaFile: starWarsSchemaFileURL,
122+
downloadTimeout: CodegenTestHelper.timeout)
123+
do {
124+
_ = try ApolloCodegen.run(from: starWarsFolderURL,
125+
with: scriptFolderURL,
126+
options: options)
127+
} catch {
128+
switch error {
129+
case ApolloCodegen.CodegenError.multipleFilesButNotDirectoryURL(let url):
130+
XCTAssertEqual(url, outputFile)
131+
default:
132+
XCTFail("Unexpected error running codegen: \(error.localizedDescription)")
133+
134+
}
135+
}
136+
}
137+
138+
func testTryingToUseAFolderURLToOutputASingleFileFails() {
139+
let scriptFolderURL = CodegenTestHelper.cliFolderURL()
140+
let starWarsFolderURL = CodegenTestHelper.starWarsFolderURL()
141+
let starWarsSchemaFileURL = CodegenTestHelper.starWarsSchemaFileURL()
142+
let outputFolder = CodegenTestHelper.outputFolderURL()
143+
144+
let options = ApolloCodegenOptions(outputFormat: .singleFile(atFileURL: outputFolder),
145+
urlToSchemaFile: starWarsSchemaFileURL,
146+
downloadTimeout: CodegenTestHelper.timeout)
147+
do {
148+
_ = try ApolloCodegen.run(from: starWarsFolderURL,
149+
with: scriptFolderURL,
150+
options: options)
151+
} catch {
152+
switch error {
153+
case ApolloCodegen.CodegenError.singleFileButNotSwiftFileURL(let url):
154+
XCTAssertEqual(url, outputFolder)
155+
default:
156+
XCTFail("Unexpected error running codegen: \(error.localizedDescription)")
157+
}
158+
}
159+
}
160+
113161
func testCodegenWithSingleFileOutputsSingleFile() throws {
114162
let scriptFolderURL = CodegenTestHelper.cliFolderURL()
115163
let starWarsFolderURL = CodegenTestHelper.starWarsFolderURL()

Tests/ApolloCodegenTests/URLExtensionsTests.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import Foundation
1010
import XCTest
11-
import ApolloCodegenLib
11+
@testable import ApolloCodegenLib
1212
import ApolloCore
1313

1414
class URLExtensionsTests: XCTestCase {
@@ -68,4 +68,39 @@ class URLExtensionsTests: XCTestCase {
6868

6969
XCTAssertEqual(child, expectedFile)
7070
}
71+
72+
func testIsDirectoryForExistingDirectory() {
73+
let parentDirectory = FileFinder.findParentFolder()
74+
XCTAssertTrue(FileManager.default.apollo.folderExists(at: parentDirectory))
75+
XCTAssertTrue(parentDirectory.apollo.isDirectoryURL)
76+
}
77+
78+
func testIsDirectoryForExistingFile() {
79+
let currentFileURL = FileFinder.fileURL()
80+
XCTAssertTrue(FileManager.default.apollo.fileExists(at: currentFileURL))
81+
XCTAssertFalse(currentFileURL.apollo.isDirectoryURL)
82+
}
83+
84+
func testIsSwiftFileForExistingFile() {
85+
let currentFileURL = FileFinder.fileURL()
86+
XCTAssertTrue(FileManager.default.apollo.fileExists(at: currentFileURL))
87+
XCTAssertTrue(currentFileURL.apollo.isSwiftFileURL)
88+
}
89+
90+
func testIsSwiftFileForNonExistentFileWithSingleExtension() {
91+
let currentDirectory = FileFinder.findParentFolder()
92+
let doesntExist = currentDirectory.appendingPathComponent("test.swift")
93+
94+
XCTAssertFalse(FileManager.default.apollo.fileExists(at: doesntExist))
95+
XCTAssertTrue(doesntExist.apollo.isSwiftFileURL)
96+
}
97+
98+
func testIsSwiftFileForNonExistentFileWithMultipleExtensions() {
99+
let currentDirectory = FileFinder.findParentFolder()
100+
let doesntExist = currentDirectory.appendingPathComponent("test.graphql.swift")
101+
102+
XCTAssertFalse(FileManager.default.apollo.fileExists(at: doesntExist))
103+
XCTAssertTrue(doesntExist.apollo.isSwiftFileURL)
104+
}
105+
71106
}

0 commit comments

Comments
 (0)