Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-AWSSDKforJavav2-4f61398.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "bugfix",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Reduce generated client source and bytecode size for JSON, CBOR, and Smithy RPC v2 protocol services by generating the error-metadata mapper once per client instead of once per operation."
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ protected void addFields(Builder type) {
.addField(protocolSpec.protocolFactory(model))
.addField(SdkClientConfiguration.class, "clientConfiguration", PRIVATE, FINAL);

protocolSpec.errorResponseMapperField().ifPresent(type::addField);

// Kinesis doesn't support CBOR for STS yet so need another protocol factory for JSON
if (model.getMetadata().isCborProtocol()) {
type.addField(AwsJsonProtocolFactory.class, "jsonProtocolFactory", PRIVATE, FINAL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ protected void addFields(TypeSpec.Builder type) {
.addField(SyncClientHandler.class, "clientHandler", PRIVATE, FINAL)
.addField(protocolSpec.protocolFactory(model))
.addField(SdkClientConfiguration.class, "clientConfiguration", PRIVATE, FINAL);
protocolSpec.errorResponseMapperField().ifPresent(type::addField);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,39 +168,48 @@ public Optional<CodeBlock> errorResponseHandler(OperationModel opModel) {
String protocolFactory = protocolFactoryLiteral(model, opModel);

CodeBlock.Builder builder = CodeBlock.builder();
builder.add("\n$T<$T> errorResponseHandler = createErrorResponseHandler($L, operationMetadata, "
+ "exceptionMetadataMapper);",
HttpResponseHandler.class, AwsServiceException.class, protocolFactory);

return Optional.of(builder.build());
}

@Override
public Optional<FieldSpec> errorResponseMapperField() {
ParameterizedTypeName metadataMapperType = ParameterizedTypeName.get(
ClassName.get(Function.class),
ClassName.get(String.class),
ParameterizedTypeName.get(Optional.class, ExceptionMetadata.class));

builder.add("\n$T exceptionMetadataMapper = errorCode -> {\n", metadataMapperType);
builder.add("if (errorCode == null) {\n");
builder.add("return $T.empty();\n", Optional.class);
builder.add("}\n");
builder.add("switch (errorCode) {\n");
CodeBlock.Builder initializer = CodeBlock.builder();
initializer.add("errorCode -> {\n");
initializer.add("if (errorCode == null) {\n");
initializer.add("return $T.empty();\n", Optional.class);
initializer.add("}\n");
initializer.add("switch (errorCode) {\n");
model.getShapes().values().stream()
.filter(shape -> shape.getShapeType() == ShapeType.Exception)
.forEach(exceptionShape -> {
String exceptionName = exceptionShape.getShapeName();
String errorCode = exceptionShape.getErrorCode();

builder.add("case $S:\n", errorCode);
builder.add("return $T.of($T.builder()\n", Optional.class, ExceptionMetadata.class)
.add(".errorCode($S)\n", errorCode);
builder.add(populateHttpStatusCode(exceptionShape, model));
builder.add(".exceptionBuilderSupplier($T::builder)\n",
poetExtensions.getModelClassFromShape(exceptionShape))
.add(".build());\n");
initializer.add("case $S:\n", errorCode);
initializer.add("return $T.of($T.builder()\n", Optional.class, ExceptionMetadata.class)
.add(".errorCode($S)\n", errorCode);
initializer.add(populateHttpStatusCode(exceptionShape, model));
initializer.add(".exceptionBuilderSupplier($T::builder)\n",
poetExtensions.getModelClassFromShape(exceptionShape))
.add(".build());\n");
});

builder.add("default: return $T.empty();\n", Optional.class);
builder.add("}\n");
builder.add("};\n");

builder.add("$T<$T> errorResponseHandler = createErrorResponseHandler($L, operationMetadata, exceptionMetadataMapper);",
HttpResponseHandler.class, AwsServiceException.class, protocolFactory);
initializer.add("default: return $T.empty();\n", Optional.class);
initializer.add("}\n");
initializer.add("}");

return Optional.of(builder.build());
return Optional.of(FieldSpec.builder(metadataMapperType, "exceptionMetadataMapper",
Modifier.PRIVATE, Modifier.FINAL)
.initializer(initializer.build())
.build());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ default Optional<MethodSpec> createEventstreamErrorResponseHandler() {
return Optional.empty();
}

/**
* The per-client field holding the non-streaming {@code exceptionMetadataMapper}, when the protocol emits one. The
* mapper switches over the whole model's exceptions and is identical for every operation, so it is generated once as
* a client field instead of once per operation body.
*/
default Optional<FieldSpec> errorResponseMapperField() {
return Optional.empty();
}

default List<MethodSpec> additionalMethods() {
return new ArrayList<>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,22 @@ public class ClientLambdaCallSiteCeilingTest {
* fixture's count by its operation count, which is at least 4 for every fixture listed here, so the headroom absorbs
* a couple of new shared per-client lambdas without absorbing a per-operation one. Single-operation fixtures are
* excluded for that reason: no headroom smaller than one operation exists.
*
* <p>The JSON, CBOR and Smithy RPC v2 fixtures are legitimately higher than the rest because those protocols emit a
* per-operation {@code exceptionMetadataMapper} lambda whose body switches over that operation's error codes.
*/
private static Stream<Arguments> ceilings() {
return Stream.of(
Arguments.of("test-json-async-client-class.java", 44),
Arguments.of("test-json-client-class.java", 18),
Arguments.of("test-aws-json-async-client-class.java", 38),
Arguments.of("test-cbor-async-client-class.java", 38),
Arguments.of("test-cbor-client-class.java", 16),
Arguments.of("test-rpcv2-async-client-class.java", 19),
Arguments.of("test-rpcv2-sync.java", 18),
Arguments.of("test-json-async-client-class.java", 29),
Arguments.of("test-json-client-class.java", 6),
Arguments.of("test-aws-json-async-client-class.java", 25),
Arguments.of("test-cbor-async-client-class.java", 25),
Arguments.of("test-cbor-client-class.java", 6),
Arguments.of("test-rpcv2-async-client-class.java", 7),
Arguments.of("test-rpcv2-sync.java", 6),
Arguments.of("test-query-async-client-class.java", 14),
Arguments.of("test-query-client-class.java", 5),
Arguments.of("test-xml-async-client-class.java", 18),
Arguments.of("test-xml-client-class.java", 5),
Arguments.of("test-unsigned-payload-trait-async-client-class.java", 17),
Arguments.of("test-unsigned-payload-trait-sync-client-class.java", 16)
Arguments.of("test-unsigned-payload-trait-async-client-class.java", 7),
Arguments.of("test-unsigned-payload-trait-sync-client-class.java", 6)
);
}

Expand Down
Loading
Loading