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 @@ -31,6 +31,14 @@ public class AwsQueryDeserializeBenchmark {
private static final String VERSION = "1999-12-31";
private static final String CONTENT_TYPE = "text/xml";

/**
* Empty body fixture for response tests that have no body. AWS Query responses are XML, so an empty
* {@code byte[]} cleanly hits the {@code XmlCodec} empty-buffer fast path. None of the current
* {@code GetMetricDataResponse_*} test cases have empty bodies, but keeping the fixture consistent with
* {@link RestXmlDeserializeBenchmark} makes the auto-derive workaround in {@link DeserializeState} unnecessary.
*/
private static final byte[] EMPTY_XML_BODY = new byte[0];

@Param({
"awsQuery_GetMetricDataResponse_S",
"awsQuery_GetMetricDataResponse_M",
Expand All @@ -44,7 +52,7 @@ public class AwsQueryDeserializeBenchmark {
@Setup
public void setup() {
protocol = new AwsQueryClientProtocol(SERVICE_ID, VERSION);
state = DeserializeState.forTestCase(testCaseId, GENERATED_PACKAGE, null, CONTENT_TYPE, false);
state = DeserializeState.forTestCase(testCaseId, GENERATED_PACKAGE, EMPTY_XML_BODY, CONTENT_TYPE, false);
}

@Benchmark
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ public class RestXmlDeserializeBenchmark {
"software.amazon.smithy.java.benchmarks.serde.generated.restxml.model";
private static final ShapeId SERVICE_ID =
ShapeId.from("com.amazonaws.sdk.benchmark#AwsRestXmlDataPlane");
/** Pass null to DeserializeState to use the auto-derived <ShapeName/> default. */
private static final byte[] EMPTY_XML_BODY = null;
/**
* Empty body fixture for response tests that have no body. Feeding zero bytes here accurately models the wire
* reality of an empty 200 response and exercises that fast path.
*/
private static final byte[] EMPTY_XML_BODY = new byte[0];
private static final String CONTENT_TYPE = "application/xml";

@Param({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.java.xml;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.time.Instant;
import software.amazon.smithy.java.core.schema.Schema;
import software.amazon.smithy.java.core.serde.SerializationException;
import software.amazon.smithy.java.core.serde.ShapeDeserializer;
import software.amazon.smithy.java.core.serde.document.Document;

/**
* A no-op {@link ShapeDeserializer} for empty XML payloads.
*/
final class EmptyXmlDeserializer implements ShapeDeserializer {

static final EmptyXmlDeserializer INSTANCE = new EmptyXmlDeserializer();

private EmptyXmlDeserializer() {}

@Override
public boolean isNull() {
return true;
}

@Override
public <T> void readStruct(Schema schema, T state, StructMemberConsumer<T> consumer) {}

@Override
public <T> void readList(Schema schema, T state, ListMemberConsumer<T> consumer) {}

@Override
public <T> void readStringMap(Schema schema, T state, MapMemberConsumer<String, T> consumer) {}

// Scalar reads are not valid at the top level of an empty XML body.
@Override
public boolean readBoolean(Schema schema) {
throw empty(schema);
}

@Override
public ByteBuffer readBlob(Schema schema) {
throw empty(schema);
}

@Override
public byte readByte(Schema schema) {
throw empty(schema);
}

@Override
public short readShort(Schema schema) {
throw empty(schema);
}

@Override
public int readInteger(Schema schema) {
throw empty(schema);
}

@Override
public long readLong(Schema schema) {
throw empty(schema);
}

@Override
public float readFloat(Schema schema) {
throw empty(schema);
}

@Override
public double readDouble(Schema schema) {
throw empty(schema);
}

@Override
public BigInteger readBigInteger(Schema schema) {
throw empty(schema);
}

@Override
public BigDecimal readBigDecimal(Schema schema) {
throw empty(schema);
}

@Override
public String readString(Schema schema) {
throw empty(schema);
}

@Override
public Document readDocument() {
throw new SerializationException("Cannot read a document value from an empty XML payload");
}

@Override
public Instant readTimestamp(Schema schema) {
throw empty(schema);
}

private static SerializationException empty(Schema schema) {
return new SerializationException("Cannot read " + schema.id() + " value from an empty XML payload");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ public ShapeSerializer createSerializer(OutputStream sink) {

@Override
public ShapeDeserializer createDeserializer(ByteBuffer source) {
if (source == null || !source.hasRemaining()) {
return EmptyXmlDeserializer.INSTANCE;
}

try {
var reader = xmlInputFactory.createXMLStreamReader(ByteBufferUtils.byteBufferInputStream(source));
return XmlDeserializer.topLevel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ public void deserializesXml() {
}
}

@Test
public void deserializesEmptyBody() {
try (var codec = XmlCodec.builder().build()) {
// Empty ByteBuffer: top-level readStruct should be a no-op
var pojo = codec.deserializeShape(ByteBuffer.allocate(0), new TestPojo.Builder());
assertThat(pojo.name, equalTo(null));
assertThat(pojo.date, equalTo(null));
assertThat(pojo.numbers, equalTo(List.of()));
}
}

@Test
public void serializesXml() {
try (var codec = XmlCodec.builder().build()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ public Schema member(String memberName) {
return resolvedMembers.members.get(memberName);
}

@Override
public Schema member(int memberIndex) {
resolveInternal();
return resolvedMembers.memberList.get(memberIndex);
}

@Override
public Set<Integer> intEnumValues() {
return intEnumValues;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public Schema member(String memberName) {
return members.get(memberName);
}

@Override
public Schema member(int memberIndex) {
return memberList.get(memberIndex);
}

@Override
public Set<Integer> intEnumValues() {
return intEnumValues;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ public Schema member(String memberName) {
return members.get(memberName);
}

@Override
public Schema member(int memberIndex) {
return memberList.get(memberIndex);
}

@Override
public Set<Integer> intEnumValues() {
return intEnumValues;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,22 @@ public Schema member(String memberName) {
return null;
}

/**
* Get a member by its index.
*
* <p>Index is the position of the member in declaration order, matching
* what {@link Schema#memberIndex()} returns for that member. Direct array
* indexing — significantly faster than {@link #member(String)} when the
* caller already has a member's index in hand (for example, when walking
* a precomputed plan).
*
* @param memberIndex Index of the member, 0-based in declaration order.
* @return the found member, or null if this schema has no such index.
*/
public Schema member(int memberIndex) {
return null;
}

/**
* Get the member of a list.
*
Expand Down
Loading