Skip to content

Commit 5889fa5

Browse files
committed
Refactor JSON and Protobuf wire formats
Motivation: - Reduce unnecessary complexity by introducing a flag-based implementation for `JsonWireFormat`. Changes: - Simplified `JsonWireFormat` by replacing boolean fields with a compact flag-based system. - Added caching for `JsonWireFormat` to improve memory efficiency. - Introduced a `mediaType` method to both `JsonWireFormat` and `ProtobufWireFormat` for determining content type dynamically. - Updated all components to use the new media type method for content type handling. - Replaced hardcoded wire format comparisons with reusable `mediaType`. Signed-off-by: Daniel Fiala <danfiala23@gmail.com>
1 parent 9d8a4ff commit 5889fa5

13 files changed

Lines changed: 117 additions & 119 deletions

File tree

vertx-grpc-client/src/main/java/io/vertx/grpc/client/impl/Http2GrpcOutboundStream.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
import io.vertx.grpc.common.GrpcError;
1515
import io.vertx.grpc.common.GrpcHeaderNames;
1616
import io.vertx.grpc.common.GrpcMessage;
17-
import io.vertx.grpc.common.JsonWireFormat;
18-
import io.vertx.grpc.common.ProtobufWireFormat;
1917
import io.vertx.grpc.common.ServiceName;
20-
import io.vertx.grpc.common.WireFormat;
2118
import io.vertx.grpc.common.impl.DefaultGrpcMessage;
2219
import io.vertx.grpc.common.impl.GrpcFrame;
2320
import io.vertx.grpc.common.impl.GrpcHeadersFrame;
@@ -99,15 +96,7 @@ private Future<Void> handleHeadersFrame(GrpcHeadersFrame frame, boolean end) {
9996
httpRequest.putHeader(GrpcHeaderNames.GRPC_TIMEOUT, headerValue);
10097
}
10198

102-
String contentType;
103-
WireFormat format = frame.format();
104-
if (format instanceof ProtobufWireFormat) {
105-
contentType = "application/grpc";
106-
} else if (format instanceof JsonWireFormat) {
107-
contentType = "application/grpc+json";
108-
} else {
109-
throw new UnsupportedOperationException();
110-
}
99+
String contentType = frame.format().mediaType();
111100

112101
String uri = serviceName.pathOf(methodName);
113102
httpRequest.putHeader(HttpHeaders.CONTENT_TYPE, contentType);

vertx-grpc-common/src/main/java/io/vertx/grpc/common/GrpcMessageDecoder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.vertx.core.json.Json;
2222
import io.vertx.core.json.JsonObject;
2323
import io.vertx.core.json.JsonArray;
24+
import io.vertx.grpc.common.impl.ProtobufJsonReader;
2425

2526
import java.util.function.Supplier;
2627

vertx-grpc-common/src/main/java/io/vertx/grpc/common/GrpcMessageEncoder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.vertx.core.buffer.Buffer;
77
import io.vertx.core.json.Json;
88
import io.vertx.core.json.JsonObject;
9+
import io.vertx.grpc.common.impl.ProtobufJsonWriter;
910

1011
public interface GrpcMessageEncoder<T> {
1112

vertx-grpc-common/src/main/java/io/vertx/grpc/common/JsonWireFormat.java

Lines changed: 92 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import io.vertx.core.json.JsonObject;
55

66
/**
7-
* JSON {@link WireFormat}. Carries the protobuf-aware writer/reader flags that
8-
* {@link ProtobufJsonWriter} and {@link ProtobufJsonReader} consult.
7+
* JSON {@link WireFormat}. Carries the protobuf-aware writer/reader flags that the JSON
8+
* encoder and decoder consult.
99
* <p>
1010
* Instances are immutable. Each flag setter returns a new instance with that flag updated:
1111
* <pre>
@@ -19,160 +19,150 @@ public class JsonWireFormat implements WireFormat {
1919

2020
public static final String NAME = "json";
2121

22-
/**
23-
* Default {@code alwaysPrintFieldsWithNoPresence} flag = {@code false}.
24-
*/
25-
public static final boolean DEFAULT_ALWAYS_PRINT_FIELDS_WITH_NO_PRESENCE = false;
22+
private static final String MEDIA_TYPE = "application/grpc+json";
23+
private static final JsonWireFormat[] CACHE = new JsonWireFormat[1 << Flag.values().length];
2624

27-
/**
28-
* Default {@code omittingInsignificantWhitespace} flag = {@code false}.
29-
*/
30-
public static final boolean DEFAULT_OMITTING_INSIGNIFICANT_WHITESPACE = false;
25+
private final byte flags;
3126

32-
/**
33-
* Default {@code preservingProtoFieldNames} flag = {@code false}.
34-
*/
35-
public static final boolean DEFAULT_PRESERVING_PROTO_FIELD_NAMES = false;
27+
public JsonWireFormat() {
28+
this((byte) 0);
29+
}
3630

37-
/**
38-
* Default {@code printingEnumsAsInts} flag = {@code false}.
39-
*/
40-
public static final boolean DEFAULT_PRINTING_ENUMS_AS_INTS = false;
31+
private JsonWireFormat(byte flags) {
32+
this.flags = flags;
33+
}
4134

42-
/**
43-
* Default {@code sortingMapKeys} flag = {@code false}.
44-
*/
45-
public static final boolean DEFAULT_SORTING_MAP_KEYS = false;
35+
public JsonWireFormat(JsonObject json) {
36+
this(read(json));
37+
}
4638

47-
/**
48-
* Default {@code ignoringUnknownFields} flag = {@code false}.
49-
*/
50-
public static final boolean DEFAULT_IGNORING_UNKNOWN_FIELDS = false;
39+
static JsonWireFormat of(int flags) {
40+
JsonWireFormat fmt = CACHE[flags];
41+
if (fmt == null) {
42+
fmt = new JsonWireFormat((byte) flags);
43+
CACHE[flags] = fmt;
44+
}
45+
return fmt;
46+
}
5147

52-
private final boolean alwaysPrintFieldsWithNoPresence;
53-
private final boolean omittingInsignificantWhitespace;
54-
private final boolean preservingProtoFieldNames;
55-
private final boolean printingEnumsAsInts;
56-
private final boolean sortingMapKeys;
57-
private final boolean ignoringUnknownFields;
48+
private static byte read(JsonObject json) {
49+
int flags = 0;
50+
for (Flag flag : Flag.values()) {
51+
if (json.getBoolean(flag.key, false)) {
52+
flags |= flag.mask;
53+
}
54+
}
55+
return (byte) flags;
56+
}
5857

59-
public JsonWireFormat() {
60-
this(
61-
DEFAULT_ALWAYS_PRINT_FIELDS_WITH_NO_PRESENCE,
62-
DEFAULT_OMITTING_INSIGNIFICANT_WHITESPACE,
63-
DEFAULT_PRESERVING_PROTO_FIELD_NAMES,
64-
DEFAULT_PRINTING_ENUMS_AS_INTS,
65-
DEFAULT_SORTING_MAP_KEYS,
66-
DEFAULT_IGNORING_UNKNOWN_FIELDS
67-
);
68-
}
69-
70-
private JsonWireFormat(
71-
boolean alwaysPrintFieldsWithNoPresence,
72-
boolean omittingInsignificantWhitespace,
73-
boolean preservingProtoFieldNames,
74-
boolean printingEnumsAsInts,
75-
boolean sortingMapKeys,
76-
boolean ignoringUnknownFields
77-
) {
78-
this.alwaysPrintFieldsWithNoPresence = alwaysPrintFieldsWithNoPresence;
79-
this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
80-
this.preservingProtoFieldNames = preservingProtoFieldNames;
81-
this.printingEnumsAsInts = printingEnumsAsInts;
82-
this.sortingMapKeys = sortingMapKeys;
83-
this.ignoringUnknownFields = ignoringUnknownFields;
58+
private boolean isSet(Flag flag) {
59+
return (flags & flag.mask) != 0;
8460
}
8561

86-
public JsonWireFormat(JsonObject json) {
87-
this(
88-
json.getBoolean("alwaysPrintFieldsWithNoPresence", DEFAULT_ALWAYS_PRINT_FIELDS_WITH_NO_PRESENCE),
89-
json.getBoolean("omittingInsignificantWhitespace", DEFAULT_OMITTING_INSIGNIFICANT_WHITESPACE),
90-
json.getBoolean("preservingProtoFieldNames", DEFAULT_PRESERVING_PROTO_FIELD_NAMES),
91-
json.getBoolean("printingEnumsAsInts", DEFAULT_PRINTING_ENUMS_AS_INTS),
92-
json.getBoolean("sortingMapKeys", DEFAULT_SORTING_MAP_KEYS),
93-
json.getBoolean("ignoringUnknownFields", DEFAULT_IGNORING_UNKNOWN_FIELDS)
94-
);
62+
private JsonWireFormat with(Flag flag, boolean value) {
63+
return of(value ? flags | flag.mask : flags & ~flag.mask);
9564
}
9665

9766
@Override
9867
public final String name() {
9968
return NAME;
10069
}
10170

71+
@Override
72+
public String mediaType() {
73+
return MEDIA_TYPE;
74+
}
75+
76+
/**
77+
* @return whether fields without presence are always printed, including those left at their default value
78+
*/
10279
public boolean alwaysPrintFieldsWithNoPresence() {
103-
return alwaysPrintFieldsWithNoPresence;
80+
return isSet(Flag.ALWAYS_PRINT_FIELDS_WITH_NO_PRESENCE);
10481
}
10582

10683
/**
10784
* @return a copy of this format with {@code alwaysPrintFieldsWithNoPresence} set to {@code value}
10885
*/
10986
public JsonWireFormat alwaysPrintFieldsWithNoPresence(boolean value) {
110-
return new JsonWireFormat(value, omittingInsignificantWhitespace, preservingProtoFieldNames, printingEnumsAsInts, sortingMapKeys, ignoringUnknownFields);
87+
return with(Flag.ALWAYS_PRINT_FIELDS_WITH_NO_PRESENCE, value);
11188
}
11289

90+
/**
91+
* @return whether insignificant whitespace is omitted, producing a compact single-line output
92+
*/
11393
public boolean omittingInsignificantWhitespace() {
114-
return omittingInsignificantWhitespace;
94+
return isSet(Flag.OMITTING_INSIGNIFICANT_WHITESPACE);
11595
}
11696

11797
/**
11898
* @return a copy of this format with {@code omittingInsignificantWhitespace} set to {@code value}
11999
*/
120100
public JsonWireFormat omittingInsignificantWhitespace(boolean value) {
121-
return new JsonWireFormat(alwaysPrintFieldsWithNoPresence, value, preservingProtoFieldNames, printingEnumsAsInts, sortingMapKeys, ignoringUnknownFields);
101+
return with(Flag.OMITTING_INSIGNIFICANT_WHITESPACE, value);
122102
}
123103

104+
/**
105+
* @return whether the original proto field names are used instead of the lowerCamelCase JSON names
106+
*/
124107
public boolean preservingProtoFieldNames() {
125-
return preservingProtoFieldNames;
108+
return isSet(Flag.PRESERVING_PROTO_FIELD_NAMES);
126109
}
127110

128111
/**
129112
* @return a copy of this format with {@code preservingProtoFieldNames} set to {@code value}
130113
*/
131114
public JsonWireFormat preservingProtoFieldNames(boolean value) {
132-
return new JsonWireFormat(alwaysPrintFieldsWithNoPresence, omittingInsignificantWhitespace, value, printingEnumsAsInts, sortingMapKeys, ignoringUnknownFields);
115+
return with(Flag.PRESERVING_PROTO_FIELD_NAMES, value);
133116
}
134117

118+
/**
119+
* @return whether enum values are printed as their integer number instead of their name
120+
*/
135121
public boolean printingEnumsAsInts() {
136-
return printingEnumsAsInts;
122+
return isSet(Flag.PRINTING_ENUMS_AS_INTS);
137123
}
138124

139125
/**
140126
* @return a copy of this format with {@code printingEnumsAsInts} set to {@code value}
141127
*/
142128
public JsonWireFormat printingEnumsAsInts(boolean value) {
143-
return new JsonWireFormat(alwaysPrintFieldsWithNoPresence, omittingInsignificantWhitespace, preservingProtoFieldNames, value, sortingMapKeys, ignoringUnknownFields);
129+
return with(Flag.PRINTING_ENUMS_AS_INTS, value);
144130
}
145131

132+
/**
133+
* @return whether map entries are emitted with their keys sorted, for deterministic output
134+
*/
146135
public boolean sortingMapKeys() {
147-
return sortingMapKeys;
136+
return isSet(Flag.SORTING_MAP_KEYS);
148137
}
149138

150139
/**
151140
* @return a copy of this format with {@code sortingMapKeys} set to {@code value}
152141
*/
153142
public JsonWireFormat sortingMapKeys(boolean value) {
154-
return new JsonWireFormat(alwaysPrintFieldsWithNoPresence, omittingInsignificantWhitespace, preservingProtoFieldNames, printingEnumsAsInts, value, ignoringUnknownFields);
143+
return with(Flag.SORTING_MAP_KEYS, value);
155144
}
156145

146+
/**
147+
* @return whether unknown fields encountered while parsing are ignored rather than rejected
148+
*/
157149
public boolean ignoringUnknownFields() {
158-
return ignoringUnknownFields;
150+
return isSet(Flag.IGNORING_UNKNOWN_FIELDS);
159151
}
160152

161153
/**
162154
* @return a copy of this format with {@code ignoringUnknownFields} set to {@code value}
163155
*/
164156
public JsonWireFormat ignoringUnknownFields(boolean value) {
165-
return new JsonWireFormat(alwaysPrintFieldsWithNoPresence, omittingInsignificantWhitespace, preservingProtoFieldNames, printingEnumsAsInts, sortingMapKeys, value);
157+
return with(Flag.IGNORING_UNKNOWN_FIELDS, value);
166158
}
167159

168160
public JsonObject toJson() {
169-
return new JsonObject()
170-
.put("alwaysPrintFieldsWithNoPresence", alwaysPrintFieldsWithNoPresence)
171-
.put("omittingInsignificantWhitespace", omittingInsignificantWhitespace)
172-
.put("preservingProtoFieldNames", preservingProtoFieldNames)
173-
.put("printingEnumsAsInts", printingEnumsAsInts)
174-
.put("sortingMapKeys", sortingMapKeys)
175-
.put("ignoringUnknownFields", ignoringUnknownFields);
161+
JsonObject json = new JsonObject();
162+
for (Flag flag : Flag.values()) {
163+
json.put(flag.key, isSet(flag));
164+
}
165+
return json;
176166
}
177167

178168
@Override
@@ -195,4 +185,21 @@ public int hashCode() {
195185
public String toString() {
196186
return NAME;
197187
}
188+
189+
private enum Flag {
190+
ALWAYS_PRINT_FIELDS_WITH_NO_PRESENCE("alwaysPrintFieldsWithNoPresence"),
191+
OMITTING_INSIGNIFICANT_WHITESPACE("omittingInsignificantWhitespace"),
192+
PRESERVING_PROTO_FIELD_NAMES("preservingProtoFieldNames"),
193+
PRINTING_ENUMS_AS_INTS("printingEnumsAsInts"),
194+
SORTING_MAP_KEYS("sortingMapKeys"),
195+
IGNORING_UNKNOWN_FIELDS("ignoringUnknownFields");
196+
197+
final String key;
198+
final int mask;
199+
200+
Flag(String key) {
201+
this.key = key;
202+
this.mask = 1 << ordinal();
203+
}
204+
}
198205
}

vertx-grpc-common/src/main/java/io/vertx/grpc/common/ProtobufWireFormat.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ public class ProtobufWireFormat implements WireFormat {
99

1010
public static final String NAME = "proto";
1111

12+
private static final String MEDIA_TYPE = "application/grpc";
13+
1214
@Override
1315
public final String name() {
1416
return NAME;
1517
}
1618

19+
@Override
20+
public String mediaType() {
21+
return MEDIA_TYPE;
22+
}
23+
1724
@Override
1825
public boolean equals(Object o) {
1926
if (this == o) {

vertx-grpc-common/src/main/java/io/vertx/grpc/common/WireFormat.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ public interface WireFormat {
3131
/**
3232
* Canonical JSON wire format.
3333
*/
34-
JsonWireFormat JSON = new JsonWireFormat();
34+
JsonWireFormat JSON = JsonWireFormat.of(0);
3535

3636
/**
3737
* @return the canonical name of this wire format, e.g. {@code "proto"} or {@code "json"}
3838
*/
3939
String name();
40+
41+
/**
42+
* @return the media type carrying this wire format, e.g. {@code "application/grpc"} or {@code "application/grpc+json"}
43+
*/
44+
String mediaType();
4045
}

vertx-grpc-common/src/main/java/io/vertx/grpc/common/ProtobufJsonReader.java renamed to vertx-grpc-common/src/main/java/io/vertx/grpc/common/impl/ProtobufJsonReader.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
package io.vertx.grpc.common;
1+
package io.vertx.grpc.common.impl;
22

33
import com.google.protobuf.Message;
4-
import io.vertx.codegen.annotations.GenIgnore;
54
import io.vertx.core.buffer.Buffer;
6-
import io.vertx.grpc.common.impl.ProtobufJsonReaderImpl;
5+
import io.vertx.grpc.common.CodecException;
6+
import io.vertx.grpc.common.JsonWireFormat;
77

88
/**
99
* Reads JSON {@link Buffer} payloads into a protobuf {@link Message.Builder}.
1010
* <p>
1111
* Use {@link #create(JsonWireFormat)} to get an instance. The default implementation is backed
1212
* by {@code com.google.protobuf.util.JsonFormat}.
1313
*/
14-
@GenIgnore(GenIgnore.PERMITTED_TYPE)
1514
public interface ProtobufJsonReader {
1615

1716
/**

vertx-grpc-common/src/main/java/io/vertx/grpc/common/impl/ProtobufJsonReaderImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import io.vertx.core.buffer.Buffer;
77
import io.vertx.grpc.common.CodecException;
88
import io.vertx.grpc.common.JsonWireFormat;
9-
import io.vertx.grpc.common.ProtobufJsonReader;
109

1110
import java.nio.charset.StandardCharsets;
1211

vertx-grpc-common/src/main/java/io/vertx/grpc/common/ProtobufJsonWriter.java renamed to vertx-grpc-common/src/main/java/io/vertx/grpc/common/impl/ProtobufJsonWriter.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
package io.vertx.grpc.common;
1+
package io.vertx.grpc.common.impl;
22

33
import com.google.protobuf.MessageOrBuilder;
4-
import io.vertx.codegen.annotations.GenIgnore;
54
import io.vertx.core.buffer.Buffer;
6-
import io.vertx.grpc.common.impl.ProtobufJsonWriterImpl;
5+
import io.vertx.grpc.common.CodecException;
6+
import io.vertx.grpc.common.JsonWireFormat;
77

88
/**
99
* Writes a protobuf {@link MessageOrBuilder} as a JSON {@link Buffer}.
1010
* <p>
1111
* Use {@link #create(JsonWireFormat)} to get an instance. The default implementation is backed
1212
* by {@code com.google.protobuf.util.JsonFormat}.
1313
*/
14-
@GenIgnore(GenIgnore.PERMITTED_TYPE)
1514
public interface ProtobufJsonWriter {
1615

1716
/**

0 commit comments

Comments
 (0)