diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json b/.changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json new file mode 100644 index 000000000000..7da1e9b8995c --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Updated codegen to add the null check and pass it in instead of throw NPE when serializing enum to string" +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/MemberCopierSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/MemberCopierSpec.java index ee0a13698ddd..31939a2a81f5 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/MemberCopierSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/MemberCopierSpec.java @@ -224,7 +224,9 @@ private String copyMethodBody(CodeBlock.Builder code, BuilderTransform builderTr case NONE: return inputVariableName; case ENUM_TO_STRING: - code.add("$T $N = $N.toString();", String.class, outputVariableName, inputVariableName); + code.add( + "$T $N = $N == null ? null : $N.toString();", + String.class, outputVariableName, inputVariableName, inputVariableName); return outputVariableName; case STRING_TO_ENUM: code.add("$1T $2N = $1T.fromValue($3N);", enumType, outputVariableName, inputVariableName); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofenumscopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofenumscopier.java index 973996a4202f..53a85458f881 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofenumscopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofenumscopier.java @@ -33,7 +33,7 @@ static List copyEnumToString(Collection listOfEnumsParam) { } else { List modifiableList = new ArrayList<>(listOfEnumsParam.size()); listOfEnumsParam.forEach(entry -> { - String result = entry.toString(); + String result = entry == null ? null : entry.toString(); modifiableList.add(result); }); list = Collections.unmodifiableList(modifiableList); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofmapofenumtostringcopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofmapofenumtostringcopier.java index c2e6eed148fb..590a650e3bec 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofmapofenumtostringcopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/listofmapofenumtostringcopier.java @@ -53,7 +53,7 @@ static List> copyEnumToString(Collection modifiableMap = new LinkedHashMap<>(entry.size()); entry.forEach((key, value) -> { - String result = key.toString(); + String result = key == null ? null : key.toString(); modifiableMap.put(result, value); }); map = Collections.unmodifiableMap(modifiableMap); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtoenumcopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtoenumcopier.java index 724a0e946705..b937c8136eb4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtoenumcopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtoenumcopier.java @@ -32,8 +32,8 @@ static Map copyEnumToString(Map mapOfEnumToE } else { Map modifiableMap = new LinkedHashMap<>(mapOfEnumToEnumParam.size()); mapOfEnumToEnumParam.forEach((key, value) -> { - String result = key.toString(); - String result1 = value.toString(); + String result = key == null ? null : key.toString(); + String result1 = value == null ? null : value.toString(); modifiableMap.put(result, result1); }); map = Collections.unmodifiableMap(modifiableMap); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtolistofenumscopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtolistofenumscopier.java index ff0d8aa06a1c..864b7dd72738 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtolistofenumscopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtolistofenumscopier.java @@ -47,14 +47,14 @@ static Map> copyEnumToString(Map> modifiableMap = new LinkedHashMap<>(mapOfEnumToListOfEnumsParam.size()); mapOfEnumToListOfEnumsParam.forEach((key, value) -> { - String result = key.toString(); + String result = key == null ? null : key.toString(); List list; if (value == null || value instanceof SdkAutoConstructList) { list = DefaultSdkAutoConstructList.getInstance(); } else { List modifiableList = new ArrayList<>(value.size()); value.forEach(entry -> { - String result1 = entry.toString(); + String result1 = entry == null ? null : entry.toString(); modifiableList.add(result1); }); list = Collections.unmodifiableList(modifiableList); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtomapofstringtoenumcopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtomapofstringtoenumcopier.java index ccfaa58a6db6..71515ed451c6 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtomapofstringtoenumcopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtomapofstringtoenumcopier.java @@ -43,14 +43,14 @@ static Map> copyEnumToString( } else { Map> modifiableMap = new LinkedHashMap<>(mapOfEnumToMapOfStringToEnumParam.size()); mapOfEnumToMapOfStringToEnumParam.forEach((key, value) -> { - String result = key.toString(); + String result = key == null ? null : key.toString(); Map map1; if (value == null || value instanceof SdkAutoConstructMap) { map1 = DefaultSdkAutoConstructMap.getInstance(); } else { Map modifiableMap1 = new LinkedHashMap<>(value.size()); value.forEach((key1, value1) -> { - String result1 = value1.toString(); + String result1 = value1 == null ? null : value1.toString(); modifiableMap1.put(key1, result1); }); map1 = Collections.unmodifiableMap(modifiableMap1); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtosimplestructcopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtosimplestructcopier.java index 92cfdfdc3b2e..00a4eeb83be4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtosimplestructcopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtosimplestructcopier.java @@ -62,7 +62,7 @@ static Map copyEnumToString(Map modifiableMap = new LinkedHashMap<>(mapOfEnumToSimpleStructParam.size()); mapOfEnumToSimpleStructParam.forEach((key, value) -> { - String result = key.toString(); + String result = key == null ? null : key.toString(); modifiableMap.put(result, value); }); map = Collections.unmodifiableMap(modifiableMap); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtostringcopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtostringcopier.java index ea6b32d062e6..ca56adcfd53e 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtostringcopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofenumtostringcopier.java @@ -32,7 +32,7 @@ static Map copyEnumToString(Map mapOfEnumToStr } else { Map modifiableMap = new LinkedHashMap<>(mapOfEnumToStringParam.size()); mapOfEnumToStringParam.forEach((key, value) -> { - String result = key.toString(); + String result = key == null ? null : key.toString(); modifiableMap.put(result, value); }); map = Collections.unmodifiableMap(modifiableMap); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofstringtoenumcopier.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofstringtoenumcopier.java index 3182110bb021..a093b2e12a2d 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofstringtoenumcopier.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/mapofstringtoenumcopier.java @@ -32,7 +32,7 @@ static Map copyEnumToString(Map mapOfStringToE } else { Map modifiableMap = new LinkedHashMap<>(mapOfStringToEnumParam.size()); mapOfStringToEnumParam.forEach((key, value) -> { - String result = value.toString(); + String result = value == null ? null : value.toString(); modifiableMap.put(key, result); }); map = Collections.unmodifiableMap(modifiableMap); diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/model/MemberCopierTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/model/MemberCopierTest.java new file mode 100644 index 000000000000..d4bcf1091db6 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/model/MemberCopierTest.java @@ -0,0 +1,63 @@ +/* + * 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.services.protocolrestjson.model; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; + +public class MemberCopierTest { + private MockSyncHttpClient mockHttpClient; + private ProtocolRestJsonClient client; + + @BeforeEach + public void setupClient() { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse200(); + + client = ProtocolRestJsonClient.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", + "skid"))) + .region(Region.US_EAST_1) + .httpClient(mockHttpClient) + .build(); + } + + @Test + public void enumListWithNulls_serializesWithoutNPE() { + AllTypesRequest request = AllTypesRequest.builder() + .listOfEnums(Arrays.asList(EnumType.ENUM_VALUE1, null, EnumType.ENUM_VALUE2)) + .build(); + + assertDoesNotThrow(() -> client.allTypes(request)); + } + + @Test + public void stringListWithNulls_serializesWithoutNPE() { + AllTypesRequest request = AllTypesRequest.builder() + .simpleList(Arrays.asList("Foo", null, "Bar")) + .build(); + + assertDoesNotThrow(() -> client.allTypes(request)); + } +} diff --git a/test/protocol-tests-core/src/main/resources/software/amazon/awssdk/protocol/suites/cases/json-core-input.json b/test/protocol-tests-core/src/main/resources/software/amazon/awssdk/protocol/suites/cases/json-core-input.json index 032f77b856a8..322f1e352aec 100644 --- a/test/protocol-tests-core/src/main/resources/software/amazon/awssdk/protocol/suites/cases/json-core-input.json +++ b/test/protocol-tests-core/src/main/resources/software/amazon/awssdk/protocol/suites/cases/json-core-input.json @@ -640,5 +640,28 @@ } } } + }, + { + "description": "List of enums with null values are marshalled correctly without NPE", + "given": { + "input": { + "ListOfEnums": [ + "EnumValue1", + null, + "EnumValue2" + ] + } + }, + "when": { + "action": "marshall", + "operation": "AllTypes" + }, + "then": { + "serializedAs": { + "body": { + "jsonEquals": "{\"ListOfEnums\": [\"EnumValue1\", null, \"EnumValue2\"]}" + } + } + } } ]