Skip to content

Update generator plugin to support validators #6142

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

Merged
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
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "Code Generator Maven Plugin",
"contributor": "",
"description": "Update the generator plugin to support model validation during code generation. In addition, this adds the `writeValidationReport` flag to support writing the validation report to disk."
}
5 changes: 5 additions & 0 deletions codegen-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
<groupId>software.amazon.awssdk</groupId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<artifactId>utils</artifactId>
<groupId>software.amazon.awssdk</groupId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
Expand All @@ -30,21 +34,23 @@
import org.apache.maven.project.MavenProject;
import software.amazon.awssdk.codegen.C2jModels;
import software.amazon.awssdk.codegen.CodeGenerator;
import software.amazon.awssdk.codegen.IntermediateModelBuilder;
import software.amazon.awssdk.codegen.internal.Utils;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel;
import software.amazon.awssdk.codegen.model.service.EndpointRuleSetModel;
import software.amazon.awssdk.codegen.model.service.Paginators;
import software.amazon.awssdk.codegen.model.service.ServiceModel;
import software.amazon.awssdk.codegen.model.service.Waiters;
import software.amazon.awssdk.codegen.utils.ModelLoaderUtils;
import software.amazon.awssdk.utils.StringUtils;

/**
* The Maven mojo to generate Java client code using software.amazon.awssdk:codegen module.
*/
@Mojo(name = "generate")
public class GenerationMojo extends AbstractMojo {

private static final String MODEL_FILE = "service-2.json";
private static final String CUSTOMIZATION_CONFIG_FILE = "customization.config";
private static final String WAITERS_FILE = "waiters-2.json";
Expand All @@ -62,6 +68,8 @@ public class GenerationMojo extends AbstractMojo {
@Parameter(property = "writeIntermediateModel", defaultValue = "false")
private boolean writeIntermediateModel;

@Parameter(property = "writeValidationReport", defaultValue = "false")
private boolean writeValidationReport;

@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
Expand All @@ -76,22 +84,59 @@ public void execute() throws MojoExecutionException {
this.resourcesDirectory = Paths.get(outputDirectory).resolve("generated-resources").resolve("sdk-resources");
this.testsDirectory = Paths.get(outputDirectory).resolve("generated-test-sources").resolve("sdk-tests");

findModelRoots().forEach(p -> {
Path modelRootPath = p.modelRoot;
getLog().info("Loading from: " + modelRootPath.toString());
generateCode(C2jModels.builder()
.customizationConfig(p.customizationConfig)
.serviceModel(loadServiceModel(modelRootPath))
.waitersModel(loadWaiterModel(modelRootPath))
.paginatorsModel(loadPaginatorModel(modelRootPath))
.endpointRuleSetModel(loadEndpointRuleSetModel(modelRootPath))
.endpointTestSuiteModel(loadEndpointTestSuiteModel(modelRootPath))
.build());
List<GenerationParams> generationParams = initGenerationParams();

Map<String, IntermediateModel> serviceNameToModelMap = new HashMap<>();

generationParams.forEach(
params -> {
IntermediateModel model = params.intermediateModel;
String lowercaseServiceName = StringUtils.lowerCase(model.getMetadata().getServiceName());
IntermediateModel previous = serviceNameToModelMap.put(lowercaseServiceName, model);
if (previous != null) {
String warning = String.format("Multiple service models found with service name %s. Model validation "
+ "will likely be incorrect", lowercaseServiceName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is will likely be incorrect do we have any valid cases where multiple models have same service name , why just warn and not error ?

Copy link
Contributor Author

@dagnir dagnir May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a valid case honestly. I only encountered this in testing when the generator runs for the codegen-generated-classes-test since we have a lot of test models there and some have the same name, and all in the same package, so erroring out in that case is not productive.

The worst case scenario is compilation will just fail in that case so this should be fine. We could consider adding a "skipValidation" flag in the future.

getLog().warn(warning);
}
});

// Update each param with the intermediate model it shares models with, if any
generationParams.forEach(params -> {
CustomizationConfig customizationConfig = params.intermediateModel.getCustomizationConfig();

if (customizationConfig.getShareModelConfig() != null) {
String shareModelWithName = customizationConfig.getShareModelConfig().getShareModelWith();
params.withShareModelsTarget(serviceNameToModelMap.get(shareModelWithName));
}
});

generationParams.forEach(this::generateCode);

project.addCompileSourceRoot(sourcesDirectory.toFile().getAbsolutePath());
project.addTestCompileSourceRoot(testsDirectory.toFile().getAbsolutePath());
}

private List<GenerationParams> initGenerationParams() throws MojoExecutionException {
List<ModelRoot> modelRoots = findModelRoots().collect(Collectors.toList());

return modelRoots.stream().map(r -> {
Path modelRootPath = r.modelRoot;
getLog().info("Loading from: " + modelRootPath.toString());
C2jModels c2jModels = C2jModels.builder()
.customizationConfig(r.customizationConfig)
.serviceModel(loadServiceModel(modelRootPath))
.waitersModel(loadWaiterModel(modelRootPath))
.paginatorsModel(loadPaginatorModel(modelRootPath))
.endpointRuleSetModel(loadEndpointRuleSetModel(modelRootPath))
.endpointTestSuiteModel(loadEndpointTestSuiteModel(modelRootPath))
.build();
String intermediateModelFileNamePrefix = intermediateModelFileNamePrefix(c2jModels);
IntermediateModel intermediateModel = new IntermediateModelBuilder(c2jModels).build();
return new GenerationParams().withIntermediateModel(intermediateModel)
.withIntermediateModelFileNamePrefix(intermediateModelFileNamePrefix);
}).collect(Collectors.toList());
}

private Stream<ModelRoot> findModelRoots() throws MojoExecutionException {
try {
return Files.find(codeGenResources.toPath(), 10, this::isModelFile)
Expand All @@ -111,13 +156,15 @@ private boolean isModelFile(Path p, BasicFileAttributes a) {
return p.toString().endsWith(MODEL_FILE);
}

private void generateCode(C2jModels models) {
private void generateCode(GenerationParams params) {
CodeGenerator.builder()
.models(models)
.intermediateModel(params.intermediateModel)
.shareModelsTarget(params.shareModelsTarget)
.sourcesDirectory(sourcesDirectory.toFile().getAbsolutePath())
.resourcesDirectory(resourcesDirectory.toFile().getAbsolutePath())
.testsDirectory(testsDirectory.toFile().getAbsolutePath())
.intermediateModelFileNamePrefix(intermediateModelFileNamePrefix(models))
.intermediateModelFileNamePrefix(params.intermediateModelFileNamePrefix)
.emitValidationReport(writeValidationReport)
.build()
.execute();
}
Expand Down Expand Up @@ -178,4 +225,25 @@ private ModelRoot(Path modelRoot, CustomizationConfig customizationConfig) {
this.customizationConfig = customizationConfig;
}
}

private static class GenerationParams {
private IntermediateModel intermediateModel;
private IntermediateModel shareModelsTarget;
private String intermediateModelFileNamePrefix;

public GenerationParams withIntermediateModel(IntermediateModel intermediateModel) {
this.intermediateModel = intermediateModel;
return this;
}

public GenerationParams withShareModelsTarget(IntermediateModel shareModelsTarget) {
this.shareModelsTarget = shareModelsTarget;
return this;
}

public GenerationParams withIntermediateModelFileNamePrefix(String intermediateModelFileNamePrefix) {
this.intermediateModelFileNamePrefix = intermediateModelFileNamePrefix;
return this;
}
}
}
Loading