Skip to content
Closed
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
2 changes: 1 addition & 1 deletion client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<name>Quarkus - Openapi Generator - Client - Deployment</name>

<properties>
<version.org.openapitools>7.8.0</version.org.openapitools>
<version.org.openapitools>7.10.0</version.org.openapitools>
<version.org.slf4j>2.0.16</version.org.slf4j>
<version.com.github.jknack>4.3.1</version.com.github.jknack>
<version.io.swagger.parser>2.1.24</version.io.swagger.parser>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public enum ConfigName {
USE_FIELD_NAME_IN_PART_FILENAME("use-field-name-in-part-filename"),
ADDITIONAL_PROPERTIES_AS_ATTRIBUTE("additional-properties-as-attribute"),
ADDITIONAL_REQUEST_ARGS("additional-request-args"),
REMOVE_OPERATION_ID_PREFIX("remove-operation-id-prefix"),
BEAN_VALIDATION("use-bean-validation");

private final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ public class SpecItemConfig extends CommonItemConfig {
*/
@ConfigItem(name = "model-name-prefix")
public Optional<String> modelNamePrefix;

/**
* Remove operation id prefix
*/
@ConfigItem(name = "remove-operation-id-prefix")
public Optional<String> removeOperationIdPrefix;

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.INPUT_BASE_DIR;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.MODEL_NAME_PREFIX;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.MODEL_NAME_SUFFIX;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.REMOVE_OPERATION_ID_PREFIX;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.TEMPLATE_BASE_DIR;
import static io.quarkiverse.openapi.generator.deployment.CodegenConfig.ConfigName.VALIDATE_SPEC;

Expand Down Expand Up @@ -228,6 +229,9 @@ protected void generate(OpenApiGeneratorOptions options) {
getModelNamePrefix(config, openApiFilePath)
.ifPresent(generator::withModelNamePrefix);

getRemoveOperationIdPrefix(config, openApiFilePath)
.ifPresent(generator::withRemoveOperationIdPrefix);

getValues(config, openApiFilePath, CodegenConfig.ConfigName.MUTINY, Boolean.class)
.ifPresent(generator::withMutiny);

Expand Down Expand Up @@ -350,6 +354,11 @@ private Optional<String> getModelNamePrefix(final Config config, final Path open
.getOptionalValue(getSpecConfigName(MODEL_NAME_PREFIX, openApiFilePath), String.class);
}

private Optional<Boolean> getRemoveOperationIdPrefix(final Config config, final Path openApiFilePath) {
return config
.getOptionalValue(getSpecConfigName(REMOVE_OPERATION_ID_PREFIX, openApiFilePath), Boolean.class);
}

private Optional<String> getInputBaseDirRelativeToModule(final Path sourceDir, final Config config) {
return config.getOptionalValue(getGlobalConfigName(INPUT_BASE_DIR), String.class).map(baseDir -> {
int srcIndex = sourceDir.toString().lastIndexOf("src");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkiverse.openapi.generator.deployment.template;

import java.util.List;
import java.util.concurrent.ExecutionException;

import io.quarkus.qute.EvalContext;
import io.quarkus.qute.Expression;

final class ExprEvaluator {

private ExprEvaluator() {
}

@SuppressWarnings("unchecked")
public static <T> T evaluate(EvalContext context, Expression expression) throws ExecutionException, InterruptedException {
return (T) context.evaluate(expression).toCompletableFuture().get();
}

@SuppressWarnings("unchecked")
public static <T> T[] evaluate(EvalContext context, List<Expression> expressions, Class<T> type)
throws ExecutionException, InterruptedException {
T[] results = (T[]) java.lang.reflect.Array.newInstance(type, expressions.size());

for (int i = 0; i < expressions.size(); i++) {
Expression expression = expressions.get(i);
T result = type.cast(context.evaluate(expression).toCompletableFuture().get());
results[i] = result;
}

return results;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;

import org.openapitools.codegen.model.OperationMap;

import io.quarkiverse.openapi.generator.deployment.codegen.OpenApiGeneratorOutputPaths;
import io.quarkus.qute.EvalContext;
import io.quarkus.qute.Expression;
Expand All @@ -18,9 +20,8 @@
* implement and use them.
*/
public class OpenApiNamespaceResolver implements NamespaceResolver {
private static final String GENERATE_DEPRECATED_PROP = "generateDeprecated";

static final OpenApiNamespaceResolver INSTANCE = new OpenApiNamespaceResolver();
private static final String GENERATE_DEPRECATED_PROP = "generateDeprecated";

private OpenApiNamespaceResolver() {
}
Expand Down Expand Up @@ -53,6 +54,10 @@ public String parseUri(String uri) {
return OpenApiGeneratorOutputPaths.getRelativePath(Path.of(uri)).toString().replace(File.separatorChar, '/');
}

public boolean hasAuthMethods(OperationMap operations) {
return operations != null && operations.getOperation().stream().anyMatch(operation -> operation.hasAuthMethods);
}

@Override
public CompletionStage<Object> resolve(EvalContext context) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ public QuteTemplatingEngineAdapter() {
.addDefaults()
.addValueResolver(new ReflectionValueResolver())
.addNamespaceResolver(OpenApiNamespaceResolver.INSTANCE)
.addNamespaceResolver(StrNamespaceResolver.INSTANCE)
.removeStandaloneLines(true)
.strictRendering(false)
.strictRendering(true)
.build();
}

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

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import io.quarkus.qute.EvalContext;
import io.quarkus.qute.NamespaceResolver;

/**
* Namespace resolver to mimic the function of io.quarkus.qute.runtime.extensions.StringTemplateExtensions.
* This extension is built-in with Qute when used in a context with Quarkus DI.
* Since these extensions are built in build time by Qute we can't use them because our process also runs in build time without
* a CDI context.
* So any namespace resolver auto-generated by Quarkus won't be added to our engine.
*
* @see <a href="https://quarkus.io/guides/qute-reference#template_extension_methods">Template Extension Methods</a>
*/
public class StrNamespaceResolver implements NamespaceResolver {

static final StrNamespaceResolver INSTANCE = new StrNamespaceResolver();

private StrNamespaceResolver() {
}

@Override
public String getNamespace() {
return "str";
}

/**
* @see io.quarkus.qute.runtime.extensions.StringTemplateExtensions#fmt(String, String, Object...)
*/
public String fmt(String format, Object... args) {
return String.format(format, args);
}

@Override
public CompletionStage<Object> resolve(EvalContext context) {
switch (context.getName()) {
case "fmt":
if (context.getParams().size() < 2) {
throw new IllegalArgumentException(
"Missing required parameter for 'fmt'. Make sure that the function has at least two parameters");
}
try {
return CompletableFuture.completedFuture(
fmt(ExprEvaluator.evaluate(context, context.getParams().get(0)),
ExprEvaluator.evaluate(context, context.getParams().subList(1, context.getParams().size()),
Object.class)));
} catch (Exception e) {
throw new RuntimeException(e);
}
default:
throw new IllegalArgumentException("There's no method named '" + context.getName() + "'");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,14 @@
public abstract class OpenApiClientGeneratorWrapper {

public static final String VERBOSE = "verbose";
private static final String ONCE_LOGGER = "org.openapitools.codegen.utils.oncelogger.enabled";
/**
* Security scheme for which to apply security constraints even if the OpenAPI definition has no security definition
*/
public static final String DEFAULT_SECURITY_SCHEME = "defaultSecurityScheme";
public static final String SUPPORTS_ADDITIONAL_PROPERTIES_AS_ATTRIBUTE = "supportsAdditionalPropertiesWithComposedSchema";
private static final Map<String, String> defaultTypeMappings = Map.of(
"date", "LocalDate",
"DateTime", "OffsetDateTime");
private static final Map<String, String> defaultImportMappings = Map.of(
"LocalDate", "java.time.LocalDate",
private static final String ONCE_LOGGER = "org.openapitools.codegen.utils.oncelogger.enabled";
private static final Map<String, String> defaultTypeMappings = Map.of("date", "LocalDate", "DateTime", "OffsetDateTime");
private static final Map<String, String> defaultImportMappings = Map.of("LocalDate", "java.time.LocalDate",
"OffsetDateTime", "java.time.OffsetDateTime");
private final QuarkusCodegenConfigurator configurator;
private final DefaultGenerator generator;
Expand All @@ -53,8 +50,8 @@ public abstract class OpenApiClientGeneratorWrapper {
private String modelPackage = "";

OpenApiClientGeneratorWrapper(final QuarkusCodegenConfigurator configurator, final Path specFilePath, final Path outputDir,
final boolean verbose,
final boolean validateSpec) {
final boolean verbose, final boolean validateSpec) {

// do not generate docs nor tests
GlobalSettings.setProperty(CodegenConstants.API_DOCS, FALSE.toString());
GlobalSettings.setProperty(CodegenConstants.API_TESTS, FALSE.toString());
Expand All @@ -78,17 +75,44 @@ public abstract class OpenApiClientGeneratorWrapper {
defaultTypeMappings.forEach(this.configurator::addTypeMapping);
defaultImportMappings.forEach(this.configurator::addImportMapping);

this.generator = new DefaultGenerator();
}
this.setDefaults();

public OpenApiClientGeneratorWrapper withApiPackage(final String pkg) {
this.apiPackage = pkg;
return this;
this.generator = new DefaultGenerator();
}

public OpenApiClientGeneratorWrapper withModelPackage(final String pkg) {
this.modelPackage = pkg;
return this;
/**
* A few properties from the "with*" methods must be injected in the Qute context by default since we turned strict model
* rendering.
* This way we avoid side effects in the model such as "NOT_FOUND" strings printed everywhere.
*
* @see <a href="https://quarkus.io/guides/qute-reference#configuration-reference">Qute - Configuration Reference</a>
*/
private void setDefaults() {
// Set default values directly here
this.configurator.addAdditionalProperty("additionalApiTypeAnnotations", new String[0]);
this.configurator.addAdditionalProperty("additionalPropertiesAsAttribute", FALSE);
this.configurator.addAdditionalProperty("additionalEnumTypeUnexpectedMember", FALSE);
this.configurator.addAdditionalProperty("additionalEnumTypeUnexpectedMemberName", "");
this.configurator.addAdditionalProperty("additionalEnumTypeUnexpectedMemberStringValue", "");
this.configurator.addAdditionalProperty("additionalRequestArgs", new String[0]);
this.configurator.addAdditionalProperty("classes-codegen", new HashMap<>());
this.configurator.addAdditionalProperty("circuit-breaker", new HashMap<>());
this.configurator.addAdditionalProperty("configKey", "");
this.configurator.addAdditionalProperty("datatypeWithEnum", "");
this.configurator.addAdditionalProperty("enable-security-generation", TRUE);
this.configurator.addAdditionalProperty("generate-part-filename", FALSE);
this.configurator.addAdditionalProperty("mutiny", FALSE);
this.configurator.addAdditionalProperty("mutiny-operation-ids", new HashMap<>());
this.configurator.addAdditionalProperty("mutiny-return-response", FALSE);
this.configurator.addAdditionalProperty("part-filename-value", "");
this.configurator.addAdditionalProperty("return-response", FALSE);
this.configurator.addAdditionalProperty("skipFormModel", TRUE);
this.configurator.addAdditionalProperty("templateDir", "");
this.configurator.addAdditionalProperty("use-bean-validation", FALSE);
this.configurator.addAdditionalProperty("use-field-name-in-part-filename", FALSE);
this.configurator.addAdditionalProperty("verbose", FALSE);
// TODO: expose as properties https://github.com/quarkiverse/quarkus-openapi-generator/issues/869
this.configurator.addAdditionalProperty(CodegenConstants.SERIALIZABLE_MODEL, FALSE);
}

/**
Expand All @@ -98,30 +122,30 @@ public OpenApiClientGeneratorWrapper withModelPackage(final String pkg) {
* @return this wrapper
*/
public OpenApiClientGeneratorWrapper withCircuitBreakerConfig(final Map<String, List<String>> config) {
if (config != null) {
configurator.addAdditionalProperty("circuit-breaker", config);
}
Optional.ofNullable(config).ifPresent(cfg -> {
this.configurator.addAdditionalProperty("circuit-breaker", config);
});
return this;
}

public OpenApiClientGeneratorWrapper withClassesCodeGenConfig(final Map<String, Object> config) {
if (config != null) {
configurator.addAdditionalProperty("classes-codegen", config);
}
Optional.ofNullable(config).ifPresent(cfg -> {
this.configurator.addAdditionalProperty("classes-codegen", cfg);
});
return this;
}

public OpenApiClientGeneratorWrapper withMutiny(final Boolean config) {
if (config != null) {
configurator.addAdditionalProperty("mutiny", config);
}
Optional.ofNullable(config).ifPresent(cfg -> {
this.configurator.addAdditionalProperty("mutiny", cfg);
});
return this;
}

public OpenApiClientGeneratorWrapper withMutinyReturnResponse(final Boolean config) {
if (config != null) {
configurator.addAdditionalProperty("mutiny-return-response", config);
}
Optional.ofNullable(config).ifPresent(cfg -> {
this.configurator.addAdditionalProperty("mutiny-return-response", cfg);
});
return this;
}

Expand Down Expand Up @@ -209,16 +233,16 @@ public OpenApiClientGeneratorWrapper withAdditionalEnumTypeUnexpectedMemberStrin
* @return this wrapper
*/
public OpenApiClientGeneratorWrapper withAdditionalApiTypeAnnotationsConfig(final String additionalApiTypeAnnotations) {
if (additionalApiTypeAnnotations != null) {
Optional.ofNullable(additionalApiTypeAnnotations).ifPresent(cfg -> {
this.configurator.addAdditionalProperty("additionalApiTypeAnnotations", additionalApiTypeAnnotations.split(";"));
}
});
return this;
}

public OpenApiClientGeneratorWrapper withAdditionalRequestArgs(final String additionalRequestArgs) {
if (additionalRequestArgs != null) {
Optional.ofNullable(additionalRequestArgs).ifPresent(cfg -> {
this.configurator.addAdditionalProperty("additionalRequestArgs", additionalRequestArgs.split(";"));
}
});
return this;
}

Expand Down Expand Up @@ -261,6 +285,17 @@ public OpenApiClientGeneratorWrapper withModelNamePrefix(final String modelNameP
return this;
}

public OpenApiClientGeneratorWrapper withRemoveOperationIdPrefix(final Boolean modelNamePrefix) {
this.configurator.setRemoveOperationIdPrefix(modelNamePrefix);
return this;
}

/**
* Main entrypoint, or where to generate the files based on the given base package.
*
* @param basePackage Java package name, e.g. org.acme
* @return a list of generated files
*/
public List<File> generate(final String basePackage) {
this.basePackage = basePackage;
this.consolidatePackageNames();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
* Special value if the API response contains some new value not declared in this enum.
* You should react accordingly.
*/
{additionalEnumTypeUnexpectedMemberName}({#if e.isContainer}{e.items.dataType}{#else}{e.dataType}{/if}.valueOf("{additionalEnumTypeUnexpectedMemberStringValue}")){#if e.allowableValues},{/if}
{additionalEnumTypeUnexpectedMemberName}({#if e.isContainer.or(false)}{e.items.dataType}{#else}{e.dataType}{/if}.valueOf("{additionalEnumTypeUnexpectedMemberStringValue}")){#if e.allowableValues},{/if}
Loading