1616package software .amazon .awssdk .protocols .json .internal .unmarshall ;
1717
1818import java .util .Arrays ;
19+ import java .util .Collections ;
1920import java .util .List ;
2021import java .util .Optional ;
2122import 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}
0 commit comments