Skip to content

Commit 2353629

Browse files
committed
Cleanup how dynamic schemas work
Rather than make WrappedDocument and SchemaGuidedDocumentBuilder support all kinds of shape types, they now just support structure and document. This cleans up the handling of document and nested members so that nested structures don't have to be re-parsed each time we need to serialize them. It's also easier to figure out how the code works. Various bugs were fixed along the way and more test coverage was added.
1 parent e099948 commit 2353629

18 files changed

Lines changed: 913 additions & 909 deletions

File tree

aws/sdkv2/aws-sdkv2-shapes/src/test/java/software/amazon/smithy/java/aws/sdkv2/shapes/SdkDocumentWriterTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public static List<Arguments> restJsonTestCases() {
124124
"bar",
125125
software.amazon.awssdk.core.document.Document.fromString("a"),
126126
"BAZ",
127-
software.amazon.awssdk.core.document.Document.fromString("b"),
127+
software.amazon.awssdk.core.document.Document.fromNumber(2),
128128
"date",
129129
software.amazon.awssdk.core.document.Document
130130
.fromString(Instant.EPOCH.toString())))));
@@ -150,7 +150,7 @@ private static SerializableStruct createFoo(DynamicClient client) {
150150
"bar",
151151
Document.of("a"),
152152
"baz",
153-
Document.of("b"),
153+
Document.of(2),
154154
"date",
155155
Document.of(Instant.EPOCH))));
156156
}
@@ -170,7 +170,7 @@ public void awsJsonDoesNotUseJsonName() {
170170
"bar",
171171
software.amazon.awssdk.core.document.Document.fromString("a"),
172172
"baz",
173-
software.amazon.awssdk.core.document.Document.fromString("b"),
173+
software.amazon.awssdk.core.document.Document.fromNumber(2),
174174
"date",
175175
software.amazon.awssdk.core.document.Document.fromNumber(0.0)))));
176176
}

client/client-core/src/test/java/software/amazon/smithy/java/client/core/plugins/InjectIdempotencyTokenPluginTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private String callAndGetToken(String operation, Document input) {
9999
.addPlugin(mock)
100100
.build();
101101

102-
client.call(operation, Document.of(input));
102+
client.call(operation, input);
103103
assertThat(mock.getRequests(), not(empty()));
104104
return mock.getRequests().get(0).request().headers().firstValue("x-token");
105105
}

client/dynamic-client/src/main/java/software/amazon/smithy/java/dynamicclient/DocumentException.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@
1212
import software.amazon.smithy.java.core.serde.ShapeSerializer;
1313
import software.amazon.smithy.java.core.serde.document.Document;
1414
import software.amazon.smithy.java.dynamicschemas.SchemaConverter;
15-
import software.amazon.smithy.java.dynamicschemas.WrappedDocument;
15+
import software.amazon.smithy.java.dynamicschemas.StructDocument;
1616
import software.amazon.smithy.model.shapes.ShapeId;
1717

1818
/**
1919
* A {@link ModeledException} that provides access to the contents of the exception as a document.
2020
*/
2121
public final class DocumentException extends ModeledException {
2222

23-
private final WrappedDocument document;
23+
private final StructDocument document;
2424

25-
DocumentException(Schema schema, String message, WrappedDocument document) {
25+
DocumentException(Schema schema, String message, StructDocument document) {
2626
super(schema, message);
2727
this.document = document;
2828
}
@@ -54,7 +54,7 @@ public Document getContents() {
5454
public static final class SchemaGuidedExceptionBuilder implements ShapeBuilder<ModeledException> {
5555

5656
private final Schema target;
57-
private final ShapeBuilder<WrappedDocument> delegateBuilder;
57+
private final ShapeBuilder<StructDocument> delegateBuilder;
5858

5959
public SchemaGuidedExceptionBuilder(ShapeId service, Schema target) {
6060
this.target = target;

client/dynamic-client/src/main/java/software/amazon/smithy/java/dynamicclient/DynamicClient.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,13 @@
3131
import software.amazon.smithy.java.core.serde.TypeRegistry;
3232
import software.amazon.smithy.java.core.serde.document.Document;
3333
import software.amazon.smithy.java.dynamicschemas.SchemaConverter;
34-
import software.amazon.smithy.java.dynamicschemas.WrappedDocument;
34+
import software.amazon.smithy.java.dynamicschemas.StructDocument;
3535
import software.amazon.smithy.model.Model;
3636
import software.amazon.smithy.model.knowledge.ServiceIndex;
3737
import software.amazon.smithy.model.knowledge.TopDownIndex;
3838
import software.amazon.smithy.model.shapes.OperationShape;
3939
import software.amazon.smithy.model.shapes.ServiceShape;
4040
import software.amazon.smithy.model.shapes.ShapeId;
41-
import software.amazon.smithy.model.shapes.ShapeType;
4241
import software.amazon.smithy.model.shapes.ToShapeId;
4342

4443
/**
@@ -65,7 +64,7 @@ public final class DynamicClient extends Client {
6564

6665
private final ServiceShape service;
6766
private final Model model;
68-
private final ConcurrentMap<String, ApiOperation<WrappedDocument, WrappedDocument>> operations =
67+
private final ConcurrentMap<String, ApiOperation<StructDocument, StructDocument>> operations =
6968
new ConcurrentHashMap<>();
7069
private final SchemaConverter schemaConverter;
7170
private final Map<String, OperationShape> operationNames = new HashMap<>();
@@ -179,7 +178,7 @@ public CompletableFuture<Document> callAsync(
179178
RequestOverrideConfig overrideConfig
180179
) {
181180
var apiOperation = getApiOperation(operation);
182-
var inputStruct = new WrappedDocument(apiOperation.inputSchema(), input, service.getId());
181+
var inputStruct = StructDocument.of(apiOperation.inputSchema(), input, service.getId());
183182
return call(inputStruct, apiOperation, overrideConfig).thenApply(Function.identity());
184183
}
185184

@@ -192,13 +191,10 @@ public CompletableFuture<Document> callAsync(
192191
*/
193192
public SerializableStruct createStruct(ToShapeId shape, Document value) {
194193
var schema = schemaConverter.getSchema(model.expectShape(shape.toShapeId()));
195-
if (value.type() != ShapeType.MAP && value.type() != ShapeType.STRUCTURE) {
196-
throw new IllegalArgumentException("Document value must be a map or structure, found " + value.type());
197-
}
198-
return new WrappedDocument(schema, value, service.getId());
194+
return StructDocument.of(schema, value, service.getId());
199195
}
200196

201-
private ApiOperation<WrappedDocument, WrappedDocument> getApiOperation(String name) {
197+
private ApiOperation<StructDocument, StructDocument> getApiOperation(String name) {
202198
return operations.computeIfAbsent(name, operation -> {
203199
var shape = operationNames.get(name);
204200
if (shape == null) {

client/dynamic-client/src/main/java/software/amazon/smithy/java/dynamicclient/DynamicOperation.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
import software.amazon.smithy.java.core.schema.TraitKey;
1919
import software.amazon.smithy.java.core.serde.TypeRegistry;
2020
import software.amazon.smithy.java.dynamicschemas.SchemaConverter;
21-
import software.amazon.smithy.java.dynamicschemas.WrappedDocument;
21+
import software.amazon.smithy.java.dynamicschemas.StructDocument;
2222
import software.amazon.smithy.model.Model;
2323
import software.amazon.smithy.model.knowledge.ServiceIndex;
2424
import software.amazon.smithy.model.shapes.OperationShape;
2525
import software.amazon.smithy.model.shapes.ServiceShape;
2626
import software.amazon.smithy.model.shapes.ShapeId;
2727

28-
public final class DynamicOperation implements ApiOperation<WrappedDocument, WrappedDocument> {
28+
public final class DynamicOperation implements ApiOperation<StructDocument, StructDocument> {
2929

3030
private final ApiService service;
3131
private final Schema operationSchema;
@@ -84,12 +84,12 @@ public Schema outputSchema() {
8484
}
8585

8686
@Override
87-
public ShapeBuilder<WrappedDocument> inputBuilder() {
87+
public ShapeBuilder<StructDocument> inputBuilder() {
8888
return SchemaConverter.createDocumentBuilder(inputSchema(), service.schema().id());
8989
}
9090

9191
@Override
92-
public ShapeBuilder<WrappedDocument> outputBuilder() {
92+
public ShapeBuilder<StructDocument> outputBuilder() {
9393
return SchemaConverter.createDocumentBuilder(outputSchema(), service.schema().id());
9494
}
9595

core/src/main/java/software/amazon/smithy/java/core/serde/document/DocumentUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static void serializeNumber(ShapeSerializer serializer, Schema schema, Nu
4848
switch (schema.type()) {
4949
case BYTE -> serializer.writeByte(schema, value.byteValue());
5050
case SHORT -> serializer.writeShort(schema, value.shortValue());
51-
case INTEGER -> serializer.writeInteger(schema, value.intValue());
51+
case INTEGER, INT_ENUM -> serializer.writeInteger(schema, value.intValue());
5252
case LONG -> serializer.writeLong(schema, value.longValue());
5353
case FLOAT -> serializer.writeFloat(schema, value.floatValue());
5454
case DOUBLE -> serializer.writeDouble(schema, value.doubleValue());

core/src/main/java/software/amazon/smithy/java/core/serde/document/Documents.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public void serializeContents(ShapeSerializer serializer) {
191191
serializer.writeList(schema, values, values.size(), (values, ser) -> {
192192
for (var element : values) {
193193
if (element == null) {
194-
ser.writeNull(PreludeSchemas.DOCUMENT);
194+
ser.writeNull(schema.listMember());
195195
} else {
196196
element.serialize(ser);
197197
}

core/src/test/java/software/amazon/smithy/java/core/serde/document/ListDocumentTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import software.amazon.smithy.java.core.schema.Schema;
2020
import software.amazon.smithy.java.core.serde.ShapeSerializer;
2121
import software.amazon.smithy.java.core.serde.SpecificShapeSerializer;
22+
import software.amazon.smithy.model.shapes.ShapeId;
2223
import software.amazon.smithy.model.shapes.ShapeType;
2324

2425
public class ListDocumentTest {
@@ -106,13 +107,15 @@ public void writeDocument(Schema schema, Document value) {
106107

107108
@Override
108109
public void writeString(Schema schema, String value) {
109-
assertThat(schema, equalTo(PreludeSchemas.STRING));
110+
// We don't attempt to change the schema of a list member. Since the given list member
111+
// was a document that didn't use a member schema, it's reflected here.
112+
assertThat(schema.id(), equalTo(ShapeId.from("smithy.api#String")));
110113
writtenStrings.add(value);
111114
}
112115

113116
@Override
114117
public void writeNull(Schema schema) {
115-
assertThat(schema, equalTo(PreludeSchemas.DOCUMENT));
118+
assertThat(schema.id(), equalTo(ShapeId.from("smithy.api#Document$member")));
116119
writtenStrings.add(null);
117120
}
118121
});
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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.dynamicschemas;
7+
8+
import java.math.BigDecimal;
9+
import java.math.BigInteger;
10+
import java.nio.ByteBuffer;
11+
import java.time.Instant;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.Set;
15+
import software.amazon.smithy.java.core.schema.Schema;
16+
import software.amazon.smithy.java.core.schema.SerializableShape;
17+
import software.amazon.smithy.java.core.schema.ShapeBuilder;
18+
import software.amazon.smithy.java.core.serde.ShapeSerializer;
19+
import software.amazon.smithy.java.core.serde.document.Document;
20+
import software.amazon.smithy.model.shapes.ShapeType;
21+
22+
/**
23+
* A wrapper around another document that changes the serialized schema.
24+
*
25+
* @param document Document to wrap.
26+
* @param schema Schema to use instead.
27+
*/
28+
record ContentDocument(Document document, Schema schema) implements Document {
29+
@Override
30+
public ShapeType type() {
31+
return schema.type();
32+
}
33+
34+
@Override
35+
public void serialize(ShapeSerializer serializer) {
36+
// If it is literally a document, then don't unwrap it.
37+
if (type() == ShapeType.DOCUMENT) {
38+
serializer.writeDocument(schema, this);
39+
} else {
40+
serializeContents(serializer);
41+
}
42+
}
43+
44+
@Override
45+
public void serializeContents(ShapeSerializer serializer) {
46+
switch (type()) {
47+
case BOOLEAN -> serializer.writeBoolean(schema, asBoolean());
48+
case BYTE -> serializer.writeByte(schema, asByte());
49+
case SHORT -> serializer.writeShort(schema, asShort());
50+
case INTEGER, INT_ENUM -> serializer.writeInteger(schema, asInteger());
51+
case LONG -> serializer.writeLong(schema, asLong());
52+
case FLOAT -> serializer.writeFloat(schema, asFloat());
53+
case DOUBLE -> serializer.writeDouble(schema, asDouble());
54+
case BIG_INTEGER -> serializer.writeBigInteger(schema, asBigInteger());
55+
case BIG_DECIMAL -> serializer.writeBigDecimal(schema, asBigDecimal());
56+
case STRING, ENUM -> serializer.writeString(schema, asString());
57+
case TIMESTAMP -> serializer.writeTimestamp(schema, asTimestamp());
58+
case BLOB -> serializer.writeBlob(schema, asBlob());
59+
case DOCUMENT -> document.serializeContents(serializer);
60+
case LIST, SET -> {
61+
serializer.writeList(schema, asList(), size(), (values, ser) -> {
62+
for (var element : values) {
63+
if (element == null) {
64+
ser.writeNull(schema.listMember());
65+
} else {
66+
element.serialize(ser);
67+
}
68+
}
69+
});
70+
}
71+
case MAP -> {
72+
serializer.writeMap(schema, asStringMap(), size(), (members, s) -> {
73+
var key = schema.mapKeyMember();
74+
for (var entry : members.entrySet()) {
75+
if (entry.getValue() == null) {
76+
s.writeEntry(key, entry.getKey(), null, (t, v) -> v.writeNull(schema.mapValueMember()));
77+
} else {
78+
s.writeEntry(key, entry.getKey(), entry.getValue(), Document::serialize);
79+
}
80+
}
81+
});
82+
}
83+
// Note that Structure and Union are always going to be a StructDocument and appear here.
84+
default -> throw new UnsupportedOperationException("Unsupported type: " + type());
85+
}
86+
}
87+
88+
@Override
89+
public BigDecimal asBigDecimal() {
90+
return document.asBigDecimal();
91+
}
92+
93+
@Override
94+
public BigInteger asBigInteger() {
95+
return document.asBigInteger();
96+
}
97+
98+
@Override
99+
public ByteBuffer asBlob() {
100+
return document.asBlob();
101+
}
102+
103+
@Override
104+
public boolean asBoolean() {
105+
return document.asBoolean();
106+
}
107+
108+
@Override
109+
public byte asByte() {
110+
return document.asByte();
111+
}
112+
113+
@Override
114+
public double asDouble() {
115+
return document.asDouble();
116+
}
117+
118+
@Override
119+
public float asFloat() {
120+
return document.asFloat();
121+
}
122+
123+
@Override
124+
public int asInteger() {
125+
return document.asInteger();
126+
}
127+
128+
@Override
129+
public List<Document> asList() {
130+
return document.asList();
131+
}
132+
133+
@Override
134+
public long asLong() {
135+
return document.asLong();
136+
}
137+
138+
@Override
139+
public Number asNumber() {
140+
return document.asNumber();
141+
}
142+
143+
@Override
144+
public Object asObject() {
145+
return document.asObject();
146+
}
147+
148+
@Override
149+
public <T extends SerializableShape> T asShape(ShapeBuilder<T> builder) {
150+
return document.asShape(builder);
151+
}
152+
153+
@Override
154+
public short asShort() {
155+
return document.asShort();
156+
}
157+
158+
@Override
159+
public String asString() {
160+
return document.asString();
161+
}
162+
163+
@Override
164+
public Map<String, Document> asStringMap() {
165+
return document.asStringMap();
166+
}
167+
168+
@Override
169+
public Instant asTimestamp() {
170+
return document.asTimestamp();
171+
}
172+
173+
@Override
174+
public int size() {
175+
return document.size();
176+
}
177+
178+
@Override
179+
public Document getMember(String memberName) {
180+
return document.getMember(memberName);
181+
}
182+
183+
@Override
184+
public Set<String> getMemberNames() {
185+
return document.getMemberNames();
186+
}
187+
}

0 commit comments

Comments
 (0)