Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ public class RestJson1ProtocolTests {
@HttpClientRequestTests
@ProtocolTestFilter(
skipTests = {
// TODO: These tests require a payload even when the httpPayload member is null. Should it?
"RestJsonHttpWithHeadersButNoPayload",
"RestJsonHttpWithEmptyStructurePayload",
"RestJsonHttpEmptyPrefixHeadersRequestClient" //FIXME https://github.com/smithy-lang/smithy-java/issues/647
})
public void requestTest(DataStream expected, DataStream actual) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package software.amazon.smithy.java.aws.client.restjson;

import java.net.URI;
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait;
import software.amazon.smithy.java.aws.events.AwsEventDecoderFactory;
import software.amazon.smithy.java.aws.events.AwsEventEncoderFactory;
Expand All @@ -17,14 +18,20 @@
import software.amazon.smithy.java.client.http.binding.HttpBindingClientProtocol;
import software.amazon.smithy.java.client.http.binding.HttpBindingErrorFactory;
import software.amazon.smithy.java.context.Context;
import software.amazon.smithy.java.core.schema.ApiOperation;
import software.amazon.smithy.java.core.schema.InputEventStreamingApiOperation;
import software.amazon.smithy.java.core.schema.OutputEventStreamingApiOperation;
import software.amazon.smithy.java.core.schema.SerializableStruct;
import software.amazon.smithy.java.core.schema.TraitKey;
import software.amazon.smithy.java.core.serde.Codec;
import software.amazon.smithy.java.core.serde.event.EventDecoderFactory;
import software.amazon.smithy.java.core.serde.event.EventEncoderFactory;
import software.amazon.smithy.java.core.serde.event.EventStreamingException;
import software.amazon.smithy.java.http.api.HttpRequest;
import software.amazon.smithy.java.http.binding.RequestSerializer;
import software.amazon.smithy.java.json.JsonCodec;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeType;

/**
* Implements aws.protocols#restJson1.
Expand Down Expand Up @@ -55,6 +62,29 @@ public RestJsonClientProtocol(ShapeId service) {
.build();
}

@Override
public <I extends SerializableStruct, O extends SerializableStruct> HttpRequest createRequest(
ApiOperation<I, O> operation,
I input,
Context context,
URI endpoint
) {
RequestSerializer serializer = httpBinding().requestSerializer()
.operation(operation)
.payloadCodec(payloadCodec())
.payloadMediaType(payloadMediaType())
.shapeValue(input)
.endpoint(endpoint)
.omitEmptyPayload(omitEmptyPayload())
.allowEmptyStructPayload(hasStructPayload(input));

if (operation instanceof InputEventStreamingApiOperation<?, ?, ?> i) {
serializer.eventEncoderFactory(getEventEncoderFactory(i));
}

return serializer.serializeRequest();
}

@Override
public Codec payloadCodec() {
return codec;
Expand Down Expand Up @@ -94,6 +124,17 @@ protected EventDecoderFactory<AwsEventFrame> getEventDecoderFactory(
return AwsEventDecoderFactory.forOutputStream(outputOperation, payloadCodec(), f -> f);
}

private <I extends SerializableStruct> boolean hasStructPayload(I input) {
var members = input.schema().members();
for (var member : members) {
if (member.type().equals(ShapeType.STRUCTURE)
&& member.hasTrait(TraitKey.HTTP_PAYLOAD_TRAIT)) {
return true;
}
}
return false;
}

public static final class Factory implements ClientProtocolFactory<RestJson1Trait> {
@Override
public ShapeId id() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ final class HttpBindingSerializer extends SpecificShapeSerializer implements Sha
private final String payloadMediaType;
private final boolean omitEmptyPayload;
private final boolean isFailure;
private final boolean allowEmptyStructPayload;

private final Map<String, String> labels = new LinkedHashMap<>();
private final Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Expand All @@ -75,7 +76,8 @@ final class HttpBindingSerializer extends SpecificShapeSerializer implements Sha
String payloadMediaType,
BindingMatcher bindingMatcher,
boolean omitEmptyPayload,
boolean isFailure
boolean isFailure,
boolean allowEmptyStructPayload
) {
uriPattern = httpTrait.getUri();
responseStatus = httpTrait.getCode();
Expand All @@ -84,6 +86,7 @@ final class HttpBindingSerializer extends SpecificShapeSerializer implements Sha
this.payloadMediaType = payloadMediaType;
this.omitEmptyPayload = omitEmptyPayload;
this.isFailure = isFailure;
this.allowEmptyStructPayload = allowEmptyStructPayload;
headerSerializer = new HttpHeaderSerializer(headerConsumer);
querySerializer = new HttpQuerySerializer(queryStringParams::add);
labelSerializer = new HttpLabelSerializer(labels::put);
Expand All @@ -95,7 +98,7 @@ public void writeStruct(Schema schema, SerializableStruct struct) {
responseStatus = bindingMatcher.responseStatus();
}

if (bindingMatcher.writeBody(omitEmptyPayload)) {
if (allowEmptyStructPayload || bindingMatcher.writeBody(omitEmptyPayload)) {
shapeBodyOutput = new ByteArrayOutputStream();
shapeBodySerializer = payloadCodec.createSerializer(shapeBodyOutput);
// Serialize only the body members to the codec.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class RequestSerializer {
private SerializableShape shapeValue;
private EventEncoderFactory<?> eventStreamEncodingFactory;
private boolean omitEmptyPayload = false;
private boolean allowEmptyStructPayload = false;
private final ConcurrentMap<Schema, BindingMatcher> bindingCache;

RequestSerializer(ConcurrentMap<Schema, BindingMatcher> bindingCache) {
Expand Down Expand Up @@ -119,6 +120,11 @@ public RequestSerializer omitEmptyPayload(boolean omitEmptyPayload) {
return this;
}

public RequestSerializer allowEmptyStructPayload(boolean allowEmptyStructPayload) {
this.allowEmptyStructPayload = allowEmptyStructPayload;
return this;
}

/**
* Finishes setting up the serializer and creates an HTTP request.
*
Expand All @@ -129,7 +135,6 @@ public HttpRequest serializeRequest() {
Objects.requireNonNull(operation, "operation is not set");
Objects.requireNonNull(payloadCodec, "payloadCodec is not set");
Objects.requireNonNull(endpoint, "endpoint is not set");
Objects.requireNonNull(shapeValue, "value is not set");
Objects.requireNonNull(payloadMediaType, "payloadMediaType is not set");

var matcher = bindingCache.computeIfAbsent(operation.inputSchema(), BindingMatcher::requestMatcher);
Expand All @@ -140,7 +145,8 @@ public HttpRequest serializeRequest() {
payloadMediaType,
matcher,
omitEmptyPayload,
false);
false,
allowEmptyStructPayload);
shapeValue.serialize(serializer);
serializer.flush();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ public HttpResponse serializeResponse() {
payloadMediaType,
bindingCache.computeIfAbsent(schema, BindingMatcher::responseMatcher),
omitEmptyPayload,
isFailure);
isFailure,
false);
shapeValue.serialize(serializer);
serializer.flush();

Expand Down