diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..efdca1e7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text eol=lf +*.jar -text -eol -working-tree-encoding -merge -diff \ No newline at end of file diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java index 1f88e602..e7d637b4 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java @@ -27,6 +27,7 @@ public interface CodegenConfig extends GlobalCodegenConfig { // package visibility for unit tests String BUILD_TIME_GLOBAL_PREFIX_FORMAT = "quarkus." + CODEGEN_TIME_CONFIG_PREFIX + ".%s"; String BUILD_TIME_SPEC_PREFIX_FORMAT = "quarkus." + CODEGEN_TIME_CONFIG_PREFIX + ".spec.%s"; + String BUILD_TIME_GAV_PREFIX_FORMAT = "quarkus." + CODEGEN_TIME_CONFIG_PREFIX + ".gav.%s"; List SUPPORTED_CONFIGURATIONS = Arrays.stream(ConfigName.values()).map(cn -> cn.name) .toList(); @@ -40,6 +41,7 @@ enum ConfigName { INCLUDE("include"), EXCLUDE("exclude"), ARTIFACT_ID_FILTER("artifact-id-filter"), + INCLUDE_GAVS("include-gavs"), EXCLUDE_GAVS("exclude-gavs"), VALIDATE_SPEC("validateSpec"), DEFAULT_SECURITY_SCHEME("default-security-scheme"), @@ -50,6 +52,9 @@ enum ConfigName { MODEL_NAME_SUFFIX("model-name-suffix"), MODEL_NAME_PREFIX("model-name-prefix"), + //gav configs only + SPEC_FILES("spec-files"), + //global & spec configs SKIP_FORM_MODEL("skip-form-model"), MUTINY("mutiny"), @@ -97,6 +102,12 @@ enum ConfigName { @WithName("spec") Map specItem(); + /** + * OpenAPI GAV details for codegen configuration. + */ + @WithName("gav") + Map gavItem(); + static String resolveApiPackage(final String basePackage) { return String.format("%s%s", basePackage, API_PKG_SUFFIX); } @@ -119,6 +130,13 @@ static String getSpecConfigName(ConfigName configName, final Path openApiFilePat return String.format("%s.%s", getBuildTimeSpecPropertyPrefix(openApiFilePath), configName.name); } + /** + * Return gav config name openapi-generator.codegen.gav.%s.config-name + */ + static String getGavConfigName(ConfigName configName, final Path openApiFilePath) { + return String.format("%s.%s", getBuildTimeGavPropertyPrefix(openApiFilePath), configName.name); + } + /** * Return spec config name by config-key (openapi-generator.codegen.spec.%s.config-key) property. * For example, given a configuration quarkus.openapi.generator.codegen.spec.spec_yaml.config-key=petstore, the @@ -140,6 +158,10 @@ static String getBuildTimeSpecPropertyPrefix(final Path openApiFilePath) { return String.format(BUILD_TIME_SPEC_PREFIX_FORMAT, getSanitizedFileName(openApiFilePath)); } + static String getBuildTimeGavPropertyPrefix(final Path openApiFilePath) { + return String.format(BUILD_TIME_GAV_PREFIX_FORMAT, getSanitizedFileName(openApiFilePath)); + } + static String getSanitizedFileName(final Path openApiFilePath) { return StringUtil .replaceNonAlphanumericByUnderscores(OpenApiGeneratorOutputPaths.getRelativePath(openApiFilePath).toString()); diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GavItemConfig.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GavItemConfig.java new file mode 100644 index 00000000..f876b447 --- /dev/null +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GavItemConfig.java @@ -0,0 +1,23 @@ +package io.quarkiverse.openapi.generator.deployment; + +import java.util.List; +import java.util.Optional; + +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; + +/* + * Model for the configuration of this extension. + * It's used for documentation purposes only. + * The configuration is consumed in the codegen phase, before build time. + * Not meant to be used outside this scope. + * Config items can be applied only on gav + */ +public interface GavItemConfig { + /** + * List of OpenAPI spec files in GAV to be generated + */ + @WithName("spec-files") + @WithDefault("openapi.yaml") + Optional> gavSpecFiles(); +} diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GlobalCodegenConfig.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GlobalCodegenConfig.java index 4dc562c6..0245f766 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GlobalCodegenConfig.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GlobalCodegenConfig.java @@ -74,6 +74,20 @@ public interface GlobalCodegenConfig extends CommonItemConfig { @WithName("exclude-gavs") Optional> excludeGavs(); + /** + * Option to specify GAVs for which generation should be executed only. + * + * Depending on the GAV Provider default behavior differs: + *
    + *
  • for {@link io.quarkiverse.openapi.generator.deployment.codegen.YamlOrJsonGAVCoordinateOpenApiSpecInputProvider}, all + * suitable GAVs will be considered for generation if config value is not given
  • + *
  • for {@link io.quarkiverse.openapi.generator.deployment.codegen.JarOrZipGAVCoordinateOpenApiSpecInputProvider}, only + * specified GAVs will be considered for generation if config value is available
  • + *
+ */ + @WithName("include-gavs") + Optional> includeGavs(); + /** * Create security for the referenced security scheme */ diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/AbstractGAVCoordinateOpenApiSpecInputProvider.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/AbstractGAVCoordinateOpenApiSpecInputProvider.java new file mode 100644 index 00000000..dd08fdea --- /dev/null +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/AbstractGAVCoordinateOpenApiSpecInputProvider.java @@ -0,0 +1,87 @@ +package io.quarkiverse.openapi.generator.deployment.codegen; + +import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.*; +import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +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; + +abstract class AbstractGAVCoordinateOpenApiSpecInputProvider implements OpenApiSpecInputProvider { + private static final Logger LOG = Logger.getLogger(AbstractGAVCoordinateOpenApiSpecInputProvider.class); + + @Override + public List 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 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 dependencies = context.applicationModel().getDependencies().stream() + .filter(rd -> getSupportedExtensions().contains(rd.getType().toLowerCase())) + .filter(rd -> rd.getArtifactId().matches(artifactIdFilter)) + .filter(rd -> !gavsToExclude.contains(rd.getKey().toGacString())) + .filter(rd -> specificGAVSpecInputProviderFilter(context, rd.getKey().toGacString())) + .toList(); + + if (dependencies.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(); + for (ResolvedDependency dependency : dependencies) { + var gacString = StringUtil.replaceNonAlphanumericByUnderscores(dependency.getKey().toGacString()); + var path = dependency.getResolvedPaths().stream().findFirst() + .orElseThrow(() -> new CodeGenException("Could not find maven path of %s.".formatted(gacString))); + addInputModels(context, gacString, path, inputModels); + } + return inputModels; + } + + protected abstract Set getSupportedExtensions(); + + /** + * Adds input models to the provided list based on the given context, GAC string, and path. + * This method is implemented by subclasses to generate or retrieve the appropriate + * {@code SpecInputModel} instances that will be processed during code generation. + * + * @param context the code generation context, providing access to configuration and utilities + * @param gacString the GAC (Group, Artifact, Classifier) string representing the dependency identifier + * @param path the path to the file or directory containing the input specification(s) + * @param inputModels the list to which the generated {@code SpecInputModel} instances are added + * @throws CodeGenException if an error occurs while processing the input specifications + */ + protected abstract void addInputModels(CodeGenContext context, + String gacString, + Path path, + List inputModels) throws CodeGenException; + + /** + * Filters dependencies based on specific criteria defined in the implementing class. + * This method is invoked as part of the dependency resolution process to determine + * whether a dependency identified by its GAC string should be included for further processing. + * + * @param context the code generation context, providing access to configuration and other utilities + * @param gacString the GAC (Group, Artifact, Classifier) string representing the dependency identifier + * @return true if the dependency matches the filter criteria and should be included; false otherwise + */ + protected abstract boolean specificGAVSpecInputProviderFilter(CodeGenContext context, String gacString); +} diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/JarOrZipGAVCoordinateOpenApiSpecInputProvider.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/JarOrZipGAVCoordinateOpenApiSpecInputProvider.java new file mode 100644 index 00000000..624a9127 --- /dev/null +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/JarOrZipGAVCoordinateOpenApiSpecInputProvider.java @@ -0,0 +1,111 @@ +package io.quarkiverse.openapi.generator.deployment.codegen; + +import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.*; +import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.quarkus.bootstrap.prebuild.CodeGenException; +import io.quarkus.deployment.CodeGenContext; + +/** + * Provides OpenAPI specification input from Maven GAV (GroupId:ArtifactId:Version) dependencies + * packaged as JAR or ZIP files. + *

+ * This provider extends the {@link AbstractGAVCoordinateOpenApiSpecInputProvider} and is responsible for + * scanning application dependencies to identify JAR or ZIP files that contain OpenAPI specifications + * (e.g., `openapi.yaml`). + *

+ * + *

Supported File Types

+ *

+ * The provider specifically supports dependencies packaged as: + *

+ *
    + *
  • JAR files
  • + *
  • ZIP files
  • + *
+ * + *

Scanning Behavior

+ *

+ * The provider performs the following steps: + *

+ *
    + *
  1. Checks if GAV scanning is enabled via configuration (enabled by default)
  2. + *
  3. Filters dependencies by artifact type (jar/zip)
  4. + *
  5. Applies artifact ID filtering using a regex pattern
  6. + *
  7. Excludes specific GAVs based on configuration
  8. + *
  9. Includes specific GAVs based on configuration if not available no GAVs are used
  10. + *
  11. Creates {@link ZippedSpecInputModel} instances for each matching dependency and openAPI specification file
  12. + *
+ * + *

Configuration

+ *

+ * The provider respects the following configuration properties: + *

+ *
    + *
  • {@code quarkus.openapi-generator.codegen.gav-scanning} - Enable/disable GAV scanning
  • + *
  • {@code quarkus.openapi-generator.codegen.artifact-id-filter} - Regex pattern for artifact ID filtering
  • + *
  • {@code quarkus.openapi-generator.codegen.exclude-gavs} - List of GAV coordinates to exclude + * (format: groupId:artifactId:classifier)
  • + *
  • {@code quarkus.openapi-generator.codegen.gav.com_sample_customer_service_openapi.spec-files} - List of + * openAPI specification files in com.sample:customer-service-openapi:jar
  • + *
+ * + *

Example Usage

+ * + *
+ * # 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
+ * quarkus.openapi-generator.codegen.gav.com_sample_customer_service_api.spec-files=customer.yaml,another.yaml
+ * 
+ * + * @see AbstractGAVCoordinateOpenApiSpecInputProvider + * @see ZippedSpecInputModel + * @see CodeGenContext + */ +public class JarOrZipGAVCoordinateOpenApiSpecInputProvider extends AbstractGAVCoordinateOpenApiSpecInputProvider { + private static final Set SUPPORTED_EXTENSIONS = Set.of("jar", "zip"); + + @Override + protected void addInputModels(CodeGenContext context, + String gacString, + Path path, + List inputModels) throws CodeGenException { + List rootFilesOfSpecOfDependency = context.config() + .getOptionalValues(getGavConfigName(SPEC_FILES, Paths.get(gacString)), String.class) + .orElse(List.of("openapi.yaml")); + for (String rootFileOfSpecForDependency : rootFilesOfSpecOfDependency) { + try { + inputModels.add(new ZippedSpecInputModel( + gacString, + rootFileOfSpecForDependency, + Files.newInputStream(path))); + } catch (IOException e) { + throw new CodeGenException( + "Could not open input stream of %s from %s.".formatted(gacString, path.toString()), + e); + } + } + } + + @Override + protected Set getSupportedExtensions() { + return SUPPORTED_EXTENSIONS; + } + + @Override + protected boolean specificGAVSpecInputProviderFilter(final CodeGenContext context, final String gacString) { + return new HashSet<>(context.config().getOptionalValues(getGlobalConfigName(INCLUDE_GAVS), String.class) + .orElse(List.of())) // default to empty list to disable all if not specified + .contains(gacString); + } +} diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiConfigValidator.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiConfigValidator.java index e4c55cad..dca090ab 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiConfigValidator.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiConfigValidator.java @@ -18,7 +18,7 @@ public final class OpenApiConfigValidator { private static final Logger LOGGER = LoggerFactory.getLogger(OpenApiConfigValidator.class); static final Pattern CONFIG_PATTERN = Pattern.compile( - "quarkus\\.openapi-generator\\.codegen\\.(spec.(?[\\w\\-]*)\\.)?(?[A-Za-z0-9_\\-]*)\\.?(?.+)?"); + "quarkus\\.openapi-generator\\.codegen\\.((spec|gav).(?[\\w\\-]*)\\.)?(?[A-Za-z0-9_\\-]*)\\.?(?.+)?"); private OpenApiConfigValidator() { } diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java index 023d5652..234fe7fb 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java @@ -1,6 +1,7 @@ package io.quarkiverse.openapi.generator.deployment.codegen; import java.io.IOException; +import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.channels.Channels; import java.nio.channels.FileChannel; @@ -13,6 +14,8 @@ import java.util.List; import java.util.ServiceLoader; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigSource; @@ -68,24 +71,39 @@ public boolean trigger(CodeGenContext context) throws CodeGenException { throw new CodeGenException("SpecInputModel from provider " + provider + " is null"); } try { - final Path openApiFilePath = Paths.get(outDir.toString(), inputModel.getFileName()); - Files.createDirectories(openApiFilePath.getParent()); - try (ReadableByteChannel inChannel = Channels.newChannel(inputModel.getInputStream()); - FileChannel outChannel = FileChannel.open(openApiFilePath, StandardOpenOption.WRITE, - StandardOpenOption.CREATE)) { - outChannel.transferFrom(inChannel, 0, Integer.MAX_VALUE); - LOGGER.debug("Saved OpenAPI spec input model in {}", openApiFilePath); - - OpenApiGeneratorOptions options = new OpenApiGeneratorOptions( - this.mergeConfig(context, inputModel), - openApiFilePath, - outDir, - context.workDir().resolve("classes").resolve("templates"), - isRestEasyReactive); - - this.generate(options); - generated = true; + final Path openApiFilePath; + if (inputModel instanceof ZippedSpecInputModel zippedSpecInputModel) { + final Path pathToExtract = Paths.get(outDir.toString(), inputModel.getFileName()); + if (!Files.exists(pathToExtract)) { + // only extract GAV at first iteration. if exists reuse it + Files.createDirectories(pathToExtract); + extractZip(inputModel.getInputStream(), pathToExtract); + } + openApiFilePath = Paths.get(pathToExtract.toString(), zippedSpecInputModel.getRootFileOfSpec()); + if (!Files.exists(openApiFilePath)) { + throw new CodeGenException( + String.format("Could not locate openAPI specification file %s in extracted content", + openApiFilePath)); + } + } else { + openApiFilePath = Paths.get(outDir.toString(), inputModel.getFileName()); + Files.createDirectories(openApiFilePath.getParent()); + try (ReadableByteChannel inChannel = Channels.newChannel(inputModel.getInputStream()); + FileChannel outChannel = FileChannel.open(openApiFilePath, StandardOpenOption.WRITE, + StandardOpenOption.CREATE)) { + outChannel.transferFrom(inChannel, 0, Integer.MAX_VALUE); + LOGGER.debug("Saved OpenAPI spec input model in {}", openApiFilePath); + } } + OpenApiGeneratorOptions options = new OpenApiGeneratorOptions( + this.mergeConfig(context, inputModel), + openApiFilePath, + outDir, + context.workDir().resolve("classes").resolve("templates"), + isRestEasyReactive); + + this.generate(options); + generated = true; } catch (IOException e) { throw new UncheckedIOException("Failed to save InputStream from provider " + provider + " into location ", e); @@ -103,6 +121,39 @@ private Config mergeConfig(CodeGenContext context, SpecInputModel inputModel) { .withSources(sources).build(); } + private void extractZip(InputStream inputStream, Path outputDir) throws IOException { + // Open the JAR/ZIP file as a ZipInputStream + try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + ZipEntry entry; + // Iterate through each entry in the ZIP + while ((entry = zipInputStream.getNextEntry()) != null) { + String entryName = entry.getName(); + Path entryPath = outputDir.resolve(entryName); + if (entry.isDirectory() || + SUPPORTED_EXTENSIONS_WITH_LEADING_DOT.stream().noneMatch(entryName::endsWith)) { + continue; + } + // If the ZIP file contains entries like `../../malicious_file` + if (!entryPath.toAbsolutePath().normalize().startsWith(outputDir.toAbsolutePath().normalize())) { + throw new IOException("Invalid ZIP entry: " + entryName); + } + // If it's a file, create parent directories first + if (!Files.exists(entryPath.getParent())) { + Files.createDirectories(entryPath.getParent()); + } + // Write the file + try (var outStream = Files.newOutputStream(entryPath, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING)) { + zipInputStream.transferTo(outStream); + } + // Close the current ZIP entry + zipInputStream.closeEntry(); + } + } + } + @Override public boolean shouldRun(Path sourceDir, Config config) { return !this.providers.isEmpty(); diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/YamlOrJsonGAVCoordinateOpenApiSpecInputProvider.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/YamlOrJsonGAVCoordinateOpenApiSpecInputProvider.java index 108605d3..22f70faf 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/YamlOrJsonGAVCoordinateOpenApiSpecInputProvider.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/YamlOrJsonGAVCoordinateOpenApiSpecInputProvider.java @@ -1,31 +1,26 @@ 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.CodegenConfig.ConfigName.INCLUDE_GAVS; 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.nio.file.Path; +import java.util.HashSet; import java.util.List; -import java.util.function.Predicate; - -import org.jboss.logging.Logger; +import java.util.Set; 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. *

* 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. + * specific criteria and provides them as input for OpenAPI code generation. This provider extends + * the {@link AbstractGAVCoordinateOpenApiSpecInputProvider} to integrate with the OpenAPI code + * generation process in Quarkus. *

* *

Scanning Behavior

@@ -37,6 +32,7 @@ *
  • Filters dependencies by artifact type (yaml/yml/json)
  • *
  • Applies artifact ID filtering using a regex pattern
  • *
  • Excludes specific GAVs based on configuration
  • + *
  • Includes specific GAVs based on configuration if available otherwise all GAVs are used
  • *
  • Creates {@link SpecInputModel} instances for each matching dependency
  • * * @@ -60,50 +56,37 @@ * quarkus.openapi-generator.codegen.exclude-gavs=com.example:old-api * * - * @see OpenApiSpecInputProvider + * @see AbstractGAVCoordinateOpenApiSpecInputProvider * @see SpecInputModel * @see CodeGenContext */ -public class YamlOrJsonGAVCoordinateOpenApiSpecInputProvider implements OpenApiSpecInputProvider { - private static final Logger LOG = Logger.getLogger(YamlOrJsonGAVCoordinateOpenApiSpecInputProvider.class); - +public class YamlOrJsonGAVCoordinateOpenApiSpecInputProvider extends AbstractGAVCoordinateOpenApiSpecInputProvider { @Override - public List 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(); + protected void addInputModels(CodeGenContext context, + String gacString, + Path path, + List inputModels) throws CodeGenException { + 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); } + } - List 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 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(); + @Override + protected Set getSupportedExtensions() { + return SUPPORTED_EXTENSIONS; + } - 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(); - 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); - } + @Override + protected boolean specificGAVSpecInputProviderFilter(final CodeGenContext context, final String gacString) { + List includeGavs = context.config().getOptionalValues(getGlobalConfigName(INCLUDE_GAVS), String.class) + .orElse(null); + if (includeGavs == null) { // default behavior: all GAVs are included + return true; } - return inputModels; + return new HashSet<>(includeGavs) + .contains(gacString); } } diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/ZippedSpecInputModel.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/ZippedSpecInputModel.java new file mode 100644 index 00000000..e57f565f --- /dev/null +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/ZippedSpecInputModel.java @@ -0,0 +1,40 @@ +package io.quarkiverse.openapi.generator.deployment.codegen; + +import java.io.InputStream; + +/** + * Represents a model for a zipped specification input. + * This class extends the {@code SpecInputModel} class and provides additional support + * for handling openAPI specifications within the zipped input source (jar/zip). + */ +public class ZippedSpecInputModel extends SpecInputModel { + + private final String rootFileOfSpec; + + public ZippedSpecInputModel(final String filename, final String rootFileOfSpec, final InputStream inputStream) { + super(filename, inputStream); + this.rootFileOfSpec = rootFileOfSpec; + } + + public ZippedSpecInputModel(final String filename, final String rootFileOfSpec, final InputStream inputStream, + final String basePackageName) { + super(filename, inputStream, basePackageName); + this.rootFileOfSpec = rootFileOfSpec; + } + + public ZippedSpecInputModel(final String filename, final String rootFileOfSpec, final InputStream inputStream, + final String basePackageName, + final String apiNameSuffix, final String modelNameSuffix, final String modelNamePrefix) { + super(filename, inputStream, basePackageName, apiNameSuffix, modelNameSuffix, modelNamePrefix); + this.rootFileOfSpec = rootFileOfSpec; + } + + /** + * Retrieves the root file name of the specification associated with this model. + * + * @return the root file name of the specification. + */ + public String getRootFileOfSpec() { + return rootFileOfSpec; + } +} diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/OpenApiNamespaceResolver.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/OpenApiNamespaceResolver.java index de87fff9..a7ca43ab 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/OpenApiNamespaceResolver.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/OpenApiNamespaceResolver.java @@ -61,7 +61,7 @@ public boolean genDeprecatedApiAttr(final String pkg, final String classname, @SuppressWarnings("unused") public String parseUri(String uri) { - return OpenApiGeneratorOutputPaths.getRelativePath(Path.of(URI.create(uri))).toString(); + return escapeWindowsPath(OpenApiGeneratorOutputPaths.getRelativePath(Path.of(URI.create(uri))).toString()); } @SuppressWarnings("unused") @@ -138,6 +138,10 @@ private boolean isAssignable(Class[] paramTypes, Class[] argTypes) { return true; } + private String escapeWindowsPath(String pathAsString) { + return pathAsString.replace("\\", "\\\\"); // without it would lead into compile error in generated sources + } + @Override public String getNamespace() { return "openapi"; diff --git a/client/deployment/src/main/resources/META-INF/services/io.quarkiverse.openapi.generator.deployment.codegen.OpenApiSpecInputProvider b/client/deployment/src/main/resources/META-INF/services/io.quarkiverse.openapi.generator.deployment.codegen.OpenApiSpecInputProvider index 3eb47dd6..d7b79d48 100644 --- a/client/deployment/src/main/resources/META-INF/services/io.quarkiverse.openapi.generator.deployment.codegen.OpenApiSpecInputProvider +++ b/client/deployment/src/main/resources/META-INF/services/io.quarkiverse.openapi.generator.deployment.codegen.OpenApiSpecInputProvider @@ -1 +1,2 @@ -io.quarkiverse.openapi.generator.deployment.codegen.YamlOrJsonGAVCoordinateOpenApiSpecInputProvider \ No newline at end of file +io.quarkiverse.openapi.generator.deployment.codegen.YamlOrJsonGAVCoordinateOpenApiSpecInputProvider +io.quarkiverse.openapi.generator.deployment.codegen.JarOrZipGAVCoordinateOpenApiSpecInputProvider \ No newline at end of file diff --git a/client/integration-tests/gav/src/it/01-install-gav-sources/pom.xml b/client/integration-tests/gav/src/it/01-install-gav-sources/pom.xml index 4465d6e1..30a8e2bc 100644 --- a/client/integration-tests/gav/src/it/01-install-gav-sources/pom.xml +++ b/client/integration-tests/gav/src/it/01-install-gav-sources/pom.xml @@ -31,7 +31,7 @@ io.quarkiverse.openapi.generator quarkus-openapi-generator-gav-source-echo1 ${tested.version} - ${project.basedir}/src/main/openapi/echo1.yaml + ${project.basedir}/src/main/openapi/echo.yaml yaml @@ -43,7 +43,7 @@ io.quarkiverse.openapi.generator quarkus-openapi-generator-gav-source-echo2 ${tested.version} - ${project.basedir}/src/main/openapi/echo2.yaml + ${project.basedir}/src/main/openapi/echo.yaml yaml @@ -55,10 +55,34 @@ io.quarkiverse.openapi.generator quarkus-openapi-generator-gav-source-other ${tested.version} - ${project.basedir}/src/main/openapi/other.yaml + ${project.basedir}/src/main/openapi/echo.yaml yaml + + install-selfcontained-echo-jar + package + install-file + + io.quarkiverse.openapi.generator + quarkus-openapi-generator-gav-source-selfcontained-echo + ${tested.version} + ${project.basedir}/src/main/openapi/selfcontained-echo.jar + jar + + + + install-splitted-echo-jar + package + install-file + + io.quarkiverse.openapi.generator + quarkus-openapi-generator-gav-source-splitted-echo + ${tested.version} + ${project.basedir}/src/main/openapi/splitted-echo.jar + jar + + diff --git a/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/echo1.yaml b/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/echo.yaml similarity index 100% rename from client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/echo1.yaml rename to client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/echo.yaml diff --git a/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/echo2.yaml b/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/echo2.yaml deleted file mode 100644 index f93ea218..00000000 --- a/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/echo2.yaml +++ /dev/null @@ -1,34 +0,0 @@ -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 \ No newline at end of file diff --git a/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/other.yaml b/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/other.yaml deleted file mode 100644 index f93ea218..00000000 --- a/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/other.yaml +++ /dev/null @@ -1,34 +0,0 @@ -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 \ No newline at end of file diff --git a/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/selfcontained-echo.jar b/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/selfcontained-echo.jar new file mode 100644 index 00000000..4a7dcf34 Binary files /dev/null and b/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/selfcontained-echo.jar differ diff --git a/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/splitted-echo.jar b/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/splitted-echo.jar new file mode 100644 index 00000000..7f6475be Binary files /dev/null and b/client/integration-tests/gav/src/it/01-install-gav-sources/src/main/openapi/splitted-echo.jar differ diff --git a/client/integration-tests/gav/src/it/02-consume-gav/pom.xml b/client/integration-tests/gav/src/it/02-consume-gav/pom.xml index 9da7d4e6..513a6988 100644 --- a/client/integration-tests/gav/src/it/02-consume-gav/pom.xml +++ b/client/integration-tests/gav/src/it/02-consume-gav/pom.xml @@ -59,6 +59,20 @@ yaml + + + io.quarkiverse.openapi.generator + quarkus-openapi-generator-gav-source-splitted-echo + ${tested.version} + jar + + + io.quarkiverse.openapi.generator + quarkus-openapi-generator-gav-source-selfcontained-echo + ${tested.version} + jar + + io.quarkus diff --git a/client/integration-tests/gav/src/it/02-consume-gav/src/main/resources/application.properties b/client/integration-tests/gav/src/it/02-consume-gav/src/main/resources/application.properties index c0401336..bccd5e1d 100644 --- a/client/integration-tests/gav/src/it/02-consume-gav/src/main/resources/application.properties +++ b/client/integration-tests/gav/src/it/02-consume-gav/src/main/resources/application.properties @@ -2,4 +2,11 @@ quarkus.keycloak.devservices.enabled=false quarkus.openapi-generator.codegen.artifact-id-filter=.*echo.* quarkus.openapi-generator.codegen.exclude-gavs=io.quarkiverse.openapi.generator:quarkus-openapi-generator-gav-source-echo2 -quarkus.openapi-generator.codegen.spec.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo1.serializable-model=true \ No newline at end of file +quarkus.openapi-generator.codegen.include-gavs=io.quarkiverse.openapi.generator:quarkus-openapi-generator-gav-source-echo1,\ + io.quarkiverse.openapi.generator:quarkus-openapi-generator-gav-source-selfcontained-echo,\ + io.quarkiverse.openapi.generator:quarkus-openapi-generator-gav-source-splitted-echo +quarkus.openapi-generator.codegen.spec.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo1.serializable-model=true + +quarkus.openapi-generator.codegen.gav.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_splitted_echo.spec-files=echo.yaml +quarkus.openapi-generator.codegen.gav.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_selfcontained_echo.spec-files=echo1.yaml,echo2.yaml +quarkus.openapi-generator.codegen.spec.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_selfcontained_echo_echo1_yaml.serializable-model=true \ No newline at end of file diff --git a/client/integration-tests/gav/src/it/02-consume-gav/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusGAVOpenApiTest.java b/client/integration-tests/gav/src/it/02-consume-gav/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusGAVOpenApiTest.java index 1e427c18..cb9dcdf3 100644 --- a/client/integration-tests/gav/src/it/02-consume-gav/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusGAVOpenApiTest.java +++ b/client/integration-tests/gav/src/it/02-consume-gav/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusGAVOpenApiTest.java @@ -6,20 +6,28 @@ import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + class QuarkusGAVOpenApiTest { - @Test - void echo1IsBeingGenerated() { + @ParameterizedTest + @ValueSource(strings = {"io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo1", + "io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_splitted_echo_echo_yaml", + "io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_selfcontained_echo_echo1_yaml"}) + void echo1IsBeingGenerated(String packageName) { assertThatCode( - () -> Class.forName( - "org.openapi.quarkus.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo1.api.DefaultApi")) - .doesNotThrowAnyException(); + () -> Class.forName( + "org.openapi.quarkus." + packageName + ".api.DefaultApi")) + .doesNotThrowAnyException(); } - @Test - void echoModelIsBeingGeneratedWithSerializableInterface() { + @ParameterizedTest + @ValueSource(strings = {"io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo1", + "io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_selfcontained_echo_echo1_yaml"}) + void echoModelIsBeingGeneratedWithSerializableInterface(String packageName) { assertThatCode(() -> { Class apiClass = Class.forName( - "org.openapi.quarkus.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo1.model.Echo"); + "org.openapi.quarkus." + packageName + ".model.Echo"); assertThat(apiClass.getInterfaces()).contains(Serializable.class); }).doesNotThrowAnyException(); } @@ -27,16 +35,16 @@ void echoModelIsBeingGeneratedWithSerializableInterface() { @Test void echo2IsBeingNotGenerated() { assertThatCode( - () -> Class.forName( - "org.openapi.quarkus.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo2.api.DefaultApi")) - .isInstanceOf(ClassNotFoundException.class); + () -> Class.forName( + "org.openapi.quarkus.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_echo2.api.DefaultApi")) + .isInstanceOf(ClassNotFoundException.class); } @Test void otherIsBeingNotGenerated() { assertThatCode( - () -> Class.forName( - "org.openapi.quarkus.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_other.api.DefaultApi")) - .isInstanceOf(ClassNotFoundException.class); + () -> Class.forName( + "org.openapi.quarkus.io_quarkiverse_openapi_generator_quarkus_openapi_generator_gav_source_other.api.DefaultApi")) + .isInstanceOf(ClassNotFoundException.class); } } diff --git a/docs/modules/ROOT/pages/client.adoc b/docs/modules/ROOT/pages/client.adoc index 605a1b1a..926ebf30 100644 --- a/docs/modules/ROOT/pages/client.adoc +++ b/docs/modules/ROOT/pages/client.adoc @@ -214,6 +214,11 @@ include::./includes/equals-hashcode.adoc[leveloffset=+1, opts=optional] include::./includes/dynamic-url.adoc[leveloffset=+1, opts=optional] +[[gav-integration]] +== Specification files from Maven GAV coordinates + +include::./includes/use-gav-for-spec.adoc[leveloffset=+1, opts=optional] + == Known Limitations === Supported Arguments diff --git a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc index d80f8157..4f4ab4c8 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc @@ -685,6 +685,30 @@ endif::add-copy-button-to-env-var[] |list of string | +a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-include-gavs]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-include-gavs[`quarkus.openapi-generator.codegen.include-gavs`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.openapi-generator.codegen.include-gavs+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Option to specify GAVs for which generation should be executed only. Depending on the GAV Provider default behavior differs: + + - for `io.quarkiverse.openapi.generator.deployment.codegen.YamlOrJsonGAVCoordinateOpenApiSpecInputProvider`, all suitable GAVs will be considered for generation if config value is not given + - for `io.quarkiverse.openapi.generator.deployment.codegen.JarOrZipGAVCoordinateOpenApiSpecInputProvider`, only specified GAVs will be considered for generation if config value is available + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_INCLUDE_GAVS+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_INCLUDE_GAVS+++` +endif::add-copy-button-to-env-var[] +-- +|list of string +| + a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-default-security-scheme]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-default-security-scheme[`quarkus.openapi-generator.codegen.default-security-scheme`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.openapi-generator.codegen.default-security-scheme+++[] @@ -1405,6 +1429,27 @@ endif::add-copy-button-to-env-var[] |boolean |`+++false+++` +a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-gav-gav-item-spec-files]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-gav-gav-item-spec-files[`quarkus.openapi-generator.codegen.gav."gav-item".spec-files`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.openapi-generator.codegen.gav."gav-item".spec-files+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +List of OpenAPI spec files in GAV to be generated + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_GAV__GAV_ITEM__SPEC_FILES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_GAV__GAV_ITEM__SPEC_FILES+++` +endif::add-copy-button-to-env-var[] +-- +|list of string +|`+++openapi.yaml+++` + a| [[quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation[`quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation+++[] diff --git a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc index d80f8157..4f4ab4c8 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc @@ -685,6 +685,30 @@ endif::add-copy-button-to-env-var[] |list of string | +a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-include-gavs]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-include-gavs[`quarkus.openapi-generator.codegen.include-gavs`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.openapi-generator.codegen.include-gavs+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Option to specify GAVs for which generation should be executed only. Depending on the GAV Provider default behavior differs: + + - for `io.quarkiverse.openapi.generator.deployment.codegen.YamlOrJsonGAVCoordinateOpenApiSpecInputProvider`, all suitable GAVs will be considered for generation if config value is not given + - for `io.quarkiverse.openapi.generator.deployment.codegen.JarOrZipGAVCoordinateOpenApiSpecInputProvider`, only specified GAVs will be considered for generation if config value is available + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_INCLUDE_GAVS+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_INCLUDE_GAVS+++` +endif::add-copy-button-to-env-var[] +-- +|list of string +| + a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-default-security-scheme]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-default-security-scheme[`quarkus.openapi-generator.codegen.default-security-scheme`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.openapi-generator.codegen.default-security-scheme+++[] @@ -1405,6 +1429,27 @@ endif::add-copy-button-to-env-var[] |boolean |`+++false+++` +a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-gav-gav-item-spec-files]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-gav-gav-item-spec-files[`quarkus.openapi-generator.codegen.gav."gav-item".spec-files`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.openapi-generator.codegen.gav."gav-item".spec-files+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +List of OpenAPI spec files in GAV to be generated + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_GAV__GAV_ITEM__SPEC_FILES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_GAV__GAV_ITEM__SPEC_FILES+++` +endif::add-copy-button-to-env-var[] +-- +|list of string +|`+++openapi.yaml+++` + a| [[quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation[`quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation+++[] diff --git a/docs/modules/ROOT/pages/includes/use-gav-for-spec.adoc b/docs/modules/ROOT/pages/includes/use-gav-for-spec.adoc new file mode 100644 index 00000000..81d4fd3e --- /dev/null +++ b/docs/modules/ROOT/pages/includes/use-gav-for-spec.adoc @@ -0,0 +1,73 @@ +This feature introduces the capability to load OpenAPI specifications directly from Maven GAV dependencies that are packaged +as YAML, JSON, JAR, or ZIP files. + +== Overview + +OpenAPI specifications can now be specified as Maven GAV (GroupId:ArtifactId:Version) coordinates and use them directly for +client generation. This allows developers to use external or prepackaged OpenAPI specifications as dependencies instead +of embedding them directly in their projects. + +Key features included in this implementation: + +- Compatibility with YAML, JSON, JAR and ZIP formats containing OpenAPI specifications. +- Configuration options to specify GAV dependencies via application properties. +- Support for filtering dependencies by artifact ID and excluding specific GAVs. + +== Sample Configurations + +=== YAML or JSON specification + +To load an OpenAPI specification from a Maven GAV dependency in YAML or JSON format, you can use the following configuration: + +Add the dependency to your `pom.xml`: +[source,xml] + + com.example + customer-openapi + 1.0.0 + yaml + + +GAV scanning is activated by default. YAML, YML and JSON types are supported. The default artifact ID filter is `.\*openapi.*`, +so the above dependency will be picked up automatically. + +To configure the client generation process, add common options to your `application.properties` that refer to the GAV: + +e.g. +[source,properties] +quarkus.openapi-generator.codegen.spec.com_example_customer_openapi.serializable-model=true + +=== JAR or ZIP Files containing specification +To load an OpenAPI specification from a Maven GAV dependency packaged as a JAR or ZIP file, use the following configuration: + +Add the dependency to your `pom.xml`: +[source,xml] + + com.example + customer-openapi + 1.0.0 + jar + + +GAV scanning is activated by default. JAR and ZIP types are supported. The default artifact ID filter is `.\*openapi.*`. To add +GAV scanning support for JAR and ZIP files, you also need to add each GAV explicitly in your `application.properties`: +[source,properties] +quarkus.openapi-generator.codegen.include-gavs=com.example:customer-openapi + +If your specification file is not named `openapi.yaml`, you need to specify the path to the file inside the archive. +[source,properties] +quarkus.openapi-generator.codegen.gav.com_example_customer_openapi.spec-files=customer.yaml + +==== Note +- You can also have multiple specification files inside the same archive + * e-store.jar includes customer-openapi.yaml, billing-openapi.yaml + * payment.jar includes payment-openapi.yaml + * 3 MP-Rest Clients are created. MP-Rest Clients for customer-openapi.yaml + billing-openapi.yaml in e-store.jar + *and* payment-openapi.yaml from payment.jar + +- You can have splitted specifications across multiple files inside the same archive, you need to +tell the extension the spec-files to use + +For more configuration options, refer to the xref:includes/quarkus-openapi-generator.adoc[Configuration Documentation]. + +