Skip to content

Commit bbb9561

Browse files
committed
Validate unknown member reference
Add validation for the case where an endpoint test references a non-existent operation input shape member. As part of this change, `CodeGenerator` has ben updated to catch `ModelInvalidException` and write the validation entries in the validation-report.json.
1 parent f4edf0b commit bbb9561

File tree

8 files changed

+166
-3
lines changed

8 files changed

+166
-3
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/CodeGenerator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import software.amazon.awssdk.codegen.internal.Jackson;
3030
import software.amazon.awssdk.codegen.internal.Utils;
3131
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
32+
import software.amazon.awssdk.codegen.validation.ModelInvalidException;
3233
import software.amazon.awssdk.codegen.validation.ModelValidationContext;
3334
import software.amazon.awssdk.codegen.validation.ModelValidationReport;
3435
import software.amazon.awssdk.codegen.validation.ModelValidator;
@@ -131,6 +132,13 @@ public void execute() {
131132

132133
} catch (Exception e) {
133134
log.error(() -> "Failed to generate code. ", e);
135+
136+
if (e instanceof ModelInvalidException && emitValidationReport) {
137+
ModelInvalidException invalidException = (ModelInvalidException) e;
138+
report.setValidationEntries(invalidException.validationEntries());
139+
writeValidationReport(report);
140+
}
141+
134142
throw new RuntimeException(
135143
"Failed to generate code. Exception message : " + e.getMessage(), e);
136144
}

codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/BaseGeneratorTasks.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ protected void compute() {
7171
ForkJoinTask.invokeAll(createTasks());
7272
log.info(" Completed " + taskName + ".");
7373
}
74+
} catch (RuntimeException e) {
75+
throw e;
7476
} catch (Exception e) {
7577
throw new RuntimeException(e);
7678
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
import software.amazon.awssdk.codegen.poet.PoetExtension;
6262
import software.amazon.awssdk.codegen.poet.PoetUtils;
6363
import software.amazon.awssdk.codegen.utils.AuthUtils;
64+
import software.amazon.awssdk.codegen.validation.ModelInvalidException;
65+
import software.amazon.awssdk.codegen.validation.ValidationEntry;
66+
import software.amazon.awssdk.codegen.validation.ValidationErrorId;
67+
import software.amazon.awssdk.codegen.validation.ValidationErrorSeverity;
6468
import software.amazon.awssdk.core.SdkSystemSetting;
6569
import software.amazon.awssdk.core.async.AsyncRequestBody;
6670
import software.amazon.awssdk.core.rules.testing.AsyncTestCase;
@@ -445,6 +449,20 @@ private CodeBlock requestCreation(OperationModel opModel, Map<String, TreeNode>
445449
if (opParams != null) {
446450
opParams.forEach((n, v) -> {
447451
MemberModel memberModel = opModel.getInputShape().getMemberByC2jName(n);
452+
453+
if (memberModel == null) {
454+
String detailMsg = String.format("Endpoint test definition references member '%s' on the input shape '%s' "
455+
+ "but no such member is defined.", n, opModel.getInputShape().getC2jName());
456+
ValidationEntry entry =
457+
new ValidationEntry()
458+
.withSeverity(ValidationErrorSeverity.DANGER)
459+
.withErrorId(ValidationErrorId.UNKNOWN_SHAPE_MEMBER)
460+
.withDetailMessage(detailMsg);
461+
462+
throw ModelInvalidException.builder()
463+
.validationEntries(Collections.singletonList(entry))
464+
.build();
465+
}
448466
CodeBlock memberValue = createMemberValue(memberModel, v);
449467
b.add(".$N($L)", memberModel.getFluentSetterMethodName(), memberValue);
450468
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.codegen.validation;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
22+
/**
23+
* Exception thrown during code generation to signal that the model is invalid.
24+
*/
25+
public class ModelInvalidException extends RuntimeException {
26+
private final List<ValidationEntry> validationEntries;
27+
28+
private ModelInvalidException(Builder b) {
29+
super("Validation failed with the following errors: " + b.validationEntries);
30+
this.validationEntries = Collections.unmodifiableList(new ArrayList<>(b.validationEntries));
31+
}
32+
33+
public List<ValidationEntry> validationEntries() {
34+
return validationEntries;
35+
}
36+
37+
public static Builder builder() {
38+
return new Builder();
39+
}
40+
41+
public static class Builder {
42+
private List<ValidationEntry> validationEntries;
43+
44+
public Builder validationEntries(List<ValidationEntry> validationEntries) {
45+
if (validationEntries == null) {
46+
this.validationEntries = Collections.emptyList();
47+
} else {
48+
this.validationEntries = validationEntries;
49+
}
50+
51+
return this;
52+
}
53+
54+
public ModelInvalidException build() {
55+
return new ModelInvalidException(this);
56+
}
57+
}
58+
}

codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationEntry.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.codegen.validation;
1717

18+
import software.amazon.awssdk.utils.ToString;
19+
1820
public final class ValidationEntry {
1921
private ValidationErrorId errorId;
2022
private ValidationErrorSeverity severity;
@@ -58,4 +60,13 @@ public ValidationEntry withDetailMessage(String detailMessage) {
5860
setDetailMessage(detailMessage);
5961
return this;
6062
}
63+
64+
@Override
65+
public String toString() {
66+
return ToString.builder("ValidationEntry")
67+
.add("errorId", errorId)
68+
.add("severity", severity)
69+
.add("detailMessage", detailMessage)
70+
.build();
71+
}
6172
}

codegen/src/main/java/software/amazon/awssdk/codegen/validation/ValidationErrorId.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ public enum ValidationErrorId {
2020
"The shared models between two services differ in their definition, which causes differences in the source"
2121
+ " files generated by the code generator."
2222
),
23-
MEMBER_WITH_UNKNOWN_SHAPE(
24-
"The shape declares a member targeting an unknown shape."
25-
),
23+
UNKNOWN_SHAPE_MEMBER("The model references an unknown shape member."),
2624
;
2725

2826
private final String description;

codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import static org.mockito.Mockito.verify;
2323
import static org.mockito.Mockito.when;
2424

25+
import java.io.ByteArrayOutputStream;
2526
import java.io.IOException;
27+
import java.io.InputStream;
2628
import java.nio.ByteBuffer;
2729
import java.nio.charset.StandardCharsets;
2830
import java.nio.file.FileVisitResult;
@@ -37,9 +39,13 @@
3739
import org.junit.jupiter.api.AfterEach;
3840
import org.junit.jupiter.api.BeforeEach;
3941
import org.junit.jupiter.api.Test;
42+
import software.amazon.awssdk.codegen.internal.Jackson;
4043
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
44+
import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel;
4145
import software.amazon.awssdk.codegen.poet.ClientTestModels;
46+
import software.amazon.awssdk.codegen.validation.ModelInvalidException;
4247
import software.amazon.awssdk.codegen.validation.ModelValidator;
48+
import software.amazon.awssdk.codegen.validation.ValidationErrorId;
4349

4450
public class CodeGeneratorTest {
4551
private static final String VALIDATION_REPORT_NAME = "validation-report.json";
@@ -125,6 +131,30 @@ void execute_c2jModelsAndIntermediateModel_generateSameCode() throws IOException
125131
}
126132
}
127133

134+
@Test
135+
void execute_endpointsTestReferencesUnknownOperationMember_throwsValidationError() throws IOException {
136+
ModelValidator mockValidator = mock(ModelValidator.class);
137+
when(mockValidator.validateModels(any())).thenReturn(Collections.emptyList());
138+
139+
C2jModels referenceModels = ClientTestModels.awsJsonServiceC2jModels();
140+
141+
C2jModels c2jModelsWithBadTest =
142+
C2jModels.builder()
143+
.endpointTestSuiteModel(getBrokenEndpointTestSuiteModel())
144+
.customizationConfig(referenceModels.customizationConfig())
145+
.serviceModel(referenceModels.serviceModel())
146+
.paginatorsModel(referenceModels.paginatorsModel())
147+
.build();
148+
149+
assertThatThrownBy(() -> generateCodeFromC2jModels(c2jModelsWithBadTest, outputDir, true,
150+
Collections.singletonList(mockValidator)))
151+
.hasCauseInstanceOf(ModelInvalidException.class)
152+
.matches(e -> {
153+
ModelInvalidException exception = (ModelInvalidException) e.getCause();
154+
return exception.validationEntries().get(0).getErrorId() == ValidationErrorId.UNKNOWN_SHAPE_MEMBER;
155+
});
156+
}
157+
128158
private void generateCodeFromC2jModels(C2jModels c2jModels, Path outputDir) {
129159
generateCodeFromC2jModels(c2jModels, outputDir, false, null);
130160
}
@@ -170,6 +200,18 @@ private static Path validationReportPath(Path root) {
170200
return root.resolve(Paths.get("generated-sources", "sdk", "models", VALIDATION_REPORT_NAME));
171201
}
172202

203+
private EndpointTestSuiteModel getBrokenEndpointTestSuiteModel() throws IOException {
204+
InputStream resourceAsStream = getClass().getResourceAsStream("incorrect-endpoint-tests.json");
205+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
206+
byte[] buffer = new byte[1024];
207+
int read;
208+
while ((read = resourceAsStream.read(buffer)) != -1) {
209+
baos.write(buffer, 0, read);
210+
}
211+
String json = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(baos.toByteArray())).toString();
212+
return Jackson.load(EndpointTestSuiteModel.class, json);
213+
}
214+
173215
private static void deleteDirectory(Path dir) throws IOException {
174216
Files.walkFileTree(dir, new FileVisitor<Path>() {
175217

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"testCases": [
3+
{
4+
"documentation": "Test references undefined operation member",
5+
"expect": {
6+
"error": "Some error"
7+
},
8+
"operationInputs": [
9+
{
10+
"builtInParams": {
11+
"AWS::Region": "us-east-1"
12+
},
13+
"operationName": "APostOperation",
14+
"operationParams": {
15+
"Foo": "bar"
16+
}
17+
}
18+
],
19+
"params": {
20+
"Region": "us-east-1",
21+
"UseFIPS": false,
22+
"UseDualStack": false
23+
}
24+
}
25+
]
26+
}

0 commit comments

Comments
 (0)