Skip to content

Commit 62bf0c4

Browse files
committed
feat: multiple api-groups per module
1 parent 9d80cfa commit 62bf0c4

4 files changed

Lines changed: 183 additions & 68 deletions

File tree

plugins/edc-build/src/main/java/org/eclipse/edc/plugins/edcbuild/conventions/SwaggerResolveConvention.java

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
package org.eclipse.edc.plugins.edcbuild.conventions;
1616

1717
import io.swagger.v3.plugins.gradle.tasks.ResolveTask;
18+
import org.eclipse.edc.plugins.edcbuild.extensions.ApiGroup;
1819
import org.eclipse.edc.plugins.edcbuild.extensions.BuildExtension;
20+
import org.eclipse.edc.plugins.edcbuild.extensions.SwaggerGeneratorExtension;
1921
import org.gradle.api.Project;
2022
import org.gradle.api.file.FileCollection;
2123
import org.gradle.api.plugins.JavaPluginExtension;
2224
import org.jspecify.annotations.NonNull;
2325

2426
import java.nio.file.Path;
27+
import java.util.Set;
2528
import java.util.stream.Stream;
2629

2730
import static org.eclipse.edc.plugins.edcbuild.Versions.JAKARTA_WS_RS;
@@ -34,7 +37,6 @@
3437
*/
3538
class SwaggerResolveConvention implements EdcConvention {
3639

37-
private static final String DEFAULT_API_GROUP = "";
3840
public static final String SWAGGER_GRADLE_PLUGIN = "io.swagger.core.v3.swagger-gradle-plugin";
3941

4042
public static Path defaultOutputDirectory(Project project) {
@@ -51,49 +53,71 @@ public void apply(Project target) {
5153
).forEach(dependency -> target.getDependencies().add(IMPLEMENTATION_CONFIGURATION_NAME, dependency));
5254

5355
var swaggerExt = requireExtension(target, BuildExtension.class).getSwagger();
56+
var tasks = target.getTasks();
5457

55-
var resourcePkgs = swaggerExt.getResourcePackages(); // already provides the default
56-
57-
target.getTasks().withType(ResolveTask.class).configureEach(task -> {
58-
var outputFileName = swaggerExt.getOutputFilename().getOrElse(target.getName());
59-
var fallbackOutputDir = defaultOutputDirectory(target);
60-
var apiGroup = swaggerExt.getApiGroup().getOrElse(DEFAULT_API_GROUP);
61-
62-
var outputDir = Path.of(swaggerExt.getOutputDirectory().getOrElse(fallbackOutputDir.toFile()).toURI())
63-
.resolve(apiGroup)
64-
.toString();
65-
66-
task.setOutputFileName(outputFileName);
67-
task.setOutputDir(outputDir);
68-
task.setOutputFormat(ResolveTask.Format.YAML);
69-
task.setSortOutput(true);
70-
task.setPrettyPrint(true);
71-
task.setClasspath(getClasspath(target));
72-
task.setBuildClasspath(task.getClasspath());
73-
task.setResourcePackages(resourcePkgs);
74-
});
75-
76-
target.getTasks().register("openapi", ResolveTask.class).configure(task -> {
77-
var outputDir = target.getLayout().getBuildDirectory().getAsFile().get().toPath()
78-
.resolve("docs").resolve("openapi")
79-
.toString();
80-
81-
target.getTasks().findByName("jar").dependsOn(task);
82-
task.setGroup("documentation");
83-
task.setDescription("Generates openapi specification documentation.");
84-
task.setOutputFileName(swaggerExt.getApiGroup().getOrElse("openapi"));
85-
task.setOutputDir(outputDir);
86-
task.setOutputFormat(ResolveTask.Format.YAML);
87-
task.setSortOutput(true);
88-
task.setPrettyPrint(true);
89-
task.setClasspath(getClasspath(target));
90-
task.setBuildClasspath(task.getClasspath());
91-
task.setResourcePackages(resourcePkgs);
92-
});
58+
var resolve = tasks.named("resolve");
59+
resolve.configure(it -> it.setEnabled(false));
60+
61+
var openapiAll = tasks.register("openapi");
62+
63+
target.afterEvaluate(p -> getApiGroups(swaggerExt).forEach(apiGroup -> {
64+
var resolveTask = tasks.register("resolve" + apiGroup.name(), ResolveTask.class, task -> {
65+
var fallbackOutputDir = defaultOutputDirectory(target);
66+
67+
var outputDir = Path.of(swaggerExt.getOutputDirectory().getOrElse(fallbackOutputDir.toFile()).toURI())
68+
.resolve(apiGroup.name())
69+
.toString();
70+
71+
task.setOutputFileName(swaggerExt.getOutputFilename().getOrElse(target.getName()));
72+
task.setOutputDir(outputDir);
73+
task.setClasspath(getClasspath(target));
74+
task.setResourcePackages(apiGroup.packages());
75+
76+
baseTaskConfiguration(task, target);
77+
});
78+
resolve.configure(t -> t.dependsOn(resolveTask));
79+
80+
var openapiTask = tasks.register("openapi" + apiGroup.name(), ResolveTask.class, task -> {
81+
var outputDir = target.getLayout().getBuildDirectory().getAsFile().get().toPath()
82+
.resolve("docs").resolve("openapi")
83+
.toString();
84+
85+
task.setGroup("documentation");
86+
task.setDescription("Generates openapi specification documentation.");
87+
task.setOutputFileName(apiGroup.name());
88+
task.setResourcePackages(apiGroup.packages());
89+
task.setOutputDir(outputDir);
90+
91+
baseTaskConfiguration(task, target);
92+
});
93+
openapiAll.configure(t -> t.dependsOn(openapiTask));
94+
tasks.named("jar").configure(t -> t.dependsOn(openapiTask));
95+
}));
9396

9497
});
9598
}
9699

100+
private static void baseTaskConfiguration(ResolveTask task, Project project) {
101+
task.setClasspath(getClasspath(project));
102+
task.setBuildClasspath(task.getClasspath());
103+
task.setOutputFormat(ResolveTask.Format.YAML);
104+
task.setSortOutput(true);
105+
task.setPrettyPrint(true);
106+
task.setReadAllResources(true);
107+
task.setSkip(false);
108+
task.setEncoding("UTF-8");
109+
task.setAlwaysResolveAppPath(Boolean.FALSE);
110+
task.setSkipResolveAppPath(Boolean.FALSE);
111+
task.setOpenAPI31(false);
112+
task.setConvertToOpenAPI31(false);
113+
}
114+
115+
private static @NonNull Set<ApiGroup> getApiGroups(SwaggerGeneratorExtension swaggerExt) {
116+
return swaggerExt.getApiGroup()
117+
.map(group -> Set.of(new ApiGroup(group, swaggerExt.getResourcePackages())))
118+
.getOrElse(swaggerExt.getApiGroups());
119+
}
120+
97121
private static @NonNull FileCollection getClasspath(Project target) {
98122
return requireExtension(target, JavaPluginExtension.class)
99123
.getSourceSets().getAt("main").getRuntimeClasspath();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (c) 2026 Think-it GmbH
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Think-it GmbH - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.plugins.edcbuild.extensions;
16+
17+
import java.util.Set;
18+
19+
public record ApiGroup(String name, Set<String> packages) {
20+
}

plugins/edc-build/src/main/java/org/eclipse/edc/plugins/edcbuild/extensions/SwaggerGeneratorExtension.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
import org.gradle.api.provider.Property;
1818

1919
import java.io.File;
20+
import java.util.Arrays;
21+
import java.util.HashSet;
2022
import java.util.Set;
2123

2224
public abstract class SwaggerGeneratorExtension {
2325

2426
private Set<String> resourcePackages = Set.of("org.eclipse.edc");
27+
private Set<ApiGroup> apiGroups = new HashSet<>();
2528

2629
public abstract Property<String> getOutputFilename();
2730

@@ -36,15 +39,20 @@ public void setResourcePackages(Set<String> resourcePackages) {
3639
}
3740

3841
/**
39-
* OpenApi Title of the generated and merged openapi.yaml file
42+
* Get Api Group
43+
*
44+
* @return the property
45+
* @deprecated please use `apiGroup(name, packages...)` instead
4046
*/
41-
public abstract Property<String> getTitle();
47+
@Deprecated(since = "1.5.0")
48+
public abstract Property<String> getApiGroup();
4249

43-
/**
44-
* OpenAPI description of the merged openapi.yaml file
45-
*/
46-
public abstract Property<String> getDescription();
50+
public void apiGroup(String group, String... packages) {
51+
apiGroups.add(new ApiGroup(group, new HashSet<>(Arrays.asList(packages))));
52+
}
4753

48-
public abstract Property<String> getApiGroup();
54+
public Set<ApiGroup> getApiGroups() {
55+
return apiGroups;
56+
}
4957

5058
}

plugins/edc-build/src/test/java/org/eclipse/edc/plugins/edcbuild/conventions/SwaggerResolveConventionTest.java

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
import io.swagger.v3.plugins.gradle.tasks.ResolveTask;
1818
import org.eclipse.edc.plugins.edcbuild.extensions.BuildExtension;
1919
import org.gradle.api.Project;
20+
import org.gradle.api.internal.project.ProjectInternal;
2021
import org.gradle.api.plugins.JavaPlugin;
22+
import org.gradle.api.provider.Provider;
23+
import org.gradle.api.tasks.TaskProvider;
2124
import org.gradle.testfixtures.ProjectBuilder;
2225
import org.junit.jupiter.api.BeforeEach;
2326
import org.junit.jupiter.api.Test;
@@ -30,56 +33,116 @@
3033
class SwaggerResolveConventionTest {
3134

3235
private static final String PROJECT_NAME = "testproject";
33-
private Project project;
36+
private final Project project = ProjectBuilder.builder().withName(PROJECT_NAME).build();
37+
private final SwaggerResolveConvention convention = new SwaggerResolveConvention();
3438

3539
@BeforeEach
3640
void setUp() {
37-
project = ProjectBuilder.builder().withName(PROJECT_NAME).build();
3841
project.getRepositories().mavenCentral();
3942
project.getPluginManager().apply(SWAGGER_GRADLE_PLUGIN);
4043
project.getPluginManager().apply(JavaPlugin.class);
4144
project.getExtensions().create("edcBuild", BuildExtension.class, project.getObjects());
4245
}
4346

4447
@Test
45-
void apply_whenApiGroupNotSpecified_shouldUseDefault() {
46-
var convention = new SwaggerResolveConvention();
48+
void shouldDisableTask_whenNoGroupSpecified() {
4749
convention.apply(project);
4850

4951
var resolveTask = (ResolveTask) project.getTasks().getByName("resolve");
5052

51-
assertThat(resolveTask.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml");
52-
assertThat(resolveTask.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
53-
assertThat(resolveTask.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
54-
53+
assertThat(resolveTask.isEnabled()).isFalse();
54+
assertThat(resolveTask.getDependsOn()).hasSize(0);
5555
}
5656

5757
@Test
5858
void apply_whenApiGroupSpecified_shouldAppend() {
5959
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
60-
swagger.getApiGroup().set("test-api");
61-
var convention = new SwaggerResolveConvention();
62-
convention.apply(project);
63-
64-
var resolveTask = (ResolveTask) project.getTasks().getByName("resolve");
60+
swagger.apiGroup("test-api");
6561

66-
assertThat(resolveTask.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/test-api");
67-
assertThat(resolveTask.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
68-
assertThat(resolveTask.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
62+
convention.apply(project);
63+
((ProjectInternal) project).evaluate();
64+
var resolveTask = project.getTasks().getByName("resolve");
65+
66+
assertThat(resolveTask.getDependsOn()).hasSize(1).first().isInstanceOfSatisfying(TaskProvider.class, taskProvider -> {
67+
assertThat(taskProvider.isPresent()).isTrue();
68+
assertThat(taskProvider.get()).isInstanceOfSatisfying(ResolveTask.class, actual -> {
69+
assertThat(actual.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/test-api");
70+
assertThat(actual.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
71+
assertThat(actual.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
72+
});
73+
});
6974
}
7075

7176
@Test
7277
void apply_whenOutputDirSet_shouldAppend() {
7378
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
74-
swagger.getApiGroup().set("test-api");
79+
swagger.apiGroup("test-api");
7580
swagger.getOutputDirectory().set(new File("some/funny/path"));
76-
var convention = new SwaggerResolveConvention();
81+
82+
convention.apply(project);
83+
((ProjectInternal) project).evaluate();
84+
var resolveTask = project.getTasks().getByName("resolve");
85+
86+
assertThat(resolveTask.getDependsOn()).hasSize(1).first().isInstanceOfSatisfying(TaskProvider.class, taskProvider -> {
87+
assertThat(taskProvider.isPresent()).isTrue();
88+
assertThat(taskProvider.get()).isInstanceOfSatisfying(ResolveTask.class, actual -> {
89+
assertThat(actual.getOutputDir().get().toString()).endsWith("/some/funny/path/test-api");
90+
assertThat(actual.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
91+
assertThat(actual.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
92+
});
93+
});
94+
}
95+
96+
@Test
97+
void shouldSetPackage_whenSpecified() {
98+
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
99+
swagger.apiGroup("group", "the.package.name", "another.package.name");
100+
77101
convention.apply(project);
102+
((ProjectInternal) project).evaluate();
103+
var resolveTask = project.getTasks().getByName("resolve");
78104

79-
var resolveTask = (ResolveTask) project.getTasks().getByName("resolve");
105+
assertThat(resolveTask.getDependsOn()).hasSize(1).map(TaskProvider.class::cast).map(Provider::get).map(ResolveTask.class::cast)
106+
.first().satisfies(task -> {
107+
assertThat(task.getResourcePackages().get()).containsExactlyInAnyOrder("the.package.name", "another.package.name");
108+
});
109+
}
110+
111+
@Test
112+
void shouldRegisterMultipleTasks_whenMultipleApiGroups() {
113+
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
114+
swagger.apiGroup("group-one");
115+
swagger.apiGroup("group-two");
116+
117+
convention.apply(project);
118+
((ProjectInternal) project).evaluate();
119+
var resolveTask = project.getTasks().getByName("resolve");
120+
121+
assertThat(resolveTask.getDependsOn()).hasSize(2).map(TaskProvider.class::cast).map(Provider::get).map(ResolveTask.class::cast)
122+
.anySatisfy(p -> assertThat(p.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/group-one"))
123+
.anySatisfy(p -> assertThat(p.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/group-two"));
124+
}
125+
126+
@Deprecated(since = "1.5.0")
127+
@Test
128+
void shouldApplyDeprecatedApiGroupCall_whenItIsDefined() {
129+
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
130+
swagger.getApiGroup().set("test-api"); // deprecated call
131+
swagger.apiGroup("another-one");
132+
swagger.getOutputDirectory().set(new File("some/funny/path"));
133+
134+
convention.apply(project);
135+
((ProjectInternal) project).evaluate();
136+
var resolveTask = project.getTasks().getByName("resolve");
137+
138+
assertThat(resolveTask.getDependsOn()).hasSize(1).first().isInstanceOfSatisfying(TaskProvider.class, taskProvider -> {
139+
assertThat(taskProvider.isPresent()).isTrue();
140+
assertThat(taskProvider.get()).isInstanceOfSatisfying(ResolveTask.class, actual -> {
141+
assertThat(actual.getOutputDir().get().toString()).endsWith("/some/funny/path/test-api");
142+
assertThat(actual.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
143+
assertThat(actual.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
144+
});
145+
});
80146

81-
assertThat(resolveTask.getOutputDir().get().toString()).endsWith("/some/funny/path/test-api");
82-
assertThat(resolveTask.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
83-
assertThat(resolveTask.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
84147
}
85148
}

0 commit comments

Comments
 (0)