Skip to content

Commit 0de15d4

Browse files
committed
Move the discriminator extraction for error to httpErrorDeserializer
1 parent 9e15a74 commit 0de15d4

File tree

13 files changed

+223
-157
lines changed

13 files changed

+223
-157
lines changed

aws/client/aws-client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJson11Protocol.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import software.amazon.smithy.java.client.core.ClientProtocol;
1111
import software.amazon.smithy.java.client.core.ClientProtocolFactory;
1212
import software.amazon.smithy.java.client.core.ProtocolSettings;
13-
import software.amazon.smithy.java.core.serde.document.DocumentUtils;
13+
import software.amazon.smithy.java.client.http.HttpErrorDeserializer;
1414
import software.amazon.smithy.model.shapes.ShapeId;
1515

1616
/**
@@ -25,7 +25,7 @@ public final class AwsJson11Protocol extends AwsJsonProtocol {
2525
* discriminator of documents that use relative shape IDs.
2626
*/
2727
public AwsJson11Protocol(ShapeId service) {
28-
super(TRAIT_ID, service, DocumentUtils::removeNamespaceAndUri);
28+
super(TRAIT_ID, service, new HttpErrorDeserializer.DocumentPayloadParser());
2929
}
3030

3131
@Override

aws/client/aws-client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJson1Protocol.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
import software.amazon.smithy.java.client.core.ClientProtocol;
1111
import software.amazon.smithy.java.client.core.ClientProtocolFactory;
1212
import software.amazon.smithy.java.client.core.ProtocolSettings;
13-
import software.amazon.smithy.java.core.serde.document.DocumentUtils;
13+
import software.amazon.smithy.java.client.http.ErrorTypeUtils;
14+
import software.amazon.smithy.java.client.http.HttpErrorDeserializer;
15+
import software.amazon.smithy.java.core.serde.document.Document;
16+
import software.amazon.smithy.java.core.serde.document.DocumentDeserializer;
1417
import software.amazon.smithy.model.shapes.ShapeId;
1518

1619
/**
@@ -25,7 +28,16 @@ public final class AwsJson1Protocol extends AwsJsonProtocol {
2528
* discriminator of documents that use relative shape IDs.
2629
*/
2730
public AwsJson1Protocol(ShapeId service) {
28-
super(TRAIT_ID, service, DocumentUtils::removeUri);
31+
super(TRAIT_ID, service, new Json10Parser());
32+
}
33+
34+
private static final class Json10Parser extends HttpErrorDeserializer.DocumentPayloadParser {
35+
@Override
36+
public ShapeId extractErrorType(Document document, String namespace) {
37+
return DocumentDeserializer.parseDiscriminator(
38+
ErrorTypeUtils.removeUri(ErrorTypeUtils.readTypeAndCode(document)),
39+
namespace);
40+
}
2941
}
3042

3143
@Override

aws/client/aws-client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJsonProtocol.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import java.nio.charset.StandardCharsets;
1010
import java.util.List;
1111
import java.util.Map;
12-
import java.util.function.Function;
1312
import software.amazon.smithy.java.client.http.AmznErrorHeaderExtractor;
1413
import software.amazon.smithy.java.client.http.HttpClientProtocol;
1514
import software.amazon.smithy.java.client.http.HttpErrorDeserializer;
@@ -40,18 +39,22 @@ abstract sealed class AwsJsonProtocol extends HttpClientProtocol permits AwsJson
4039
* @param service The service ID used to make X-Amz-Target, and the namespace is used when finding the
4140
* discriminator of documents that use relative shape IDs.
4241
*/
43-
public AwsJsonProtocol(ShapeId trait, ShapeId service, Function<String, String> errorTypeSanitizer) {
42+
public AwsJsonProtocol(
43+
ShapeId trait,
44+
ShapeId service,
45+
HttpErrorDeserializer.ErrorPayloadParser errorPayloadParser
46+
) {
4447
super(trait);
4548
this.service = service;
4649
this.codec = JsonCodec.builder()
4750
.defaultNamespace(service.getNamespace())
48-
.errorTypeSanitizer(errorTypeSanitizer)
4951
.useTimestampFormat(true)
5052
.build();
5153

5254
this.errorDeserializer = HttpErrorDeserializer.builder()
5355
.codec(codec)
5456
.serviceId(service)
57+
.errorPayloadParser(errorPayloadParser)
5558
.headerErrorExtractor(new AmznErrorHeaderExtractor())
5659
.build();
5760
}

aws/client/aws-client-restjson/src/main/java/software/amazon/smithy/java/aws/client/restjson/RestJsonClientProtocol.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import software.amazon.smithy.java.core.schema.SerializableStruct;
2525
import software.amazon.smithy.java.core.schema.TraitKey;
2626
import software.amazon.smithy.java.core.serde.Codec;
27-
import software.amazon.smithy.java.core.serde.document.DocumentUtils;
2827
import software.amazon.smithy.java.core.serde.event.EventDecoderFactory;
2928
import software.amazon.smithy.java.core.serde.event.EventEncoderFactory;
3029
import software.amazon.smithy.java.core.serde.event.EventStreamingException;
@@ -53,7 +52,6 @@ public RestJsonClientProtocol(ShapeId service) {
5352
.useJsonName(true)
5453
.useTimestampFormat(true)
5554
.defaultNamespace(service.getNamespace())
56-
.errorTypeSanitizer(DocumentUtils::removeNamespaceAndUri)
5755
.build();
5856

5957
this.errorDeserializer = HttpErrorDeserializer.builder()
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.client.http;
7+
8+
import software.amazon.smithy.java.core.serde.document.Document;
9+
import software.amazon.smithy.model.shapes.ShapeType;
10+
11+
public class ErrorTypeUtils {
12+
13+
/**
14+
* Read the error type from __type field of the document.
15+
*
16+
* @param document the document of the payload to read.
17+
* @return the extracted error type from the document.
18+
*/
19+
public static String readType(Document document) {
20+
String errorType = null;
21+
var member = document.getMember("__type");
22+
if (member != null && member.type() == ShapeType.STRING) {
23+
errorType = member.asString();
24+
}
25+
return errorType;
26+
}
27+
28+
/**
29+
* Read the error type from __type or code field of the document.
30+
*
31+
* @param document the document of the payload to read.
32+
* @return the extracted error type from the document.
33+
*/
34+
public static String readTypeAndCode(Document document) {
35+
String errorType = readType(document);
36+
if (errorType == null) {
37+
var member = document.getMember("code");
38+
if (member != null && member.type() == ShapeType.STRING) {
39+
errorType = member.asString();
40+
}
41+
}
42+
return errorType;
43+
}
44+
45+
/**
46+
* Removes the trailing URI in {@code __type} field or {@code code} field of the document.
47+
*
48+
* <p>For example, given {@code __type = "aws.protocoltests.restjson#FooError:http://abc.com"},
49+
* protocols like restJSON should ignore the trailing URI and keep the namespace of the error type.
50+
*
51+
* @param text The error type string.
52+
* @return The error type string without the trailing URI.
53+
*/
54+
public static String removeUri(String text) {
55+
if (text == null) {
56+
return null;
57+
}
58+
var colon = text.indexOf(':');
59+
if (colon > 0) {
60+
text = text.substring(0, colon);
61+
}
62+
return text;
63+
}
64+
65+
/**
66+
* Removes the namespace and trailing URI in {@code __type} field or {@code code} field of the document.
67+
*
68+
* <p>For example, given {@code __type = "aws.protocoltests.restjson#FooError:http://abc.com"},
69+
* protocols like awsJSON 1.1 should ignore the namespace and the trailing URI of the error type.
70+
*
71+
* @param text The error type string.
72+
* @return The error type string without the trailing URI.
73+
*/
74+
public static String removeNamespaceAndUri(String text) {
75+
if (text == null) {
76+
return null;
77+
}
78+
var hash = text.indexOf('#');
79+
if (hash > 0) {
80+
text = text.substring(hash + 1);
81+
}
82+
return removeUri(text);
83+
}
84+
}

client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpErrorDeserializer.java

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import software.amazon.smithy.java.core.serde.TypeRegistry;
1818
import software.amazon.smithy.java.core.serde.document.DiscriminatorException;
1919
import software.amazon.smithy.java.core.serde.document.Document;
20+
import software.amazon.smithy.java.core.serde.document.DocumentDeserializer;
2021
import software.amazon.smithy.java.http.api.HttpResponse;
2122
import software.amazon.smithy.java.io.datastream.DataStream;
2223
import software.amazon.smithy.model.shapes.ShapeId;
@@ -116,7 +117,6 @@ default ModeledException createErrorFromDocument(
116117
* Different protocols need different parsers to extract the ShapeId given their different response structures.
117118
* If no parser specified, {@link #DEFAULT_ERROR_PAYLOAD_PARSER} will be picked.
118119
*/
119-
@FunctionalInterface
120120
public interface ErrorPayloadParser {
121121
/**
122122
* This method should parse the response payload and extract error's ShapeId,and
@@ -191,30 +191,45 @@ public ModeledException createErrorFromDocument(
191191
}
192192
};
193193

194-
// This default parser should work for most protocols, but other protocols
195-
// that do not support document types will need a custom parser to extract error ShapeId.
196-
private static final ErrorPayloadParser DEFAULT_ERROR_PAYLOAD_PARSER = (
197-
Context context,
198-
Codec codec,
199-
KnownErrorFactory knownErrorFactory,
200-
ShapeId serviceId,
201-
TypeRegistry typeRegistry,
202-
HttpResponse response,
203-
ByteBuffer buffer) -> {
204-
var document = codec.createDeserializer(buffer).readDocument();
205-
var id = document.parseErrorType();
206-
var builder = typeRegistry.createBuilder(id, ModeledException.class);
207-
if (builder != null) {
208-
return knownErrorFactory.createErrorFromDocument(
209-
context,
210-
codec,
211-
response,
212-
buffer,
213-
document,
214-
builder);
194+
/**
195+
* An implementation of ErrorPayloadParser which provides default payload parsing and error type extraction for protocols
196+
* whose payload will be converted to a document.
197+
*/
198+
public static class DocumentPayloadParser implements ErrorPayloadParser {
199+
public CallException parsePayload(
200+
Context context,
201+
Codec codec,
202+
KnownErrorFactory knownErrorFactory,
203+
ShapeId serviceId,
204+
TypeRegistry typeRegistry,
205+
HttpResponse response,
206+
ByteBuffer buffer
207+
) {
208+
var document = codec.createDeserializer(buffer).readDocument();
209+
var id = extractErrorType(document, serviceId.getNamespace());
210+
var builder = typeRegistry.createBuilder(id, ModeledException.class);
211+
if (builder != null) {
212+
return knownErrorFactory.createErrorFromDocument(
213+
context,
214+
codec,
215+
response,
216+
buffer,
217+
document,
218+
builder);
219+
}
220+
return null;
215221
}
216-
return null;
217-
};
222+
223+
public ShapeId extractErrorType(Document document, String namespace) {
224+
return DocumentDeserializer.parseDiscriminator(
225+
ErrorTypeUtils.removeNamespaceAndUri(ErrorTypeUtils.readTypeAndCode(document)),
226+
namespace);
227+
}
228+
}
229+
230+
// This default parser should work for most protocols, but other protocols
231+
// that do not support document types will need a custom parser to extract error ShapeId.
232+
private static final ErrorPayloadParser DEFAULT_ERROR_PAYLOAD_PARSER = new DocumentPayloadParser();
218233

219234
private final Codec codec;
220235
private final HeaderErrorExtractor headerErrorExtractor;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.client.http;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
10+
import java.util.Map;
11+
import org.junit.jupiter.api.Test;
12+
import software.amazon.smithy.java.core.serde.document.Document;
13+
14+
public class ErrorTypeUtilsTest {
15+
private static final String SIMPLE_TYPE = "FooError";
16+
private static final String TYPE_WITH_URI = "FooError:http://amazon.com/smithy/com.amazon.smithy.validate/";
17+
private static final String TYPE_WITH_NAMESPACE = "aws.protocoltests.restjson#FooError";
18+
private static final String TYPE_WITH_NAMESPACE_AND_URI =
19+
"aws.protocoltests.restjson#FooError:http://amazon.com/smithy/com.amazon.smithy.validate/";
20+
21+
@Test
22+
public void testRemoveUri() {
23+
String expected = "FooError";
24+
String expectedWithNamespace = "aws.protocoltests.restjson#FooError";
25+
26+
assertEquals(expected, ErrorTypeUtils.removeUri(SIMPLE_TYPE));
27+
assertEquals(expected, ErrorTypeUtils.removeUri(TYPE_WITH_URI));
28+
assertEquals(expectedWithNamespace, ErrorTypeUtils.removeUri(TYPE_WITH_NAMESPACE));
29+
assertEquals(expectedWithNamespace, ErrorTypeUtils.removeUri(TYPE_WITH_NAMESPACE_AND_URI));
30+
}
31+
32+
@Test
33+
public void testRemoveNameSpaceAndUri() {
34+
String expected = "FooError";
35+
36+
assertEquals(expected, ErrorTypeUtils.removeNamespaceAndUri(SIMPLE_TYPE));
37+
assertEquals(expected, ErrorTypeUtils.removeNamespaceAndUri(TYPE_WITH_URI));
38+
assertEquals(expected, ErrorTypeUtils.removeNamespaceAndUri(TYPE_WITH_NAMESPACE));
39+
assertEquals(expected, ErrorTypeUtils.removeNamespaceAndUri(TYPE_WITH_NAMESPACE_AND_URI));
40+
}
41+
42+
@Test
43+
public void testReadType() {
44+
var document = Document.of(Map.of("__type", Document.of("foo")));
45+
assertEquals("foo", ErrorTypeUtils.readType(document));
46+
}
47+
48+
@Test
49+
public void testReadTypeAndCode() {
50+
var document1 = Document.of(Map.of("__type", Document.of("foo"), "code", Document.of("bar")));
51+
var document2 = Document.of(Map.of("code", Document.of("bar")));
52+
53+
assertEquals("foo", ErrorTypeUtils.readTypeAndCode(document1));
54+
assertEquals("bar", ErrorTypeUtils.readTypeAndCode(document2));
55+
}
56+
}

client/client-rpcv2-cbor/src/main/java/software/amazon/smithy/java/client/rpcv2/RpcV2CborProtocol.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import software.amazon.smithy.java.client.core.ClientProtocol;
1818
import software.amazon.smithy.java.client.core.ClientProtocolFactory;
1919
import software.amazon.smithy.java.client.core.ProtocolSettings;
20+
import software.amazon.smithy.java.client.http.ErrorTypeUtils;
2021
import software.amazon.smithy.java.client.http.HttpClientProtocol;
2122
import software.amazon.smithy.java.client.http.HttpErrorDeserializer;
2223
import software.amazon.smithy.java.context.Context;
@@ -27,6 +28,8 @@
2728
import software.amazon.smithy.java.core.schema.Unit;
2829
import software.amazon.smithy.java.core.serde.Codec;
2930
import software.amazon.smithy.java.core.serde.TypeRegistry;
31+
import software.amazon.smithy.java.core.serde.document.Document;
32+
import software.amazon.smithy.java.core.serde.document.DocumentDeserializer;
3033
import software.amazon.smithy.java.core.serde.event.EventDecoderFactory;
3134
import software.amazon.smithy.java.core.serde.event.EventEncoderFactory;
3235
import software.amazon.smithy.java.core.serde.event.EventStreamingException;
@@ -51,7 +54,11 @@ public final class RpcV2CborProtocol extends HttpClientProtocol {
5154
public RpcV2CborProtocol(ShapeId service) {
5255
super(Rpcv2CborTrait.ID);
5356
this.service = service;
54-
this.errorDeserializer = HttpErrorDeserializer.builder().codec(CBOR_CODEC).serviceId(service).build();
57+
this.errorDeserializer = HttpErrorDeserializer.builder()
58+
.codec(CBOR_CODEC)
59+
.serviceId(service)
60+
.errorPayloadParser(new CborParser())
61+
.build();
5562
}
5663

5764
@Override
@@ -163,6 +170,15 @@ private EventDecoderFactory<AwsEventFrame> getEventDecoderFactory(
163170
return AwsEventDecoderFactory.forOutputStream(outputOperation, payloadCodec(), f -> f);
164171
}
165172

173+
private static final class CborParser extends HttpErrorDeserializer.DocumentPayloadParser {
174+
@Override
175+
public ShapeId extractErrorType(Document document, String namespace) {
176+
return DocumentDeserializer.parseDiscriminator(
177+
ErrorTypeUtils.removeUri(ErrorTypeUtils.readType(document)),
178+
namespace);
179+
}
180+
}
181+
166182
public static final class Factory implements ClientProtocolFactory<Rpcv2CborTrait> {
167183
@Override
168184
public ShapeId id() {

0 commit comments

Comments
 (0)