Skip to content

Commit 14a43de

Browse files
committed
Ensure rpc 1.0/1.1 error code parsing matches smithy spec: use both __type and code fields and handle uris in body error codes
1 parent 29b3f37 commit 14a43de

File tree

3 files changed

+76
-6
lines changed

3 files changed

+76
-6
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": "Ensure rpc 1.0/1.1 error code parsing matches smithy spec: use both __type and code fields and handle uris in body error codes."
6+
}

core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/JsonErrorCodeParser.java

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.protocols.json.internal.unmarshall;
1717

1818
import java.util.Arrays;
19+
import java.util.Collections;
1920
import java.util.List;
2021
import java.util.Optional;
2122
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -37,15 +38,18 @@ public class JsonErrorCodeParser implements ErrorCodeParser {
3738

3839
static final String EXCEPTION_TYPE_HEADER = ":exception-type";
3940

41+
static final List<String> ERROR_CODE_FIELDS = Collections.unmodifiableList(Arrays.asList("__type", "code"));
42+
4043
/**
4144
* List of header keys that represent the error code sent by service.
4245
* Response should only contain one of these headers
4346
*/
4447
private final List<String> errorCodeHeaders;
45-
private final String errorCodeFieldName;
48+
private final List<String> errorCodeFieldNames;
4649

47-
public JsonErrorCodeParser(String errorCodeFieldName) {
48-
this.errorCodeFieldName = errorCodeFieldName == null ? "__type" : errorCodeFieldName;
50+
public JsonErrorCodeParser(String customErrorCodeFieldName) {
51+
this.errorCodeFieldNames = customErrorCodeFieldName == null ?
52+
ERROR_CODE_FIELDS : Collections.singletonList(customErrorCodeFieldName);
4953
this.errorCodeHeaders = Arrays.asList(X_AMZN_ERROR_TYPE, ERROR_CODE_HEADER, EXCEPTION_TYPE_HEADER);
5054
}
5155

@@ -106,12 +110,63 @@ private String parseErrorCodeFromContents(JsonNode jsonContents) {
106110
if (jsonContents == null) {
107111
return null;
108112
}
109-
JsonNode errorCodeField = jsonContents.field(errorCodeFieldName).orElse(null);
113+
JsonNode errorCodeField = errorCodeFieldNames.stream()
114+
.map(jsonContents::field)
115+
.filter(Optional::isPresent)
116+
.map(Optional::get)
117+
.findFirst()
118+
.orElse(null);
110119
if (errorCodeField == null) {
111120
return null;
112121
}
113122
String code = errorCodeField.text();
114-
int separator = code.lastIndexOf('#');
115-
return code.substring(separator + 1);
123+
// now extract the error code from the field contents following the smithy defined rules:
124+
// 1) If a : character is present, then take only the contents before the first : character in the value.
125+
// 2) If a # character is present, then take only the contents after the first # character in the value.
126+
// see: https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#operation-error-serialization
127+
int start = 0;
128+
int end = code.length();
129+
130+
// 1 - everything before the first ':'
131+
int colonIndex = code.indexOf(':');
132+
if (colonIndex >= 0) {
133+
end = colonIndex;
134+
}
135+
136+
// 2 - everything after the first '#'
137+
int hashIndex = code.indexOf('#');
138+
if (hashIndex >= 0 && hashIndex + 1 < end) {
139+
start = hashIndex + 1;
140+
}
141+
142+
return code.substring(start, end);
143+
}
144+
145+
public static String parseErrorCode(String value) {
146+
if (value == null || value.isEmpty()) {
147+
return value;
148+
}
149+
150+
int start = 0;
151+
int end = value.length();
152+
153+
// Step 1: everything before the first ':'
154+
int colonIndex = value.indexOf(':');
155+
if (colonIndex >= 0) {
156+
end = colonIndex;
157+
}
158+
159+
// Step 2: everything after the first '#'
160+
int hashIndex = value.indexOf('#');
161+
if (hashIndex >= 0 && hashIndex + 1 < end) {
162+
start = hashIndex + 1;
163+
}
164+
165+
// Fast-path: return original string if unchanged
166+
if (start == 0 && end == value.length()) {
167+
return value;
168+
}
169+
170+
return value.substring(start, end);
116171
}
117172
}

core/protocols/smithy-rpcv2-protocol/src/main/java/software/amazon/awssdk/protocols/rpcv2/internal/SdkStructuredRpcV2CborFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import software.amazon.awssdk.annotations.SdkInternalApi;
1919
import software.amazon.awssdk.protocols.json.BaseAwsStructuredJsonFactory;
20+
import software.amazon.awssdk.protocols.json.ErrorCodeParser;
2021
import software.amazon.awssdk.protocols.json.StructuredJsonGenerator;
22+
import software.amazon.awssdk.protocols.json.internal.unmarshall.JsonErrorCodeParser;
2123
import software.amazon.awssdk.thirdparty.jackson.core.JsonFactory;
2224
import software.amazon.awssdk.thirdparty.jackson.dataformat.cbor.CBORFactory;
2325
import software.amazon.awssdk.thirdparty.jackson.dataformat.cbor.CBORFactoryBuilder;
@@ -48,6 +50,13 @@ protected StructuredJsonGenerator createWriter(JsonFactory jsonFactory,
4850
public CBORFactory getJsonFactory() {
4951
return CBOR_FACTORY;
5052
}
53+
54+
@Override
55+
public ErrorCodeParser getErrorCodeParser(String customErrorCodeFieldName) {
56+
// smithy cbor ONLY supports the __type field and not code. Set a customErrorCode to use only __type
57+
String errorCodeField = customErrorCodeFieldName == null ? "__type" : customErrorCodeFieldName;
58+
return new JsonErrorCodeParser(errorCodeField);
59+
}
5160
};
5261

5362
private SdkStructuredRpcV2CborFactory() {

0 commit comments

Comments
 (0)