diff --git a/.changes/next-release/feature-AWSSDKforJavav2-7cf1e5c.json b/.changes/next-release/feature-AWSSDKforJavav2-7cf1e5c.json
new file mode 100644
index 000000000000..3fc51c387d75
--- /dev/null
+++ b/.changes/next-release/feature-AWSSDKforJavav2-7cf1e5c.json
@@ -0,0 +1,6 @@
+{
+ "type": "feature",
+ "category": "AWS SDK for Java v2",
+ "contributor": "",
+ "description": "Add code generation validation for missing request URI on an operation."
+}
diff --git a/.changes/next-release/feature-AWSSDKforJavav2-bd762da.json b/.changes/next-release/feature-AWSSDKforJavav2-bd762da.json
new file mode 100644
index 000000000000..d4fc9915dd8d
--- /dev/null
+++ b/.changes/next-release/feature-AWSSDKforJavav2-bd762da.json
@@ -0,0 +1,6 @@
+{
+ "type": "feature",
+ "category": "AWS SDK for Java v2",
+ "contributor": "",
+ "description": "Add support for defining service model validators and generating valdiation reports during code generation."
+}
diff --git a/.changes/next-release/feature-AWSSDKforJavav2-f004fae.json b/.changes/next-release/feature-AWSSDKforJavav2-f004fae.json
new file mode 100644
index 000000000000..184769e3f85c
--- /dev/null
+++ b/.changes/next-release/feature-AWSSDKforJavav2-f004fae.json
@@ -0,0 +1,6 @@
+{
+ "type": "feature",
+ "category": "AWS SDK for Java v2",
+ "contributor": "",
+ "description": "Add support for validating that shared models between two services are identical."
+}
diff --git a/.changes/next-release/feature-CodeGeneratorMavenPlugin-64c91e8.json b/.changes/next-release/feature-CodeGeneratorMavenPlugin-64c91e8.json
new file mode 100644
index 000000000000..91090c74555f
--- /dev/null
+++ b/.changes/next-release/feature-CodeGeneratorMavenPlugin-64c91e8.json
@@ -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."
+}
diff --git a/codegen-maven-plugin/pom.xml b/codegen-maven-plugin/pom.xml
index 4fdf47dfebc2..825cc5f465ab 100644
--- a/codegen-maven-plugin/pom.xml
+++ b/codegen-maven-plugin/pom.xml
@@ -57,6 +57,11 @@
software.amazon.awssdk
${awsjavasdk.version}
+
+ utils
+ software.amazon.awssdk
+ ${awsjavasdk.version}
+
org.junit.jupiter
junit-jupiter
diff --git a/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java b/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java
index 4ce4e7be116b..3d17b4d84bb2 100644
--- a/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java
+++ b/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java
@@ -17,11 +17,17 @@
import java.io.File;
import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
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;
@@ -30,21 +36,26 @@
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.Jackson;
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.codegen.validation.ModelInvalidException;
+import software.amazon.awssdk.codegen.validation.ModelValidationReport;
+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";
@@ -62,6 +73,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;
@@ -76,22 +89,72 @@ 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;
+
+ try {
+ generationParams = initGenerationParams();
+ } catch (ModelInvalidException e) {
+ if (writeValidationReport) {
+ ModelValidationReport report = new ModelValidationReport();
+ report.setValidationEntries(e.validationEntries());
+ emitValidationReport(report);
+ }
+ throw e;
+ }
+
+ Map 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);
+ 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 initGenerationParams() throws MojoExecutionException {
+ List 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 findModelRoots() throws MojoExecutionException {
try {
return Files.find(codeGenResources.toPath(), 10, this::isModelFile)
@@ -111,13 +174,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();
}
@@ -169,6 +234,17 @@ private Optional loadOptionalModel(Class clzz, Path location) {
return ModelLoaderUtils.loadOptionalModel(clzz, location.toFile());
}
+ private void emitValidationReport(ModelValidationReport report) {
+ Path modelsDir = sourcesDirectory.resolve("models");
+ try (Writer writer = Files.newBufferedWriter(modelsDir.resolve("validation-report.json"),
+ StandardCharsets.UTF_8);) {
+
+ Jackson.writeWithObjectMapper(report, writer);
+ } catch (IOException e) {
+ getLog().warn("Failed to write validation report to " + modelsDir, e);
+ }
+ }
+
private static class ModelRoot {
private final Path modelRoot;
private final CustomizationConfig customizationConfig;
@@ -178,4 +254,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;
+ }
+ }
}
diff --git a/codegen/pom.xml b/codegen/pom.xml
index a84277054daa..035db17318bc 100644
--- a/codegen/pom.xml
+++ b/codegen/pom.xml
@@ -239,5 +239,10 @@
mockito-core
compile
+
+ nl.jqno.equalsverifier
+ equalsverifier
+ test
+
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java
index 6f9bec29617f..46b15ae7dbbd 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java
@@ -21,6 +21,7 @@
import static software.amazon.awssdk.codegen.internal.Utils.isMapShape;
import static software.amazon.awssdk.codegen.internal.Utils.isScalar;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -37,10 +38,15 @@
import software.amazon.awssdk.codegen.model.intermediate.VariableModel;
import software.amazon.awssdk.codegen.model.service.Location;
import software.amazon.awssdk.codegen.model.service.Member;
+import software.amazon.awssdk.codegen.model.service.Operation;
import software.amazon.awssdk.codegen.model.service.ServiceModel;
import software.amazon.awssdk.codegen.model.service.Shape;
import software.amazon.awssdk.codegen.naming.NamingStrategy;
import software.amazon.awssdk.codegen.utils.ProtocolUtils;
+import software.amazon.awssdk.codegen.validation.ModelInvalidException;
+import software.amazon.awssdk.codegen.validation.ValidationEntry;
+import software.amazon.awssdk.codegen.validation.ValidationErrorId;
+import software.amazon.awssdk.codegen.validation.ValidationErrorSeverity;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.Validate;
@@ -346,11 +352,20 @@ private boolean isGreedy(Shape parentShape, Map allC2jShapes, Par
* @throws RuntimeException If operation can't be found.
*/
private String findRequestUri(Shape parentShape, Map allC2jShapes) {
- return builder.getService().getOperations().values().stream()
- .filter(o -> o.getInput() != null)
- .filter(o -> allC2jShapes.get(o.getInput().getShape()).equals(parentShape))
- .map(o -> o.getHttp().getRequestUri())
- .findFirst().orElseThrow(() -> new RuntimeException("Could not find request URI for input shape"));
+ Optional operation = builder.getService().getOperations().values().stream()
+ .filter(o -> o.getInput() != null)
+ .filter(o -> allC2jShapes.get(o.getInput().getShape()).equals(parentShape))
+ .findFirst();
+
+ return operation.map(o -> o.getHttp().getRequestUri())
+ .orElseThrow(() -> {
+ String detailMsg = "Could not find request URI for input shape";
+ ValidationEntry entry =
+ new ValidationEntry().withErrorId(ValidationErrorId.REQUEST_URI_NOT_FOUND)
+ .withDetailMessage(detailMsg)
+ .withSeverity(ValidationErrorSeverity.DANGER);
+ return ModelInvalidException.builder().validationEntries(Collections.singletonList(entry)).build();
+ });
}
private String deriveUnmarshallerLocationName(Shape memberShape, String memberName, Member member) {
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/CodeGenerator.java b/codegen/src/main/java/software/amazon/awssdk/codegen/CodeGenerator.java
index 4c097fadb4d2..0361fc6d0fdd 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/CodeGenerator.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/CodeGenerator.java
@@ -19,6 +19,9 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.ForkJoinTask;
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
@@ -26,13 +29,26 @@
import software.amazon.awssdk.codegen.internal.Jackson;
import software.amazon.awssdk.codegen.internal.Utils;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
+import software.amazon.awssdk.codegen.validation.ModelInvalidException;
+import software.amazon.awssdk.codegen.validation.ModelValidationContext;
+import software.amazon.awssdk.codegen.validation.ModelValidationReport;
+import software.amazon.awssdk.codegen.validation.ModelValidator;
+import software.amazon.awssdk.codegen.validation.SharedModelsValidator;
+import software.amazon.awssdk.codegen.validation.ValidationEntry;
import software.amazon.awssdk.utils.Logger;
public class CodeGenerator {
private static final Logger log = Logger.loggerFor(CodeGenerator.class);
private static final String MODEL_DIR_NAME = "models";
- private final C2jModels models;
+ private static final List DEFAULT_MODEL_VALIDATORS = Collections.singletonList(
+ new SharedModelsValidator()
+ );
+
+ private final C2jModels c2jModels;
+
+ private final IntermediateModel intermediateModel;
+ private final IntermediateModel shareModelsTarget;
private final String sourcesDirectory;
private final String resourcesDirectory;
private final String testsDirectory;
@@ -42,6 +58,9 @@ public class CodeGenerator {
*/
private final String fileNamePrefix;
+ private final List modelValidators;
+ private final boolean emitValidationReport;
+
static {
// Make sure ClassName is statically initialized before we do anything in parallel.
// Parallel static initialization of ClassName and TypeName can result in a deadlock:
@@ -50,12 +69,21 @@ public class CodeGenerator {
}
public CodeGenerator(Builder builder) {
- this.models = builder.models;
+ this.c2jModels = builder.models;
+ this.intermediateModel = builder.intermediateModel;
+
+ if (this.c2jModels != null && this.intermediateModel != null) {
+ throw new IllegalArgumentException("Only one of c2jModels and intermediateModel must be specified");
+ }
+
+ this.shareModelsTarget = builder.shareModelsTarget;
this.sourcesDirectory = builder.sourcesDirectory;
this.testsDirectory = builder.testsDirectory;
this.resourcesDirectory = builder.resourcesDirectory != null ? builder.resourcesDirectory
: builder.sourcesDirectory;
this.fileNamePrefix = builder.fileNamePrefix;
+ this.modelValidators = builder.modelValidators == null ? DEFAULT_MODEL_VALIDATORS : builder.modelValidators;
+ this.emitValidationReport = builder.emitValidationReport;
}
public static File getModelDirectory(String outputDirectory) {
@@ -76,22 +104,72 @@ public static Builder builder() {
* code.
*/
public void execute() {
- try {
- IntermediateModel intermediateModel = new IntermediateModelBuilder(models).build();
+ ModelValidationReport report = new ModelValidationReport();
+
+ IntermediateModel modelToGenerate;
+ if (c2jModels != null) {
+ modelToGenerate = new IntermediateModelBuilder(c2jModels).build();
+ } else {
+ modelToGenerate = intermediateModel;
+ }
+ List validatorEntries = runModelValidators(modelToGenerate);
+ report.setValidationEntries(validatorEntries);
+
+ if (emitValidationReport) {
+ writeValidationReport(report);
+ }
+
+ if (!validatorEntries.isEmpty()) {
+ throw new RuntimeException("Validation failed. See validation report for details.");
+ }
+
+ try {
if (fileNamePrefix != null) {
- writeIntermediateModel(intermediateModel);
+ writeIntermediateModel(modelToGenerate);
}
- emitCode(intermediateModel);
+ emitCode(modelToGenerate);
} catch (Exception e) {
log.error(() -> "Failed to generate code. ", e);
+
+ if (e instanceof ModelInvalidException && emitValidationReport) {
+ ModelInvalidException invalidException = (ModelInvalidException) e;
+ report.setValidationEntries(invalidException.validationEntries());
+ writeValidationReport(report);
+ }
+
throw new RuntimeException(
"Failed to generate code. Exception message : " + e.getMessage(), e);
}
}
+ private List runModelValidators(IntermediateModel intermediateModel) {
+ ModelValidationContext ctx = ModelValidationContext.builder()
+ .intermediateModel(intermediateModel)
+ .shareModelsTarget(shareModelsTarget)
+ .build();
+
+ List validationEntries = new ArrayList<>();
+
+ modelValidators.forEach(v -> validationEntries.addAll(v.validateModels(ctx)));
+
+ return validationEntries;
+ }
+
+ private void writeValidationReport(ModelValidationReport report) {
+ try {
+ writeModel(report, "validation-report.json");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private void writeIntermediateModel(IntermediateModel model) throws IOException {
+ writeModel(model, fileNamePrefix + "-intermediate.json");
+ }
+
+ private void writeModel(Object model, String name) throws IOException {
File modelDir = getModelDirectory(sourcesDirectory);
PrintWriter writer = null;
try {
@@ -100,7 +178,7 @@ private void writeIntermediateModel(IntermediateModel model) throws IOException
throw new RuntimeException("Failed to create " + outDir.getAbsolutePath());
}
- File outputFile = new File(modelDir, fileNamePrefix + "-intermediate.json");
+ File outputFile = new File(modelDir, name);
if (!outputFile.exists() && !outputFile.createNewFile()) {
throw new RuntimeException("Error creating file " + outputFile.getAbsolutePath());
@@ -134,10 +212,14 @@ private GeneratorTask createGeneratorTasks(IntermediateModel intermediateModel)
public static final class Builder {
private C2jModels models;
+ private IntermediateModel intermediateModel;
+ private IntermediateModel shareModelsTarget;
private String sourcesDirectory;
private String resourcesDirectory;
private String testsDirectory;
private String fileNamePrefix;
+ private List modelValidators;
+ private boolean emitValidationReport;
private Builder() {
}
@@ -147,6 +229,16 @@ public Builder models(C2jModels models) {
return this;
}
+ public Builder intermediateModel(IntermediateModel intermediateModel) {
+ this.intermediateModel = intermediateModel;
+ return this;
+ }
+
+ public Builder shareModelsTarget(IntermediateModel shareModelsTarget) {
+ this.shareModelsTarget = shareModelsTarget;
+ return this;
+ }
+
public Builder sourcesDirectory(String sourcesDirectory) {
this.sourcesDirectory = sourcesDirectory;
return this;
@@ -167,6 +259,16 @@ public Builder intermediateModelFileNamePrefix(String fileNamePrefix) {
return this;
}
+ public Builder modelValidators(List modelValidators) {
+ this.modelValidators = modelValidators;
+ return this;
+ }
+
+ public Builder emitValidationReport(boolean emitValidationReport) {
+ this.emitValidationReport = emitValidationReport;
+ return this;
+ }
+
/**
* @return An immutable {@link CodeGenerator} object.
*/
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/BaseGeneratorTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/BaseGeneratorTasks.java
index 731f70e0cba3..cdabdbf219cd 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/BaseGeneratorTasks.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/BaseGeneratorTasks.java
@@ -71,6 +71,8 @@ protected void compute() {
ForkJoinTask.invokeAll(createTasks());
log.info(" Completed " + taskName + ".");
}
+ } catch (RuntimeException e) {
+ throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ArgumentModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ArgumentModel.java
index 5013db7d3f9e..16e848303a4f 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ArgumentModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ArgumentModel.java
@@ -15,6 +15,8 @@
package software.amazon.awssdk.codegen.model.intermediate;
+import java.util.Objects;
+
public class ArgumentModel extends DocumentationModel {
private String name;
@@ -61,4 +63,28 @@ public ArgumentModel withIsEnumArg(boolean isEnumArg) {
this.isEnumArg = isEnumArg;
return this;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ ArgumentModel that = (ArgumentModel) o;
+ return isEnumArg == that.isEnumArg
+ && Objects.equals(name, that.name)
+ && Objects.equals(type, that.type);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + Objects.hashCode(name);
+ result = 31 * result + Objects.hashCode(type);
+ result = 31 * result + Boolean.hashCode(isEnumArg);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/AuthorizerModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/AuthorizerModel.java
index ce98c0dfea8e..316f4e741139 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/AuthorizerModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/AuthorizerModel.java
@@ -16,6 +16,7 @@
package software.amazon.awssdk.codegen.model.intermediate;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.util.Objects;
import software.amazon.awssdk.codegen.model.service.Location;
public class AuthorizerModel extends DocumentationModel {
@@ -63,4 +64,30 @@ public String getAddAuthTokenMethod() {
authTokenLocation));
}
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ AuthorizerModel that = (AuthorizerModel) o;
+ return Objects.equals(name, that.name)
+ && Objects.equals(interfaceName, that.interfaceName)
+ && authTokenLocation == that.authTokenLocation
+ && Objects.equals(tokenName, that.tokenName);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + Objects.hashCode(name);
+ result = 31 * result + Objects.hashCode(interfaceName);
+ result = 31 * result + Objects.hashCode(authTokenLocation);
+ result = 31 * result + Objects.hashCode(tokenName);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/DocumentationModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/DocumentationModel.java
index 5be891040acc..55fd39f4a7c7 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/DocumentationModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/DocumentationModel.java
@@ -17,6 +17,8 @@
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.escapeIllegalCharacters;
+import java.util.Objects;
+
public class DocumentationModel {
protected String documentation;
@@ -28,4 +30,22 @@ public String getDocumentation() {
public void setDocumentation(String documentation) {
this.documentation = escapeIllegalCharacters(documentation);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DocumentationModel that = (DocumentationModel) o;
+ return Objects.equals(documentation, that.documentation);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(documentation);
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EndpointDiscovery.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EndpointDiscovery.java
index 91a5f3b60f25..e372079fc541 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EndpointDiscovery.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EndpointDiscovery.java
@@ -26,4 +26,22 @@ public boolean isRequired() {
public void setRequired(boolean required) {
this.required = required;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EndpointDiscovery that = (EndpointDiscovery) o;
+ return required == that.required;
+ }
+
+ @Override
+ public int hashCode() {
+ return Boolean.hashCode(required);
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EnumModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EnumModel.java
index f469b5de99fd..652f2c2aca6e 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EnumModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/EnumModel.java
@@ -15,6 +15,8 @@
package software.amazon.awssdk.codegen.model.intermediate;
+import java.util.Objects;
+
/**
* Represents a single enum field in a enum.
*/
@@ -49,4 +51,23 @@ public String getValue() {
return value;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EnumModel enumModel = (EnumModel) o;
+ return Objects.equals(value, enumModel.value) && Objects.equals(name, enumModel.name);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hashCode(value);
+ result = 31 * result + Objects.hashCode(name);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java
index fddf93d4d72d..3e905aa1ed56 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java
@@ -28,6 +28,7 @@
import com.squareup.javapoet.ClassName;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import software.amazon.awssdk.codegen.internal.TypeUtils;
import software.amazon.awssdk.codegen.model.service.ContextParam;
@@ -785,4 +786,98 @@ public void ignoreDataTypeConversionFailures(boolean ignoreDataTypeConversionFai
public boolean ignoreDataTypeConversionFailures() {
return ignoreDataTypeConversionFailures;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ MemberModel that = (MemberModel) o;
+ return deprecated == that.deprecated
+ && required == that.required
+ && synthetic == that.synthetic
+ && idempotencyToken == that.idempotencyToken
+ && isJsonValue == that.isJsonValue
+ && eventPayload == that.eventPayload
+ && eventHeader == that.eventHeader
+ && endpointDiscoveryId == that.endpointDiscoveryId
+ && sensitive == that.sensitive
+ && xmlAttribute == that.xmlAttribute
+ && ignoreDataTypeConversionFailures == that.ignoreDataTypeConversionFailures
+ && Objects.equals(name, that.name)
+ && Objects.equals(c2jName, that.c2jName)
+ && Objects.equals(c2jShape, that.c2jShape)
+ && Objects.equals(variable, that.variable)
+ && Objects.equals(setterModel, that.setterModel)
+ && Objects.equals(getterModel, that.getterModel)
+ && Objects.equals(http, that.http)
+ && Objects.equals(deprecatedMessage, that.deprecatedMessage)
+ && Objects.equals(listModel, that.listModel)
+ && Objects.equals(mapModel, that.mapModel)
+ && Objects.equals(enumType, that.enumType)
+ && Objects.equals(xmlNameSpaceUri, that.xmlNameSpaceUri)
+ && Objects.equals(shape, that.shape)
+ && Objects.equals(fluentGetterMethodName, that.fluentGetterMethodName)
+ && Objects.equals(fluentEnumGetterMethodName, that.fluentEnumGetterMethodName)
+ && Objects.equals(fluentSetterMethodName, that.fluentSetterMethodName)
+ && Objects.equals(fluentEnumSetterMethodName, that.fluentEnumSetterMethodName)
+ && Objects.equals(existenceCheckMethodName, that.existenceCheckMethodName)
+ && Objects.equals(beanStyleGetterName, that.beanStyleGetterName)
+ && Objects.equals(beanStyleSetterName, that.beanStyleSetterName)
+ && Objects.equals(unionEnumTypeName, that.unionEnumTypeName)
+ && Objects.equals(timestampFormat, that.timestampFormat)
+ && Objects.equals(deprecatedName, that.deprecatedName)
+ && Objects.equals(fluentDeprecatedGetterMethodName, that.fluentDeprecatedGetterMethodName)
+ && Objects.equals(fluentDeprecatedSetterMethodName, that.fluentDeprecatedSetterMethodName)
+ && Objects.equals(deprecatedBeanStyleSetterMethodName, that.deprecatedBeanStyleSetterMethodName)
+ && Objects.equals(contextParam, that.contextParam);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + Objects.hashCode(name);
+ result = 31 * result + Objects.hashCode(c2jName);
+ result = 31 * result + Objects.hashCode(c2jShape);
+ result = 31 * result + Objects.hashCode(variable);
+ result = 31 * result + Objects.hashCode(setterModel);
+ result = 31 * result + Objects.hashCode(getterModel);
+ result = 31 * result + Objects.hashCode(http);
+ result = 31 * result + Boolean.hashCode(deprecated);
+ result = 31 * result + Objects.hashCode(deprecatedMessage);
+ result = 31 * result + Boolean.hashCode(required);
+ result = 31 * result + Boolean.hashCode(synthetic);
+ result = 31 * result + Objects.hashCode(listModel);
+ result = 31 * result + Objects.hashCode(mapModel);
+ result = 31 * result + Objects.hashCode(enumType);
+ result = 31 * result + Objects.hashCode(xmlNameSpaceUri);
+ result = 31 * result + Boolean.hashCode(idempotencyToken);
+ result = 31 * result + Objects.hashCode(shape);
+ result = 31 * result + Objects.hashCode(fluentGetterMethodName);
+ result = 31 * result + Objects.hashCode(fluentEnumGetterMethodName);
+ result = 31 * result + Objects.hashCode(fluentSetterMethodName);
+ result = 31 * result + Objects.hashCode(fluentEnumSetterMethodName);
+ result = 31 * result + Objects.hashCode(existenceCheckMethodName);
+ result = 31 * result + Objects.hashCode(beanStyleGetterName);
+ result = 31 * result + Objects.hashCode(beanStyleSetterName);
+ result = 31 * result + Objects.hashCode(unionEnumTypeName);
+ result = 31 * result + Boolean.hashCode(isJsonValue);
+ result = 31 * result + Objects.hashCode(timestampFormat);
+ result = 31 * result + Boolean.hashCode(eventPayload);
+ result = 31 * result + Boolean.hashCode(eventHeader);
+ result = 31 * result + Boolean.hashCode(endpointDiscoveryId);
+ result = 31 * result + Boolean.hashCode(sensitive);
+ result = 31 * result + Boolean.hashCode(xmlAttribute);
+ result = 31 * result + Objects.hashCode(deprecatedName);
+ result = 31 * result + Objects.hashCode(fluentDeprecatedGetterMethodName);
+ result = 31 * result + Objects.hashCode(fluentDeprecatedSetterMethodName);
+ result = 31 * result + Objects.hashCode(deprecatedBeanStyleSetterMethodName);
+ result = 31 * result + Objects.hashCode(contextParam);
+ result = 31 * result + Boolean.hashCode(ignoreDataTypeConversionFailures);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java
index a2a060c7a915..6b192644da1d 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import software.amazon.awssdk.codegen.checksum.HttpChecksum;
import software.amazon.awssdk.codegen.compression.RequestCompression;
import software.amazon.awssdk.codegen.docs.ClientType;
@@ -379,4 +380,63 @@ public boolean isUnsignedPayload() {
public void setUnsignedPayload(boolean unsignedPayload) {
this.unsignedPayload = unsignedPayload;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ OperationModel that = (OperationModel) o;
+ return deprecated == that.deprecated && hasBlobMemberAsPayload == that.hasBlobMemberAsPayload
+ && hasStringMemberAsPayload == that.hasStringMemberAsPayload && isAuthenticated == that.isAuthenticated
+ && isPaginated == that.isPaginated && endpointOperation == that.endpointOperation
+ && endpointCacheRequired == that.endpointCacheRequired && httpChecksumRequired == that.httpChecksumRequired
+ && unsignedPayload == that.unsignedPayload && Objects.equals(operationName, that.operationName)
+ && Objects.equals(serviceProtocol, that.serviceProtocol)
+ && Objects.equals(deprecatedMessage, that.deprecatedMessage) && Objects.equals(input, that.input)
+ && Objects.equals(returnType, that.returnType) && Objects.equals(exceptions, that.exceptions)
+ && Objects.equals(simpleMethods, that.simpleMethods) && authType == that.authType
+ && Objects.equals(auth, that.auth) && Objects.equals(endpointDiscovery, that.endpointDiscovery)
+ && Objects.equals(inputShape, that.inputShape) && Objects.equals(outputShape, that.outputShape)
+ && Objects.equals(endpointTrait, that.endpointTrait) && Objects.equals(httpChecksum, that.httpChecksum)
+ && Objects.equals(requestcompression, that.requestcompression)
+ && Objects.equals(staticContextParams, that.staticContextParams)
+ && Objects.equals(operationContextParams, that.operationContextParams);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + Objects.hashCode(operationName);
+ result = 31 * result + Objects.hashCode(serviceProtocol);
+ result = 31 * result + Boolean.hashCode(deprecated);
+ result = 31 * result + Objects.hashCode(deprecatedMessage);
+ result = 31 * result + Objects.hashCode(input);
+ result = 31 * result + Objects.hashCode(returnType);
+ result = 31 * result + Objects.hashCode(exceptions);
+ result = 31 * result + Objects.hashCode(simpleMethods);
+ result = 31 * result + Boolean.hashCode(hasBlobMemberAsPayload);
+ result = 31 * result + Boolean.hashCode(hasStringMemberAsPayload);
+ result = 31 * result + Boolean.hashCode(isAuthenticated);
+ result = 31 * result + Objects.hashCode(authType);
+ result = 31 * result + Objects.hashCode(auth);
+ result = 31 * result + Boolean.hashCode(isPaginated);
+ result = 31 * result + Boolean.hashCode(endpointOperation);
+ result = 31 * result + Boolean.hashCode(endpointCacheRequired);
+ result = 31 * result + Objects.hashCode(endpointDiscovery);
+ result = 31 * result + Objects.hashCode(inputShape);
+ result = 31 * result + Objects.hashCode(outputShape);
+ result = 31 * result + Objects.hashCode(endpointTrait);
+ result = 31 * result + Boolean.hashCode(httpChecksumRequired);
+ result = 31 * result + Objects.hashCode(httpChecksum);
+ result = 31 * result + Objects.hashCode(requestcompression);
+ result = 31 * result + Objects.hashCode(staticContextParams);
+ result = 31 * result + Objects.hashCode(operationContextParams);
+ result = 31 * result + Boolean.hashCode(unsignedPayload);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ParameterHttpMapping.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ParameterHttpMapping.java
index 22ed4a8e6880..fc9a776059a7 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ParameterHttpMapping.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ParameterHttpMapping.java
@@ -15,6 +15,7 @@
package software.amazon.awssdk.codegen.model.intermediate;
+import java.util.Objects;
import software.amazon.awssdk.codegen.model.service.Location;
import software.amazon.awssdk.core.protocol.MarshallLocation;
@@ -199,4 +200,40 @@ public MarshallLocation getMarshallLocation() {
}
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ParameterHttpMapping that = (ParameterHttpMapping) o;
+ return isPayload == that.isPayload
+ && isStreaming == that.isStreaming
+ && flattened == that.flattened
+ && isGreedy == that.isGreedy
+ && requiresLength == that.requiresLength
+ && Objects.equals(unmarshallLocationName, that.unmarshallLocationName)
+ && Objects.equals(marshallLocationName, that.marshallLocationName)
+ && Objects.equals(additionalUnmarshallingPath, that.additionalUnmarshallingPath)
+ && Objects.equals(additionalMarshallingPath, that.additionalMarshallingPath)
+ && location == that.location;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hashCode(unmarshallLocationName);
+ result = 31 * result + Objects.hashCode(marshallLocationName);
+ result = 31 * result + Objects.hashCode(additionalUnmarshallingPath);
+ result = 31 * result + Objects.hashCode(additionalMarshallingPath);
+ result = 31 * result + Boolean.hashCode(isPayload);
+ result = 31 * result + Boolean.hashCode(isStreaming);
+ result = 31 * result + Objects.hashCode(location);
+ result = 31 * result + Boolean.hashCode(flattened);
+ result = 31 * result + Boolean.hashCode(isGreedy);
+ result = 31 * result + Boolean.hashCode(requiresLength);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ReturnTypeModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ReturnTypeModel.java
index 77dff4c71481..1d46c2802cda 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ReturnTypeModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ReturnTypeModel.java
@@ -15,6 +15,8 @@
package software.amazon.awssdk.codegen.model.intermediate;
+import java.util.Objects;
+
public class ReturnTypeModel {
private String returnType;
@@ -48,4 +50,24 @@ public ReturnTypeModel withDocumentation(String documentation) {
setDocumentation(documentation);
return this;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ReturnTypeModel that = (ReturnTypeModel) o;
+ return Objects.equals(returnType, that.returnType) && Objects.equals(documentation, that.documentation);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hashCode(returnType);
+ result = 31 * result + Objects.hashCode(documentation);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModel.java
index 098ea46bc7e4..3c26965302d5 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModel.java
@@ -26,6 +26,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
import software.amazon.awssdk.codegen.model.intermediate.customization.ShapeCustomizationInfo;
import software.amazon.awssdk.codegen.model.service.XmlNamespace;
@@ -669,4 +670,84 @@ public ShapeModel withIsThrottling(boolean throttling) {
this.throttling = throttling;
return this;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ ShapeModel that = (ShapeModel) o;
+ return deprecated == that.deprecated
+ && hasPayloadMember == that.hasPayloadMember
+ && hasHeaderMember == that.hasHeaderMember
+ && hasStatusCodeMember == that.hasStatusCodeMember
+ && hasStreamingMember == that.hasStreamingMember
+ && hasRequiresLengthMember == that.hasRequiresLengthMember
+ && wrapper == that.wrapper
+ && simpleMethod == that.simpleMethod
+ && fault == that.fault
+ && isEventStream == that.isEventStream
+ && isEvent == that.isEvent
+ && document == that.document
+ && union == that.union
+ && retryable == that.retryable
+ && throttling == that.throttling
+ && Objects.equals(c2jName, that.c2jName)
+ && Objects.equals(shapeName, that.shapeName)
+ && Objects.equals(deprecatedMessage, that.deprecatedMessage)
+ && Objects.equals(type, that.type)
+ && Objects.equals(required, that.required)
+ && Objects.equals(requestSignerClassFqcn, that.requestSignerClassFqcn)
+ && Objects.equals(endpointDiscovery, that.endpointDiscovery)
+ && Objects.equals(members, that.members)
+ && Objects.equals(enums, that.enums)
+ && Objects.equals(variable, that.variable)
+ && Objects.equals(marshaller, that.marshaller)
+ && Objects.equals(unmarshaller, that.unmarshaller)
+ && Objects.equals(errorCode, that.errorCode)
+ && Objects.equals(httpStatusCode, that.httpStatusCode)
+ && Objects.equals(customization, that.customization)
+ && Objects.equals(xmlNamespace, that.xmlNamespace);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + Objects.hashCode(c2jName);
+ result = 31 * result + Objects.hashCode(shapeName);
+ result = 31 * result + Boolean.hashCode(deprecated);
+ result = 31 * result + Objects.hashCode(deprecatedMessage);
+ result = 31 * result + Objects.hashCode(type);
+ result = 31 * result + Objects.hashCode(required);
+ result = 31 * result + Boolean.hashCode(hasPayloadMember);
+ result = 31 * result + Boolean.hashCode(hasHeaderMember);
+ result = 31 * result + Boolean.hashCode(hasStatusCodeMember);
+ result = 31 * result + Boolean.hashCode(hasStreamingMember);
+ result = 31 * result + Boolean.hashCode(hasRequiresLengthMember);
+ result = 31 * result + Boolean.hashCode(wrapper);
+ result = 31 * result + Boolean.hashCode(simpleMethod);
+ result = 31 * result + Objects.hashCode(requestSignerClassFqcn);
+ result = 31 * result + Objects.hashCode(endpointDiscovery);
+ result = 31 * result + Objects.hashCode(members);
+ result = 31 * result + Objects.hashCode(enums);
+ result = 31 * result + Objects.hashCode(variable);
+ result = 31 * result + Objects.hashCode(marshaller);
+ result = 31 * result + Objects.hashCode(unmarshaller);
+ result = 31 * result + Objects.hashCode(errorCode);
+ result = 31 * result + Objects.hashCode(httpStatusCode);
+ result = 31 * result + Boolean.hashCode(fault);
+ result = 31 * result + Objects.hashCode(customization);
+ result = 31 * result + Boolean.hashCode(isEventStream);
+ result = 31 * result + Boolean.hashCode(isEvent);
+ result = 31 * result + Objects.hashCode(xmlNamespace);
+ result = 31 * result + Boolean.hashCode(document);
+ result = 31 * result + Boolean.hashCode(union);
+ result = 31 * result + Boolean.hashCode(retryable);
+ result = 31 * result + Boolean.hashCode(throttling);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/VariableModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/VariableModel.java
index bdf0668a9d21..b9355009e748 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/VariableModel.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/VariableModel.java
@@ -17,6 +17,7 @@
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
public class VariableModel extends DocumentationModel {
@@ -98,4 +99,31 @@ public String getVariableSetterType() {
public String toString() {
return variableName;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ VariableModel that = (VariableModel) o;
+ return Objects.equals(variableName, that.variableName)
+ && Objects.equals(variableType, that.variableType)
+ && Objects.equals(variableDeclarationType, that.variableDeclarationType);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + Objects.hashCode(variableName);
+ result = 31 * result + Objects.hashCode(variableType);
+ result = 31 * result + Objects.hashCode(variableDeclarationType);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ArtificialResultWrapper.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ArtificialResultWrapper.java
index e8adab25b48c..dd0b91d86301 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ArtificialResultWrapper.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ArtificialResultWrapper.java
@@ -15,6 +15,8 @@
package software.amazon.awssdk.codegen.model.intermediate.customization;
+import java.util.Objects;
+
public class ArtificialResultWrapper {
private String wrappedMemberName;
private String wrappedMemberSimpleType;
@@ -34,4 +36,25 @@ public String getWrappedMemberSimpleType() {
public void setWrappedMemberSimpleType(String wrappedMemberSimpleType) {
this.wrappedMemberSimpleType = wrappedMemberSimpleType;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ArtificialResultWrapper that = (ArtificialResultWrapper) o;
+ return Objects.equals(wrappedMemberName, that.wrappedMemberName)
+ && Objects.equals(wrappedMemberSimpleType, that.wrappedMemberSimpleType);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hashCode(wrappedMemberName);
+ result = 31 * result + Objects.hashCode(wrappedMemberSimpleType);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ShapeCustomizationInfo.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ShapeCustomizationInfo.java
index b6d3950985b2..2e031eabb9a4 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ShapeCustomizationInfo.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/customization/ShapeCustomizationInfo.java
@@ -16,6 +16,7 @@
package software.amazon.awssdk.codegen.model.intermediate.customization;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.util.Objects;
public class ShapeCustomizationInfo {
@@ -72,4 +73,33 @@ public void setStaxTargetDepthOffset(int staxTargetDepthOffset) {
public boolean hasStaxTargetDepthOffset() {
return hasStaxTargetDepthOffset;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ShapeCustomizationInfo that = (ShapeCustomizationInfo) o;
+ return skipGeneratingModelClass == that.skipGeneratingModelClass
+ && skipGeneratingMarshaller == that.skipGeneratingMarshaller
+ && skipGeneratingUnmarshaller == that.skipGeneratingUnmarshaller
+ && staxTargetDepthOffset == that.staxTargetDepthOffset
+ && hasStaxTargetDepthOffset == that.hasStaxTargetDepthOffset
+ && Objects.equals(artificialResultWrapper, that.artificialResultWrapper);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hashCode(artificialResultWrapper);
+ result = 31 * result + Boolean.hashCode(skipGeneratingModelClass);
+ result = 31 * result + Boolean.hashCode(skipGeneratingMarshaller);
+ result = 31 * result + Boolean.hashCode(skipGeneratingUnmarshaller);
+ result = 31 * result + staxTargetDepthOffset;
+ result = 31 * result + Boolean.hashCode(hasStaxTargetDepthOffset);
+ return result;
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ContextParam.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ContextParam.java
index 96f363cd84f1..8650d1145bcb 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ContextParam.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ContextParam.java
@@ -15,6 +15,8 @@
package software.amazon.awssdk.codegen.model.service;
+import java.util.Objects;
+
public class ContextParam {
private String name;
@@ -25,4 +27,22 @@ public String getName() {
public void setName(String name) {
this.name = name;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ContextParam that = (ContextParam) o;
+ return Objects.equals(name, that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name);
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java
index ce7adb6066ee..d077473f532a 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java
@@ -61,6 +61,10 @@
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.codegen.poet.PoetUtils;
import software.amazon.awssdk.codegen.utils.AuthUtils;
+import software.amazon.awssdk.codegen.validation.ModelInvalidException;
+import software.amazon.awssdk.codegen.validation.ValidationEntry;
+import software.amazon.awssdk.codegen.validation.ValidationErrorId;
+import software.amazon.awssdk.codegen.validation.ValidationErrorSeverity;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.rules.testing.AsyncTestCase;
@@ -445,6 +449,20 @@ private CodeBlock requestCreation(OperationModel opModel, Map
if (opParams != null) {
opParams.forEach((n, v) -> {
MemberModel memberModel = opModel.getInputShape().getMemberByC2jName(n);
+
+ if (memberModel == null) {
+ String detailMsg = String.format("Endpoint test definition references member '%s' on the input shape '%s' "
+ + "but no such member is defined.", n, opModel.getInputShape().getC2jName());
+ ValidationEntry entry =
+ new ValidationEntry()
+ .withSeverity(ValidationErrorSeverity.DANGER)
+ .withErrorId(ValidationErrorId.UNKNOWN_SHAPE_MEMBER)
+ .withDetailMessage(detailMsg);
+
+ throw ModelInvalidException.builder()
+ .validationEntries(Collections.singletonList(entry))
+ .build();
+ }
CodeBlock memberValue = createMemberValue(memberModel, v);
b.add(".$N($L)", memberModel.getFluentSetterMethodName(), memberValue);
});
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java
index bad36fe6594f..dfcf68b056fd 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java
@@ -29,7 +29,12 @@
import com.squareup.javapoet.TypeName;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -52,6 +57,7 @@
import software.amazon.awssdk.utils.internal.CodegenNamingUtils;
public class EndpointRulesSpecUtils {
+ private static final String RULES_ENGINE_RESOURCE_FILES_PREFIX = "software/amazon/awssdk/codegen/rules/";
private final IntermediateModel intermediateModel;
public EndpointRulesSpecUtils(IntermediateModel intermediateModel) {
@@ -213,16 +219,45 @@ public TypeName resolverReturnType() {
public List rulesEngineResourceFiles() {
URL currentJarUrl = EndpointRulesSpecUtils.class.getProtectionDomain().getCodeSource().getLocation();
+
+ // This would happen if the classes aren't loaded from a JAR, e.g. when unit testing
+ if (!currentJarUrl.toString().endsWith(".jar")) {
+ return rulesEngineFilesFromDirectory(currentJarUrl);
+ }
+
try (JarFile jarFile = new JarFile(currentJarUrl.getFile())) {
return jarFile.stream()
.map(ZipEntry::getName)
- .filter(e -> e.startsWith("software/amazon/awssdk/codegen/rules/"))
+ .filter(e -> e.startsWith(RULES_ENGINE_RESOURCE_FILES_PREFIX))
.collect(Collectors.toList());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
+ public List rulesEngineFilesFromDirectory(URL location) {
+ URI locationUri;
+ try {
+ locationUri = location.toURI();
+ if (!"file".equals(locationUri.getScheme())) {
+ throw new RuntimeException("Expected location to be a directory");
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ Path directory = Paths.get(locationUri);
+ return Files.walk(directory)
+ // Remove the root directory if the classes, paths are expected to be relative to this directory
+ .map(f -> directory.relativize(f).toString())
+ .filter(f -> f.startsWith(RULES_ENGINE_RESOURCE_FILES_PREFIX))
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
public List rulesEngineResourceFiles2() {
URL currentJarUrl = EndpointRulesSpecUtils.class.getProtectionDomain().getCodeSource().getLocation();
try (JarFile jarFile = new JarFile(currentJarUrl.getFile())) {
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelInvalidException.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelInvalidException.java
new file mode 100644
index 000000000000..28f482328253
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelInvalidException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Exception thrown during code generation to signal that the model is invalid.
+ */
+public class ModelInvalidException extends RuntimeException {
+ private final List validationEntries;
+
+ private ModelInvalidException(Builder b) {
+ super("Validation failed with the following errors: " + b.validationEntries);
+ this.validationEntries = Collections.unmodifiableList(new ArrayList<>(b.validationEntries));
+ }
+
+ public List validationEntries() {
+ return validationEntries;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private List validationEntries;
+
+ public Builder validationEntries(List validationEntries) {
+ if (validationEntries == null) {
+ this.validationEntries = Collections.emptyList();
+ } else {
+ this.validationEntries = validationEntries;
+ }
+
+ return this;
+ }
+
+ public ModelInvalidException build() {
+ return new ModelInvalidException(this);
+ }
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidationContext.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidationContext.java
new file mode 100644
index 000000000000..55c2bedcba19
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidationContext.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+import java.util.Optional;
+import software.amazon.awssdk.codegen.model.config.customization.ShareModelConfig;
+import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
+
+/**
+ * Context object for {@link ModelValidator}s. This object contains all the information available to the validations in order
+ * for them to perform their tasks.
+ */
+public final class ModelValidationContext {
+ private final IntermediateModel intermediateModel;
+ private final IntermediateModel shareModelsTarget;
+
+ private ModelValidationContext(Builder builder) {
+ this.intermediateModel = builder.intermediateModel;
+ this.shareModelsTarget = builder.shareModelsTarget;
+ }
+
+ /**
+ * The service model for which code is being generated.
+ */
+ public IntermediateModel intermediateModel() {
+ return intermediateModel;
+ }
+
+ /**
+ * The model of the service that the currently generating service shares models with. In other words, this is the service
+ * model for the service defined in {@link ShareModelConfig#getShareModelWith()}.
+ */
+ public Optional shareModelsTarget() {
+ return Optional.ofNullable(shareModelsTarget);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private IntermediateModel intermediateModel;
+ private IntermediateModel shareModelsTarget;
+
+ /**
+ * The service model for which code is being generated.
+ */
+ public Builder intermediateModel(IntermediateModel intermediateModel) {
+ this.intermediateModel = intermediateModel;
+ return this;
+ }
+
+ /**
+ * The model of the service that the currently generating service shares models with. In other words, this is the service
+ * model for the service defined in {@link ShareModelConfig#getShareModelWith()}.
+ */
+ public Builder shareModelsTarget(IntermediateModel shareModelsTarget) {
+ this.shareModelsTarget = shareModelsTarget;
+ return this;
+ }
+
+ public ModelValidationContext build() {
+ return new ModelValidationContext(this);
+ }
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidationReport.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidationReport.java
new file mode 100644
index 000000000000..1112dc2190d5
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidationReport.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ModelValidationReport {
+ private List validationEntries = Collections.emptyList();
+
+ public List getValidationEntries() {
+ return validationEntries;
+ }
+
+ public void setValidationEntries(List validationEntries) {
+ if (validationEntries != null) {
+ this.validationEntries = validationEntries;
+ } else {
+ this.validationEntries = Collections.emptyList();
+ }
+ }
+
+ public ModelValidationReport withValidationEntries(List validationEntries) {
+ setValidationEntries(validationEntries);
+ return this;
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidator.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidator.java
new file mode 100644
index 000000000000..b544a030eaf5
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ModelValidator.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+import java.util.List;
+
+public interface ModelValidator {
+ List validateModels(ModelValidationContext context);
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/SharedModelsValidator.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/SharedModelsValidator.java
new file mode 100644
index 000000000000..6b7f8471da7c
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/SharedModelsValidator.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
+import software.amazon.awssdk.codegen.model.intermediate.ListModel;
+import software.amazon.awssdk.codegen.model.intermediate.MapModel;
+import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
+import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
+import software.amazon.awssdk.utils.Logger;
+
+/**
+ * Validator that ensures any shapes shared between two services are completely identical. This validator returns a validation
+ * entry for each shape that is present in both service models but has differing definitions in each model.
+ */
+public final class SharedModelsValidator implements ModelValidator {
+ private static final Logger LOG = Logger.loggerFor(SharedModelsValidator.class);
+
+ @Override
+ public List validateModels(ModelValidationContext context) {
+ if (!context.shareModelsTarget().isPresent()) {
+ return Collections.emptyList();
+ }
+
+ return validateSharedShapes(context.intermediateModel(), context.shareModelsTarget().get());
+ }
+
+ private List validateSharedShapes(IntermediateModel m1, IntermediateModel m2) {
+ List errors = new ArrayList<>();
+
+ Map m1Shapes = m1.getShapes();
+ Map m2Shapes = m2.getShapes();
+
+ m1Shapes.forEach((name, m1Shape) -> {
+ if (!m2Shapes.containsKey(name)) {
+ return;
+ }
+
+ ShapeModel m2Shape = m2Shapes.get(name);
+
+ if (!shapesAreIdentical(m1Shape, m2Shape)) {
+ String detailMsg = String.format("Services '%s' and '%s' have differing definitions of the shared model '%s'",
+ m1.getMetadata().getServiceName(),
+ m2.getMetadata().getServiceName(),
+ name);
+ LOG.warn(() -> detailMsg);
+
+ errors.add(new ValidationEntry().withErrorId(ValidationErrorId.SHARED_MODELS_DIFFER)
+ .withSeverity(ValidationErrorSeverity.DANGER)
+ .withDetailMessage(detailMsg));
+ }
+ });
+
+ return errors;
+ }
+
+ private boolean shapesAreIdentical(ShapeModel m1, ShapeModel m2) {
+ // Note: We can't simply do m1.equals(m2) because shared models can still differ slightly in the
+ // marshalling/unmarshalling info such as the exact request operation name on the wire.
+ // In particular, we leave out comparing the `unmarshaller` and `marshaller` members of ShapeModel.
+ // Additionally, the List are not compared with equals() because we handle MemberModel equality specially
+ // as well.
+ return m1.isDeprecated() == m2.isDeprecated()
+ && m1.isHasPayloadMember() == m2.isHasPayloadMember()
+ && m1.isHasHeaderMember() == m2.isHasHeaderMember()
+ && m1.isHasStatusCodeMember() == m2.isHasStatusCodeMember()
+ && m1.isHasStreamingMember() == m2.isHasStreamingMember()
+ && m1.isHasRequiresLengthMember() == m2.isHasRequiresLengthMember()
+ && m1.isWrapper() == m2.isWrapper()
+ && m1.isSimpleMethod() == m2.isSimpleMethod()
+ && m1.isFault() == m2.isFault()
+ && m1.isEventStream() == m2.isEventStream()
+ && m1.isEvent() == m2.isEvent()
+ && m1.isDocument() == m2.isDocument()
+ && m1.isUnion() == m2.isUnion()
+ && m1.isRetryable() == m2.isRetryable()
+ && m1.isThrottling() == m2.isThrottling()
+ && Objects.equals(m1.getC2jName(), m2.getC2jName())
+ && Objects.equals(m1.getShapeName(), m2.getShapeName())
+ && Objects.equals(m1.getDeprecatedMessage(), m2.getDeprecatedMessage())
+ && Objects.equals(m1.getType(), m2.getType())
+ && Objects.equals(m1.getRequired(), m2.getRequired())
+ && Objects.equals(m1.getRequestSignerClassFqcn(), m2.getRequestSignerClassFqcn())
+ && Objects.equals(m1.getEndpointDiscovery(), m2.getEndpointDiscovery())
+ && memberListsAreIdentical(m1.getMembers(), m2.getMembers())
+ && Objects.equals(m1.getEnums(), m2.getEnums())
+ && Objects.equals(m1.getVariable(), m2.getVariable())
+ && Objects.equals(m1.getErrorCode(), m2.getErrorCode())
+ && Objects.equals(m1.getHttpStatusCode(), m2.getHttpStatusCode())
+ && Objects.equals(m1.getCustomization(), m2.getCustomization())
+ && Objects.equals(m1.getXmlNamespace(), m2.getXmlNamespace())
+ ;
+ }
+
+ private boolean memberListsAreIdentical(List memberList1, List memberList2) {
+ if (memberList1.size() != memberList2.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < memberList1.size(); i++) {
+ MemberModel m1 = memberList1.get(i);
+ MemberModel m2 = memberList2.get(i);
+ if (!memberModelsAreIdentical(m1, m2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean memberModelsAreIdentical(MemberModel m1, MemberModel m2) {
+ // Similar to ShapeModel, can't call equals() directly. It has a ShapeModel property that is ignored, and ListModel and
+ // MapModel are treated similarly
+ return m1.isDeprecated() == m2.isDeprecated()
+ && m1.isRequired() == m2.isRequired()
+ && m1.isSynthetic() == m2.isSynthetic()
+ && m1.isIdempotencyToken() == m2.isIdempotencyToken()
+ && m1.isJsonValue() == m2.isJsonValue()
+ && m1.isEventPayload() == m2.isEventPayload()
+ && m1.isEventHeader() == m2.isEventHeader()
+ && m1.isEndpointDiscoveryId() == m2.isEndpointDiscoveryId()
+ && m1.isSensitive() == m2.isSensitive()
+ && m1.isXmlAttribute() == m2.isXmlAttribute()
+ && m1.ignoreDataTypeConversionFailures() == m2.ignoreDataTypeConversionFailures()
+ && Objects.equals(m1.getName(), m2.getName())
+ && Objects.equals(m1.getC2jName(), m2.getC2jName())
+ && Objects.equals(m1.getC2jShape(), m2.getC2jShape())
+ && Objects.equals(m1.getVariable(), m2.getVariable())
+ && Objects.equals(m1.getSetterModel(), m2.getSetterModel())
+ && Objects.equals(m1.getGetterModel(), m2.getGetterModel())
+ && Objects.equals(m1.getHttp(), m2.getHttp())
+ && Objects.equals(m1.getDeprecatedMessage(), m2.getDeprecatedMessage())
+ // Note: not equals()
+ && listModelsAreIdentical(m1.getListModel(), m2.getListModel())
+ // Note: not equals()
+ && mapModelsAreIdentical(m1.getMapModel(), m2.getMapModel())
+ && Objects.equals(m1.getEnumType(), m2.getEnumType())
+ && Objects.equals(m1.getXmlNameSpaceUri(), m2.getXmlNameSpaceUri())
+ && Objects.equals(m1.getFluentGetterMethodName(), m2.getFluentGetterMethodName())
+ && Objects.equals(m1.getFluentEnumGetterMethodName(), m2.getFluentEnumGetterMethodName())
+ && Objects.equals(m1.getFluentSetterMethodName(), m2.getFluentSetterMethodName())
+ && Objects.equals(m1.getFluentEnumSetterMethodName(), m2.getFluentEnumSetterMethodName())
+ && Objects.equals(m1.getExistenceCheckMethodName(), m2.getExistenceCheckMethodName())
+ && Objects.equals(m1.getBeanStyleGetterMethodName(), m2.getBeanStyleGetterMethodName())
+ && Objects.equals(m1.getBeanStyleSetterMethodName(), m2.getBeanStyleSetterMethodName())
+ && Objects.equals(m1.getUnionEnumTypeName(), m2.getUnionEnumTypeName())
+ && Objects.equals(m1.getTimestampFormat(), m2.getTimestampFormat())
+ && Objects.equals(m1.getDeprecatedName(), m2.getDeprecatedName())
+ && Objects.equals(m1.getDeprecatedFluentGetterMethodName(), m2.getDeprecatedFluentGetterMethodName())
+ && Objects.equals(m1.getDeprecatedFluentSetterMethodName(), m2.getDeprecatedFluentSetterMethodName())
+ && Objects.equals(m1.getDeprecatedBeanStyleSetterMethodName(), m2.getDeprecatedBeanStyleSetterMethodName())
+ && Objects.equals(m1.getContextParam(), m2.getContextParam());
+ }
+
+ private boolean listModelsAreIdentical(ListModel m1, ListModel m2) {
+ if (m1 == null ^ m2 == null) {
+ return false;
+ }
+
+ if (m1 == null) {
+ return true;
+ }
+
+ return Objects.equals(m1.getImplType(), m2.getImplType())
+ && Objects.equals(m1.getMemberType(), m2.getMemberType())
+ && Objects.equals(m1.getInterfaceType(), m2.getInterfaceType())
+ // Note: not equals()
+ && memberModelsAreIdentical(m1.getListMemberModel(), m2.getListMemberModel())
+ && Objects.equals(m1.getMemberLocationName(), m2.getMemberLocationName())
+ && Objects.equals(m1.getMemberAdditionalMarshallingPath(), m2.getMemberAdditionalMarshallingPath())
+ && Objects.equals(m1.getMemberAdditionalUnmarshallingPath(), m2.getMemberAdditionalUnmarshallingPath());
+ }
+
+ private boolean mapModelsAreIdentical(MapModel m1, MapModel m2) {
+ if (m1 == null ^ m2 == null) {
+ return false;
+ }
+
+ if (m1 == null) {
+ return true;
+ }
+
+ return Objects.equals(m1.getImplType(), m2.getImplType())
+ && Objects.equals(m1.getInterfaceType(), m2.getInterfaceType())
+ && Objects.equals(m1.getKeyLocationName(), m2.getKeyLocationName())
+ // Note: not equals()
+ && memberModelsAreIdentical(m1.getKeyModel(), m2.getKeyModel())
+ && Objects.equals(m1.getValueLocationName(), m2.getValueLocationName())
+ // Note: not equals()
+ && memberModelsAreIdentical(m1.getValueModel(), m2.getValueModel());
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationEntry.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationEntry.java
new file mode 100644
index 000000000000..4e84bd625185
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationEntry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+import software.amazon.awssdk.utils.ToString;
+
+public final class ValidationEntry {
+ private ValidationErrorId errorId;
+ private ValidationErrorSeverity severity;
+ private String detailMessage;
+
+ public ValidationErrorId getErrorId() {
+ return errorId;
+ }
+
+ public void setErrorId(ValidationErrorId errorId) {
+ this.errorId = errorId;
+ }
+
+ public ValidationEntry withErrorId(ValidationErrorId errorId) {
+ setErrorId(errorId);
+ return this;
+ }
+
+ public ValidationErrorSeverity getSeverity() {
+ return severity;
+ }
+
+ public void setSeverity(ValidationErrorSeverity severity) {
+ this.severity = severity;
+ }
+
+ public ValidationEntry withSeverity(ValidationErrorSeverity severity) {
+ setSeverity(severity);
+ return this;
+ }
+
+ public String getDetailMessage() {
+ return detailMessage;
+ }
+
+ public void setDetailMessage(String detailMessage) {
+ this.detailMessage = detailMessage;
+ }
+
+ public ValidationEntry withDetailMessage(String detailMessage) {
+ setDetailMessage(detailMessage);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToString.builder("ValidationEntry")
+ .add("errorId", errorId)
+ .add("severity", severity)
+ .add("detailMessage", detailMessage)
+ .build();
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationErrorId.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationErrorId.java
new file mode 100644
index 000000000000..37c488e11fc5
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationErrorId.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+public enum ValidationErrorId {
+ SHARED_MODELS_DIFFER(
+ "The shared models between two services differ in their definition, which causes differences in the source"
+ + " files generated by the code generator."
+ ),
+ UNKNOWN_SHAPE_MEMBER("The model references an unknown shape member."),
+ REQUEST_URI_NOT_FOUND("The request URI does not exist."),
+ ;
+
+ private final String description;
+
+ ValidationErrorId(String description) {
+ this.description = description;
+ }
+
+
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationErrorSeverity.java b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationErrorSeverity.java
new file mode 100644
index 000000000000..39b6b015e42a
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationErrorSeverity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+public enum ValidationErrorSeverity {
+ // Denotes an error that MUST be addressed.
+ DANGER,
+ ;
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java
new file mode 100644
index 000000000000..c776e0295bea
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.codegen.internal.Jackson;
+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.ServiceModel;
+import software.amazon.awssdk.codegen.poet.ClientTestModels;
+import software.amazon.awssdk.codegen.validation.ModelInvalidException;
+import software.amazon.awssdk.codegen.validation.ModelValidator;
+import software.amazon.awssdk.codegen.validation.ValidationErrorId;
+
+public class CodeGeneratorTest {
+ private static final String VALIDATION_REPORT_NAME = "validation-report.json";
+
+ private Path outputDir;
+
+ @BeforeEach
+ void methodSetup() throws IOException {
+ outputDir = Files.createTempDirectory(null);
+ }
+
+ @AfterEach
+ void methodTeardown() throws IOException {
+ deleteDirectory(outputDir);
+ }
+
+ @Test
+ void build_cj2ModelsAndIntermediateModelSet_throws() {
+ assertThatThrownBy(() -> CodeGenerator.builder()
+ .models(C2jModels.builder().build())
+ .intermediateModel(new IntermediateModel())
+ .build())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Only one of");
+ }
+
+ @Test
+ void execute_emitValidationReportIsFalse_doesNotEmitValidationReport() throws IOException {
+ generateCodeFromC2jModels(ClientTestModels.awsJsonServiceC2jModels(), outputDir);
+ assertThat(Files.exists(validationReportPath(outputDir))).isFalse();
+ }
+
+ @Test
+ void execute_emitValidationReportIsTrue_emitsValidationReport() throws IOException {
+ generateCodeFromC2jModels(ClientTestModels.awsJsonServiceC2jModels(), outputDir, true, null);
+ assertThat(Files.exists(validationReportPath(outputDir))).isTrue();
+ }
+
+ @Test
+ void execute_invokesModelValidators() {
+ ModelValidator mockValidator = mock(ModelValidator.class);
+ when(mockValidator.validateModels(any())).thenReturn(Collections.emptyList());
+
+ generateCodeFromC2jModels(ClientTestModels.awsJsonServiceC2jModels(), outputDir, true,
+ Collections.singletonList(mockValidator));
+
+ verify(mockValidator).validateModels(any());
+ }
+
+ @Test
+ void execute_c2jModelsAndIntermediateModel_generateSameCode() throws IOException {
+ Path c2jModelsOutputDir = outputDir.resolve("c2jModels");
+ generateCodeFromC2jModels(ClientTestModels.awsJsonServiceC2jModels(), c2jModelsOutputDir, false, Collections.emptyList());
+
+ Path intermediateModelOutputDir = outputDir.resolve("intermediate-model");
+ generateCodeFromIntermediateModel(ClientTestModels.awsJsonServiceModels(), intermediateModelOutputDir);
+
+ List c2jModels_generatedFiles = Files.walk(c2jModelsOutputDir)
+ .sorted()
+ .map(c2jModelsOutputDir::relativize)
+ .collect(Collectors.toList());
+
+ List intermediateModels_generatedFiles = Files.walk(intermediateModelOutputDir)
+ .sorted()
+ .map(intermediateModelOutputDir::relativize)
+ .collect(Collectors.toList());
+
+ assertThat(c2jModels_generatedFiles).isNotEmpty();
+
+ // Ensure same exact set of files
+ assertThat(c2jModels_generatedFiles).isEqualTo(intermediateModels_generatedFiles);
+
+ // All files should be exactly the same
+ for (Path generatedFile : c2jModels_generatedFiles) {
+ Path c2jGenerated = c2jModelsOutputDir.resolve(generatedFile);
+ Path intermediateGenerated = intermediateModelOutputDir.resolve(generatedFile);
+
+ if (Files.isDirectory(c2jGenerated)) {
+ assertThat(Files.isDirectory(intermediateGenerated)).isTrue();
+ } else {
+ assertThat(readToString(c2jGenerated)).isEqualTo(readToString(intermediateGenerated));
+ }
+ }
+ }
+
+ @Test
+ void execute_endpointsTestReferencesUnknownOperationMember_throwsValidationError() throws IOException {
+ ModelValidator mockValidator = mock(ModelValidator.class);
+ when(mockValidator.validateModels(any())).thenReturn(Collections.emptyList());
+
+ C2jModels referenceModels = ClientTestModels.awsJsonServiceC2jModels();
+
+ C2jModels c2jModelsWithBadTest =
+ C2jModels.builder()
+ .endpointTestSuiteModel(getBrokenEndpointTestSuiteModel())
+ .customizationConfig(referenceModels.customizationConfig())
+ .serviceModel(referenceModels.serviceModel())
+ .paginatorsModel(referenceModels.paginatorsModel())
+ .build();
+
+ assertThatThrownBy(() -> generateCodeFromC2jModels(c2jModelsWithBadTest, outputDir, true,
+ Collections.singletonList(mockValidator)))
+ .hasCauseInstanceOf(ModelInvalidException.class)
+ .matches(e -> {
+ ModelInvalidException exception = (ModelInvalidException) e.getCause();
+ return exception.validationEntries().get(0).getErrorId() == ValidationErrorId.UNKNOWN_SHAPE_MEMBER;
+ });
+ }
+
+ @Test
+ void execute_operationHasNoRequestUri_throwsValidationError() throws IOException {
+ C2jModels models = C2jModels.builder()
+ .customizationConfig(CustomizationConfig.create())
+ .serviceModel(getMissingRequestUriServiceModel())
+ .build();
+
+ assertThatThrownBy(() -> generateCodeFromC2jModels(models, outputDir, true, Collections.emptyList()))
+ .isInstanceOf(ModelInvalidException.class)
+ .matches(e -> ((ModelInvalidException) e).validationEntries().get(0).getErrorId()
+ == ValidationErrorId.REQUEST_URI_NOT_FOUND);
+ }
+
+ private void generateCodeFromC2jModels(C2jModels c2jModels, Path outputDir) {
+ generateCodeFromC2jModels(c2jModels, outputDir, false, null);
+ }
+
+ private void generateCodeFromC2jModels(C2jModels c2jModels, Path outputDir,
+ boolean emitValidationReport,
+ List modelValidators) {
+ Path sources = outputDir.resolve("generated-sources").resolve("sdk");
+ Path resources = outputDir.resolve("generated-resources").resolve("sdk-resources");
+ Path tests = outputDir.resolve("generated-test-sources").resolve("sdk-tests");
+
+ CodeGenerator.builder()
+ .models(c2jModels)
+ .sourcesDirectory(sources.toAbsolutePath().toString())
+ .resourcesDirectory(resources.toAbsolutePath().toString())
+ .testsDirectory(tests.toAbsolutePath().toString())
+ .emitValidationReport(emitValidationReport)
+ .modelValidators(modelValidators)
+ .build()
+ .execute();
+ }
+
+ private void generateCodeFromIntermediateModel(IntermediateModel intermediateModel, Path outputDir) {
+ Path sources = outputDir.resolve("generated-sources").resolve("sdk");
+ Path resources = outputDir.resolve("generated-resources").resolve("sdk-resources");
+ Path tests = outputDir.resolve("generated-test-sources").resolve("sdk-tests");
+
+ CodeGenerator.builder()
+ .intermediateModel(intermediateModel)
+ .sourcesDirectory(sources.toAbsolutePath().toString())
+ .resourcesDirectory(resources.toAbsolutePath().toString())
+ .testsDirectory(tests.toAbsolutePath().toString())
+ .build()
+ .execute();
+ }
+
+ private static String readToString(Path p) throws IOException {
+ ByteBuffer bb = ByteBuffer.wrap(Files.readAllBytes(p));
+ return StandardCharsets.UTF_8.decode(bb).toString();
+ }
+
+ private static Path validationReportPath(Path root) {
+ return root.resolve(Paths.get("generated-sources", "sdk", "models", VALIDATION_REPORT_NAME));
+ }
+
+ private EndpointTestSuiteModel getBrokenEndpointTestSuiteModel() throws IOException {
+ String json = resourceAsString("incorrect-endpoint-tests.json");
+ return Jackson.load(EndpointTestSuiteModel.class, json);
+ }
+
+ private ServiceModel getMissingRequestUriServiceModel() throws IOException {
+ String json = resourceAsString("no-request-uri-operation-service.json");
+ return Jackson.load(ServiceModel.class, json);
+ }
+
+ private String resourceAsString(String name) throws IOException {
+ ByteArrayOutputStream baos;
+ try (InputStream resourceAsStream = getClass().getResourceAsStream(name)) {
+ baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = resourceAsStream.read(buffer)) != -1) {
+ baos.write(buffer, 0, read);
+ }
+ }
+ return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(baos.toByteArray())).toString();
+ }
+
+ private static void deleteDirectory(Path dir) throws IOException {
+ Files.walkFileTree(dir, new FileVisitor() {
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+ return FileVisitResult.TERMINATE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ArgumentModelTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ArgumentModelTest.java
new file mode 100644
index 000000000000..107a6e6cdb66
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ArgumentModelTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class ArgumentModelTest {
+ @Test
+ public void equals_isCorrect() {
+ EqualsVerifier.simple()
+ .forClass(ArgumentModel.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/AuthorizerModelTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/AuthorizerModelTest.java
new file mode 100644
index 000000000000..28d8dd845412
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/AuthorizerModelTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class AuthorizerModelTest {
+ @Test
+ public void equals_isCorrect() {
+ EqualsVerifier.simple().forClass(AuthorizerModel.class).usingGetClass().verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/MemberModelTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/MemberModelTest.java
new file mode 100644
index 000000000000..bd4a0859603f
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/MemberModelTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class MemberModelTest {
+ @Test
+ public void equals_isCorrect() {
+ ListModel redListModel = new ListModel();
+ redListModel.setMemberLocationName("RedLocation");
+ ListModel blueListModel = new ListModel();
+ blueListModel.setMemberLocationName("BlueLocation");
+
+ MemberModel redMemberModel = new MemberModel();
+ redMemberModel.setC2jName("RedC2jName");
+ MemberModel blueMemberModel = new MemberModel();
+ blueMemberModel.setC2jName("BlueC2jName");
+
+ EqualsVerifier.simple().forClass(MemberModel.class)
+ .withPrefabValues(ListModel.class, redListModel, blueListModel)
+ .withPrefabValues(MemberModel.class, redMemberModel, blueMemberModel)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/OperationModelTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/OperationModelTest.java
new file mode 100644
index 000000000000..531d0b1aa55e
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/OperationModelTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class OperationModelTest {
+ @Test
+ void equals_isCorrect() {
+ MemberModel blueMemberModel = new MemberModel();
+ blueMemberModel.setName("blue");
+ MemberModel redMemberModel = new MemberModel();
+ redMemberModel.setName("red");
+
+ EqualsVerifier.simple()
+ .forClass(OperationModel.class)
+ .withPrefabValues(MemberModel.class, blueMemberModel, redMemberModel)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ParameterHttpMappingTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ParameterHttpMappingTest.java
new file mode 100644
index 000000000000..cd142cb34c2c
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ParameterHttpMappingTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class ParameterHttpMappingTest {
+ @Test
+ void equals_isCorrect() {
+ EqualsVerifier.simple()
+ .forClass(ParameterHttpMapping.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ReturnTypeModelTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ReturnTypeModelTest.java
new file mode 100644
index 000000000000..53e99f514403
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ReturnTypeModelTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class ReturnTypeModelTest {
+ @Test
+ void equals_isCorrect() {
+ EqualsVerifier.simple()
+ .forClass(ReturnTypeModel.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModelTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModelTest.java
new file mode 100644
index 000000000000..08fb79681e96
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModelTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class ShapeModelTest {
+
+
+ @Test
+ public void equals_isCorrect() {
+ MemberModel blueMemberModel = new MemberModel();
+ blueMemberModel.setName("blue");
+ MemberModel redMemberModel = new MemberModel();
+ redMemberModel.setName("red");
+
+ EqualsVerifier.simple()
+ .forClass(ShapeModel.class)
+ .withPrefabValues(MemberModel.class, blueMemberModel, redMemberModel)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/VariableModelTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/VariableModelTest.java
new file mode 100644
index 000000000000..55ea2f39123a
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/VariableModelTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class VariableModelTest {
+ @Test
+ void equals_isCorrect() {
+ EqualsVerifier.simple()
+ .forClass(VariableModel.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/customization/ArtificialResultWrapperTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/customization/ArtificialResultWrapperTest.java
new file mode 100644
index 000000000000..aa29412a5f5f
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/customization/ArtificialResultWrapperTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate.customization;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class ArtificialResultWrapperTest {
+ @Test
+ void equals_isCorrect() {
+ EqualsVerifier.simple()
+ .forClass(ArtificialResultWrapper.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/customization/ShapeCustomizationInfoTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/customization/ShapeCustomizationInfoTest.java
new file mode 100644
index 000000000000..3126117f100d
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/customization/ShapeCustomizationInfoTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate.customization;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class ShapeCustomizationInfoTest {
+ @Test
+ void equals_isCorrect() {
+ EqualsVerifier.simple()
+ .forClass(ShapeCustomizationInfo.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/service/ContextParamTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/service/ContextParamTest.java
new file mode 100644
index 000000000000..937688b70cb4
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/service/ContextParamTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.service;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+public class ContextParamTest {
+ @Test
+ void equals_isCorrect() {
+ EqualsVerifier.simple()
+ .forClass(ContextParam.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
index 54dd331118ce..36360b4b3b37 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
@@ -47,6 +47,17 @@ public static IntermediateModel awsJsonServiceModels() {
return new IntermediateModelBuilder(models).build();
}
+ public static C2jModels awsJsonServiceC2jModels() {
+ File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/json/service-2.json").getFile());
+ File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/json/customization.config").getFile());
+ File paginatorsModel = new File(ClientTestModels.class.getResource("client/c2j/json/paginators.json").getFile());
+ return C2jModels.builder()
+ .serviceModel(getServiceModel(serviceModel))
+ .customizationConfig(getCustomizationConfig(customizationModel))
+ .paginatorsModel(getPaginatorsModel(paginatorsModel))
+ .build();
+ }
+
public static IntermediateModel cborServiceModels() {
File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/json/service-2.json").getFile());
File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/cbor/customization.config").getFile());
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/validation/SharedModelsValidatorTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/validation/SharedModelsValidatorTest.java
new file mode 100644
index 000000000000..a485956d94bc
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/validation/SharedModelsValidatorTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.validation;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
+import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
+import software.amazon.awssdk.codegen.model.intermediate.Metadata;
+import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
+import software.amazon.awssdk.codegen.poet.ClientTestModels;
+
+public class SharedModelsValidatorTest {
+ private final ModelValidator validator = new SharedModelsValidator();
+
+ @Test
+ void validateModels_noTargetService_noValidationErrors() {
+ assertThat(runValidation(ClientTestModels.awsJsonServiceModels(), null)).isEmpty();
+ }
+
+ @Test
+ void validateModels_targetServiceTriviallyIdentical_noValidationErrors() {
+ assertThat(runValidation(ClientTestModels.awsJsonServiceModels(), ClientTestModels.awsJsonServiceModels())).isEmpty();
+ }
+
+ @Test
+ void validateModels_noSharedShapes_noValidationErrors() {
+ IntermediateModel target = ClientTestModels.awsJsonServiceModels();
+ Map renamedShapes = target.getShapes()
+ .entrySet()
+ .stream()
+ .collect(Collectors.toMap(e -> "Copy" + e.getKey(), Map.Entry::getValue));
+ target.setShapes(renamedShapes);
+
+ assertThat(runValidation(ClientTestModels.awsJsonServiceModels(), target)).isEmpty();
+ }
+
+ @Test
+ void validateModels_sharedShapesNotIdentical_emitsValidationError() {
+ IntermediateModel target = ClientTestModels.awsJsonServiceModels();
+ Map modifiedShapes = target.getShapes()
+ .entrySet()
+ .stream()
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ e -> {
+ ShapeModel shapeModel = e.getValue();
+ shapeModel.setDeprecated(!shapeModel.isDeprecated());
+ return shapeModel;
+ }));
+
+ target.setShapes(modifiedShapes);
+
+ List validationEntries = runValidation(ClientTestModels.awsJsonServiceModels(), target);
+
+ assertThat(validationEntries).hasSize(modifiedShapes.size());
+
+ assertThat(validationEntries).allMatch(e -> e.getErrorId() == ValidationErrorId.SHARED_MODELS_DIFFER
+ && e.getSeverity() == ValidationErrorSeverity.DANGER);
+ }
+
+ @Test
+ void validateModels_shapesDontHaveSameMemberNames_emitsValidationError() {
+ IntermediateModel fooService = new IntermediateModel();
+ fooService.setMetadata(new Metadata().withServiceName("Foo"));
+
+ IntermediateModel barService = new IntermediateModel();
+ barService.setMetadata(new Metadata().withServiceName("Bar"));
+
+ String shapeName = "TestShape";
+
+ ShapeModel shape1 = new ShapeModel();
+ MemberModel member1 = new MemberModel();
+ member1.setName("Shape1Member");
+ shape1.setMembers(Arrays.asList(member1));
+
+ ShapeModel shape2 = new ShapeModel();
+ MemberModel member2 = new MemberModel();
+ member2.setName("Shape2Member");
+ shape2.setMembers(Arrays.asList(member2));
+
+ Map fooServiceShapes = new HashMap<>();
+ fooServiceShapes.put(shapeName, shape1);
+ fooService.setShapes(fooServiceShapes);
+
+ Map barServiceShapes = new HashMap<>();
+ barServiceShapes.put(shapeName, shape2);
+ barService.setShapes(barServiceShapes);
+
+ List validationEntries = runValidation(fooService, barService);
+
+ assertThat(validationEntries).hasSize(1);
+ }
+
+ @Test
+ void validateModels_shapesDontHaveSameMembers_emitsValidationError() {
+ IntermediateModel fooService = new IntermediateModel();
+ fooService.setMetadata(new Metadata().withServiceName("Foo"));
+
+ IntermediateModel barService = new IntermediateModel();
+ barService.setMetadata(new Metadata().withServiceName("Bar"));
+
+ String shapeName = "TestShape";
+ ShapeModel shape1 = new ShapeModel();
+
+ ShapeModel shape2 = new ShapeModel();
+ shape2.setMembers(Arrays.asList(new MemberModel(), new MemberModel()));
+
+ Map fooServiceShapes = new HashMap<>();
+ fooServiceShapes.put(shapeName, shape1);
+ fooService.setShapes(fooServiceShapes);
+
+ Map barServiceShapes = new HashMap<>();
+ barServiceShapes.put(shapeName, shape2);
+ barService.setShapes(barServiceShapes);
+
+ List validationEntries = runValidation(fooService, barService);
+
+ assertThat(validationEntries).hasSize(1);
+ }
+
+ private List runValidation(IntermediateModel m1, IntermediateModel m2) {
+ ModelValidationContext ctx = ModelValidationContext.builder()
+ .intermediateModel(m1)
+ .shareModelsTarget(m2)
+ .build();
+
+ return validator.validateModels(ctx);
+ }
+}
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/incorrect-endpoint-tests.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/incorrect-endpoint-tests.json
new file mode 100644
index 000000000000..861ba12cf3c5
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/incorrect-endpoint-tests.json
@@ -0,0 +1,26 @@
+{
+ "testCases": [
+ {
+ "documentation": "Test references undefined operation member",
+ "expect": {
+ "error": "Some error"
+ },
+ "operationInputs": [
+ {
+ "builtInParams": {
+ "AWS::Region": "us-east-1"
+ },
+ "operationName": "APostOperation",
+ "operationParams": {
+ "Foo": "bar"
+ }
+ }
+ ],
+ "params": {
+ "Region": "us-east-1",
+ "UseFIPS": false,
+ "UseDualStack": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/no-request-uri-operation-service.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/no-request-uri-operation-service.json
new file mode 100644
index 000000000000..d7caffad37e0
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/no-request-uri-operation-service.json
@@ -0,0 +1,43 @@
+{
+ "version": "2.0",
+ "metadata": {
+ "apiVersion": "2010-05-08",
+ "endpointPrefix": "json-service-endpoint",
+ "globalEndpoint": "json-service.amazonaws.com",
+ "protocol": "rest-json",
+ "serviceAbbreviation": "Rest Json Service",
+ "serviceFullName": "Some Service That Uses Rest-Json Protocol",
+ "serviceId": "Rest Json Service",
+ "signingName": "json-service",
+ "signatureVersion": "v4",
+ "uid": "json-service-2010-05-08",
+ "xmlNamespace": "https://json-service.amazonaws.com/doc/2010-05-08/"
+ },
+ "operations": {
+ "OperationWithUriMappedParam": {
+ "name": "OperationWithUriMappedParam",
+ "http": {
+ "method": "GET"
+ },
+ "input": {
+ "shape": "OperationWithUriMappedParamRequest"
+ }
+ }
+ },
+ "shapes": {
+ "OperationWithUriMappedParamRequest": {
+ "type": "structure",
+ "members": {
+ "StringMember": {
+ "shape": "String",
+ "location": "uri",
+ "locationName": "stringMember"
+ }
+ }
+ },
+ "String": {
+ "type": "string"
+ }
+ },
+ "documentation": "A service that is implemented using the rest-json protocol"
+}