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 @@ -4,7 +4,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

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

enum ConfigName {
//global configs
VERBOSE("verbose"),
INPUT_BASE_DIR("input-base-dir"),
GAV_SCANNING("gav-scanning"),
TEMPLATE_BASE_DIR("template-base-dir"),
INCLUDE("include"),
EXCLUDE("exclude"),
ARTIFACT_ID_FILTER("artifact-id-filter"),
EXCLUDE_GAVS("exclude-gavs"),
VALIDATE_SPEC("validateSpec"),
DEFAULT_SECURITY_SCHEME("default-security-scheme"),

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkiverse.openapi.generator.deployment;

import java.util.List;
import java.util.Optional;

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

/**
* Whether or not to skip gav scanning.
*/
@WithName("gav-scanning")
@WithDefault("true")
boolean gavScanning();

/**
* Option to change the directory where template files must be found.
*/
Expand All @@ -53,6 +61,19 @@ public interface GlobalCodegenConfig extends CommonItemConfig {
@WithName("exclude")
Optional<String> exclude();

/**
* Option to filter artifactId from generation
*/
@WithName("artifact-id-filter")
@WithDefault(".*openapi.*")
Optional<String> artifactIdFilter();

/**
* Option to exclude GAVs from generation
*/
@WithName("exclude-gavs")
Optional<List<String>> excludeGavs();

/**
* Create security for the referenced security scheme
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ public String providerId() {

@Override
public String[] inputExtensions() {
return new String[] { JSON, YAML, YML };
return SUPPORTED_EXTENSIONS_WITH_LEADING_DOT.toArray(new String[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -56,9 +57,12 @@
*/
public abstract class OpenApiGeneratorCodeGenBase implements CodeGenProvider {

static final String YAML = ".yaml";
static final String YML = ".yml";
static final String JSON = ".json";
static final String YAML = "yaml";
static final String YML = "yml";
static final String JSON = "json";

static final Set<String> SUPPORTED_EXTENSIONS = Set.of(YAML, YML, JSON);
static final Set<String> SUPPORTED_EXTENSIONS_WITH_LEADING_DOT = Set.of("." + YAML, "." + YML, "." + JSON);

private static final String DEFAULT_PACKAGE = "org.openapi.quarkus";
private static final String CONFIG_KEY_PROPERTY = "config-key";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.InputStream;
import java.util.List;

import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.CodeGenContext;

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package io.quarkiverse.openapi.generator.deployment.codegen;

import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.getGlobalConfigName;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.ARTIFACT_ID_FILTER;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.EXCLUDE_GAVS;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.GAV_SCANNING;
import static io.quarkiverse.openapi.generator.deployment.codegen.OpenApiGeneratorCodeGenBase.SUPPORTED_EXTENSIONS;

import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import org.jboss.logging.Logger;

import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.CodeGenContext;
import io.quarkus.maven.dependency.ResolvedDependency;
import io.smallrye.config.common.utils.StringUtil;

/**
* Provides OpenAPI specification input from Maven GAV (GroupId:ArtifactId:Version) coordinates.
* <p>
* This provider scans the application's dependencies for YAML or JSON files that match
* specific criteria and provides them as input for OpenAPI code generation. It implements
* the {@link OpenApiSpecInputProvider} interface to integrate with the OpenAPI Generator's
* code generation pipeline.
* </p>
*
* <h2>Scanning Behavior</h2>
* <p>
* The provider performs the following steps:
* </p>
* <ol>
* <li>Checks if GAV scanning is enabled via configuration (enabled by default)</li>
* <li>Filters dependencies by artifact type (yaml/yml/json)</li>
* <li>Applies artifact ID filtering using a regex pattern</li>
* <li>Excludes specific GAVs based on configuration</li>
* <li>Creates {@link SpecInputModel} instances for each matching dependency</li>
* </ol>
*
* <h2>Configuration</h2>
* <p>
* The provider respects the following configuration properties:
* </p>
* <ul>
* <li>{@code quarkus.openapi-generator.codegen.gav-scanning} - Enable/disable GAV scanning</li>
* <li>{@code quarkus.openapi-generator.codegen.artifact-id-filter} - Regex pattern for artifact ID filtering</li>
* <li>{@code quarkus.openapi-generator.codegen.exclude-gavs} - List of GAV coordinates to exclude
* (format: groupId:artifactId:classifier)</li>
* </ul>
*
* <h2>Example Usage</h2>
*
* <pre>
* # application.properties
* quarkus.openapi-generator.codegen.gav-scanning=true
* quarkus.openapi-generator.codegen.artifact-id-filter=.*api.*
* quarkus.openapi-generator.codegen.exclude-gavs=com.example:old-api
* </pre>
*
* @see OpenApiSpecInputProvider
* @see SpecInputModel
* @see CodeGenContext
*/
public class YamlOrJsonGAVCoordinateOpenApiSpecInputProvider implements OpenApiSpecInputProvider {
private static final Logger LOG = Logger.getLogger(YamlOrJsonGAVCoordinateOpenApiSpecInputProvider.class);

@Override
public List<SpecInputModel> read(CodeGenContext context) throws CodeGenException {
if (!context.config().getOptionalValue(getGlobalConfigName(GAV_SCANNING), Boolean.class)
.orElse(true)) {
LOG.debug("GAV scanning is disabled.");
return List.of();
}

List<String> gavsToExclude = context.config().getOptionalValues(getGlobalConfigName(EXCLUDE_GAVS), String.class)
.orElse(List.of());
String artifactIdFilter = context.config().getOptionalValue(getGlobalConfigName(ARTIFACT_ID_FILTER), String.class)
.filter(Predicate.not(String::isBlank))
.orElse(".*openapi.*");

List<ResolvedDependency> yamlDependencies = context.applicationModel().getDependencies().stream()
.filter(rd -> SUPPORTED_EXTENSIONS.contains(rd.getType().toLowerCase()))
.filter(rd -> rd.getArtifactId().matches(artifactIdFilter))
.filter(rd -> !gavsToExclude.contains(rd.getKey().toGacString()))
.toList();

if (yamlDependencies.isEmpty()) {
LOG.debug("No suitable GAV dependencies found. ArtifactIdFilter was %s and gavsToExclude were %s."
.formatted(artifactIdFilter, gavsToExclude));
return List.of();
}
var inputModels = new ArrayList<SpecInputModel>();
for (ResolvedDependency yamlDependency : yamlDependencies) {
var gacString = StringUtil.replaceNonAlphanumericByUnderscores(yamlDependency.getKey().toGacString());
var path = yamlDependency.getResolvedPaths().stream().findFirst()
.orElseThrow(() -> new CodeGenException("Could not find maven path of %s.".formatted(gacString)));
try {
inputModels.add(new SpecInputModel(gacString, Files.newInputStream(path)));
} catch (IOException e) {
throw new CodeGenException("Could not open input stream of %s from %s.".formatted(gacString, path.toString()),
e);
}
}
return inputModels;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkiverse.openapi.generator.deployment.codegen.YamlOrJsonGAVCoordinateOpenApiSpecInputProvider
67 changes: 67 additions & 0 deletions client/integration-tests/gav-source/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>quarkus-openapi-generator-integration-tests</artifactId>
<groupId>io.quarkiverse.openapi.generator</groupId>
<version>3.0.0-lts-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-openapi-generator-gav-source</artifactId>
<name>Quarkus - OpenAPI Generator - Integration Tests - Client - GAV source</name>
<description>Example project for OpenAPI with GAV source</description>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<executions>
<execution>
<id>install-echo1</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactId>quarkus-openapi-generator-gav-source-echo1</artifactId>
<groupId>io.quarkiverse.openapi.generator</groupId>
<version>3.0.0-SNAPSHOT</version>
<file>src/main/openapi/echo.yaml</file>
<packaging>yaml</packaging>
</configuration>
</execution>
<execution>
<id>install-echo2</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactId>quarkus-openapi-generator-gav-source-echo2</artifactId>
<groupId>io.quarkiverse.openapi.generator</groupId>
<version>3.0.0-SNAPSHOT</version>
<file>src/main/openapi/echo.yaml</file>
<packaging>yaml</packaging>
</configuration>
</execution>
<execution>
<id>install-other</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactId>quarkus-openapi-generator-gav-source-other</artifactId>
<groupId>io.quarkiverse.openapi.generator</groupId>
<version>3.0.0-SNAPSHOT</version>
<file>src/main/openapi/echo.yaml</file>
<packaging>yaml</packaging>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
34 changes: 34 additions & 0 deletions client/integration-tests/gav-source/src/main/openapi/echo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
openapi: 3.0.3
info:
title: echo
version: '1.0.0'
description: ""
paths:
/echo:
post:
summary: Echo
operationId: echo
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Echo'
components:
schemas:
Echo:
type: object
properties:
echo:
type: string
Message:
type: object
properties:
message:
type: string
Loading