Skip to content
This repository was archived by the owner on Sep 26, 2023. It is now read-only.

Commit 4524fad

Browse files
fix(httpjson): handle message derived query params (#1784)
* fix(httpjson): handle message derived query params Fixes #1783 Some message derived types such as Duration, FieldMask or Int32Value would not be correctly handled by String.valueOf(). Instead, the toJson() method is used to make it compliant with the protobuf languague guide * fix(protoparser): decompose messages in query prms Some message type objects will be passed as query params. These may have nested properties that will now be generated as ?&foo.bar=1&foo.baz=2 * test(serializer): add test for complex msg obj * fix(format): format ProtoRestSerializer files * fix(queryparam): use json approach to process msgs also fixed best practice issues pointed out in last commit's PR * fix(queryparam): use numeric value for root enums enums passed as root object to putQueryParam will now be serialized as their numeric value * chore: Refactoring the fix. * test(queryparam): atomized tests Also added tests for serializing objects that contain Any typed messages. Note that the type registry must have the tested types beforehand, so they were added in the test class setup * test(queryparam): test objects w/ well-known types Co-authored-by: Blake Li <[email protected]>
1 parent 9cc2ccc commit 4524fad

File tree

2 files changed

+133
-11
lines changed

2 files changed

+133
-11
lines changed

gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@
3131

3232
import com.google.api.core.BetaApi;
3333
import com.google.common.collect.ImmutableList;
34+
import com.google.gson.JsonElement;
35+
import com.google.gson.JsonParser;
3436
import com.google.protobuf.InvalidProtocolBufferException;
3537
import com.google.protobuf.Message;
3638
import com.google.protobuf.TypeRegistry;
3739
import com.google.protobuf.util.JsonFormat;
3840
import com.google.protobuf.util.JsonFormat.Printer;
3941
import java.io.IOException;
4042
import java.io.Reader;
43+
import java.util.ArrayList;
4144
import java.util.List;
4245
import java.util.Map;
4346

@@ -49,6 +52,7 @@
4952
*/
5053
@BetaApi
5154
public class ProtoRestSerializer<RequestT extends Message> {
55+
5256
private final TypeRegistry registry;
5357

5458
private ProtoRestSerializer(TypeRegistry registry) {
@@ -75,7 +79,7 @@ static <RequestT extends Message> ProtoRestSerializer<RequestT> create(TypeRegis
7579
* @throws InvalidProtocolBufferException if failed to serialize the protobuf message to JSON
7680
* format
7781
*/
78-
String toJson(RequestT message, boolean numericEnum) {
82+
String toJson(Message message, boolean numericEnum) {
7983
try {
8084
Printer printer = JsonFormat.printer().usingTypeRegistry(registry);
8185
if (numericEnum) {
@@ -118,6 +122,23 @@ public void putPathParam(Map<String, String> fields, String fieldName, Object fi
118122
fields.put(fieldName, String.valueOf(fieldValue));
119123
}
120124

125+
private void putDecomposedMessageQueryParam(
126+
Map<String, List<String>> fields, String fieldName, JsonElement parsed) {
127+
if (parsed.isJsonPrimitive() || parsed.isJsonNull()) {
128+
putQueryParam(fields, fieldName, parsed.getAsString());
129+
} else if (parsed.isJsonArray()) {
130+
for (JsonElement element : parsed.getAsJsonArray()) {
131+
putDecomposedMessageQueryParam(fields, fieldName, element);
132+
}
133+
} else {
134+
// it is a json object
135+
for (String key : parsed.getAsJsonObject().keySet()) {
136+
putDecomposedMessageQueryParam(
137+
fields, String.format("%s.%s", fieldName, key), parsed.getAsJsonObject().get(key));
138+
}
139+
}
140+
}
141+
121142
/**
122143
* Puts a message field in {@code fields} map which will be used to populate query parameters of a
123144
* request.
@@ -127,16 +148,25 @@ public void putPathParam(Map<String, String> fields, String fieldName, Object fi
127148
* @param fieldValue a field value
128149
*/
129150
public void putQueryParam(Map<String, List<String>> fields, String fieldName, Object fieldValue) {
130-
ImmutableList.Builder<String> paramValueList = ImmutableList.builder();
131-
if (fieldValue instanceof List<?>) {
132-
for (Object fieldValueItem : (List<?>) fieldValue) {
133-
paramValueList.add(String.valueOf(fieldValueItem));
151+
List<String> currentParamValueList = new ArrayList<>();
152+
List<Object> toProcess =
153+
fieldValue instanceof List<?> ? (List<Object>) fieldValue : ImmutableList.of(fieldValue);
154+
for (Object fieldValueItem : toProcess) {
155+
if (fieldValueItem instanceof Message) {
156+
String json = toJson(((Message) fieldValueItem), true);
157+
JsonElement parsed = JsonParser.parseString(json);
158+
putDecomposedMessageQueryParam(fields, fieldName, parsed);
159+
} else {
160+
currentParamValueList.add(String.valueOf(fieldValueItem));
134161
}
135-
} else {
136-
paramValueList.add(String.valueOf(fieldValue));
137162
}
138-
139-
fields.put(fieldName, paramValueList.build());
163+
if (currentParamValueList.isEmpty()) {
164+
// We try to avoid putting non-leaf level fields to the query params
165+
return;
166+
}
167+
List<String> accumulativeParamValueList = fields.getOrDefault(fieldName, new ArrayList<>());
168+
accumulativeParamValueList.addAll(currentParamValueList);
169+
fields.put(fieldName, accumulativeParamValueList);
140170
}
141171

142172
/**

gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,17 @@
3131
package com.google.api.gax.httpjson;
3232

3333
import com.google.common.truth.Truth;
34+
import com.google.protobuf.Duration;
3435
import com.google.protobuf.Field;
3536
import com.google.protobuf.Field.Cardinality;
37+
import com.google.protobuf.FieldMask;
38+
import com.google.protobuf.FloatValue;
39+
import com.google.protobuf.Int32Value;
3640
import com.google.protobuf.Option;
41+
import com.google.protobuf.Timestamp;
42+
import com.google.protobuf.TypeRegistry;
43+
import com.google.rpc.RetryInfo;
44+
import com.google.type.Interval;
3745
import java.io.IOException;
3846
import java.io.StringReader;
3947
import java.util.Arrays;
@@ -53,7 +61,14 @@ public class ProtoRestSerializerTest {
5361

5462
@Before
5563
public void setUp() {
56-
requestSerializer = ProtoRestSerializer.create();
64+
// tests with Any type messages require corresponding descriptors in the type registry
65+
requestSerializer =
66+
ProtoRestSerializer.create(
67+
TypeRegistry.newBuilder()
68+
.add(FieldMask.getDescriptor())
69+
.add(Duration.getDescriptor())
70+
.build());
71+
5772
field =
5873
Field.newBuilder()
5974
.setNumber(2)
@@ -163,7 +178,7 @@ public void putPathParam() {
163178
}
164179

165180
@Test
166-
public void putQueryParam() {
181+
public void putQueryParamPrimitive() {
167182
Map<String, List<String>> fields = new HashMap<>();
168183
requestSerializer.putQueryParam(fields, "optName1", 1);
169184
requestSerializer.putQueryParam(fields, "optName2", 0);
@@ -181,6 +196,83 @@ public void putQueryParam() {
181196
Truth.assertThat(fields).isEqualTo(expectedFields);
182197
}
183198

199+
@Test
200+
public void putQueryParamComplexObject() {
201+
Map<String, List<String>> fields = new HashMap<>();
202+
requestSerializer.putQueryParam(fields, "object", field);
203+
204+
Map<String, List<String>> expectedFields = new HashMap<>();
205+
expectedFields.put("object.cardinality", Arrays.asList("1"));
206+
expectedFields.put("object.name", Arrays.asList("field_name1"));
207+
expectedFields.put("object.number", Arrays.asList("2"));
208+
expectedFields.put("object.options.name", Arrays.asList("opt_name1", "opt_name2"));
209+
210+
Truth.assertThat(fields).isEqualTo(expectedFields);
211+
}
212+
213+
@Test
214+
public void putQueryParamComplexObjectDuration() {
215+
Map<String, List<String>> fields = new HashMap<>();
216+
Duration duration = Duration.newBuilder().setSeconds(1).setNanos(1).build();
217+
RetryInfo input = RetryInfo.newBuilder().setRetryDelay(duration).build();
218+
requestSerializer.putQueryParam(fields, "retry_info", input);
219+
220+
Map<String, List<String>> expectedFields = new HashMap<>();
221+
expectedFields.put("retry_info.retryDelay", Arrays.asList("1.000000001s"));
222+
223+
Truth.assertThat(fields).isEqualTo(expectedFields);
224+
}
225+
226+
@Test
227+
public void putQueryParamComplexObjectTimestamp() {
228+
Map<String, List<String>> fields = new HashMap<>();
229+
Timestamp start = Timestamp.newBuilder().setSeconds(1).setNanos(1).build();
230+
Timestamp end = Timestamp.newBuilder().setSeconds(2).setNanos(2).build();
231+
Interval input = Interval.newBuilder().setStartTime(start).setEndTime(end).build();
232+
233+
requestSerializer.putQueryParam(fields, "object", input);
234+
235+
Map<String, List<String>> expectedFields = new HashMap<>();
236+
expectedFields.put("object.startTime", Arrays.asList("1970-01-01T00:00:01.000000001Z"));
237+
expectedFields.put("object.endTime", Arrays.asList("1970-01-01T00:00:02.000000002Z"));
238+
239+
Truth.assertThat(fields).isEqualTo(expectedFields);
240+
}
241+
242+
@Test
243+
public void putQueryParamDuration() {
244+
queryParamHelper(Duration.newBuilder().setSeconds(1).setNanos(1).build(), "1.000000001s");
245+
}
246+
247+
@Test
248+
public void putQueryParamTimestamp() {
249+
queryParamHelper(
250+
Timestamp.newBuilder().setSeconds(1).setNanos(1).build(), "1970-01-01T00:00:01.000000001Z");
251+
}
252+
253+
@Test
254+
public void putQueryParamFieldMask() {
255+
queryParamHelper(FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build(), "a.b,c.d");
256+
}
257+
258+
@Test
259+
public void putQueryParamInt32Value() {
260+
queryParamHelper(Int32Value.of(1), "1");
261+
}
262+
263+
@Test
264+
public void putQueryParamFloatValue() {
265+
queryParamHelper(FloatValue.of(1.1f), "1.1");
266+
}
267+
268+
private void queryParamHelper(Object value, String expected) {
269+
Map<String, List<String>> fields = new HashMap<>();
270+
requestSerializer.putQueryParam(fields, "value", value);
271+
Map<String, List<String>> expectedFields = new HashMap<>();
272+
expectedFields.put("value", Arrays.asList(expected));
273+
Truth.assertThat(fields).isEqualTo(expectedFields);
274+
}
275+
184276
@Test
185277
public void toBody() {
186278
String body = requestSerializer.toBody("bodyField1", field, false);

0 commit comments

Comments
 (0)