Skip to content

Commit 63315ab

Browse files
michaelsonnleitnerMichael Sonnleitner
andauthored
Support GAV coordinates for client generation #1340 (#1343)
* Support GAV coordinates for client generation #1340 * Support GAV coordinates for client generation #1340 * avoid star imports * SUPPORTED_EXTENSIONS refactored for better use * add filter for artifactIds & improved config naming(singular/plural) & tests simplicity & add config options to GlobalCodegenConfig * windows problem with colon in filename & extended test with specific spec mapping to ensure that config key is right * use verify phase to store artifacts to local mvn repo to get website / codeql action green * renaming and javadoc * use package phase for artifact installation to get codeql action running --------- Co-authored-by: Michael Sonnleitner <[email protected]>
1 parent 0f08eca commit 63315ab

File tree

15 files changed

+528
-7
lines changed

15 files changed

+528
-7
lines changed

client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.util.Arrays;
55
import java.util.List;
66
import java.util.Map;
7-
import java.util.stream.Collectors;
87

98
import io.quarkiverse.openapi.generator.deployment.codegen.OpenApiGeneratorOutputPaths;
109
import io.quarkus.runtime.annotations.ConfigPhase;
@@ -30,15 +29,18 @@ public interface CodegenConfig extends GlobalCodegenConfig {
3029
String BUILD_TIME_SPEC_PREFIX_FORMAT = "quarkus." + CODEGEN_TIME_CONFIG_PREFIX + ".spec.%s";
3130

3231
List<String> SUPPORTED_CONFIGURATIONS = Arrays.stream(ConfigName.values()).map(cn -> cn.name)
33-
.collect(Collectors.toList());
32+
.toList();
3433

3534
enum ConfigName {
3635
//global configs
3736
VERBOSE("verbose"),
3837
INPUT_BASE_DIR("input-base-dir"),
38+
GAV_SCANNING("gav-scanning"),
3939
TEMPLATE_BASE_DIR("template-base-dir"),
4040
INCLUDE("include"),
4141
EXCLUDE("exclude"),
42+
ARTIFACT_ID_FILTER("artifact-id-filter"),
43+
EXCLUDE_GAVS("exclude-gavs"),
4244
VALIDATE_SPEC("validateSpec"),
4345
DEFAULT_SECURITY_SCHEME("default-security-scheme"),
4446

client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GlobalCodegenConfig.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkiverse.openapi.generator.deployment;
22

3+
import java.util.List;
34
import java.util.Optional;
45

56
import io.smallrye.config.WithDefault;
@@ -27,6 +28,13 @@ public interface GlobalCodegenConfig extends CommonItemConfig {
2728
@WithName("input-base-dir")
2829
Optional<String> inputBaseDir();
2930

31+
/**
32+
* Whether or not to skip gav scanning.
33+
*/
34+
@WithName("gav-scanning")
35+
@WithDefault("true")
36+
boolean gavScanning();
37+
3038
/**
3139
* Option to change the directory where template files must be found.
3240
*/
@@ -53,6 +61,19 @@ public interface GlobalCodegenConfig extends CommonItemConfig {
5361
@WithName("exclude")
5462
Optional<String> exclude();
5563

64+
/**
65+
* Option to filter artifactId from generation
66+
*/
67+
@WithName("artifact-id-filter")
68+
@WithDefault(".*openapi.*")
69+
Optional<String> artifactIdFilter();
70+
71+
/**
72+
* Option to exclude GAVs from generation
73+
*/
74+
@WithName("exclude-gavs")
75+
Optional<List<String>> excludeGavs();
76+
5677
/**
5778
* Create security for the referenced security scheme
5879
*/

client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ public String providerId() {
88

99
@Override
1010
public String[] inputExtensions() {
11-
return new String[] { JSON, YAML, YML };
11+
return SUPPORTED_EXTENSIONS_WITH_LEADING_DOT.toArray(new String[0]);
1212
}
1313
}

client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.Optional;
29+
import java.util.Set;
2930
import java.util.function.Predicate;
3031
import java.util.stream.Collectors;
3132
import java.util.stream.Stream;
@@ -56,9 +57,12 @@
5657
*/
5758
public abstract class OpenApiGeneratorCodeGenBase implements CodeGenProvider {
5859

59-
static final String YAML = ".yaml";
60-
static final String YML = ".yml";
61-
static final String JSON = ".json";
60+
static final String YAML = "yaml";
61+
static final String YML = "yml";
62+
static final String JSON = "json";
63+
64+
static final Set<String> SUPPORTED_EXTENSIONS = Set.of(YAML, YML, JSON);
65+
static final Set<String> SUPPORTED_EXTENSIONS_WITH_LEADING_DOT = Set.of("." + YAML, "." + YML, "." + JSON);
6266

6367
private static final String DEFAULT_PACKAGE = "org.openapi.quarkus";
6468
private static final String CONFIG_KEY_PROPERTY = "config-key";

client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiSpecInputProvider.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.InputStream;
44
import java.util.List;
55

6+
import io.quarkus.bootstrap.prebuild.CodeGenException;
67
import io.quarkus.deployment.CodeGenContext;
78

89
/**
@@ -13,8 +14,10 @@ public interface OpenApiSpecInputProvider {
1314
/**
1415
* Fetch OpenAPI specification files from a given source.
1516
*
17+
* @param context the current codegen context.
18+
* @throws CodeGenException if an error occurs while reading the spec files.
1619
* @return a list of spec files in {@link InputStream} format.
1720
*/
18-
List<SpecInputModel> read(CodeGenContext context);
21+
List<SpecInputModel> read(CodeGenContext context) throws CodeGenException;
1922

2023
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package io.quarkiverse.openapi.generator.deployment.codegen;
2+
3+
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.getGlobalConfigName;
4+
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.ARTIFACT_ID_FILTER;
5+
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.EXCLUDE_GAVS;
6+
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.GAV_SCANNING;
7+
import static io.quarkiverse.openapi.generator.deployment.codegen.OpenApiGeneratorCodeGenBase.SUPPORTED_EXTENSIONS;
8+
9+
import java.io.IOException;
10+
import java.nio.file.Files;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.function.Predicate;
14+
15+
import org.jboss.logging.Logger;
16+
17+
import io.quarkus.bootstrap.prebuild.CodeGenException;
18+
import io.quarkus.deployment.CodeGenContext;
19+
import io.quarkus.maven.dependency.ResolvedDependency;
20+
import io.smallrye.config.common.utils.StringUtil;
21+
22+
/**
23+
* Provides OpenAPI specification input from Maven GAV (GroupId:ArtifactId:Version) coordinates.
24+
* <p>
25+
* This provider scans the application's dependencies for YAML or JSON files that match
26+
* specific criteria and provides them as input for OpenAPI code generation. It implements
27+
* the {@link OpenApiSpecInputProvider} interface to integrate with the OpenAPI Generator's
28+
* code generation pipeline.
29+
* </p>
30+
*
31+
* <h2>Scanning Behavior</h2>
32+
* <p>
33+
* The provider performs the following steps:
34+
* </p>
35+
* <ol>
36+
* <li>Checks if GAV scanning is enabled via configuration (enabled by default)</li>
37+
* <li>Filters dependencies by artifact type (yaml/yml/json)</li>
38+
* <li>Applies artifact ID filtering using a regex pattern</li>
39+
* <li>Excludes specific GAVs based on configuration</li>
40+
* <li>Creates {@link SpecInputModel} instances for each matching dependency</li>
41+
* </ol>
42+
*
43+
* <h2>Configuration</h2>
44+
* <p>
45+
* The provider respects the following configuration properties:
46+
* </p>
47+
* <ul>
48+
* <li>{@code quarkus.openapi-generator.codegen.gav-scanning} - Enable/disable GAV scanning</li>
49+
* <li>{@code quarkus.openapi-generator.codegen.artifact-id-filter} - Regex pattern for artifact ID filtering</li>
50+
* <li>{@code quarkus.openapi-generator.codegen.exclude-gavs} - List of GAV coordinates to exclude
51+
* (format: groupId:artifactId:classifier)</li>
52+
* </ul>
53+
*
54+
* <h2>Example Usage</h2>
55+
*
56+
* <pre>
57+
* # application.properties
58+
* quarkus.openapi-generator.codegen.gav-scanning=true
59+
* quarkus.openapi-generator.codegen.artifact-id-filter=.*api.*
60+
* quarkus.openapi-generator.codegen.exclude-gavs=com.example:old-api
61+
* </pre>
62+
*
63+
* @see OpenApiSpecInputProvider
64+
* @see SpecInputModel
65+
* @see CodeGenContext
66+
*/
67+
public class YamlOrJsonGAVCoordinateOpenApiSpecInputProvider implements OpenApiSpecInputProvider {
68+
private static final Logger LOG = Logger.getLogger(YamlOrJsonGAVCoordinateOpenApiSpecInputProvider.class);
69+
70+
@Override
71+
public List<SpecInputModel> read(CodeGenContext context) throws CodeGenException {
72+
if (!context.config().getOptionalValue(getGlobalConfigName(GAV_SCANNING), Boolean.class)
73+
.orElse(true)) {
74+
LOG.debug("GAV scanning is disabled.");
75+
return List.of();
76+
}
77+
78+
List<String> gavsToExclude = context.config().getOptionalValues(getGlobalConfigName(EXCLUDE_GAVS), String.class)
79+
.orElse(List.of());
80+
String artifactIdFilter = context.config().getOptionalValue(getGlobalConfigName(ARTIFACT_ID_FILTER), String.class)
81+
.filter(Predicate.not(String::isBlank))
82+
.orElse(".*openapi.*");
83+
84+
List<ResolvedDependency> yamlDependencies = context.applicationModel().getDependencies().stream()
85+
.filter(rd -> SUPPORTED_EXTENSIONS.contains(rd.getType().toLowerCase()))
86+
.filter(rd -> rd.getArtifactId().matches(artifactIdFilter))
87+
.filter(rd -> !gavsToExclude.contains(rd.getKey().toGacString()))
88+
.toList();
89+
90+
if (yamlDependencies.isEmpty()) {
91+
LOG.debug("No suitable GAV dependencies found. ArtifactIdFilter was %s and gavsToExclude were %s."
92+
.formatted(artifactIdFilter, gavsToExclude));
93+
return List.of();
94+
}
95+
var inputModels = new ArrayList<SpecInputModel>();
96+
for (ResolvedDependency yamlDependency : yamlDependencies) {
97+
var gacString = StringUtil.replaceNonAlphanumericByUnderscores(yamlDependency.getKey().toGacString());
98+
var path = yamlDependency.getResolvedPaths().stream().findFirst()
99+
.orElseThrow(() -> new CodeGenException("Could not find maven path of %s.".formatted(gacString)));
100+
try {
101+
inputModels.add(new SpecInputModel(gacString, Files.newInputStream(path)));
102+
} catch (IOException e) {
103+
throw new CodeGenException("Could not open input stream of %s from %s.".formatted(gacString, path.toString()),
104+
e);
105+
}
106+
}
107+
return inputModels;
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.quarkiverse.openapi.generator.deployment.codegen.YamlOrJsonGAVCoordinateOpenApiSpecInputProvider
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<parent>
4+
<artifactId>quarkus-openapi-generator-integration-tests</artifactId>
5+
<groupId>io.quarkiverse.openapi.generator</groupId>
6+
<version>3.0.0-SNAPSHOT</version>
7+
</parent>
8+
<modelVersion>4.0.0</modelVersion>
9+
10+
<artifactId>quarkus-openapi-generator-gav-source</artifactId>
11+
<name>Quarkus - OpenAPI Generator - Integration Tests - Client - GAV source</name>
12+
<description>Example project for OpenAPI with GAV source</description>
13+
14+
<build>
15+
<plugins>
16+
<plugin>
17+
<groupId>org.apache.maven.plugins</groupId>
18+
<artifactId>maven-install-plugin</artifactId>
19+
<executions>
20+
<execution>
21+
<id>install-echo1</id>
22+
<goals>
23+
<goal>install-file</goal>
24+
</goals>
25+
<phase>package</phase>
26+
<configuration>
27+
<artifactId>quarkus-openapi-generator-gav-source-echo1</artifactId>
28+
<groupId>io.quarkiverse.openapi.generator</groupId>
29+
<version>3.0.0-SNAPSHOT</version>
30+
<file>src/main/openapi/echo.yaml</file>
31+
<packaging>yaml</packaging>
32+
</configuration>
33+
</execution>
34+
<execution>
35+
<id>install-echo2</id>
36+
<goals>
37+
<goal>install-file</goal>
38+
</goals>
39+
<phase>package</phase>
40+
<configuration>
41+
<artifactId>quarkus-openapi-generator-gav-source-echo2</artifactId>
42+
<groupId>io.quarkiverse.openapi.generator</groupId>
43+
<version>3.0.0-SNAPSHOT</version>
44+
<file>src/main/openapi/echo.yaml</file>
45+
<packaging>yaml</packaging>
46+
</configuration>
47+
</execution>
48+
<execution>
49+
<id>install-other</id>
50+
<goals>
51+
<goal>install-file</goal>
52+
</goals>
53+
<phase>package</phase>
54+
<configuration>
55+
<artifactId>quarkus-openapi-generator-gav-source-other</artifactId>
56+
<groupId>io.quarkiverse.openapi.generator</groupId>
57+
<version>3.0.0-SNAPSHOT</version>
58+
<file>src/main/openapi/echo.yaml</file>
59+
<packaging>yaml</packaging>
60+
</configuration>
61+
</execution>
62+
</executions>
63+
</plugin>
64+
</plugins>
65+
</build>
66+
67+
</project>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
openapi: 3.0.3
2+
info:
3+
title: echo
4+
version: '1.0.0'
5+
description: ""
6+
paths:
7+
/echo:
8+
post:
9+
summary: Echo
10+
operationId: echo
11+
requestBody:
12+
content:
13+
application/json:
14+
schema:
15+
$ref: "#/components/schemas/Message"
16+
responses:
17+
"200":
18+
description: OK
19+
content:
20+
application/json:
21+
schema:
22+
$ref: '#/components/schemas/Echo'
23+
components:
24+
schemas:
25+
Echo:
26+
type: object
27+
properties:
28+
echo:
29+
type: string
30+
Message:
31+
type: object
32+
properties:
33+
message:
34+
type: string

0 commit comments

Comments
 (0)