Skip to content

Commit f3d1154

Browse files
authored
Use fault trait when determining exception status code (#3331)
* Use fault trait when determining exception status code AWS-JSON services use the Fault trait to designate exceptions that are the service's fault (i.e. 500), rather than HTTP status code bindings like REST services. In cases where the response being unmarshalled does *NOT* have a status code (e.g. because the response was constructed rather from some other data rather than an actual HTTP response), the JSON unmarshaller will now also check the metadata if it's marked as a fault. * Move logic to codegen time * Update changelog
1 parent d1d282f commit f3d1154

File tree

7 files changed

+186
-44
lines changed

7 files changed

+186
-44
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "AWS-JSON services use the Fault trait to designate exceptions that are the service's fault (i.e. 500), rather than HTTP status code bindings like REST services. In cases where the response being unmarshalled does *NOT* have a status code (e.g. because the response was constructed rather from some other data rather than an actual HTTP response), the status code will default to 500 for faults and 400 for non-faults."
6+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ protected final ShapeModel generateShapeModel(String javaClassName, String shape
8585
shapeModel.withIsEvent(shape.isEvent());
8686
shapeModel.withXmlNamespace(shape.getXmlNamespace());
8787
shapeModel.withIsUnion(shape.isUnion());
88+
shapeModel.withIsFault(shape.isFault());
8889

8990
boolean hasHeaderMember = false;
9091
boolean hasStatusCodeMember = false;

codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModel.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class ShapeModel extends DocumentationModel implements HasDeprecation {
6161

6262
private String errorCode;
6363
private Integer httpStatusCode;
64+
private boolean fault;
6465

6566
private ShapeCustomizationInfo customization = new ShapeCustomizationInfo();
6667

@@ -638,4 +639,13 @@ public boolean isUnion() {
638639
public void withIsUnion(boolean union) {
639640
this.union = union;
640641
}
642+
643+
public boolean isFault() {
644+
return fault;
645+
}
646+
647+
public ShapeModel withIsFault(boolean fault) {
648+
this.fault = fault;
649+
return this;
650+
}
641651
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/ProtocolSpec.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler;
2929
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
3030
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
31+
import software.amazon.awssdk.codegen.model.intermediate.Protocol;
3132
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
3233
import software.amazon.awssdk.codegen.model.intermediate.ShapeType;
3334
import software.amazon.awssdk.codegen.model.service.AuthType;
@@ -71,19 +72,30 @@ default List<CodeBlock> registerModeledExceptions(IntermediateModel model, PoetE
7172
return model.getShapes().values().stream()
7273
.filter(s -> s.getShapeType() == ShapeType.Exception)
7374
.map(e -> CodeBlock.builder()
74-
.add(".registerModeledException($T.builder().errorCode($S)"
75-
+ ".exceptionBuilderSupplier($T::builder)$L.build())",
75+
.add(".registerModeledException($T.builder()"
76+
+ ".errorCode($S)"
77+
+ ".exceptionBuilderSupplier($T::builder)"
78+
+ "$L" // populateHttpStatusCode
79+
+ ".build())",
7680
ExceptionMetadata.class,
7781
e.getErrorCode(),
7882
poetExtensions.getModelClass(e.getShapeName()),
79-
populateHttpStatusCode(e))
83+
populateHttpStatusCode(e, model))
8084
.build())
8185
.collect(Collectors.toList());
8286
}
8387

84-
default String populateHttpStatusCode(ShapeModel shapeModel) {
85-
return shapeModel.getHttpStatusCode() != null
86-
? String.format(".httpStatusCode(%d)", shapeModel.getHttpStatusCode()) : "";
88+
default String populateHttpStatusCode(ShapeModel shapeModel, IntermediateModel model) {
89+
Integer statusCode = shapeModel.getHttpStatusCode();
90+
91+
if (statusCode == null && model.getMetadata().getProtocol() == Protocol.AWS_JSON) {
92+
if (shapeModel.isFault()) {
93+
statusCode = 500;
94+
} else {
95+
statusCode = 400;
96+
}
97+
}
98+
return statusCode != null ? String.format(".httpStatusCode(%d)", statusCode) : "";
8799
}
88100

89101
default String hostPrefixExpression(OperationModel opModel) {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/json/service-2.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
"errors": [
8282
{
8383
"shape": "InvalidInputException"
84+
},
85+
{
86+
"shape": "ServiceFaultException"
8487
}
8588
],
8689
"documentation": "<p>Performs a post operation to the query service and has modelled output</p>"
@@ -259,13 +262,15 @@
259262
}
260263
},
261264
"documentation": "<p>The request was rejected because an invalid or out-of-range value was supplied for an input parameter.</p>",
262-
"error": {
263-
"code": "InvalidInput",
264-
"httpStatusCode": 400,
265-
"senderFault": true
266-
},
267265
"exception": true
268266
},
267+
"ServiceFaultException": {
268+
"type": "structure",
269+
"members": {
270+
},
271+
"exception": true,
272+
"fault": true
273+
},
269274
"nestedMember": {
270275
"type": "structure",
271276
"required": [

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-json-async-client-class.java

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyResponse;
8282
import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyRequest;
8383
import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyResponse;
84+
import software.amazon.awssdk.services.json.model.ServiceFaultException;
8485
import software.amazon.awssdk.services.json.model.StreamingInputOperationRequest;
8586
import software.amazon.awssdk.services.json.model.StreamingInputOperationResponse;
8687
import software.amazon.awssdk.services.json.model.StreamingInputOutputOperationRequest;
@@ -216,6 +217,7 @@ public CompletableFuture<APostOperationResponse> aPostOperation(APostOperationRe
216217
* <ul>
217218
* <li>InvalidInputException The request was rejected because an invalid or out-of-range value was supplied
218219
* for an input parameter.</li>
220+
* <li>ServiceFaultException</li>
219221
* <li>SdkException Base class for all exceptions that can be thrown by the SDK (both service and client).
220222
* Can be used for catch all scenarios.</li>
221223
* <li>SdkClientException If any client side error occurs such as an IO related failure, failure to get
@@ -641,31 +643,31 @@ public CompletableFuture<OperationWithChecksumRequiredResponse> operationWithChe
641643
*/
642644
@Override
643645
public CompletableFuture<OperationWithNoneAuthTypeResponse> operationWithNoneAuthType(
644-
OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) {
646+
OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) {
645647
List<MetricPublisher> metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest
646-
.overrideConfiguration().orElse(null));
648+
.overrideConfiguration().orElse(null));
647649
MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector
648-
.create("ApiCall");
650+
.create("ApiCall");
649651
try {
650652
apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service");
651653
apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithNoneAuthType");
652654
JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false)
653-
.isPayloadJson(true).build();
655+
.isPayloadJson(true).build();
654656

655657
HttpResponseHandler<OperationWithNoneAuthTypeResponse> responseHandler = protocolFactory.createResponseHandler(
656-
operationMetadata, OperationWithNoneAuthTypeResponse::builder);
658+
operationMetadata, OperationWithNoneAuthTypeResponse::builder);
657659

658660
HttpResponseHandler<AwsServiceException> errorResponseHandler = createErrorResponseHandler(protocolFactory,
659-
operationMetadata);
661+
operationMetadata);
660662

661663
CompletableFuture<OperationWithNoneAuthTypeResponse> executeFuture = clientHandler
662-
.execute(new ClientExecutionParams<OperationWithNoneAuthTypeRequest, OperationWithNoneAuthTypeResponse>()
663-
.withOperationName("OperationWithNoneAuthType")
664-
.withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory))
665-
.withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler)
666-
.withMetricCollector(apiCallMetricCollector)
667-
.putExecutionAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false)
668-
.withInput(operationWithNoneAuthTypeRequest));
664+
.execute(new ClientExecutionParams<OperationWithNoneAuthTypeRequest, OperationWithNoneAuthTypeResponse>()
665+
.withOperationName("OperationWithNoneAuthType")
666+
.withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory))
667+
.withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler)
668+
.withMetricCollector(apiCallMetricCollector)
669+
.putExecutionAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false)
670+
.withInput(operationWithNoneAuthTypeRequest));
669671
CompletableFuture<OperationWithNoneAuthTypeResponse> whenCompleted = executeFuture.whenComplete((r, e) -> {
670672
metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
671673
});
@@ -755,7 +757,7 @@ public CompletableFuture<PaginatedOperationWithResultKeyResponse> paginatedOpera
755757
* The following are few ways to use the response class:
756758
* </p>
757759
* 1) Using the subscribe helper method
758-
*
760+
*
759761
* <pre>
760762
* {@code
761763
* software.amazon.awssdk.services.json.paginators.PaginatedOperationWithResultKeyPublisher publisher = client.paginatedOperationWithResultKeyPaginator(request);
@@ -765,19 +767,19 @@ public CompletableFuture<PaginatedOperationWithResultKeyResponse> paginatedOpera
765767
* </pre>
766768
*
767769
* 2) Using a custom subscriber
768-
*
770+
*
769771
* <pre>
770772
* {@code
771773
* software.amazon.awssdk.services.json.paginators.PaginatedOperationWithResultKeyPublisher publisher = client.paginatedOperationWithResultKeyPaginator(request);
772774
* publisher.subscribe(new Subscriber<software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyResponse>() {
773-
*
775+
*
774776
* public void onSubscribe(org.reactivestreams.Subscriber subscription) { //... };
775-
*
776-
*
777+
*
778+
*
777779
* public void onNext(software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyResponse response) { //... };
778780
* });}
779781
* </pre>
780-
*
782+
*
781783
* As the response is a publisher, it can work well with third party reactive streams implementations like RxJava2.
782784
* <p>
783785
* <b>Please notice that the configuration of MaxResults won't limit the number of results you get with the
@@ -888,7 +890,7 @@ public CompletableFuture<PaginatedOperationWithoutResultKeyResponse> paginatedOp
888890
* The following are few ways to use the response class:
889891
* </p>
890892
* 1) Using the subscribe helper method
891-
*
893+
*
892894
* <pre>
893895
* {@code
894896
* software.amazon.awssdk.services.json.paginators.PaginatedOperationWithoutResultKeyPublisher publisher = client.paginatedOperationWithoutResultKeyPaginator(request);
@@ -898,19 +900,19 @@ public CompletableFuture<PaginatedOperationWithoutResultKeyResponse> paginatedOp
898900
* </pre>
899901
*
900902
* 2) Using a custom subscriber
901-
*
903+
*
902904
* <pre>
903905
* {@code
904906
* software.amazon.awssdk.services.json.paginators.PaginatedOperationWithoutResultKeyPublisher publisher = client.paginatedOperationWithoutResultKeyPaginator(request);
905907
* publisher.subscribe(new Subscriber<software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyResponse>() {
906-
*
908+
*
907909
* public void onSubscribe(org.reactivestreams.Subscriber subscription) { //... };
908-
*
909-
*
910+
*
911+
*
910912
* public void onNext(software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyResponse response) { //... };
911913
* });}
912914
* </pre>
913-
*
915+
*
914916
* As the response is a publisher, it can work well with third party reactive streams implementations like RxJava2.
915917
* <p>
916918
* <b>Please notice that the configuration of MaxResults won't limit the number of results you get with the
@@ -1050,8 +1052,8 @@ public <ReturnT> CompletableFuture<ReturnT> streamingInputOutputOperation(
10501052
try {
10511053
apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service");
10521054
apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOutputOperation");
1053-
Pair<AsyncResponseTransformer<StreamingInputOutputOperationResponse, ReturnT>, CompletableFuture<Void>> pair =
1054-
AsyncResponseTransformerUtils.wrapWithEndOfStreamFuture(asyncResponseTransformer);
1055+
Pair<AsyncResponseTransformer<StreamingInputOutputOperationResponse, ReturnT>, CompletableFuture<Void>> pair = AsyncResponseTransformerUtils
1056+
.wrapWithEndOfStreamFuture(asyncResponseTransformer);
10551057
asyncResponseTransformer = pair.left();
10561058
CompletableFuture<Void> endOfStreamFuture = pair.right();
10571059
streamingInputOutputOperationRequest = applySignerOverride(streamingInputOutputOperationRequest,
@@ -1092,7 +1094,7 @@ public <ReturnT> CompletableFuture<ReturnT> streamingInputOutputOperation(
10921094
} catch (Throwable t) {
10931095
AsyncResponseTransformer<StreamingInputOutputOperationResponse, ReturnT> finalAsyncResponseTransformer = asyncResponseTransformer;
10941096
runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring",
1095-
() -> finalAsyncResponseTransformer.exceptionOccurred(t));
1097+
() -> finalAsyncResponseTransformer.exceptionOccurred(t));
10961098
metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
10971099
return CompletableFutureUtils.failedFuture(t);
10981100
}
@@ -1133,8 +1135,8 @@ public <ReturnT> CompletableFuture<ReturnT> streamingOutputOperation(
11331135
try {
11341136
apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service");
11351137
apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation");
1136-
Pair<AsyncResponseTransformer<StreamingOutputOperationResponse, ReturnT>, CompletableFuture<Void>> pair =
1137-
AsyncResponseTransformerUtils.wrapWithEndOfStreamFuture(asyncResponseTransformer);
1138+
Pair<AsyncResponseTransformer<StreamingOutputOperationResponse, ReturnT>, CompletableFuture<Void>> pair = AsyncResponseTransformerUtils
1139+
.wrapWithEndOfStreamFuture(asyncResponseTransformer);
11381140
asyncResponseTransformer = pair.left();
11391141
CompletableFuture<Void> endOfStreamFuture = pair.right();
11401142
JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true)
@@ -1168,7 +1170,7 @@ public <ReturnT> CompletableFuture<ReturnT> streamingOutputOperation(
11681170
} catch (Throwable t) {
11691171
AsyncResponseTransformer<StreamingOutputOperationResponse, ReturnT> finalAsyncResponseTransformer = asyncResponseTransformer;
11701172
runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring",
1171-
() -> finalAsyncResponseTransformer.exceptionOccurred(t));
1173+
() -> finalAsyncResponseTransformer.exceptionOccurred(t));
11721174
metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
11731175
return CompletableFutureUtils.failedFuture(t);
11741176
}
@@ -1186,8 +1188,11 @@ private <T extends BaseAwsJsonProtocolFactory.Builder<T>> T init(T builder) {
11861188
.protocol(AwsJsonProtocol.AWS_JSON)
11871189
.protocolVersion("1.1")
11881190
.registerModeledException(
1189-
ExceptionMetadata.builder().errorCode("InvalidInput")
1190-
.exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build());
1191+
ExceptionMetadata.builder().errorCode("InvalidInputException")
1192+
.exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build())
1193+
.registerModeledException(
1194+
ExceptionMetadata.builder().errorCode("ServiceFaultException")
1195+
.exceptionBuilderSupplier(ServiceFaultException::builder).httpStatusCode(500).build());
11911196
}
11921197

11931198
private static List<MetricPublisher> resolveMetricPublishers(SdkClientConfiguration clientConfiguration,

0 commit comments

Comments
 (0)