getCopyOpenApiSpecTo() {
return copyOpenApiSpecTo;
}
/**
- * Copy the generated OpenAPI spec to the given file. The format is determined by the file
- * extension: .yaml, .yml or .json.
- *
- * Example:
- *
- *
{@code
- * openAPI {
- * copyOpenApiSpecTo = file("$projectDir/docs/openapi.yaml")
- * }
- * }
+ * Copy open API files to either an output location or specific file.
*
- * @param copyOpenApiSpecTo Destination file.
+ * @param copyOpenApiSpecTo Output directory or file.
*/
- public void setCopyOpenApiSpecTo(@Nullable File copyOpenApiSpecTo) {
+ public void setCopyOpenApiSpecTo(@Nullable List copyOpenApiSpecTo) {
this.copyOpenApiSpecTo = copyOpenApiSpecTo;
}
diff --git a/modules/jooby-maven-plugin/src/main/java/io/jooby/maven/OpenAPIMojo.java b/modules/jooby-maven-plugin/src/main/java/io/jooby/maven/OpenAPIMojo.java
index 2c4be14630..d5eadd175f 100644
--- a/modules/jooby-maven-plugin/src/main/java/io/jooby/maven/OpenAPIMojo.java
+++ b/modules/jooby-maven-plugin/src/main/java/io/jooby/maven/OpenAPIMojo.java
@@ -10,14 +10,9 @@
import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE_PLUS_RUNTIME;
import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -58,8 +53,7 @@ public class OpenAPIMojo extends BaseMojo {
@Parameter private List adoc;
- @Parameter(property = "openAPI.copyOpenApiSpecTo")
- private File copyOpenApiSpecTo;
+ @Parameter private List copyOpenApiSpecTo;
@Override
protected void doExecute(List projects, String mainClass) throws Exception {
@@ -83,6 +77,8 @@ protected void doExecute(List projects, String mainClass) throws E
tool.setClassLoader(classLoader);
tool.setOutputDir(outputDir);
tool.setSources(sources);
+ tool.setCopyOpenApiSpecTo(
+ copyOpenApiSpecTo == null ? null : copyOpenApiSpecTo.stream().map(File::toPath).toList());
trim(includes).ifPresent(tool::setIncludes);
trim(excludes).ifPresent(tool::setExcludes);
if (javadoc != null && !javadoc.trim().isEmpty()) {
@@ -92,48 +88,10 @@ protected void doExecute(List projects, String mainClass) throws E
var result = tool.generate(mainClass);
var adocPath = ofNullable(adoc).orElse(List.of()).stream().map(File::toPath).toList();
- var written = new ArrayList();
for (var format : OpenAPIGenerator.Format.values()) {
- written.addAll(tool.export(result, format, Map.of("adoc", adocPath)));
+ tool.export(result, format, Map.of("adoc", adocPath))
+ .forEach(output -> getLog().info(" writing: " + output));
}
- written.forEach(output -> getLog().info(" writing: " + output));
-
- if (copyOpenApiSpecTo != null) {
- var destination = copyOpenApiSpecTo.toPath();
- copySpec(written, destination);
- getLog().info(" copying: " + destination);
- }
- }
-
- public static void copySpec(List written, Path destination) throws IOException {
- var format = specFormat(destination);
- var source =
- written.stream()
- .filter(path -> path.getFileName().toString().endsWith("." + format.extension()))
- .findFirst()
- .orElseThrow(
- () ->
- new IOException(
- String.format(
- "OpenAPI %s output not found for copyOpenApiSpecTo: %s",
- format.name(), destination)));
- var parent = destination.getParent();
- if (parent != null) {
- Files.createDirectories(parent);
- }
- Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
- }
-
- public static OpenAPIGenerator.Format specFormat(Path destination) {
- var name = destination.getFileName().toString().toLowerCase(Locale.ROOT);
- if (name.endsWith(".json")) {
- return OpenAPIGenerator.Format.JSON;
- }
- if (name.endsWith(".yaml") || name.endsWith(".yml")) {
- return OpenAPIGenerator.Format.YAML;
- }
- throw new IllegalArgumentException(
- "copyOpenApiSpecTo must end with .yaml, .yml or .json: " + destination);
}
private Optional trim(String value) {
@@ -239,7 +197,7 @@ public String getJavadoc() {
*
* @return Destination file.
*/
- public @Nullable File getCopyOpenApiSpecTo() {
+ public @Nullable List getCopyOpenApiSpecTo() {
return copyOpenApiSpecTo;
}
@@ -255,7 +213,7 @@ public String getJavadoc() {
*
* @param copyOpenApiSpecTo Destination file.
*/
- public void setCopyOpenApiSpecTo(@Nullable File copyOpenApiSpecTo) {
+ public void setCopyOpenApiSpecTo(@Nullable List copyOpenApiSpecTo) {
this.copyOpenApiSpecTo = copyOpenApiSpecTo;
}
}
diff --git a/modules/jooby-maven-plugin/src/test/java/io/jooby/maven/OpenAPIMojoTest.java b/modules/jooby-maven-plugin/src/test/java/io/jooby/maven/OpenAPIMojoTest.java
deleted file mode 100644
index e5c138a431..0000000000
--- a/modules/jooby-maven-plugin/src/test/java/io/jooby/maven/OpenAPIMojoTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package io.jooby.maven;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-
-import io.jooby.openapi.OpenAPIGenerator;
-
-public class OpenAPIMojoTest {
-
- @Test
- public void specFormat() {
- assertEquals(
- OpenAPIGenerator.Format.YAML, OpenAPIMojo.specFormat(Path.of("docs/openapi.yaml")));
- assertEquals(
- OpenAPIGenerator.Format.YAML, OpenAPIMojo.specFormat(Path.of("docs/openapi.yml")));
- assertEquals(
- OpenAPIGenerator.Format.JSON, OpenAPIMojo.specFormat(Path.of("docs/openapi.json")));
- assertThrows(
- IllegalArgumentException.class, () -> OpenAPIMojo.specFormat(Path.of("docs/openapi.txt")));
- }
-
- @Test
- public void copyYamlSpec(@TempDir Path tempDir) throws Exception {
- var outputDir = tempDir.resolve("classes/myapp");
- Files.createDirectories(outputDir);
- var source = outputDir.resolve("App.yaml");
- Files.writeString(source, "openapi: 3.0.1");
-
- var destination = tempDir.resolve("docs/openapi.yml");
- OpenAPIMojo.copySpec(List.of(source), destination);
-
- assertTrue(Files.isRegularFile(destination));
- assertEquals(Files.readString(source), Files.readString(destination));
- }
-
- @Test
- public void copyJsonSpec(@TempDir Path tempDir) throws Exception {
- var outputDir = tempDir.resolve("classes/myapp");
- Files.createDirectories(outputDir);
- var source = outputDir.resolve("App.json");
- Files.writeString(source, "{\"openapi\":\"3.0.1\"}");
-
- var destination = tempDir.resolve("docs/openapi.json");
- OpenAPIMojo.copySpec(List.of(source), destination);
-
- assertTrue(Files.isRegularFile(destination));
- assertEquals(Files.readString(source), Files.readString(destination));
- }
-}
diff --git a/modules/jooby-openapi/src/main/java/io/jooby/openapi/OpenAPIGenerator.java b/modules/jooby-openapi/src/main/java/io/jooby/openapi/OpenAPIGenerator.java
index 103b1a7689..d6e26831f5 100644
--- a/modules/jooby-openapi/src/main/java/io/jooby/openapi/OpenAPIGenerator.java
+++ b/modules/jooby-openapi/src/main/java/io/jooby/openapi/OpenAPIGenerator.java
@@ -60,6 +60,11 @@ protected String toString(
OpenAPIGenerator tool, OpenAPI result, Map options) {
return tool.toYaml(result);
}
+
+ @Override
+ public List extension() {
+ return List.of("yaml", "yml");
+ }
},
ADOC {
@@ -94,13 +99,36 @@ public List write(OpenAPIGenerator tool, OpenAPI result, Map extension() {
+ return List.of(name().toLowerCase());
+ }
+
+ public boolean matches(Path path) {
+ return extension().stream()
+ .anyMatch(ext -> path.getFileName().toString().endsWith("." + ext));
+ }
+
+ public static Format from(Path path) {
+ for (var format : values()) {
+ if (format.matches(path)) {
+ return format;
+ }
+ }
+ return null;
}
/**
@@ -151,6 +179,8 @@ public List write(OpenAPIGenerator tool, OpenAPI result, Map copyOpenApiSpecTo;
+
/** Default constructor. */
public OpenAPIGenerator() {}
@@ -174,7 +204,7 @@ public List export(OpenAPI openAPI, Format format, Map opt
if (appname.endsWith("Kt")) {
appname = appname.substring(0, appname.length() - 2);
}
- output = output.resolve(appname + "." + format.extension());
+ output = output.resolve(appname + "." + format.extension().getFirst());
} else {
throw new ClassCastException(openAPI.getClass() + " is not a " + OpenAPIExt.class);
}
@@ -182,10 +212,40 @@ public List export(OpenAPI openAPI, Format format, Map opt
if (!Files.exists(output.getParent())) {
Files.createDirectories(output.getParent());
}
- var allOptions = new HashMap<>(options);
- allOptions.put("output", output);
- allOptions.put("outputDir", output.getParent());
- return format.write(this, openAPI, allOptions);
+ var outputs = new ArrayList();
+ outputs.add(output);
+ if (format.isOpenApiSpec() && copyOpenApiSpecTo != null && !copyOpenApiSpecTo.isEmpty()) {
+ var defaultFileName = output.getFileName().toString();
+ for (var copyTo : copyOpenApiSpecTo) {
+ if (format.matches(copyTo)) {
+ outputs.add(copyTo);
+ } else {
+ if (Format.from(copyTo) == null) {
+ // must be a directory
+ outputs.add(copyTo.resolve(defaultFileName));
+ }
+ }
+ }
+ }
+ var result = new ArrayList();
+ for (var outputPath : outputs) {
+ var outputDir = outputPath.getParent();
+ if (!Files.exists(outputDir)) {
+ Files.createDirectories(outputDir);
+ }
+ var allOptions = new HashMap<>(options);
+ allOptions.put("output", outputPath);
+ allOptions.put("outputDir", outputDir);
+ result.addAll(format.write(this, openAPI, allOptions));
+ }
+ return result;
+ }
+
+ private Path resolveOutputPath(Path path, String defaultFileName) {
+ if (Files.isDirectory(path)) {
+ return path.resolve(defaultFileName);
+ }
+ return path;
}
/**
@@ -553,6 +613,24 @@ public void setSpecVersion(String version) {
}
}
+ /**
+ * List directories or files where the openAPI spec must be copied.
+ *
+ * @return List directories or files where the openAPI spec must be copied.
+ */
+ public List getCopyOpenApiSpecTo() {
+ return copyOpenApiSpecTo;
+ }
+
+ /**
+ * Copy open API files to either an output location or specific file.
+ *
+ * @param copyOpenApiSpecTo Output directory or file.
+ */
+ public void setCopyOpenApiSpecTo(List copyOpenApiSpecTo) {
+ this.copyOpenApiSpecTo = copyOpenApiSpecTo;
+ }
+
/**
* True/On to enabled.
*
diff --git a/modules/jooby-openapi/src/test/java/issues/Issue3977.java b/modules/jooby-openapi/src/test/java/issues/Issue3977.java
new file mode 100644
index 0000000000..58dc43d758
--- /dev/null
+++ b/modules/jooby-openapi/src/test/java/issues/Issue3977.java
@@ -0,0 +1,82 @@
+/*
+ * Jooby https://jooby.io
+ * Apache License Version 2.0 https://jooby.io/LICENSE.txt
+ * Copyright 2014 Edgar Espina
+ */
+package issues;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import io.jooby.internal.openapi.OpenAPIExt;
+import io.jooby.openapi.OpenAPIGenerator;
+import io.swagger.v3.oas.models.info.Info;
+
+public class Issue3977 {
+ private Path outDir = Paths.get(System.getProperty("user.dir"), "target", "test-classes");
+
+ @Test
+ public void shouldCopyYamlToDir() throws IOException {
+ var copyDir = Paths.get(System.getProperty("user.dir"), "target", "spec");
+ var output = export("App", OpenAPIGenerator.Format.YAML, List.of(copyDir));
+ output.forEach(it -> assertTrue(Files.exists(it)));
+ assertTrue(output.contains(copyDir.resolve("App.yaml")));
+ }
+
+ @Test
+ public void shouldCopyJsonToDir() throws IOException {
+ var copyDir = Paths.get(System.getProperty("user.dir"), "target", "spec");
+ var output = export("App", OpenAPIGenerator.Format.JSON, List.of(copyDir));
+ output.forEach(it -> assertTrue(Files.exists(it)));
+ assertTrue(output.contains(copyDir.resolve("App.json")));
+ }
+
+ @Test
+ public void shouldCopyJsonFile() throws IOException {
+ var copyFile = Paths.get(System.getProperty("user.dir"), "target", "files", "open-api.json");
+ var output = export("App", OpenAPIGenerator.Format.JSON, List.of(copyFile));
+ output.forEach(it -> assertTrue(Files.exists(it)));
+ assertTrue(output.contains(copyFile));
+ }
+
+ @Test
+ public void shouldCopyYamlFile() throws IOException {
+ var copyFile = Paths.get(System.getProperty("user.dir"), "target", "files", "open-api.yaml");
+ var output = export("App", OpenAPIGenerator.Format.YAML, List.of(copyFile));
+ output.forEach(it -> assertTrue(Files.exists(it)));
+ assertTrue(output.contains(copyFile));
+ }
+
+ @Test
+ public void shouldCopyYmlFile() throws IOException {
+ var copyFile = Paths.get(System.getProperty("user.dir"), "target", "files", "open-api.yml");
+ var output = export("App", OpenAPIGenerator.Format.YAML, List.of(copyFile));
+ output.forEach(it -> assertTrue(Files.exists(it)));
+ assertTrue(output.contains(copyFile));
+ }
+
+ private List export(String source, OpenAPIGenerator.Format format, List copySpecTo)
+ throws IOException {
+ Info info = new Info();
+ info.setTitle("API");
+ info.setVersion("1.0");
+ info.setDescription("API description");
+
+ OpenAPIExt openAPI = new OpenAPIExt();
+ openAPI.setInfo(info);
+ openAPI.setSource(source);
+
+ OpenAPIGenerator generator = new OpenAPIGenerator();
+ generator.setOutputDir(outDir);
+ generator.setCopyOpenApiSpecTo(copySpecTo);
+ return generator.export(openAPI, format, Map.of());
+ }
+}