Skip to content

Commit e18a0f3

Browse files
committed
Add Document support to RPCv2 CBOR
1 parent 21a03f4 commit e18a0f3

File tree

10 files changed

+815
-49
lines changed

10 files changed

+815
-49
lines changed

codecs/cbor-codec/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ extra["moduleName"] = "software.amazon.smithy.java.cbor"
1111
dependencies {
1212
api(project(":core"))
1313
testFixturesImplementation(libs.assertj.core)
14+
testImplementation(project(":codecs:json-codec"))
1415
}

codecs/cbor-codec/src/main/java/software/amazon/smithy/java/cbor/CborDeserializer.java

+56-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.ArrayList;
1616
import java.util.Arrays;
1717
import java.util.HashMap;
18+
import java.util.LinkedHashMap;
1819
import java.util.List;
1920
import java.util.Map;
2021
import java.util.concurrent.ConcurrentHashMap;
@@ -92,15 +93,18 @@ private Schema getMemberIfSame(Object o, byte[] bytes, int off, int len) {
9293
private static final Map<Schema, Canonicalizer> CANONICALIZERS = new ConcurrentHashMap<>();
9394

9495
private final CborParser parser;
96+
private final CborSettings settings;
9597
private final byte[] payload;
9698

97-
CborDeserializer(byte[] payload) {
99+
CborDeserializer(byte[] payload, CborSettings settings) {
98100
this.parser = new CborParser(payload);
101+
this.settings = settings;
99102
this.payload = payload;
100103
parser.advance();
101104
}
102105

103-
CborDeserializer(ByteBuffer byteBuffer) {
106+
CborDeserializer(ByteBuffer byteBuffer, CborSettings settings) {
107+
this.settings = settings;
104108
if (byteBuffer.hasArray()) {
105109
byte[] payload = byteBuffer.array();
106110
this.payload = payload;
@@ -248,12 +252,12 @@ private static float float16(int hbits) {
248252

249253
@Override
250254
public BigInteger readBigInteger(Schema schema) {
251-
return null;
255+
throw new UnsupportedOperationException("BigInteger is not supported yet");
252256
}
253257

254258
@Override
255259
public BigDecimal readBigDecimal(Schema schema) {
256-
return null;
260+
throw new UnsupportedOperationException("BigDecimal is not supported yet");
257261
}
258262

259263
@Override
@@ -267,7 +271,54 @@ public String readString(Schema schema) {
267271

268272
@Override
269273
public Document readDocument() {
270-
throw new UnsupportedOperationException("RPCv2 does not support Documents");
274+
var token = parser.currentToken();
275+
if (token == Token.FINISHED) {
276+
throw new SerializationException("No CBOR value to read");
277+
}
278+
return switch (token) {
279+
case Token.POS_INT, Token.NEG_INT -> Document.of(readLong(null));
280+
case Token.NULL -> null;
281+
case Token.TEXT_STRING -> Document.of(readString(null));
282+
case Token.BYTE_STRING -> Document.of(readBlob(null));
283+
case Token.TRUE -> Document.of(true);
284+
case Token.FALSE -> Document.of(false);
285+
case Token.EPOCH_INEG, Token.EPOCH_IPOS, Token.EPOCH_F -> Document.of(readTimestamp(null));
286+
case Token.FLOAT -> {
287+
int pos = parser.getPosition();
288+
int len = parser.getItemLength();
289+
long fp = CborReadUtil.readLong(payload, token, pos, len);
290+
// ordered by how likely it is we'll encounter each case
291+
if (len == 8) { // double
292+
yield Document.of(Double.longBitsToDouble(fp));
293+
} else if (len == 4) { // float
294+
yield Document.of(Float.intBitsToFloat((int) fp));
295+
} else { // b == 2 - half-precision float
296+
yield Document.of(float16((int) fp));
297+
}
298+
}
299+
case Token.POS_BIGINT, Token.NEG_BIGINT -> Document.of(readBigInteger(null));
300+
case Token.START_ARRAY -> {
301+
List<Document> values = new ArrayList<>();
302+
for (token = parser.advance(); token != Token.END_ARRAY; token = parser.advance()) {
303+
values.add(readDocument());
304+
}
305+
yield Document.of(values);
306+
}
307+
case Token.START_OBJECT -> {
308+
Map<String, Document> values = new LinkedHashMap<>();
309+
for (token = parser.advance(); token != Token.END_OBJECT; token = parser.advance()) {
310+
if (token != Token.KEY) {
311+
throw badType("struct member", token);
312+
}
313+
314+
var key = CborReadUtil.readTextString(payload, parser.getPosition(), parser.getItemLength());
315+
parser.advance();
316+
values.put(key, readDocument());
317+
}
318+
yield CborDocuments.of(values, settings);
319+
}
320+
default -> throw new SerializationException("Unexpected token: " + Token.name(token));
321+
};
271322
}
272323

273324
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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.cbor;
7+
8+
import java.util.Map;
9+
import java.util.Set;
10+
import software.amazon.smithy.java.core.schema.PreludeSchemas;
11+
import software.amazon.smithy.java.core.schema.Schema;
12+
import software.amazon.smithy.java.core.serde.ShapeDeserializer;
13+
import software.amazon.smithy.java.core.serde.ShapeSerializer;
14+
import software.amazon.smithy.java.core.serde.document.Document;
15+
import software.amazon.smithy.java.core.serde.document.DocumentDeserializer;
16+
import software.amazon.smithy.model.shapes.ShapeId;
17+
import software.amazon.smithy.model.shapes.ShapeType;
18+
import software.amazon.smithy.utils.SmithyInternalApi;
19+
20+
@SmithyInternalApi
21+
public final class CborDocuments {
22+
23+
private static final Schema STRING_MAP_KEY;
24+
25+
static {
26+
var tempSchema = Schema.structureBuilder(PreludeSchemas.DOCUMENT.id())
27+
.putMember("key", PreludeSchemas.STRING)
28+
.build();
29+
STRING_MAP_KEY = tempSchema.mapKeyMember();
30+
}
31+
32+
private CborDocuments() {}
33+
34+
public static Document of(Map<String, Document> values, CborSettings settings) {
35+
return new MapDocument(values, settings);
36+
}
37+
38+
private static final class MapDocument implements Document {
39+
private final Map<String, Document> values;
40+
private final CborSettings settings;
41+
42+
private MapDocument(Map<String, Document> values, CborSettings settings) {
43+
this.values = values;
44+
this.settings = settings;
45+
}
46+
47+
@Override
48+
public ShapeType type() {
49+
return ShapeType.MAP;
50+
}
51+
52+
@Override
53+
public ShapeId discriminator() {
54+
String discriminator = null;
55+
var member = values.get("__type");
56+
if (member != null && member.type() == ShapeType.STRING) {
57+
discriminator = member.asString();
58+
}
59+
return DocumentDeserializer.parseDiscriminator(discriminator, settings.defaultNamespace());
60+
}
61+
62+
@Override
63+
public Map<String, Document> asStringMap() {
64+
return values;
65+
}
66+
67+
@Override
68+
public Document getMember(String memberName) {
69+
return values.get(memberName);
70+
}
71+
72+
@Override
73+
public Set<String> getMemberNames() {
74+
return values.keySet();
75+
}
76+
77+
@Override
78+
public int size() {
79+
return values.size();
80+
}
81+
82+
@Override
83+
public ShapeDeserializer createDeserializer() {
84+
return new CborDocumentSerializer(settings, this);
85+
}
86+
87+
@Override
88+
public void serializeContents(ShapeSerializer serializer) {
89+
serializer.writeMap(PreludeSchemas.DOCUMENT, values, values.size(), (stringMap, mapSerializer) -> {
90+
for (var e : stringMap.entrySet()) {
91+
mapSerializer.writeEntry(STRING_MAP_KEY, e.getKey(), e.getValue(), Document::serializeContents);
92+
}
93+
});
94+
}
95+
96+
@Override
97+
public boolean equals(Object obj) {
98+
return Document.equals(this, obj);
99+
}
100+
101+
@Override
102+
public int hashCode() {
103+
return values.hashCode();
104+
}
105+
}
106+
107+
/**
108+
* Customized version of DocumentDeserializer to account for the settings of the CBOR codec.
109+
*/
110+
private static final class CborDocumentSerializer extends DocumentDeserializer {
111+
112+
private final CborSettings settings;
113+
114+
CborDocumentSerializer(CborSettings settings, Document value) {
115+
super(value);
116+
this.settings = settings;
117+
}
118+
119+
@Override
120+
protected DocumentDeserializer deserializer(Document nextValue) {
121+
return new CborDocumentSerializer(settings, nextValue);
122+
}
123+
}
124+
}

codecs/cbor-codec/src/main/java/software/amazon/smithy/java/cbor/CborSerdeProvider.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ public interface CborSerdeProvider {
1616

1717
String getName();
1818

19-
ShapeDeserializer newDeserializer(byte[] source, Rpcv2CborCodec.Settings settings);
19+
ShapeDeserializer newDeserializer(byte[] source, CborSettings settings);
2020

21-
ShapeDeserializer newDeserializer(ByteBuffer source, Rpcv2CborCodec.Settings settings);
21+
ShapeDeserializer newDeserializer(ByteBuffer source, CborSettings settings);
2222

23-
ShapeSerializer newSerializer(OutputStream sink, Rpcv2CborCodec.Settings settings);
23+
ShapeSerializer newSerializer(OutputStream sink, CborSettings settings);
2424

25-
ByteBuffer serialize(SerializableStruct struct, Rpcv2CborCodec.Settings settings);
25+
ByteBuffer serialize(SerializableStruct struct, CborSettings settings);
2626
}

codecs/cbor-codec/src/main/java/software/amazon/smithy/java/cbor/CborSerializer.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@
3636
import software.amazon.smithy.java.core.schema.SerializableStruct;
3737
import software.amazon.smithy.java.core.serde.InterceptingSerializer;
3838
import software.amazon.smithy.java.core.serde.MapSerializer;
39+
import software.amazon.smithy.java.core.serde.SerializationException;
3940
import software.amazon.smithy.java.core.serde.ShapeSerializer;
41+
import software.amazon.smithy.java.core.serde.SpecificShapeSerializer;
4042
import software.amazon.smithy.java.core.serde.document.Document;
43+
import software.amazon.smithy.model.shapes.ShapeType;
4144

4245
final class CborSerializer implements ShapeSerializer {
4346
private static final int MAP_STREAM = TYPE_MAP | INDEFINITE;
@@ -48,6 +51,7 @@ final class CborSerializer implements ShapeSerializer {
4851
private final Sink sink;
4952
private final CborMapSerializer mapSerializer = new CborMapSerializer();
5053
private final CborStructSerializer structSerializer = new CborStructSerializer();
54+
private SerializeDocumentContents serializeDocumentContents;
5155

5256
public CborSerializer(Sink sink) {
5357
this.sink = sink;
@@ -266,7 +270,14 @@ public void writeBigDecimal(Schema schema, BigDecimal value) {
266270

267271
@Override
268272
public void writeDocument(Schema schema, Document value) {
269-
throw new UnsupportedOperationException();
273+
if (value.type() != ShapeType.STRUCTURE) {
274+
value.serializeContents(this);
275+
} else {
276+
if (serializeDocumentContents == null) {
277+
serializeDocumentContents = new SerializeDocumentContents(this);
278+
}
279+
value.serializeContents(serializeDocumentContents);
280+
}
270281
}
271282

272283
private final class CborStructSerializer extends InterceptingSerializer {
@@ -292,4 +303,26 @@ public <T> void writeEntry(
292303
valueSerializer.accept(state, CborSerializer.this);
293304
}
294305
}
306+
307+
private static final class SerializeDocumentContents extends SpecificShapeSerializer {
308+
private final CborSerializer parent;
309+
310+
SerializeDocumentContents(CborSerializer parent) {
311+
this.parent = parent;
312+
}
313+
314+
@Override
315+
public void writeStruct(Schema schema, SerializableStruct struct) {
316+
try {
317+
parent.startMap(-1);
318+
parent.tagAndLength(TYPE_TEXTSTRING, 6);
319+
parent.sink.writeAscii("__type");
320+
parent.writeString(null, schema.id().toString());
321+
struct.serializeMembers(parent.structSerializer);
322+
parent.endMap();
323+
} catch (Exception e) {
324+
throw new SerializationException(e);
325+
}
326+
}
327+
}
295328
}

0 commit comments

Comments
 (0)