diff --git a/README.md b/README.md index c9cf9f78..60d722e7 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,12 @@ Add openapi-diff to your POM to show diffs when you test your Maven project. You true true + + ${project.basedir}/../maven/target/diff.txt + + ${project.basedir}/../maven/target/diff.json + + ${project.basedir}/../maven/target/diff.md @@ -167,6 +173,7 @@ public class Main { ``` ### Render difference + --- #### HTML @@ -176,11 +183,9 @@ String html = new HtmlRender("Changelog", .render(diff); try { - FileWriter fw = new FileWriter( - "testNewApi.html"); + FileWriter fw = new FileWriter("testNewApi.html"); fw.write(html); fw.close(); - } catch (IOException e) { e.printStackTrace(); } @@ -191,11 +196,9 @@ try { ```java String render = new MarkdownRender().render(diff); try { - FileWriter fw = new FileWriter( - "testDiff.md"); + FileWriter fw = new FileWriter("testDiff.md"); fw.write(render); fw.close(); - } catch (IOException e) { e.printStackTrace(); } @@ -207,11 +210,9 @@ try { ```java String render = new JsonRender().render(diff); try { - FileWriter fw = new FileWriter( - "testDiff.json"); + FileWriter fw = new FileWriter("testDiff.json"); fw.write(render); fw.close(); - } catch (IOException e) { e.printStackTrace(); } diff --git a/core/src/main/java/org/openapitools/openapidiff/core/utils/ChangedUtils.java b/core/src/main/java/org/openapitools/openapidiff/core/utils/ChangedUtils.java index 27a5e0e2..6da806a9 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/utils/ChangedUtils.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/utils/ChangedUtils.java @@ -5,7 +5,9 @@ public class ChangedUtils { - private ChangedUtils() {} + private ChangedUtils() { + throw new UnsupportedOperationException("Utility class. Do not instantiate"); + } public static boolean isUnchanged(Changed changed) { return changed == null || changed.isUnchanged(); diff --git a/core/src/main/java/org/openapitools/openapidiff/core/utils/Copy.java b/core/src/main/java/org/openapitools/openapidiff/core/utils/Copy.java index 4179fd35..73c9a073 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/utils/Copy.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/utils/Copy.java @@ -5,7 +5,9 @@ public class Copy { - private Copy() {} + private Copy() { + throw new UnsupportedOperationException("Utility class. Do not instantiate"); + } public static Map copyMap(Map map) { if (map == null) { diff --git a/core/src/main/java/org/openapitools/openapidiff/core/utils/EndpointUtils.java b/core/src/main/java/org/openapitools/openapidiff/core/utils/EndpointUtils.java index 749b0bdb..c749d8a5 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/utils/EndpointUtils.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/utils/EndpointUtils.java @@ -10,7 +10,9 @@ public class EndpointUtils { - private EndpointUtils() {} + private EndpointUtils() { + throw new UnsupportedOperationException("Utility class. Do not instantiate"); + } public static Collection convert2Endpoints( String pathUrl, Map map) { diff --git a/core/src/main/java/org/openapitools/openapidiff/core/utils/FileUtils.java b/core/src/main/java/org/openapitools/openapidiff/core/utils/FileUtils.java new file mode 100644 index 00000000..a2c8b147 --- /dev/null +++ b/core/src/main/java/org/openapitools/openapidiff/core/utils/FileUtils.java @@ -0,0 +1,35 @@ +package org.openapitools.openapidiff.core.utils; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.openapitools.openapidiff.core.model.ChangedOpenApi; +import org.openapitools.openapidiff.core.output.Render; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class FileUtils { + private static final Logger logger = LoggerFactory.getLogger(FileUtils.class); + + private FileUtils() { + throw new UnsupportedOperationException("Utility class. Do not instantiate"); + } + + public static void writeToFile( + final Render render, final ChangedOpenApi diff, final String fileName) { + if (fileName == null || fileName.isEmpty()) { + logger.debug("File name cannot be null or empty."); + return; + } + + final Path filePath = Paths.get(fileName); + try (final FileOutputStream outputStream = new FileOutputStream(filePath.toFile()); + final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)) { + render.render(diff, outputStreamWriter); + } catch (final IOException e) { + logger.error("Exception while writing to file {}", fileName, e); + } + } +} diff --git a/core/src/main/resources/logback.xml b/core/src/main/resources/logback.xml new file mode 100644 index 00000000..018a4277 --- /dev/null +++ b/core/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/core/src/test/java/org/openapitools/openapidiff/core/utils/FileUtilsTest.java b/core/src/test/java/org/openapitools/openapidiff/core/utils/FileUtilsTest.java new file mode 100644 index 00000000..5d9b4d79 --- /dev/null +++ b/core/src/test/java/org/openapitools/openapidiff/core/utils/FileUtilsTest.java @@ -0,0 +1,58 @@ +package org.openapitools.openapidiff.core.utils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.openapitools.openapidiff.core.model.ChangedOpenApi; +import org.openapitools.openapidiff.core.output.ConsoleRender; + +class FileUtilsTest { + private ChangedOpenApi changedOpenApi; + + @BeforeEach + void setup() { + changedOpenApi = new ChangedOpenApi(); + changedOpenApi.setChangedSchemas(Collections.emptyList()); + changedOpenApi.setChangedOperations(Collections.emptyList()); + changedOpenApi.setNewEndpoints(Collections.emptyList()); + changedOpenApi.setMissingEndpoints(Collections.emptyList()); + } + + @Test + void writeToFile_filenameIsNull_doesNothing() { + assertDoesNotThrow(() -> FileUtils.writeToFile(new ConsoleRender(), changedOpenApi, null)); + } + + @Test + void writeToFile_filenameIsEmpty_doesNothing() { + assertDoesNotThrow( + () -> FileUtils.writeToFile(new ConsoleRender(), changedOpenApi, StringUtils.EMPTY)); + } + + @Test + void writeToFile_fileExists_overwrites_file(@TempDir Path tempDir) throws IOException { + final Path path = tempDir.resolve("output.txt"); + Files.write(path, "Test".getBytes(StandardCharsets.UTF_8)); + + assertDoesNotThrow( + () -> FileUtils.writeToFile(new ConsoleRender(), changedOpenApi, path.toString())); + assertThat(path).exists().content().isNotEqualTo("Test"); + } + + @Test + void writeToFile_fileDoesNotExist_createsFile(@TempDir Path tempDir) { + final Path path = tempDir.resolve("output.txt"); + assertDoesNotThrow( + () -> FileUtils.writeToFile(new ConsoleRender(), changedOpenApi, path.toString())); + assertThat(path).exists().content().isNotBlank(); + } +} diff --git a/maven-example/pom.xml b/maven-example/pom.xml index 46d9dda3..bb3d9c7c 100644 --- a/maven-example/pom.xml +++ b/maven-example/pom.xml @@ -33,6 +33,9 @@ ${project.basedir}/../maven/src/test/resources/oldspec.yaml ${project.basedir}/../maven/src/test/resources/newspec.yaml true + ${project.basedir}/../maven/target/diff.txt + ${project.basedir}/../maven/target/diff.json + ${project.basedir}/../maven/target/diff.md diff --git a/maven/src/main/java/org/openapitools/openapidiff/maven/OpenApiDiffMojo.java b/maven/src/main/java/org/openapitools/openapidiff/maven/OpenApiDiffMojo.java index af402c9f..7e0e82fb 100644 --- a/maven/src/main/java/org/openapitools/openapidiff/maven/OpenApiDiffMojo.java +++ b/maven/src/main/java/org/openapitools/openapidiff/maven/OpenApiDiffMojo.java @@ -1,7 +1,11 @@ package org.openapitools.openapidiff.maven; +import static org.openapitools.openapidiff.core.utils.FileUtils.writeToFile; + import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -11,10 +15,13 @@ import org.openapitools.openapidiff.core.OpenApiCompare; import org.openapitools.openapidiff.core.model.ChangedOpenApi; import org.openapitools.openapidiff.core.output.ConsoleRender; +import org.openapitools.openapidiff.core.output.JsonRender; +import org.openapitools.openapidiff.core.output.MarkdownRender; /** A Maven Mojo that diffs two OpenAPI specifications and reports on differences. */ @Mojo(name = "diff", defaultPhase = LifecyclePhase.TEST) public class OpenApiDiffMojo extends AbstractMojo { + @Parameter(property = "oldSpec") String oldSpec; @@ -30,6 +37,15 @@ public class OpenApiDiffMojo extends AbstractMojo { @Parameter(property = "skip", defaultValue = "false") Boolean skip = false; + @Parameter(property = "consoleOutputFileName") + String consoleOutputFileName; + + @Parameter(property = "jsonOutputFileName") + String jsonOutputFileName; + + @Parameter(property = "markdownOutputFileName") + String markdownOutputFileName; + @Override public void execute() throws MojoExecutionException, MojoFailureException { if (Boolean.TRUE.equals(skip)) { @@ -39,10 +55,18 @@ public void execute() throws MojoExecutionException, MojoFailureException { try { final ChangedOpenApi diff = OpenApiCompare.fromLocations(oldSpec, newSpec); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); - new ConsoleRender().render(diff, outputStreamWriter); - getLog().info(outputStream.toString()); + + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)) { + new ConsoleRender().render(diff, outputStreamWriter); + getLog().info(outputStream.toString()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + writeDiffAsTextToFile(diff); + writeDiffAsJsonToFile(diff); + writeDiffAsMarkdownToFile(diff); if (failOnIncompatible && diff.isIncompatible()) { throw new BackwardIncompatibilityException("The API changes broke backward compatibility"); @@ -55,4 +79,16 @@ public void execute() throws MojoExecutionException, MojoFailureException { throw new MojoExecutionException("Unexpected error", e); } } + + private void writeDiffAsTextToFile(final ChangedOpenApi diff) { + writeToFile(new ConsoleRender(), diff, consoleOutputFileName); + } + + private void writeDiffAsJsonToFile(final ChangedOpenApi diff) { + writeToFile(new JsonRender(), diff, jsonOutputFileName); + } + + private void writeDiffAsMarkdownToFile(final ChangedOpenApi diff) { + writeToFile(new MarkdownRender(), diff, markdownOutputFileName); + } } diff --git a/maven/src/test/java/org/openapitools/openapidiff/maven/OpenApiDiffMojoTest.java b/maven/src/test/java/org/openapitools/openapidiff/maven/OpenApiDiffMojoTest.java index 7deae423..10bd186e 100644 --- a/maven/src/test/java/org/openapitools/openapidiff/maven/OpenApiDiffMojoTest.java +++ b/maven/src/test/java/org/openapitools/openapidiff/maven/OpenApiDiffMojoTest.java @@ -1,15 +1,44 @@ package org.openapitools.openapidiff.maven; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import org.apache.maven.plugin.MojoExecutionException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class OpenApiDiffMojoTest { + + private static final Logger logger = LoggerFactory.getLogger(OpenApiDiffMojoTest.class); + + private final File oldSpecFile = new File("src/test/resources/oldspec.yaml"); + private final File newSpecFile = new File("src/test/resources/newspec.yaml"); + + private final File consoleOutputfile = new File("target/diff.txt"); + private final File markdownOutputfile = new File("target/diff.md"); + private final File jsonOutputfile = new File("target/diff.json"); + + @BeforeEach + void setup() { + cleanupGeneratedFiles(); + } + + @AfterEach + void tearDown() { + cleanupGeneratedFiles(); + } + @Test void Should_NotThrow_When_SpecHasNoChanges() { - final String oldSpec = new File("src/test/resources/oldspec.yaml").getAbsolutePath(); + final String oldSpec = oldSpecFile.getAbsolutePath(); final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); mojo.oldSpec = oldSpec; @@ -22,8 +51,8 @@ void Should_NotThrow_When_SpecHasNoChanges() { @Test void Should_NotThrow_When_SpecIsCompatible() { final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); - mojo.oldSpec = new File("src/test/resources/oldspec.yaml").getAbsolutePath(); - mojo.newSpec = new File("src/test/resources/newspec.yaml").getAbsolutePath(); + mojo.oldSpec = oldSpecFile.getAbsolutePath(); + mojo.newSpec = newSpecFile.getAbsolutePath(); mojo.failOnIncompatible = true; assertDoesNotThrow(mojo::execute); @@ -32,8 +61,8 @@ void Should_NotThrow_When_SpecIsCompatible() { @Test void Should_Throw_When_SpecIsDifferent() { final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); - mojo.oldSpec = new File("src/test/resources/oldspec.yaml").getAbsolutePath(); - mojo.newSpec = new File("src/test/resources/newspec.yaml").getAbsolutePath(); + mojo.oldSpec = oldSpecFile.getAbsolutePath(); + mojo.newSpec = newSpecFile.getAbsolutePath(); mojo.failOnChanged = true; assertThrows(ApiChangedException.class, mojo::execute); @@ -43,7 +72,7 @@ void Should_Throw_When_SpecIsDifferent() { void Should_MojoExecutionException_When_MissingOldSpec() { final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); mojo.oldSpec = new File("DOES_NOT_EXIST").getAbsolutePath(); - mojo.newSpec = new File("src/test/resources/newspec.yaml").getAbsolutePath(); + mojo.newSpec = newSpecFile.getAbsolutePath(); assertThrows(MojoExecutionException.class, mojo::execute); } @@ -51,7 +80,7 @@ void Should_MojoExecutionException_When_MissingOldSpec() { @Test void Should_MojoExecutionException_When_MissingNewSpec() { final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); - mojo.oldSpec = new File("src/test/resources/oldspec.yaml").getAbsolutePath(); + mojo.oldSpec = oldSpecFile.getAbsolutePath(); mojo.newSpec = new File("DOES_NOT_EXIST").getAbsolutePath(); assertThrows(MojoExecutionException.class, mojo::execute); @@ -60,8 +89,8 @@ void Should_MojoExecutionException_When_MissingNewSpec() { @Test void Should_NotThrow_When_DefaultsAndSpecIsIncompatible() { final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); - mojo.oldSpec = new File("src/test/resources/newspec.yaml").getAbsolutePath(); - mojo.newSpec = new File("src/test/resources/oldspec.yaml").getAbsolutePath(); + mojo.oldSpec = newSpecFile.getAbsolutePath(); + mojo.newSpec = oldSpecFile.getAbsolutePath(); assertDoesNotThrow(mojo::execute); } @@ -69,8 +98,8 @@ void Should_NotThrow_When_DefaultsAndSpecIsIncompatible() { @Test void Should_BackwardIncompatibilityException_When_WantsExceptionAndSpecIsIncompatible() { final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); - mojo.oldSpec = new File("src/test/resources/newspec.yaml").getAbsolutePath(); - mojo.newSpec = new File("src/test/resources/oldspec.yaml").getAbsolutePath(); + mojo.oldSpec = newSpecFile.getAbsolutePath(); + mojo.newSpec = oldSpecFile.getAbsolutePath(); mojo.failOnIncompatible = true; assertThrows(BackwardIncompatibilityException.class, mojo::execute); @@ -79,11 +108,73 @@ void Should_BackwardIncompatibilityException_When_WantsExceptionAndSpecIsIncompa @Test void Should_Skip_Mojo_WhenSkipIsTrue() { final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); - mojo.oldSpec = new File("src/test/resources/newspec.yaml").getAbsolutePath(); - mojo.newSpec = new File("src/test/resources/oldspec.yaml").getAbsolutePath(); + mojo.oldSpec = newSpecFile.getAbsolutePath(); + mojo.newSpec = oldSpecFile.getAbsolutePath(); mojo.failOnIncompatible = true; mojo.skip = true; assertDoesNotThrow(mojo::execute); } + + @Test + void Should_outputToTextFile_When_SpecIsDifferent() { + final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); + mojo.oldSpec = oldSpecFile.getAbsolutePath(); + mojo.newSpec = newSpecFile.getAbsolutePath(); + + mojo.consoleOutputFileName = consoleOutputfile.getAbsolutePath(); + mojo.failOnChanged = true; + + assertThrows(ApiChangedException.class, mojo::execute); + + assertTrue(Files.exists(consoleOutputfile.toPath())); + } + + @Test + void Should_outputToMarkdownFile_When_SpecIsDifferent() { + final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); + mojo.oldSpec = oldSpecFile.getAbsolutePath(); + mojo.newSpec = newSpecFile.getAbsolutePath(); + + mojo.markdownOutputFileName = markdownOutputfile.getAbsolutePath(); + mojo.failOnChanged = true; + + assertThrows(ApiChangedException.class, mojo::execute); + + assertTrue(Files.exists(markdownOutputfile.toPath())); + } + + @Test + void Should_outputToJsonFile_When_SpecIsDifferent() { + final OpenApiDiffMojo mojo = new OpenApiDiffMojo(); + mojo.oldSpec = oldSpecFile.getAbsolutePath(); + mojo.newSpec = newSpecFile.getAbsolutePath(); + + mojo.jsonOutputFileName = jsonOutputfile.getAbsolutePath(); + mojo.failOnChanged = true; + + assertThrows(ApiChangedException.class, mojo::execute); + + assertTrue(Files.exists(jsonOutputfile.toPath())); + } + + private void cleanupGeneratedFiles() { + try { + Files.deleteIfExists(Paths.get(consoleOutputfile.getPath())); + } catch (IOException ioException) { + logger.warn("Exception while trying to delete file {}", consoleOutputfile.getAbsolutePath()); + } + + try { + Files.deleteIfExists(Paths.get(markdownOutputfile.getPath())); + } catch (IOException ioException) { + logger.warn("Exception while trying to delete file {}", markdownOutputfile.getAbsolutePath()); + } + + try { + Files.deleteIfExists(Paths.get(jsonOutputfile.getPath())); + } catch (IOException ioException) { + logger.warn("Exception while trying to delete file {}", jsonOutputfile.getAbsolutePath()); + } + } } diff --git a/maven/src/test/resources/logback.xml b/maven/src/test/resources/logback.xml new file mode 100644 index 00000000..018a4277 --- /dev/null +++ b/maven/src/test/resources/logback.xml @@ -0,0 +1,11 @@ + + + + [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file