Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements for the generator #1059

Merged
merged 5 commits into from
Jun 21, 2023
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 @@ -24,6 +24,7 @@ def openapiGenerate = tasks.register("generateOpenApi", OpenApiGeneratorTask) {
openApiDefinition.convention(layout.projectDirectory.file("petstore.json"))
outputDirectory.convention(layout.buildDirectory.dir("generated/openapi"))
generatorKind.convention("client")
outputKinds.convention(["models", "apis", "supportingFiles", "modelTests", "apiTests"])
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
Expand Down Expand Up @@ -63,9 +64,13 @@ public Provider<Directory> getGeneratedSourcesDirectory() {

@Internal
public Provider<Directory> getGeneratedTestSourcesDirectory() {
return getOutputDirectory().dir("src/test/java");
return getOutputDirectory().dir("src/test/groovy");
}

@Input
public abstract ListProperty<String> getOutputKinds();


@Inject
protected abstract ExecOperations getExecOperations();

Expand All @@ -82,6 +87,7 @@ public void execute() throws IOException {
args.add(getGeneratorKind().get());
args.add(getOpenApiDefinition().get().getAsFile().toURI().toString());
args.add(getOutputDirectory().get().getAsFile().getAbsolutePath());
args.add(String.join(",", getOutputKinds().get()));
javaexec.args(args);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ protected AbstractMicronautJavaCodegen() {

// Add reserved words
String[] reservedWordsArray = {
"client", "format", "queryvalue", "queryparam", "pathvariable", "header", "cookie",
"authorization", "body", "application"
"Client", "Format", "QueryValue", "QueryParam", "PathVariable", "Header", "Cookie",
"Authorization", "Body", "application"
};
reservedWords.addAll(Arrays.asList(reservedWordsArray));
}
Expand Down Expand Up @@ -312,6 +312,9 @@ public void processOpts() {
setSerializationLibrary((String) additionalProperties.get(CodegenConstants.SERIALIZATION_LIBRARY));
}
additionalProperties.put(this.serializationLibrary.toLowerCase(Locale.US), true);
if (SerializationLibraryKind.MICRONAUT_SERDE_JACKSON.name().equals(serializationLibrary)) {
additionalProperties.put(SerializationLibraryKind.JACKSON.name().toLowerCase(Locale.US), true);
}

// Add all the supporting files
String resourceFolder = projectFolder + "/resources";
Expand Down Expand Up @@ -360,6 +363,11 @@ public void processOpts() {
additionalProperties.put("resourceFolder", resourceFolder);
additionalProperties.put("apiFolder", apiFolder);
additionalProperties.put("modelFolder", modelFolder);

additionalProperties.put("formatNoEmptyLines", new Formatting.LineFormatter(0));
additionalProperties.put("formatOneEmptyLine", new Formatting.LineFormatter(1));
additionalProperties.put("formatSingleLine", new Formatting.SingleLineFormatter());
additionalProperties.put("indent", new Formatting.IndentFormatter(4));
}

// CHECKSTYLE:OFF
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright 2017-2023 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.openapi.generator;

import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* A class with lambdas to format mustache-generated code and formatting utility functions.
*/
public enum Formatting {
/* This class is not supposed to be initialized */;

/**
* Remove whitespace on the right of the line.
* @param line The line to be trimmed.
* @return The trimmed line.
*/
public static String rightTrim(String line) {
int end = 0;
for (int i = line.length(); i > 0; --i) {
if (!Character.isWhitespace(line.charAt(i - 1))) {
end = i;
break;
}
}
return line.substring(0, end);
}

/**
* Remove whitespace from both sides of the line.
* @param line The line to be trimmed.
* @return The trimmed line.
*/
public static String trim(String line) {
int start = line.length();
for (int i = 0; i < line.length(); ++i) {
if (!Character.isWhitespace(line.charAt(i))) {
start = i;
break;
}
}
int end = start;
for (int i = line.length(); i > start; --i) {
if (!Character.isWhitespace(line.charAt(i - 1))) {
end = i;
break;
}
}
return line.substring(start, end);
}

/**
* A formatter that is responsible for removing extra empty lines in mustache files.
*/
public static class LineFormatter implements Mustache.Lambda {

private final int maxEmptyLines;

/**
* Create the lambda.
*
* @param maxEmptyLines maximal empty lines.
*/
public LineFormatter(int maxEmptyLines) {
this.maxEmptyLines = maxEmptyLines;
}

@Override
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
String text = fragment.execute();
String finalWhitespace = getFinalWhitespace(text);

String lines =
Arrays.stream(text.split("\n"))
.map(Formatting::rightTrim)
.filter(new LineSkippingPredicate())
.collect(Collectors.joining("\n"));
if (!lines.isEmpty() && maxEmptyLines == 0) {
lines = "\n" + lines + "\n";
}
if (!lines.isEmpty()) {
lines = lines + finalWhitespace;
}
writer.write(lines);
}

private String getFinalWhitespace(String text) {
int i = text.length();
while (i > 0 && Character.isWhitespace(text.charAt(i - 1)) && text.charAt(i - 1) != '\n') {
--i;
}
return text.substring(i);
}

private class LineSkippingPredicate implements Predicate<String> {
private int emptyLines;

@Override
public boolean test(String s) {
if (s.isBlank()) {
++emptyLines;
} else {
emptyLines = 0;
}
return emptyLines <= maxEmptyLines;
}
}
}

/**
* A formatter that collects everything in a single line.
*/
public static class SingleLineFormatter implements Mustache.Lambda {

@Override
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
String text =
fragment.execute()
.replaceAll("\\s+", " ")
.replaceAll("(?<=<)\\s+|\\s+(?=>)", "");
writer.write(Formatting.trim(text));
}

}

/**
* A lambda that allows indenting its contents.
*/
public static class IndentFormatter implements Mustache.Lambda {

private final String indent;

public IndentFormatter(int indentSize) {
indent = " ".repeat(Math.max(0, indentSize));
}

@Override
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
String text =
Arrays.stream(fragment.execute().split("\n"))
.map(line -> indent + line)
.collect(Collectors.joining("\n"));
writer.write(text);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class JavaMicronautServerCodegen extends AbstractMicronautJavaCodegen<JavaMicron
public static final String OPT_GENERATE_CONTROLLER_FROM_EXAMPLES = "generateControllerFromExamples";
public static final String OPT_GENERATE_IMPLEMENTATION_FILES = "generateImplementationFiles";
public static final String OPT_GENERATE_OPERATIONS_TO_RETURN_NOT_IMPLEMENTED = "generateOperationsToReturnNotImplemented";
public static final String OPT_GENERATE_HARD_NULLABLE = "generateHardNullable";

public static final String EXTENSION_ROLES = "x-roles";
public static final String ANONYMOUS_ROLE_KEY = "isAnonymous()";
Expand All @@ -56,6 +57,7 @@ class JavaMicronautServerCodegen extends AbstractMicronautJavaCodegen<JavaMicron
protected boolean generateOperationsToReturnNotImplemented = true;
protected boolean generateControllerFromExamples = false;
protected boolean useAuth = true;
protected boolean generateHardNullable = true;

public JavaMicronautServerCodegen() {
super();
Expand All @@ -81,6 +83,8 @@ public JavaMicronautServerCodegen() {
generateOperationsToReturnNotImplemented));

cliOptions.add(CliOption.newBoolean(OPT_USE_AUTH, "Whether to import authorization and to annotate controller methods accordingly", useAuth));
cliOptions.add(CliOption.newBoolean(OPT_GENERATE_HARD_NULLABLE, "Whether to generate and use an inherited nullable annotation", generateHardNullable));


// Set the type mappings
// It could be also StreamingFileUpload
Expand Down Expand Up @@ -159,32 +163,44 @@ public void processOpts() {
}
writePropertyBack(OPT_USE_AUTH, useAuth);

if (additionalProperties.containsKey(OPT_GENERATE_HARD_NULLABLE)) {
generateHardNullable = convertPropertyToBoolean(OPT_GENERATE_HARD_NULLABLE);
}
writePropertyBack(OPT_GENERATE_HARD_NULLABLE, generateHardNullable);

// Api file
apiTemplateFiles.clear();
setApiNamePrefix(API_PREFIX);
setApiNameSuffix(API_SUFFIX);
apiTemplateFiles.put("server/controller-interface.mustache", ".java");

// Add documentation files
supportingFiles.add(new SupportingFile("server/doc/README.mustache", "", "README.md").doNotOverwrite());
apiDocTemplateFiles.clear();
apiDocTemplateFiles.put("server/doc/controller_doc.mustache", ".md");

// Add test files
apiTestTemplateFiles.clear();
// Controller Implementation is generated as a test file - so that it is not overwritten
if (generateImplementationFiles) {
// Add documentation files
supportingFiles.add(new SupportingFile("server/doc/README.mustache", "", "README.md").doNotOverwrite());
apiDocTemplateFiles.clear();
apiDocTemplateFiles.put("server/doc/controller_doc.mustache", ".md");

// Add Application.java file
String invokerFolder = (sourceFolder + '/' + invokerPackage).replace('.', '/');
supportingFiles.add(new SupportingFile("common/configuration/Application.mustache", invokerFolder, "Application.java").doNotOverwrite());

// Controller Implementation is generated as a test file - so that it is not overwritten
apiTestTemplateFiles.put("server/controller-implementation.mustache", ".java");

// Add test files
if (testTool.equals(OPT_TEST_JUNIT)) {
apiTestTemplateFiles.put("server/test/controller_test.mustache", ".java");
} else if (testTool.equals(OPT_TEST_SPOCK)) {
apiTestTemplateFiles.put("server/test/controller_test.groovy.mustache", ".groovy");
}
}

// Add Application.java file
String invokerFolder = (sourceFolder + '/' + invokerPackage).replace('.', '/');
supportingFiles.add(new SupportingFile("common/configuration/Application.mustache", invokerFolder, "Application.java").doNotOverwrite());
// Add HardNullable.java file
if (generateHardNullable) {
String folder = (sourceFolder + '.' + invokerPackage + ".annotation").replace('.', File.separatorChar);
supportingFiles.add(new SupportingFile("server/HardNullable.mustache", folder, "HardNullable.java"));
}
}

@Override
Expand Down Expand Up @@ -265,7 +281,7 @@ public JavaMicronautServerOptionsBuilder optionsBuilder() {

static class DefaultServerOptionsBuilder implements JavaMicronautServerOptionsBuilder {
private String controllerPackage;
private boolean generateAbstractClasses;
private boolean generateImplementationFiles;
private boolean generateControllerFromExamples;
private boolean generateOperationsToReturnNotImplemented = true;
private boolean useAuth = true;
Expand All @@ -277,8 +293,8 @@ public JavaMicronautServerOptionsBuilder withControllerPackage(String controller
}

@Override
public JavaMicronautServerOptionsBuilder withGenerateAbstractClasses(boolean abstractClasses) {
this.generateAbstractClasses = abstractClasses;
public JavaMicronautServerOptionsBuilder withGenerateImplementationFiles(boolean generateImplementationFiles) {
this.generateImplementationFiles = generateImplementationFiles;
return this;
}

Expand All @@ -301,13 +317,13 @@ public JavaMicronautServerOptionsBuilder withAuthentication(boolean useAuth) {
}

ServerOptions build() {
return new ServerOptions(controllerPackage, generateAbstractClasses, generateOperationsToReturnNotImplemented, generateControllerFromExamples, useAuth);
return new ServerOptions(controllerPackage, generateImplementationFiles, generateOperationsToReturnNotImplemented, generateControllerFromExamples, useAuth);
}
}

record ServerOptions(
String controllerPackage,
boolean generateAbstractClasses,
boolean generateImplementationFiles,
boolean generateOperationsToReturnNotImplemented,
boolean generateControllerFromExamples,
boolean useAuth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ public interface JavaMicronautServerOptionsBuilder extends GeneratorOptionsBuild
JavaMicronautServerOptionsBuilder withControllerPackage(String controllerPackage);

/**
* If set to true, the code generator will output abstract classes
* for the controller, instead of concrete implementations.
* Whether to generate controller implementations that need to be filled in.
*
* @param abstractClasses the abstract classes flag
* @param generateImplementationFiles the implementation files flag
* @return this builder
*/
JavaMicronautServerOptionsBuilder withGenerateAbstractClasses(boolean abstractClasses);
JavaMicronautServerOptionsBuilder withGenerateImplementationFiles(boolean generateImplementationFiles);

/**
* If set to true, controller operations will return not implemented status.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* Marker interface for all Micronaut code generators, used
* to avoid leaking internal types to public APIs.
*
* @param <T> generatorOptionsBuilder
* @param <T> The type of generator options builder.
*/
public interface MicronautCodeGenerator<T extends GeneratorOptionsBuilder> {
T optionsBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ private void configureServerOptions() {
if (serverOptions.controllerPackage() != null) {
serverCodegen.setControllerPackage(serverOptions.controllerPackage());
}
serverCodegen.setGenerateImplementationFiles(serverOptions.generateAbstractClasses());
serverCodegen.setGenerateImplementationFiles(serverOptions.generateImplementationFiles());
serverCodegen.setGenerateOperationsToReturnNotImplemented(serverOptions.generateOperationsToReturnNotImplemented());
serverCodegen.setGenerateControllerFromExamples(serverOptions.generateControllerFromExamples());
serverCodegen.setUseAuth(serverCodegen.useAuth);
serverCodegen.setUseAuth(serverOptions.useAuth());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isBodyParam}}@Body {{>common/params/beanValidation}}{{>client/params/type}} {{paramName}}{{/isBodyParam}}
{{#isBodyParam}}@Body {{>common/params/validation}}{{>client/params/type}} {{paramName}}{{/isBodyParam}}
Loading