From 9af414989adaab78795855474c7971da4b2cb30b Mon Sep 17 00:00:00 2001 From: Joffrey Bion Date: Wed, 1 Apr 2020 14:49:16 +0200 Subject: [PATCH] Add SchemaFinder to enable directory support in maven/gradle plugins (#32) Resolves: https://github.com/kobylynskyi/graphql-java-codegen/issues/30 Co-authored-by: Joffrey Bion --- .../codegen/supplier/SchemaFinder.java | 95 +++++++++ .../codegen/supplier/SchemaFinderTest.java | 201 ++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 src/main/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinder.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinderTest.java diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinder.java b/src/main/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinder.java new file mode 100644 index 00000000..0851e118 --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinder.java @@ -0,0 +1,95 @@ +package com.kobylynskyi.graphql.codegen.supplier; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Walks a directory tree to find GraphQL schema files. + */ +public class SchemaFinder { + + public static final String DEFAULT_INCLUDE_PATTERN = ".*\\.graphqls"; + + public static final boolean DEFAULT_RECURSIVE = true; + + private final Path rootDir; + + private boolean recursive = DEFAULT_RECURSIVE; + + private Pattern includePattern = Pattern.compile(DEFAULT_INCLUDE_PATTERN); + + private Set excludedFiles = Collections.emptySet(); + + /** + * Creates a new SchemaFinder with the given directory as root of the search. + * + * @param rootDir + * the starting directory of the search. Must not be null. + */ + public SchemaFinder(Path rootDir) { + if (rootDir == null) { + throw new IllegalArgumentException("rootDir is required for schema search"); + } + this.rootDir = rootDir; + } + + /** + * Sets whether the file search should be recursive. + */ + public void setRecursive(boolean recursive) { + this.recursive = recursive; + } + + /** + * Sets the Java pattern that filenames should match to be included in the result. Matching files are only included + * if they're not part of the excluded files. + * + * @see #setExcludedFiles(Set) + */ + public void setIncludePattern(String includePattern) { + this.includePattern = Pattern.compile(includePattern); + } + + /** + * Sets a set of paths to exclude from the search even if they match the include pattern. The provided paths are + * either absolute or relative to the root directory provided in the constructor. + * + * @see #setIncludePattern(String) + */ + public void setExcludedFiles(Set excludedFiles) { + this.excludedFiles = excludedFiles.stream().map(rootDir::resolve).collect(Collectors.toSet()); + } + + /** + * Walks the directory tree starting at the root provided in the constructor to find GraphQL schemas. + * + * @return an alphabetically-sorted list of file paths matching the current configuration + * + * @throws IOException + * if any I/O error occurs while reading the file system + */ + public List findSchemas() throws IOException { + int maxDepth = recursive ? Integer.MAX_VALUE : 1; + try (Stream paths = Files.find(rootDir, maxDepth, (path, attrs) -> shouldInclude(path))) { + return paths.map(Path::toString).sorted().collect(Collectors.toList()); + } + } + + private boolean shouldInclude(Path path) { + if (Files.isDirectory(path)) { + return false; + } + if (excludedFiles.contains(path)) { + return false; + } + String filename = path.getFileName().toString(); + return includePattern.matcher(filename).matches(); + } +} diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinderTest.java b/src/test/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinderTest.java new file mode 100644 index 00000000..38250510 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/supplier/SchemaFinderTest.java @@ -0,0 +1,201 @@ +package com.kobylynskyi.graphql.codegen.supplier; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class SchemaFinderTest { + + private Path root; + + @BeforeEach + public void setup() throws IOException { + root = Files.createTempDirectory("test-schema"); + root.toFile().deleteOnExit(); + } + + @Test + public void nullRootDir_fails() { + assertThrows(IllegalArgumentException.class, () -> new SchemaFinder(null)); + } + + @Test + public void absentRootDir_fails() { + SchemaFinder finder = new SchemaFinder(Paths.get("does-not-exist")); + assertThrows(NoSuchFileException.class, finder::findSchemas); + } + + @Test + public void emptyRootDir() throws IOException { + SchemaFinder finder = new SchemaFinder(root); + assertEquals(Collections.emptyList(), finder.findSchemas()); + } + + @Test + public void emptyRootDir_nonRecursive() throws IOException { + SchemaFinder finder = new SchemaFinder(root); + finder.setRecursive(false); + assertEquals(Collections.emptyList(), finder.findSchemas()); + } + + @Test + public void singleFile_matchesDefaultRegex() throws IOException { + Path singleFile = Files.createFile(root.resolve("single.graphqls")); + + SchemaFinder finder = new SchemaFinder(root); + List expected = Collections.singletonList(singleFile.toString()); + assertEquals(expected, finder.findSchemas()); + } + + @Test + public void singleFile_matchesCustomRegex() throws IOException { + Path singleFile = Files.createFile(root.resolve("my-file.txt")); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern("my-.*\\.txt"); + List expected = Collections.singletonList(singleFile.toString()); + assertEquals(expected, finder.findSchemas()); + } + + @Test + public void singleFile_filteredOutByRegex() throws IOException { + Files.createFile(root.resolve("not-a-match.txt")); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern("very-specific-regex"); + assertEquals(Collections.emptyList(), finder.findSchemas()); + } + + @Test + public void singleFile_filteredOutByRelativeExclude() throws IOException { + String excludedFilename = "excluded.txt"; + Files.createFile(root.resolve(excludedFilename)); + + SchemaFinder finder = new SchemaFinder(root); + finder.setExcludedFiles(Collections.singleton(excludedFilename)); + assertEquals(Collections.emptyList(), finder.findSchemas()); + } + + @Test + public void singleFile_filteredOutByAbsoluteExclude() throws IOException { + Path excludedFilePath = root.resolve("excluded.txt").toAbsolutePath(); + Files.createFile(excludedFilePath); + + SchemaFinder finder = new SchemaFinder(root); + finder.setExcludedFiles(Collections.singleton(excludedFilePath.toString())); + assertEquals(Collections.emptyList(), finder.findSchemas()); + } + + @Test + public void multipleFiles_shouldBeInAlphabeticalOrder() throws IOException { + Path file1 = Files.createFile(root.resolve("file1.good")); + Path file3 = Files.createFile(root.resolve("file3.good")); + Path file2 = Files.createFile(root.resolve("file2.good")); + + List expected = Arrays.asList(file1.toString(), file2.toString(), file3.toString()); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern("file\\d\\.good"); + assertEquals(expected, finder.findSchemas()); + } + + @Test + public void multipleFiles_withRegexFilter() throws IOException { + Path singleFile = Files.createFile(root.resolve("abc.good")); + Files.createFile(root.resolve("abc.bad")); + + List expected = Collections.singletonList(singleFile.toString()); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern(".*\\.good"); + assertEquals(expected, finder.findSchemas()); + } + + @Test + public void multipleFiles_withExclude() throws IOException { + Path singleFile = Files.createFile(root.resolve("abc.good")); + String excludedFilename = "excluded.good"; + Files.createFile(root.resolve(excludedFilename)); + + List expected = Collections.singletonList(singleFile.toString()); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern(".*\\.good"); + finder.setExcludedFiles(Collections.singleton(excludedFilename)); + assertEquals(expected, finder.findSchemas()); + } + + @Test + public void nestedDir_recursive() throws IOException { + Path rootFile = Files.createFile(root.resolve("abc.good")); + Path dir1 = Files.createDirectory(root.resolve("dir1")); + Path dir1File = Files.createFile(dir1.resolve("file1.good")); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern(".*\\.good"); + finder.setRecursive(true); + + List expectedRec = Arrays.asList(rootFile.toString(), dir1File.toString()); + assertEquals(expectedRec, finder.findSchemas()); + } + + @Test + public void nestedDir_nonRecursive() throws IOException { + Path rootFile = Files.createFile(root.resolve("abc.good")); + Path dir1 = Files.createDirectory(root.resolve("dir1")); + Files.createFile(dir1.resolve("file1.good")); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern(".*\\.good"); + finder.setRecursive(false); + + List expected = Collections.singletonList(rootFile.toString()); + assertEquals(expected, finder.findSchemas()); + } + + @Test + public void nestedDir_mixedCases() throws IOException { + Path rootFile = Files.createFile(root.resolve("abc.good")); + Path dir1 = Files.createDirectory(root.resolve("dir1")); + Path dir2 = Files.createDirectory(root.resolve("dir2")); + Files.createFile(root.resolve("abc.bad")); + Files.createFile(root.resolve("excluded1.good")); + + // inside dir1 (mix subdirs, good files, bad files) + Path dir1File = Files.createFile(dir1.resolve("file1.good")); + Path subdir = Files.createDirectory(dir1.resolve("subdir")); + Files.createDirectory(dir1.resolve("empty")); // empty subdir + Files.createFile(dir1.resolve("abc.bad")); + + // inside dir1/subdir (only good files) + Path subdirFile = Files.createFile(subdir.resolve("subdirFile.good")); + + // inside dir2 (only bad files) + Files.createFile(dir2.resolve("abc.bad")); + Files.createFile(dir2.resolve("excluded2.good")); + + List expected = Collections.singletonList(rootFile.toString()); + + SchemaFinder finder = new SchemaFinder(root); + finder.setIncludePattern(".*\\.good"); + finder.setExcludedFiles(new HashSet<>(Arrays.asList("excluded1.good", "dir2/excluded2.good"))); + finder.setRecursive(false); + assertEquals(expected, finder.findSchemas()); + + List expectedRec = Arrays.asList(rootFile.toString(), dir1File.toString(), subdirFile.toString()); + finder.setRecursive(true); + assertEquals(expectedRec, finder.findSchemas()); + } +} \ No newline at end of file -- GitLab