Skip to content

Commit 2a4c6e9

Browse files
committed
Support modeled exceptions in event streams
1 parent f0b1a54 commit 2a4c6e9

File tree

9 files changed

+136
-211
lines changed

9 files changed

+136
-211
lines changed

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

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616
package software.amazon.awssdk.codegen;
1717

1818
import java.util.HashMap;
19-
import java.util.List;
2019
import java.util.Map;
2120
import software.amazon.awssdk.codegen.internal.Utils;
2221
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
2322
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
2423
import software.amazon.awssdk.codegen.model.intermediate.ShapeType;
25-
import software.amazon.awssdk.codegen.model.service.ErrorMap;
2624
import software.amazon.awssdk.codegen.model.service.ErrorTrait;
27-
import software.amazon.awssdk.codegen.model.service.Operation;
25+
import software.amazon.awssdk.codegen.model.service.Shape;
2826

2927
/**
3028
* Constructs the exception shapes for the intermediate model. Analyzes the operations in the
@@ -46,28 +44,21 @@ private Map<String, ShapeModel> constructExceptionShapes() {
4644
// Java shape models, to be constructed
4745
final Map<String, ShapeModel> javaShapes = new HashMap<String, ShapeModel>();
4846

49-
for (Map.Entry<String, Operation> entry : getServiceModel().getOperations().entrySet()) {
47+
for (Map.Entry<String, Shape> shape : getServiceModel().getShapes().entrySet()) {
48+
if (shape.getValue().isException()) {
49+
String errorShapeName = shape.getKey();
50+
String javaClassName = getNamingStrategy().getExceptionName(errorShapeName);
5051

51-
Operation operation = entry.getValue();
52-
List<ErrorMap> operationErrors = operation.getErrors();
52+
ShapeModel exceptionShapeModel = generateShapeModel(javaClassName,
53+
errorShapeName);
5354

54-
if (operationErrors != null) {
55-
for (ErrorMap error : operationErrors) {
56-
57-
String errorShapeName = error.getShape();
58-
String javaClassName = getNamingStrategy().getExceptionName(errorShapeName);
59-
60-
ShapeModel exceptionShapeModel = generateShapeModel(javaClassName,
61-
errorShapeName);
62-
63-
exceptionShapeModel.setType(ShapeType.Exception.getValue());
64-
exceptionShapeModel.setErrorCode(getErrorCode(errorShapeName));
65-
if (exceptionShapeModel.getDocumentation() == null) {
66-
exceptionShapeModel.setDocumentation(error.getDocumentation());
67-
}
68-
69-
javaShapes.put(javaClassName, exceptionShapeModel);
55+
exceptionShapeModel.setType(ShapeType.Exception.getValue());
56+
exceptionShapeModel.setErrorCode(getErrorCode(errorShapeName));
57+
if (exceptionShapeModel.getDocumentation() == null) {
58+
exceptionShapeModel.setDocumentation(shape.getValue().getDocumentation());
7059
}
60+
61+
javaShapes.put(javaClassName, exceptionShapeModel);
7162
}
7263
}
7364

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

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
import java.util.Optional;
2929
import java.util.stream.Collectors;
3030
import javax.lang.model.element.Modifier;
31-
3231
import software.amazon.awssdk.awscore.eventstream.EventStreamAsyncResponseTransformer;
33-
import software.amazon.awssdk.awscore.eventstream.EventStreamExceptionJsonUnmarshaller;
3432
import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionJsonUnmarshaller;
3533
import software.amazon.awssdk.awscore.exception.AwsServiceException;
3634
import software.amazon.awssdk.awscore.internal.protocol.json.AwsJsonProtocol;
@@ -159,19 +157,6 @@ public CodeBlock responseHandler(IntermediateModel model, OperationModel opModel
159157
});
160158
builder.add(".defaultUnmarshaller((in) -> $T.UNKNOWN)\n"
161159
+ ".build());\n", eventStreamUtils.eventStreamBaseClass());
162-
163-
builder.add("\n\n$T<$T> exceptionHandler = $L.createResponseHandler(\n" +
164-
" new JsonOperationMetadata().withPayloadJson(true).withHasStreamingSuccessResponse(false),\n" +
165-
" $T.builder()\n" +
166-
" .defaultUnmarshaller(x -> $T.populateDefaultException($T::builder, x))\n" +
167-
" .build());",
168-
HttpResponseHandler.class,
169-
WildcardTypeName.subtypeOf(Throwable.class),
170-
protocolFactory,
171-
EventStreamExceptionJsonUnmarshaller.class,
172-
EventStreamExceptionJsonUnmarshaller.class,
173-
baseExceptionClassName(model));
174-
175160
}
176161
return builder.build();
177162
}
@@ -229,7 +214,7 @@ public CodeBlock asyncExecutionHandler(OperationModel opModel) {
229214
CodeBlock.Builder builder = CodeBlock.builder();
230215
if (opModel.hasEventStreamOutput()) {
231216
builder.add("$T<$T, $T> asyncResponseTransformer = new $T<>(\n"
232-
+ "asyncResponseHandler, responseHandler, eventResponseHandler, exceptionHandler);\n",
217+
+ "asyncResponseHandler, responseHandler, eventResponseHandler, errorResponseHandler, serviceName());\n",
233218
ClassName.get(AsyncResponseTransformer.class),
234219
ClassName.get(SdkResponse.class),
235220
ClassName.get(Void.class),

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
1212
import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler;
1313
import software.amazon.awssdk.awscore.eventstream.EventStreamAsyncResponseTransformer;
14-
import software.amazon.awssdk.awscore.eventstream.EventStreamExceptionJsonUnmarshaller;
1514
import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionJsonUnmarshaller;
1615
import software.amazon.awssdk.awscore.exception.AwsServiceException;
1716
import software.amazon.awssdk.awscore.internal.protocol.json.AwsJsonProtocol;
@@ -42,7 +41,6 @@
4241
import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersRequest;
4342
import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersResponse;
4443
import software.amazon.awssdk.services.json.model.InvalidInputException;
45-
import software.amazon.awssdk.services.json.model.JsonException;
4644
import software.amazon.awssdk.services.json.model.JsonRequest;
4745
import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyRequest;
4846
import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyResponse;
@@ -222,18 +220,9 @@ public CompletableFuture<Void> eventStreamOperation(EventStreamOperationRequest
222220
.addUnmarshaller("EventTwo", EventTwoUnmarshaller.getInstance())
223221
.defaultUnmarshaller((in) -> EventStream.UNKNOWN).build());
224222

225-
HttpResponseHandler<? extends Throwable> exceptionHandler = jsonProtocolFactory
226-
.createResponseHandler(
227-
new JsonOperationMetadata().withPayloadJson(true).withHasStreamingSuccessResponse(false),
228-
EventStreamExceptionJsonUnmarshaller
229-
.builder()
230-
.defaultUnmarshaller(
231-
x -> EventStreamExceptionJsonUnmarshaller.populateDefaultException(
232-
JsonException::builder, x)).build());
233-
234223
HttpResponseHandler<AwsServiceException> errorResponseHandler = createErrorResponseHandler(jsonProtocolFactory);
235224
AsyncResponseTransformer<SdkResponse, Void> asyncResponseTransformer = new EventStreamAsyncResponseTransformer<>(
236-
asyncResponseHandler, responseHandler, eventResponseHandler, exceptionHandler);
225+
asyncResponseHandler, responseHandler, eventResponseHandler, errorResponseHandler, serviceName());
237226

238227
return clientHandler.execute(
239228
new ClientExecutionParams<EventStreamOperationRequest, SdkResponse>()

core/aws-core/src/main/java/software/amazon/awssdk/awscore/eventstream/EventStreamAsyncResponseTransformer.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.awscore.eventstream;
1717

1818
import static java.util.Collections.singletonList;
19+
import static software.amazon.awssdk.core.http.HttpResponseHandler.X_AMZN_REQUEST_ID_HEADER;
1920
import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError;
2021

2122
import java.io.ByteArrayInputStream;
@@ -37,6 +38,7 @@
3738
import software.amazon.awssdk.core.exception.SdkClientException;
3839
import software.amazon.awssdk.core.http.HttpResponseHandler;
3940
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
41+
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
4042
import software.amazon.awssdk.core.internal.util.ThrowableUtils;
4143
import software.amazon.awssdk.http.AbortableInputStream;
4244
import software.amazon.awssdk.http.SdkHttpFullResponse;
@@ -106,6 +108,18 @@ public class EventStreamAsyncResponseTransformer<ResponseT, EventT>
106108
*/
107109
private final AtomicReference<Throwable> error = new AtomicReference<>();
108110

111+
/**
112+
* The name of the aws service
113+
*/
114+
private final String serviceName;
115+
116+
/**
117+
* Request Id for the streaming request. The value is populated when the initial response is received from the service.
118+
* As request id is not sent in event messages (including exceptions), this can be returned by the SDK along with
119+
* received exception details.
120+
*/
121+
private String requestId = null;
122+
109123
/**
110124
* @param eventStreamResponseTransformer Response transformer provided by customer.
111125
* @param initialResponseUnmarshaller Unmarshaller for the initial-response event stream message.
@@ -115,19 +129,26 @@ public EventStreamAsyncResponseTransformer(
115129
EventStreamResponseHandler<ResponseT, EventT> eventStreamResponseTransformer,
116130
HttpResponseHandler<? extends ResponseT> initialResponseUnmarshaller,
117131
HttpResponseHandler<? extends EventT> eventUnmarshaller,
118-
HttpResponseHandler<? extends Throwable> exceptionUnmarshaller) {
132+
HttpResponseHandler<? extends Throwable> exceptionUnmarshaller,
133+
String serviceName) {
119134

120135
this.eventStreamResponseTransformer = eventStreamResponseTransformer;
121136
this.initialResponseUnmarshaller = initialResponseUnmarshaller;
122137
this.eventUnmarshaller = eventUnmarshaller;
123138
this.exceptionUnmarshaller = exceptionUnmarshaller;
139+
this.serviceName = serviceName;
124140
}
125141

126142
@Override
127143
public void responseReceived(SdkResponse response) {
128144
// We use a void unmarshaller and unmarshall the actual response in the message
129145
// decoder when we receive the initial-response frame. TODO not clear
130146
// how we would handle REST protocol which would unmarshall the response from the HTTP headers
147+
if (response != null && response.sdkHttpResponse() != null) {
148+
this.requestId = response.sdkHttpResponse()
149+
.firstMatchingHeader(X_AMZN_REQUEST_ID_HEADER)
150+
.orElse(null);
151+
}
131152
}
132153

133154
@Override
@@ -205,8 +226,18 @@ private MessageDecoder createDecoder() {
205226
EMPTY_EXECUTION_ATTRIBUTES));
206227
}
207228
} else if (isError(m) || isException(m)) {
208-
Throwable exception = exceptionUnmarshaller.handle(adaptMessageToResponse(m, true),
209-
EMPTY_EXECUTION_ATTRIBUTES);
229+
SdkHttpFullResponse errorResponse = adaptMessageToResponse(m, true);
230+
if (!errorResponse.firstMatchingHeader(X_AMZN_REQUEST_ID_HEADER).isPresent() &&
231+
requestId != null) {
232+
errorResponse = errorResponse.toBuilder()
233+
.putHeader(X_AMZN_REQUEST_ID_HEADER, requestId)
234+
.build();
235+
}
236+
237+
Throwable exception = exceptionUnmarshaller.handle(errorResponse,
238+
new ExecutionAttributes()
239+
.putAttribute(SdkExecutionAttribute.SERVICE_NAME,
240+
serviceName));
210241
runAndLogError(log, "Error thrown from exceptionOccurred, ignoring.", () -> exceptionOccurred(exception));
211242
}
212243
} catch (Exception e) {

core/aws-core/src/main/java/software/amazon/awssdk/awscore/eventstream/EventStreamExceptionJsonUnmarshaller.java

Lines changed: 0 additions & 140 deletions
This file was deleted.

0 commit comments

Comments
 (0)