Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
package org.eclipse.edc.plugins.edcbuild.conventions;

import io.swagger.v3.plugins.gradle.tasks.ResolveTask;
import org.eclipse.edc.plugins.edcbuild.extensions.ApiGroup;
import org.eclipse.edc.plugins.edcbuild.extensions.BuildExtension;
import org.eclipse.edc.plugins.edcbuild.extensions.SwaggerGeneratorExtension;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaPluginExtension;
import org.jspecify.annotations.NonNull;

import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Stream;

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

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

public static Path defaultOutputDirectory(Project project) {
Expand All @@ -51,49 +53,71 @@
).forEach(dependency -> target.getDependencies().add(IMPLEMENTATION_CONFIGURATION_NAME, dependency));

var swaggerExt = requireExtension(target, BuildExtension.class).getSwagger();
var tasks = target.getTasks();

var resourcePkgs = swaggerExt.getResourcePackages(); // already provides the default

target.getTasks().withType(ResolveTask.class).configureEach(task -> {
var outputFileName = swaggerExt.getOutputFilename().getOrElse(target.getName());
var fallbackOutputDir = defaultOutputDirectory(target);
var apiGroup = swaggerExt.getApiGroup().getOrElse(DEFAULT_API_GROUP);

var outputDir = Path.of(swaggerExt.getOutputDirectory().getOrElse(fallbackOutputDir.toFile()).toURI())
.resolve(apiGroup)
.toString();

task.setOutputFileName(outputFileName);
task.setOutputDir(outputDir);
task.setOutputFormat(ResolveTask.Format.YAML);
task.setSortOutput(true);
task.setPrettyPrint(true);
task.setClasspath(getClasspath(target));
task.setBuildClasspath(task.getClasspath());
task.setResourcePackages(resourcePkgs);
});

target.getTasks().register("openapi", ResolveTask.class).configure(task -> {
var outputDir = target.getLayout().getBuildDirectory().getAsFile().get().toPath()
.resolve("docs").resolve("openapi")
.toString();

target.getTasks().findByName("jar").dependsOn(task);
task.setGroup("documentation");
task.setDescription("Generates openapi specification documentation.");
task.setOutputFileName(swaggerExt.getApiGroup().getOrElse("openapi"));
task.setOutputDir(outputDir);
task.setOutputFormat(ResolveTask.Format.YAML);
task.setSortOutput(true);
task.setPrettyPrint(true);
task.setClasspath(getClasspath(target));
task.setBuildClasspath(task.getClasspath());
task.setResourcePackages(resourcePkgs);
});
var resolve = tasks.named("resolve");
resolve.configure(it -> it.setEnabled(false));

var openapiAll = tasks.register("openapi");

target.afterEvaluate(p -> getApiGroups(swaggerExt).forEach(apiGroup -> {
var resolveTask = tasks.register("resolve" + apiGroup.name(), ResolveTask.class, task -> {
var fallbackOutputDir = defaultOutputDirectory(target);

var outputDir = Path.of(swaggerExt.getOutputDirectory().getOrElse(fallbackOutputDir.toFile()).toURI())
.resolve(apiGroup.name())
.toString();

task.setOutputFileName(swaggerExt.getOutputFilename().getOrElse(target.getName()));
task.setOutputDir(outputDir);
task.setClasspath(getClasspath(target));
task.setResourcePackages(apiGroup.packages());

baseTaskConfiguration(task, target);
});
resolve.configure(t -> t.dependsOn(resolveTask));

var openapiTask = tasks.register("openapi" + apiGroup.name(), ResolveTask.class, task -> {
var outputDir = target.getLayout().getBuildDirectory().getAsFile().get().toPath()
.resolve("docs").resolve("openapi")
.toString();

task.setGroup("documentation");
task.setDescription("Generates openapi specification documentation.");
task.setOutputFileName(apiGroup.name());
task.setResourcePackages(apiGroup.packages());
task.setOutputDir(outputDir);

baseTaskConfiguration(task, target);
});
openapiAll.configure(t -> t.dependsOn(openapiTask));
tasks.named("jar").configure(t -> t.dependsOn(openapiTask));
}));

});
}

private static void baseTaskConfiguration(ResolveTask task, Project project) {
task.setClasspath(getClasspath(project));
task.setBuildClasspath(task.getClasspath());
task.setOutputFormat(ResolveTask.Format.YAML);
task.setSortOutput(true);
task.setPrettyPrint(true);
task.setReadAllResources(true);
task.setSkip(false);
task.setEncoding("UTF-8");
task.setAlwaysResolveAppPath(Boolean.FALSE);
task.setSkipResolveAppPath(Boolean.FALSE);
task.setOpenAPI31(false);
task.setConvertToOpenAPI31(false);
}

private static @NonNull Set<ApiGroup> getApiGroups(SwaggerGeneratorExtension swaggerExt) {
return swaggerExt.getApiGroup()

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note

Invoking
SwaggerGeneratorExtension.getApiGroup
should be avoided because it has been deprecated.
.map(group -> Set.of(new ApiGroup(group, swaggerExt.getResourcePackages())))
.getOrElse(swaggerExt.getApiGroups());
}

private static @NonNull FileCollection getClasspath(Project target) {
return requireExtension(target, JavaPluginExtension.class)
.getSourceSets().getAt("main").getRuntimeClasspath();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 Think-it GmbH
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Think-it GmbH - initial API and implementation
*
*/

package org.eclipse.edc.plugins.edcbuild.extensions;

import java.util.Set;

public record ApiGroup(String name, Set<String> packages) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
import org.gradle.api.provider.Property;

import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public abstract class SwaggerGeneratorExtension {

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

public abstract Property<String> getOutputFilename();

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

/**
* OpenApi Title of the generated and merged openapi.yaml file
* Get Api Group
*
* @return the property
* @deprecated please use `apiGroup(name, packages...)` instead
*/
public abstract Property<String> getTitle();
@Deprecated(since = "1.5.0")
public abstract Property<String> getApiGroup();

/**
* OpenAPI description of the merged openapi.yaml file
*/
public abstract Property<String> getDescription();
public void apiGroup(String group, String... packages) {
apiGroups.add(new ApiGroup(group, new HashSet<>(Arrays.asList(packages))));
}

public abstract Property<String> getApiGroup();
public Set<ApiGroup> getApiGroups() {
return apiGroups;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
import io.swagger.v3.plugins.gradle.tasks.ResolveTask;
import org.eclipse.edc.plugins.edcbuild.extensions.BuildExtension;
import org.gradle.api.Project;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -30,56 +33,116 @@
class SwaggerResolveConventionTest {

private static final String PROJECT_NAME = "testproject";
private Project project;
private final Project project = ProjectBuilder.builder().withName(PROJECT_NAME).build();
private final SwaggerResolveConvention convention = new SwaggerResolveConvention();

@BeforeEach
void setUp() {
project = ProjectBuilder.builder().withName(PROJECT_NAME).build();
project.getRepositories().mavenCentral();
project.getPluginManager().apply(SWAGGER_GRADLE_PLUGIN);
project.getPluginManager().apply(JavaPlugin.class);
project.getExtensions().create("edcBuild", BuildExtension.class, project.getObjects());
}

@Test
void apply_whenApiGroupNotSpecified_shouldUseDefault() {
var convention = new SwaggerResolveConvention();
void shouldDisableTask_whenNoGroupSpecified() {
convention.apply(project);

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

assertThat(resolveTask.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml");
assertThat(resolveTask.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
assertThat(resolveTask.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);

assertThat(resolveTask.isEnabled()).isFalse();
assertThat(resolveTask.getDependsOn()).hasSize(0);
}

@Test
void apply_whenApiGroupSpecified_shouldAppend() {
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
swagger.getApiGroup().set("test-api");
var convention = new SwaggerResolveConvention();
convention.apply(project);

var resolveTask = (ResolveTask) project.getTasks().getByName("resolve");
swagger.apiGroup("test-api");

assertThat(resolveTask.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/test-api");
assertThat(resolveTask.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
assertThat(resolveTask.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
convention.apply(project);
((ProjectInternal) project).evaluate();
var resolveTask = project.getTasks().getByName("resolve");

assertThat(resolveTask.getDependsOn()).hasSize(1).first().isInstanceOfSatisfying(TaskProvider.class, taskProvider -> {
assertThat(taskProvider.isPresent()).isTrue();
assertThat(taskProvider.get()).isInstanceOfSatisfying(ResolveTask.class, actual -> {
assertThat(actual.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/test-api");
assertThat(actual.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
assertThat(actual.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
});
});
}

@Test
void apply_whenOutputDirSet_shouldAppend() {
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
swagger.getApiGroup().set("test-api");
swagger.apiGroup("test-api");
swagger.getOutputDirectory().set(new File("some/funny/path"));
var convention = new SwaggerResolveConvention();

convention.apply(project);
((ProjectInternal) project).evaluate();
var resolveTask = project.getTasks().getByName("resolve");

assertThat(resolveTask.getDependsOn()).hasSize(1).first().isInstanceOfSatisfying(TaskProvider.class, taskProvider -> {
assertThat(taskProvider.isPresent()).isTrue();
assertThat(taskProvider.get()).isInstanceOfSatisfying(ResolveTask.class, actual -> {
assertThat(actual.getOutputDir().get().toString()).endsWith("/some/funny/path/test-api");
assertThat(actual.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
assertThat(actual.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
});
});
}

@Test
void shouldSetPackage_whenSpecified() {
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
swagger.apiGroup("group", "the.package.name", "another.package.name");

convention.apply(project);
((ProjectInternal) project).evaluate();
var resolveTask = project.getTasks().getByName("resolve");

var resolveTask = (ResolveTask) project.getTasks().getByName("resolve");
assertThat(resolveTask.getDependsOn()).hasSize(1).map(TaskProvider.class::cast).map(Provider::get).map(ResolveTask.class::cast)
.first().satisfies(task -> {
assertThat(task.getResourcePackages().get()).containsExactlyInAnyOrder("the.package.name", "another.package.name");
});
}

@Test
void shouldRegisterMultipleTasks_whenMultipleApiGroups() {
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
swagger.apiGroup("group-one");
swagger.apiGroup("group-two");

convention.apply(project);
((ProjectInternal) project).evaluate();
var resolveTask = project.getTasks().getByName("resolve");

assertThat(resolveTask.getDependsOn()).hasSize(2).map(TaskProvider.class::cast).map(Provider::get).map(ResolveTask.class::cast)
.anySatisfy(p -> assertThat(p.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/group-one"))
.anySatisfy(p -> assertThat(p.getOutputDir().get().toString()).endsWith("/resources/openapi/yaml/group-two"));
}

@Deprecated(since = "1.5.0")
@Test
void shouldApplyDeprecatedApiGroupCall_whenItIsDefined() {
var swagger = ConventionFunctions.requireExtension(project, BuildExtension.class).getSwagger();
swagger.getApiGroup().set("test-api"); // deprecated call
swagger.apiGroup("another-one");
swagger.getOutputDirectory().set(new File("some/funny/path"));

convention.apply(project);
((ProjectInternal) project).evaluate();
var resolveTask = project.getTasks().getByName("resolve");

assertThat(resolveTask.getDependsOn()).hasSize(1).first().isInstanceOfSatisfying(TaskProvider.class, taskProvider -> {
assertThat(taskProvider.isPresent()).isTrue();
assertThat(taskProvider.get()).isInstanceOfSatisfying(ResolveTask.class, actual -> {
assertThat(actual.getOutputDir().get().toString()).endsWith("/some/funny/path/test-api");
assertThat(actual.getOutputFileName().get()).isEqualTo(PROJECT_NAME);
assertThat(actual.getOutputFormat().get()).isEqualTo(ResolveTask.Format.YAML);
});
});

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