Skip to content

Commit 3cb392d

Browse files
gradle: persistent openapi spec
1 parent 745ca82 commit 3cb392d

2 files changed

Lines changed: 87 additions & 4 deletions

File tree

docs/asciidoc/modules/openapi.adoc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,17 @@ To avoid this behaviour you can specify maven build phase which suits your needs
133133
|Copy the generated OpenAPI spec to the given file in the project repository. The format is
134134
determined by the file extension: `.yaml`, `.yml` or `.json`. Example:
135135

136+
.Maven:
137+
136138
<copyOpenApiSpecTo>${project.basedir}/docs/openapi.yaml</copyOpenApiSpecTo>
137139

138-
|`===
140+
.Gradle:
141+
142+
{
143+
copyOpenApiSpecTo = file("$projectDir/docs/openapi.yaml")
144+
}
145+
146+
|===
139147

140148
=== Usage
141149

modules/jooby-gradle-plugin/src/main/java/io/jooby/gradle/OpenAPITask.java

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@
1515
import org.jspecify.annotations.Nullable;
1616

1717
import java.io.File;
18+
import java.io.IOException;
19+
import java.nio.file.Files;
1820
import java.nio.file.Path;
21+
import java.nio.file.StandardCopyOption;
22+
import java.util.ArrayList;
1923
import java.util.List;
24+
import java.util.Locale;
2025
import java.util.Map;
2126
import java.util.Optional;
2227

@@ -44,6 +49,8 @@ public class OpenAPITask extends BaseTask {
4449

4550
private String javadoc;
4651

52+
private File copyOpenApiSpecTo;
53+
4754
/**
4855
* Creates an OpenAPI task.
4956
*/
@@ -58,7 +65,7 @@ public OpenAPITask() {}
5865
public void generate() throws Throwable {
5966
List<Project> projects = getProjects();
6067

61-
String mainClass = Optional.ofNullable(this.mainClass)
68+
String mainClass = ofNullable(this.mainClass)
6269
.orElseGet(() -> computeMainClassName(projects));
6370
var sources = projects.stream()
6471
.flatMap(project -> {
@@ -94,10 +101,48 @@ public void generate() throws Throwable {
94101
OpenAPI result = tool.generate(mainClass);
95102

96103
var adocPath = ofNullable(adoc).orElse(List.of()).stream().map(File::toPath).toList();
104+
var written = new ArrayList<Path>();
97105
for (var format : OpenAPIGenerator.Format.values()) {
98-
tool.export(result, format, Map.of("adoc", adocPath))
99-
.forEach(output -> getLogger().info(" writing: " + output));
106+
written.addAll(tool.export(result, format, Map.of("adoc", adocPath)));
107+
}
108+
written.forEach(output -> getLogger().info(" writing: " + output));
109+
110+
if (copyOpenApiSpecTo != null) {
111+
var destination = copyOpenApiSpecTo.toPath();
112+
copySpec(written, destination);
113+
getLogger().info(" copying: " + destination);
114+
}
115+
}
116+
117+
static void copySpec(List<Path> written, Path destination) throws IOException {
118+
var format = specFormat(destination);
119+
var source =
120+
written.stream()
121+
.filter(path -> path.getFileName().toString().endsWith("." + format.extension()))
122+
.findFirst()
123+
.orElseThrow(
124+
() ->
125+
new IOException(
126+
String.format(
127+
"OpenAPI %s output not found for copyOpenApiSpecTo: %s",
128+
format.name(), destination)));
129+
var parent = destination.getParent();
130+
if (parent != null) {
131+
Files.createDirectories(parent);
100132
}
133+
Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
134+
}
135+
136+
static OpenAPIGenerator.Format specFormat(Path destination) {
137+
var name = destination.getFileName().toString().toLowerCase(Locale.ROOT);
138+
if (name.endsWith(".json")) {
139+
return OpenAPIGenerator.Format.JSON;
140+
}
141+
if (name.endsWith(".yaml") || name.endsWith(".yml")) {
142+
return OpenAPIGenerator.Format.YAML;
143+
}
144+
throw new IllegalArgumentException(
145+
"copyOpenApiSpecTo must end with .yaml, .yml or .json: " + destination);
101146
}
102147

103148
/**
@@ -243,6 +288,36 @@ public void setAdoc(List<File> adoc) {
243288
this.adoc = adoc;
244289
}
245290

291+
/**
292+
* Copy the generated OpenAPI spec to the given file. The format is determined by the file
293+
* extension: <code>.yaml</code>, <code>.yml</code> or <code>.json</code>.
294+
*
295+
* @return Destination file.
296+
*/
297+
@Input
298+
@org.gradle.api.tasks.Optional
299+
public @Nullable File getCopyOpenApiSpecTo() {
300+
return copyOpenApiSpecTo;
301+
}
302+
303+
/**
304+
* Copy the generated OpenAPI spec to the given file. The format is determined by the file
305+
* extension: <code>.yaml</code>, <code>.yml</code> or <code>.json</code>.
306+
*
307+
* <p>Example:
308+
*
309+
* <pre>{@code
310+
* openAPI {
311+
* copyOpenApiSpecTo = file("$projectDir/docs/openapi.yaml")
312+
* }
313+
* }</pre>
314+
*
315+
* @param copyOpenApiSpecTo Destination file.
316+
*/
317+
public void setCopyOpenApiSpecTo(@Nullable File copyOpenApiSpecTo) {
318+
this.copyOpenApiSpecTo = copyOpenApiSpecTo;
319+
}
320+
246321
private Optional<String> trim(String value) {
247322
if (value == null || value.trim().isEmpty()) {
248323
return Optional.empty();

0 commit comments

Comments
 (0)