From e7a9caac97f6c93a375c65a7212a38c58ba56b85 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 31 Aug 2022 20:56:42 +0000 Subject: [PATCH 1/9] 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 --- .../api/gax/httpjson/ProtoRestSerializer.java | 23 ++++++++++++++++--- .../gax/httpjson/ProtoRestSerializerTest.java | 18 +++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index 298a4ce4f..a7aac7fc1 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -31,8 +31,10 @@ import com.google.api.core.BetaApi; import com.google.common.collect.ImmutableList; +import com.google.protobuf.GeneratedMessageV3; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; +import com.google.protobuf.MessageOrBuilder; import com.google.protobuf.TypeRegistry; import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat.Printer; @@ -75,7 +77,7 @@ static ProtoRestSerializer create(TypeRegis * @throws InvalidProtocolBufferException if failed to serialize the protobuf message to JSON * format */ - String toJson(RequestT message, boolean numericEnum) { + String toJson(MessageOrBuilder message, boolean numericEnum) { try { Printer printer = JsonFormat.printer().usingTypeRegistry(registry); if (numericEnum) { @@ -130,10 +132,10 @@ public void putQueryParam(Map> fields, String fieldName, Ob ImmutableList.Builder paramValueList = ImmutableList.builder(); if (fieldValue instanceof List) { for (Object fieldValueItem : (List) fieldValue) { - paramValueList.add(String.valueOf(fieldValueItem)); + paramValueList.add(toQueryParamValue(fieldValueItem)); } } else { - paramValueList.add(String.valueOf(fieldValue)); + paramValueList.add(toQueryParamValue(fieldValue)); } fields.put(fieldName, paramValueList.build()); @@ -159,4 +161,19 @@ public String toBody(String fieldName, RequestT fieldValue) { public String toBody(String fieldName, RequestT fieldValue, boolean numericEnum) { return toJson(fieldValue, numericEnum); } + + /** + * Serializes an object to a query parameter Handles the case of a message such as Duration, + * FieldMask or Int32Value to prevent wrong formatting that String.valueOf() would make + * + * @param fieldValue a field value to serialize + */ + public String toQueryParamValue(Object fieldValue) { + if (fieldValue instanceof GeneratedMessageV3) { + return toJson(((GeneratedMessageV3) fieldValue).toBuilder(), false) + .replaceAll("^\"", "") + .replaceAll("\"$", ""); + } + return String.valueOf(fieldValue); + } } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index 0256f6726..8ce65625e 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -31,9 +31,14 @@ package com.google.api.gax.httpjson; import com.google.common.truth.Truth; +import com.google.protobuf.Duration; import com.google.protobuf.Field; import com.google.protobuf.Field.Cardinality; +import com.google.protobuf.FieldMask; +import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; import com.google.protobuf.Option; +import com.google.protobuf.Timestamp; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; @@ -170,6 +175,14 @@ public void putQueryParam() { requestSerializer.putQueryParam(fields, "optName3", "three"); requestSerializer.putQueryParam(fields, "optName4", ""); requestSerializer.putQueryParam(fields, "optName5", Arrays.asList("four", "five")); + requestSerializer.putQueryParam( + fields, "optName6", Duration.newBuilder().setSeconds(1).setNanos(1).build()); + requestSerializer.putQueryParam( + fields, "optName7", Timestamp.newBuilder().setSeconds(1).setNanos(1).build()); + requestSerializer.putQueryParam( + fields, "optName8", FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build()); + requestSerializer.putQueryParam(fields, "optName9", Int32Value.of(1)); + requestSerializer.putQueryParam(fields, "optName10", FloatValue.of(1.1f)); Map> expectedFields = new HashMap<>(); expectedFields.put("optName1", Arrays.asList("1")); @@ -177,6 +190,11 @@ public void putQueryParam() { expectedFields.put("optName3", Arrays.asList("three")); expectedFields.put("optName4", Arrays.asList("")); expectedFields.put("optName5", Arrays.asList("four", "five")); + expectedFields.put("optName6", Arrays.asList("1.000000001s")); + expectedFields.put("optName7", Arrays.asList("1970-01-01T00:00:01.000000001Z")); + expectedFields.put("optName8", Arrays.asList("a.b,c.d")); + expectedFields.put("optName9", Arrays.asList("1")); + expectedFields.put("optName10", Arrays.asList("1.1")); Truth.assertThat(fields).isEqualTo(expectedFields); } From a61065b25427885aa0b620a213dc07374a9b0c9d Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 2 Sep 2022 21:41:29 +0000 Subject: [PATCH 2/9] 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 --- .../httpjson/JsonSerializableMessages.java | 5 ++ .../api/gax/httpjson/ProtoRestSerializer.java | 69 +++++++++++++++++-- 2 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java new file mode 100644 index 000000000..1935b0ee2 --- /dev/null +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java @@ -0,0 +1,5 @@ +package com.google.api.gax.httpjson; + +public class JsonSerializableMessages { + +} diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index a7aac7fc1..3323c2475 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -31,17 +31,28 @@ import com.google.api.core.BetaApi; import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.FloatValue; import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.Int64Value; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; import com.google.protobuf.TypeRegistry; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat.Printer; import java.io.IOException; import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * This class serializes/deserializes protobuf {@link Message} for REST interactions. It serializes @@ -52,6 +63,25 @@ @BetaApi public class ProtoRestSerializer { private final TypeRegistry registry; + private static final Set> jsonSerializableMessages = new HashSet( + Arrays.asList( + com.google.protobuf.BoolValue.class, + com.google.protobuf.BytesValue.class, + com.google.protobuf.DoubleValue.class, + com.google.protobuf.Duration.class, + com.google.protobuf.FieldMask.class, + com.google.protobuf.FloatValue.class, + com.google.protobuf.Int32Value.class, + com.google.protobuf.Int64Value.class, + com.google.protobuf.StringValue.class, + com.google.protobuf.Timestamp.class, + com.google.protobuf.UInt32Value.class, + com.google.protobuf.UInt64Value.class + )); + + private boolean isNonSerializableMessageValue(Object value) { + return value instanceof GeneratedMessageV3 && !jsonSerializableMessages.contains(value.getClass()); + } private ProtoRestSerializer(TypeRegistry registry) { this.registry = registry; @@ -120,6 +150,16 @@ public void putPathParam(Map fields, String fieldName, Object fi fields.put(fieldName, String.valueOf(fieldValue)); } + private void putDecomposedMessageQueryParam( + Map> fields, String fieldName, Object fieldValue + ) { + for (Map.Entry fieldEntry : ((GeneratedMessageV3) fieldValue) + .getAllFields().entrySet()) { + Object value = fieldEntry.getValue(); + putQueryParam(fields, String.format("%s.%s",fieldName, fieldEntry.getKey().toProto().getName()), fieldEntry.getValue()); + } + } + /** * Puts a message field in {@code fields} map which will be used to populate query parameters of a * request. @@ -129,16 +169,34 @@ public void putPathParam(Map fields, String fieldName, Object fi * @param fieldValue a field value */ public void putQueryParam(Map> fields, String fieldName, Object fieldValue) { - ImmutableList.Builder paramValueList = ImmutableList.builder(); + ArrayList paramValueList = new ArrayList(); if (fieldValue instanceof List) { + boolean hasProcessedMessage = false; for (Object fieldValueItem : (List) fieldValue) { - paramValueList.add(toQueryParamValue(fieldValueItem)); + if (isNonSerializableMessageValue(fieldValueItem)) { + putDecomposedMessageQueryParam(fields, fieldName, fieldValueItem); + hasProcessedMessage = true; + } else { + paramValueList.add(toQueryParamValue(fieldValueItem)); + } + } + if (hasProcessedMessage) { + return; } } else { - paramValueList.add(toQueryParamValue(fieldValue)); + if (isNonSerializableMessageValue(fieldValue)) { + putDecomposedMessageQueryParam(fields, fieldName, fieldValue); + return; + } else { + paramValueList.add(toQueryParamValue(fieldValue)); + } } - fields.put(fieldName, paramValueList.build()); + if (fields.containsKey(fieldName)) { + fields.get(fieldName).addAll(paramValueList); + } else { + fields.put(fieldName, paramValueList); + } } /** @@ -174,6 +232,9 @@ public String toQueryParamValue(Object fieldValue) { .replaceAll("^\"", "") .replaceAll("\"$", ""); } + if (fieldValue instanceof ByteString) { + return ((ByteString) fieldValue).toStringUtf8(); + } return String.valueOf(fieldValue); } } From 63e0118128cc1ca30bf626031290920417c9e668 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 6 Sep 2022 17:31:04 +0000 Subject: [PATCH 3/9] test(serializer): add test for complex msg obj --- .../gax/httpjson/JsonSerializableMessages.java | 5 ----- .../api/gax/httpjson/ProtoRestSerializer.java | 6 +++++- .../gax/httpjson/ProtoRestSerializerTest.java | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) delete mode 100644 gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java deleted file mode 100644 index 1935b0ee2..000000000 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/JsonSerializableMessages.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.google.api.gax.httpjson; - -public class JsonSerializableMessages { - -} diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index 3323c2475..f50359dce 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -63,6 +63,9 @@ @BetaApi public class ProtoRestSerializer { private final TypeRegistry registry; + + // well-known types obtained from + // https://github.com/googleapis/gapic-showcase/blob/fe414784c18878d704b884348d84c68fd6b87466/util/genrest/resttools/populatefield.go#L27 private static final Set> jsonSerializableMessages = new HashSet( Arrays.asList( com.google.protobuf.BoolValue.class, @@ -227,7 +230,8 @@ public String toBody(String fieldName, RequestT fieldValue, boolean numericEnum) * @param fieldValue a field value to serialize */ public String toQueryParamValue(Object fieldValue) { - if (fieldValue instanceof GeneratedMessageV3) { + // This will match with message types that are serializable (e.g. FieldMask) + if (fieldValue instanceof GeneratedMessageV3 && !isNonSerializableMessageValue(fieldValue)) { return toJson(((GeneratedMessageV3) fieldValue).toBuilder(), false) .replaceAll("^\"", "") .replaceAll("\"$", ""); diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index 8ce65625e..677eabc02 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -31,6 +31,9 @@ package com.google.api.gax.httpjson; import com.google.common.truth.Truth; +import com.google.longrunning.Operation; +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.Field; import com.google.protobuf.Field.Cardinality; @@ -39,8 +42,10 @@ import com.google.protobuf.Int32Value; import com.google.protobuf.Option; import com.google.protobuf.Timestamp; +import com.google.rpc.Status; import java.io.IOException; import java.io.StringReader; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -183,6 +188,16 @@ public void putQueryParam() { fields, "optName8", FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build()); requestSerializer.putQueryParam(fields, "optName9", Int32Value.of(1)); requestSerializer.putQueryParam(fields, "optName10", FloatValue.of(1.1f)); + com.google.longrunning.Operation operation = Operation.newBuilder() + .setDone(true) + .setError(Status.newBuilder() + .addDetails(Any.newBuilder().setValue(ByteString.copyFrom("error-1", + Charset.defaultCharset())).build()) + .addDetails(Any.newBuilder().setValue(ByteString.copyFrom("error-2", + Charset.defaultCharset())).build())) + .setName("test") + .build(); + requestSerializer.putQueryParam(fields, "optName11", operation); Map> expectedFields = new HashMap<>(); expectedFields.put("optName1", Arrays.asList("1")); @@ -195,6 +210,9 @@ public void putQueryParam() { expectedFields.put("optName8", Arrays.asList("a.b,c.d")); expectedFields.put("optName9", Arrays.asList("1")); expectedFields.put("optName10", Arrays.asList("1.1")); + expectedFields.put("optName11.name", Arrays.asList("test")); + expectedFields.put("optName11.done", Arrays.asList("true")); + expectedFields.put("optName11.error.details.value", Arrays.asList("error-1", "error-2")); Truth.assertThat(fields).isEqualTo(expectedFields); } From 16689a9380fe2b59168ac8aba3b83e1358bd791b Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 6 Sep 2022 18:29:30 +0000 Subject: [PATCH 4/9] fix(format): format ProtoRestSerializer files --- .../api/gax/httpjson/ProtoRestSerializer.java | 51 +++++++++---------- .../gax/httpjson/ProtoRestSerializerTest.java | 24 +++++---- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index f50359dce..fa5ac5dc7 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -30,19 +30,13 @@ package com.google.api.gax.httpjson; import com.google.api.core.BetaApi; -import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.DoubleValue; -import com.google.protobuf.FloatValue; import com.google.protobuf.GeneratedMessageV3; -import com.google.protobuf.Int64Value; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; import com.google.protobuf.TypeRegistry; -import com.google.protobuf.UInt32Value; -import com.google.protobuf.UInt64Value; import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat.Printer; import java.io.IOException; @@ -66,24 +60,25 @@ public class ProtoRestSerializer { // well-known types obtained from // https://github.com/googleapis/gapic-showcase/blob/fe414784c18878d704b884348d84c68fd6b87466/util/genrest/resttools/populatefield.go#L27 - private static final Set> jsonSerializableMessages = new HashSet( - Arrays.asList( - com.google.protobuf.BoolValue.class, - com.google.protobuf.BytesValue.class, - com.google.protobuf.DoubleValue.class, - com.google.protobuf.Duration.class, - com.google.protobuf.FieldMask.class, - com.google.protobuf.FloatValue.class, - com.google.protobuf.Int32Value.class, - com.google.protobuf.Int64Value.class, - com.google.protobuf.StringValue.class, - com.google.protobuf.Timestamp.class, - com.google.protobuf.UInt32Value.class, - com.google.protobuf.UInt64Value.class - )); + private static final Set> jsonSerializableMessages = + new HashSet( + Arrays.asList( + com.google.protobuf.BoolValue.class, + com.google.protobuf.BytesValue.class, + com.google.protobuf.DoubleValue.class, + com.google.protobuf.Duration.class, + com.google.protobuf.FieldMask.class, + com.google.protobuf.FloatValue.class, + com.google.protobuf.Int32Value.class, + com.google.protobuf.Int64Value.class, + com.google.protobuf.StringValue.class, + com.google.protobuf.Timestamp.class, + com.google.protobuf.UInt32Value.class, + com.google.protobuf.UInt64Value.class)); private boolean isNonSerializableMessageValue(Object value) { - return value instanceof GeneratedMessageV3 && !jsonSerializableMessages.contains(value.getClass()); + return value instanceof GeneratedMessageV3 + && !jsonSerializableMessages.contains(value.getClass()); } private ProtoRestSerializer(TypeRegistry registry) { @@ -154,12 +149,14 @@ public void putPathParam(Map fields, String fieldName, Object fi } private void putDecomposedMessageQueryParam( - Map> fields, String fieldName, Object fieldValue - ) { - for (Map.Entry fieldEntry : ((GeneratedMessageV3) fieldValue) - .getAllFields().entrySet()) { + Map> fields, String fieldName, Object fieldValue) { + for (Map.Entry fieldEntry : + ((GeneratedMessageV3) fieldValue).getAllFields().entrySet()) { Object value = fieldEntry.getValue(); - putQueryParam(fields, String.format("%s.%s",fieldName, fieldEntry.getKey().toProto().getName()), fieldEntry.getValue()); + putQueryParam( + fields, + String.format("%s.%s", fieldName, fieldEntry.getKey().toProto().getName()), + fieldEntry.getValue()); } } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index 677eabc02..cf904c1ce 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -188,15 +188,21 @@ public void putQueryParam() { fields, "optName8", FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build()); requestSerializer.putQueryParam(fields, "optName9", Int32Value.of(1)); requestSerializer.putQueryParam(fields, "optName10", FloatValue.of(1.1f)); - com.google.longrunning.Operation operation = Operation.newBuilder() - .setDone(true) - .setError(Status.newBuilder() - .addDetails(Any.newBuilder().setValue(ByteString.copyFrom("error-1", - Charset.defaultCharset())).build()) - .addDetails(Any.newBuilder().setValue(ByteString.copyFrom("error-2", - Charset.defaultCharset())).build())) - .setName("test") - .build(); + com.google.longrunning.Operation operation = + Operation.newBuilder() + .setDone(true) + .setError( + Status.newBuilder() + .addDetails( + Any.newBuilder() + .setValue(ByteString.copyFrom("error-1", Charset.defaultCharset())) + .build()) + .addDetails( + Any.newBuilder() + .setValue(ByteString.copyFrom("error-2", Charset.defaultCharset())) + .build())) + .setName("test") + .build(); requestSerializer.putQueryParam(fields, "optName11", operation); Map> expectedFields = new HashMap<>(); From 2edd1202c2625e7143931ca866161deb44202810 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Sep 2022 00:24:52 +0000 Subject: [PATCH 5/9] fix(queryparam): use json approach to process msgs also fixed best practice issues pointed out in last commit's PR --- .../api/gax/httpjson/ProtoRestSerializer.java | 104 +++++------------- .../gax/httpjson/ProtoRestSerializerTest.java | 29 +---- 2 files changed, 34 insertions(+), 99 deletions(-) diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index fa5ac5dc7..a1d68c798 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -30,9 +30,9 @@ package com.google.api.gax.httpjson; import com.google.api.core.BetaApi; -import com.google.protobuf.ByteString; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.GeneratedMessageV3; +import com.google.common.collect.ImmutableList; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; @@ -42,11 +42,8 @@ import java.io.IOException; import java.io.Reader; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; /** * This class serializes/deserializes protobuf {@link Message} for REST interactions. It serializes @@ -58,29 +55,6 @@ public class ProtoRestSerializer { private final TypeRegistry registry; - // well-known types obtained from - // https://github.com/googleapis/gapic-showcase/blob/fe414784c18878d704b884348d84c68fd6b87466/util/genrest/resttools/populatefield.go#L27 - private static final Set> jsonSerializableMessages = - new HashSet( - Arrays.asList( - com.google.protobuf.BoolValue.class, - com.google.protobuf.BytesValue.class, - com.google.protobuf.DoubleValue.class, - com.google.protobuf.Duration.class, - com.google.protobuf.FieldMask.class, - com.google.protobuf.FloatValue.class, - com.google.protobuf.Int32Value.class, - com.google.protobuf.Int64Value.class, - com.google.protobuf.StringValue.class, - com.google.protobuf.Timestamp.class, - com.google.protobuf.UInt32Value.class, - com.google.protobuf.UInt64Value.class)); - - private boolean isNonSerializableMessageValue(Object value) { - return value instanceof GeneratedMessageV3 - && !jsonSerializableMessages.contains(value.getClass()); - } - private ProtoRestSerializer(TypeRegistry registry) { this.registry = registry; } @@ -149,14 +123,20 @@ public void putPathParam(Map fields, String fieldName, Object fi } private void putDecomposedMessageQueryParam( - Map> fields, String fieldName, Object fieldValue) { - for (Map.Entry fieldEntry : - ((GeneratedMessageV3) fieldValue).getAllFields().entrySet()) { - Object value = fieldEntry.getValue(); + Map> fields, String fieldName, JsonElement parsed) { + if (parsed.isJsonPrimitive() || parsed.isJsonNull()) { putQueryParam( - fields, - String.format("%s.%s", fieldName, fieldEntry.getKey().toProto().getName()), - fieldEntry.getValue()); + fields, fieldName, parsed.getAsString().replaceAll("^\"", "").replaceAll("\"$", "")); + } else if (parsed.isJsonArray()) { + for (JsonElement element : parsed.getAsJsonArray()) { + putDecomposedMessageQueryParam(fields, fieldName, element); + } + } else { + // it is a json object + for (String key : parsed.getAsJsonObject().keySet()) { + putDecomposedMessageQueryParam( + fields, String.format("%s.%s", fieldName, key), parsed.getAsJsonObject().get(key)); + } } } @@ -169,29 +149,22 @@ private void putDecomposedMessageQueryParam( * @param fieldValue a field value */ public void putQueryParam(Map> fields, String fieldName, Object fieldValue) { - ArrayList paramValueList = new ArrayList(); - if (fieldValue instanceof List) { - boolean hasProcessedMessage = false; - for (Object fieldValueItem : (List) fieldValue) { - if (isNonSerializableMessageValue(fieldValueItem)) { - putDecomposedMessageQueryParam(fields, fieldName, fieldValueItem); - hasProcessedMessage = true; - } else { - paramValueList.add(toQueryParamValue(fieldValueItem)); - } - } - if (hasProcessedMessage) { - return; - } - } else { - if (isNonSerializableMessageValue(fieldValue)) { - putDecomposedMessageQueryParam(fields, fieldName, fieldValue); - return; + ArrayList paramValueList = new ArrayList<>(); + List toProcess = + fieldValue instanceof List ? (List) fieldValue : ImmutableList.of(fieldValue); + for (Object fieldValueItem : toProcess) { + if (fieldValueItem instanceof Message) { + String json = toJson(((Message) fieldValueItem).toBuilder(), true); + JsonElement parsed = JsonParser.parseString(json); + putDecomposedMessageQueryParam(fields, fieldName, parsed); } else { - paramValueList.add(toQueryParamValue(fieldValue)); + paramValueList.add(String.valueOf(fieldValueItem)); } } - + if (paramValueList.isEmpty()) { + // we have processed a message and added its properties into the query + return; + } if (fields.containsKey(fieldName)) { fields.get(fieldName).addAll(paramValueList); } else { @@ -219,23 +192,4 @@ public String toBody(String fieldName, RequestT fieldValue) { public String toBody(String fieldName, RequestT fieldValue, boolean numericEnum) { return toJson(fieldValue, numericEnum); } - - /** - * Serializes an object to a query parameter Handles the case of a message such as Duration, - * FieldMask or Int32Value to prevent wrong formatting that String.valueOf() would make - * - * @param fieldValue a field value to serialize - */ - public String toQueryParamValue(Object fieldValue) { - // This will match with message types that are serializable (e.g. FieldMask) - if (fieldValue instanceof GeneratedMessageV3 && !isNonSerializableMessageValue(fieldValue)) { - return toJson(((GeneratedMessageV3) fieldValue).toBuilder(), false) - .replaceAll("^\"", "") - .replaceAll("\"$", ""); - } - if (fieldValue instanceof ByteString) { - return ((ByteString) fieldValue).toStringUtf8(); - } - return String.valueOf(fieldValue); - } } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index cf904c1ce..c7cd39fe2 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -31,9 +31,6 @@ package com.google.api.gax.httpjson; import com.google.common.truth.Truth; -import com.google.longrunning.Operation; -import com.google.protobuf.Any; -import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.Field; import com.google.protobuf.Field.Cardinality; @@ -42,10 +39,8 @@ import com.google.protobuf.Int32Value; import com.google.protobuf.Option; import com.google.protobuf.Timestamp; -import com.google.rpc.Status; import java.io.IOException; import java.io.StringReader; -import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -188,22 +183,7 @@ public void putQueryParam() { fields, "optName8", FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build()); requestSerializer.putQueryParam(fields, "optName9", Int32Value.of(1)); requestSerializer.putQueryParam(fields, "optName10", FloatValue.of(1.1f)); - com.google.longrunning.Operation operation = - Operation.newBuilder() - .setDone(true) - .setError( - Status.newBuilder() - .addDetails( - Any.newBuilder() - .setValue(ByteString.copyFrom("error-1", Charset.defaultCharset())) - .build()) - .addDetails( - Any.newBuilder() - .setValue(ByteString.copyFrom("error-2", Charset.defaultCharset())) - .build())) - .setName("test") - .build(); - requestSerializer.putQueryParam(fields, "optName11", operation); + requestSerializer.putQueryParam(fields, "optName11", field); Map> expectedFields = new HashMap<>(); expectedFields.put("optName1", Arrays.asList("1")); @@ -216,9 +196,10 @@ public void putQueryParam() { expectedFields.put("optName8", Arrays.asList("a.b,c.d")); expectedFields.put("optName9", Arrays.asList("1")); expectedFields.put("optName10", Arrays.asList("1.1")); - expectedFields.put("optName11.name", Arrays.asList("test")); - expectedFields.put("optName11.done", Arrays.asList("true")); - expectedFields.put("optName11.error.details.value", Arrays.asList("error-1", "error-2")); + expectedFields.put("optName11.name", Arrays.asList("field_name1")); + expectedFields.put("optName11.number", Arrays.asList("2")); + expectedFields.put("optName11.options.name", Arrays.asList("opt_name1", "opt_name2")); + expectedFields.put("optName11.cardinality", Arrays.asList("1")); Truth.assertThat(fields).isEqualTo(expectedFields); } From 08b655b6e634509c7cf432f18005dcadca45a045 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Sep 2022 18:02:59 +0000 Subject: [PATCH 6/9] fix(queryparam): use numeric value for root enums enums passed as root object to putQueryParam will now be serialized as their numeric value --- .../api/gax/httpjson/ProtoRestSerializer.java | 3 ++ .../gax/httpjson/ProtoRestSerializerTest.java | 50 ++++++++++++------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index a1d68c798..17ba88c9f 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -36,6 +36,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.ProtocolMessageEnum; import com.google.protobuf.TypeRegistry; import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat.Printer; @@ -157,6 +158,8 @@ public void putQueryParam(Map> fields, String fieldName, Ob String json = toJson(((Message) fieldValueItem).toBuilder(), true); JsonElement parsed = JsonParser.parseString(json); putDecomposedMessageQueryParam(fields, fieldName, parsed); + } else if (fieldValueItem instanceof ProtocolMessageEnum) { + paramValueList.add(String.valueOf(((ProtocolMessageEnum) fieldValueItem).getNumber())); } else { paramValueList.add(String.valueOf(fieldValueItem)); } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index c7cd39fe2..a9b4fb560 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -168,22 +168,13 @@ public void putPathParam() { } @Test - public void putQueryParam() { + public void putQueryParamPrimitive() { Map> fields = new HashMap<>(); requestSerializer.putQueryParam(fields, "optName1", 1); requestSerializer.putQueryParam(fields, "optName2", 0); requestSerializer.putQueryParam(fields, "optName3", "three"); requestSerializer.putQueryParam(fields, "optName4", ""); requestSerializer.putQueryParam(fields, "optName5", Arrays.asList("four", "five")); - requestSerializer.putQueryParam( - fields, "optName6", Duration.newBuilder().setSeconds(1).setNanos(1).build()); - requestSerializer.putQueryParam( - fields, "optName7", Timestamp.newBuilder().setSeconds(1).setNanos(1).build()); - requestSerializer.putQueryParam( - fields, "optName8", FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build()); - requestSerializer.putQueryParam(fields, "optName9", Int32Value.of(1)); - requestSerializer.putQueryParam(fields, "optName10", FloatValue.of(1.1f)); - requestSerializer.putQueryParam(fields, "optName11", field); Map> expectedFields = new HashMap<>(); expectedFields.put("optName1", Arrays.asList("1")); @@ -191,15 +182,36 @@ public void putQueryParam() { expectedFields.put("optName3", Arrays.asList("three")); expectedFields.put("optName4", Arrays.asList("")); expectedFields.put("optName5", Arrays.asList("four", "five")); - expectedFields.put("optName6", Arrays.asList("1.000000001s")); - expectedFields.put("optName7", Arrays.asList("1970-01-01T00:00:01.000000001Z")); - expectedFields.put("optName8", Arrays.asList("a.b,c.d")); - expectedFields.put("optName9", Arrays.asList("1")); - expectedFields.put("optName10", Arrays.asList("1.1")); - expectedFields.put("optName11.name", Arrays.asList("field_name1")); - expectedFields.put("optName11.number", Arrays.asList("2")); - expectedFields.put("optName11.options.name", Arrays.asList("opt_name1", "opt_name2")); - expectedFields.put("optName11.cardinality", Arrays.asList("1")); + + Truth.assertThat(fields).isEqualTo(expectedFields); + } + + @Test + public void putQueryParamComplexObject() { + Map> fields = new HashMap<>(); + requestSerializer.putQueryParam( + fields, "optName1", Duration.newBuilder().setSeconds(1).setNanos(1).build()); + requestSerializer.putQueryParam( + fields, "optName2", Timestamp.newBuilder().setSeconds(1).setNanos(1).build()); + requestSerializer.putQueryParam( + fields, "optName3", FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build()); + requestSerializer.putQueryParam(fields, "optName4", Int32Value.of(1)); + requestSerializer.putQueryParam(fields, "optName5", FloatValue.of(1.1f)); + requestSerializer.putQueryParam(fields, "optName6", field); + requestSerializer.putQueryParam( + fields, "optName7", Arrays.asList(Cardinality.CARDINALITY_REPEATED)); + + Map> expectedFields = new HashMap<>(); + expectedFields.put("optName1", Arrays.asList("1.000000001s")); + expectedFields.put("optName2", Arrays.asList("1970-01-01T00:00:01.000000001Z")); + expectedFields.put("optName3", Arrays.asList("a.b,c.d")); + expectedFields.put("optName4", Arrays.asList("1")); + expectedFields.put("optName5", Arrays.asList("1.1")); + expectedFields.put("optName6.name", Arrays.asList("field_name1")); + expectedFields.put("optName6.number", Arrays.asList("2")); + expectedFields.put("optName6.options.name", Arrays.asList("opt_name1", "opt_name2")); + expectedFields.put("optName6.cardinality", Arrays.asList("1")); + expectedFields.put("optName7", Arrays.asList("3")); Truth.assertThat(fields).isEqualTo(expectedFields); } From 46b8bd75c95bff69a98734fef713db3a6ebeabb9 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Fri, 9 Sep 2022 16:25:14 -0400 Subject: [PATCH 7/9] chore: Refactoring the fix. --- .../api/gax/httpjson/ProtoRestSerializer.java | 28 ++++++++----------- .../gax/httpjson/ProtoRestSerializerTest.java | 3 -- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index 17ba88c9f..0c5158f76 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -35,8 +35,6 @@ import com.google.gson.JsonParser; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; -import com.google.protobuf.MessageOrBuilder; -import com.google.protobuf.ProtocolMessageEnum; import com.google.protobuf.TypeRegistry; import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat.Printer; @@ -54,6 +52,7 @@ */ @BetaApi public class ProtoRestSerializer { + private final TypeRegistry registry; private ProtoRestSerializer(TypeRegistry registry) { @@ -80,7 +79,7 @@ static ProtoRestSerializer create(TypeRegis * @throws InvalidProtocolBufferException if failed to serialize the protobuf message to JSON * format */ - String toJson(MessageOrBuilder message, boolean numericEnum) { + String toJson(Message message, boolean numericEnum) { try { Printer printer = JsonFormat.printer().usingTypeRegistry(registry); if (numericEnum) { @@ -126,8 +125,7 @@ public void putPathParam(Map fields, String fieldName, Object fi private void putDecomposedMessageQueryParam( Map> fields, String fieldName, JsonElement parsed) { if (parsed.isJsonPrimitive() || parsed.isJsonNull()) { - putQueryParam( - fields, fieldName, parsed.getAsString().replaceAll("^\"", "").replaceAll("\"$", "")); + putQueryParam(fields, fieldName, parsed.getAsString()); } else if (parsed.isJsonArray()) { for (JsonElement element : parsed.getAsJsonArray()) { putDecomposedMessageQueryParam(fields, fieldName, element); @@ -150,29 +148,25 @@ private void putDecomposedMessageQueryParam( * @param fieldValue a field value */ public void putQueryParam(Map> fields, String fieldName, Object fieldValue) { - ArrayList paramValueList = new ArrayList<>(); + List currentParamValueList = new ArrayList<>(); List toProcess = fieldValue instanceof List ? (List) fieldValue : ImmutableList.of(fieldValue); for (Object fieldValueItem : toProcess) { if (fieldValueItem instanceof Message) { - String json = toJson(((Message) fieldValueItem).toBuilder(), true); + String json = toJson(((Message) fieldValueItem), true); JsonElement parsed = JsonParser.parseString(json); putDecomposedMessageQueryParam(fields, fieldName, parsed); - } else if (fieldValueItem instanceof ProtocolMessageEnum) { - paramValueList.add(String.valueOf(((ProtocolMessageEnum) fieldValueItem).getNumber())); } else { - paramValueList.add(String.valueOf(fieldValueItem)); + currentParamValueList.add(String.valueOf(fieldValueItem)); } } - if (paramValueList.isEmpty()) { - // we have processed a message and added its properties into the query + if (currentParamValueList.isEmpty()) { + // We try to avoid putting non-leaf level fields to the query params return; } - if (fields.containsKey(fieldName)) { - fields.get(fieldName).addAll(paramValueList); - } else { - fields.put(fieldName, paramValueList); - } + List accumulativeParamValueList = fields.getOrDefault(fieldName, new ArrayList<>()); + accumulativeParamValueList.addAll(currentParamValueList); + fields.put(fieldName, accumulativeParamValueList); } /** diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index a9b4fb560..0f76e12f8 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -198,8 +198,6 @@ public void putQueryParamComplexObject() { requestSerializer.putQueryParam(fields, "optName4", Int32Value.of(1)); requestSerializer.putQueryParam(fields, "optName5", FloatValue.of(1.1f)); requestSerializer.putQueryParam(fields, "optName6", field); - requestSerializer.putQueryParam( - fields, "optName7", Arrays.asList(Cardinality.CARDINALITY_REPEATED)); Map> expectedFields = new HashMap<>(); expectedFields.put("optName1", Arrays.asList("1.000000001s")); @@ -211,7 +209,6 @@ public void putQueryParamComplexObject() { expectedFields.put("optName6.number", Arrays.asList("2")); expectedFields.put("optName6.options.name", Arrays.asList("opt_name1", "opt_name2")); expectedFields.put("optName6.cardinality", Arrays.asList("1")); - expectedFields.put("optName7", Arrays.asList("3")); Truth.assertThat(fields).isEqualTo(expectedFields); } From 471a1343df1bd2ee672bf8473b33393bfa202aa7 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Sep 2022 21:21:37 +0000 Subject: [PATCH 8/9] 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 --- .../gax/httpjson/ProtoRestSerializerTest.java | 89 +++++++++++++++---- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index 0f76e12f8..c5b84700b 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -31,6 +31,7 @@ package com.google.api.gax.httpjson; import com.google.common.truth.Truth; +import com.google.protobuf.Any; import com.google.protobuf.Duration; import com.google.protobuf.Field; import com.google.protobuf.Field.Cardinality; @@ -39,6 +40,7 @@ import com.google.protobuf.Int32Value; import com.google.protobuf.Option; import com.google.protobuf.Timestamp; +import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; @@ -58,7 +60,14 @@ public class ProtoRestSerializerTest { @Before public void setUp() { - requestSerializer = ProtoRestSerializer.create(); + // tests with Any type messages require corresponding descriptors in the type registry + requestSerializer = + ProtoRestSerializer.create( + TypeRegistry.newBuilder() + .add(FieldMask.getDescriptor()) + .add(Duration.getDescriptor()) + .build()); + field = Field.newBuilder() .setNumber(2) @@ -189,30 +198,72 @@ public void putQueryParamPrimitive() { @Test public void putQueryParamComplexObject() { Map> fields = new HashMap<>(); - requestSerializer.putQueryParam( - fields, "optName1", Duration.newBuilder().setSeconds(1).setNanos(1).build()); - requestSerializer.putQueryParam( - fields, "optName2", Timestamp.newBuilder().setSeconds(1).setNanos(1).build()); - requestSerializer.putQueryParam( - fields, "optName3", FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build()); - requestSerializer.putQueryParam(fields, "optName4", Int32Value.of(1)); - requestSerializer.putQueryParam(fields, "optName5", FloatValue.of(1.1f)); - requestSerializer.putQueryParam(fields, "optName6", field); + Any fieldMask = Any.pack(FieldMask.newBuilder().addPaths("a.b.c").addPaths("d.e.f").build()); + Any duration1 = Any.pack(Duration.newBuilder().setSeconds(1).setNanos(1).build()); + Any duration2 = Any.pack(Duration.newBuilder().setSeconds(2).setNanos(2).build()); + Field value = + Field.newBuilder() + .setNumber(2) + .setName("well_known_container") + .addOptions(Option.newBuilder().setName("duration").setValue(duration1).build()) + .addOptions(Option.newBuilder().setName("duration").setValue(duration2).build()) + .addOptions(Option.newBuilder().setName("mask").setValue(fieldMask).build()) + .setCardinality(Cardinality.CARDINALITY_OPTIONAL) + .build(); + requestSerializer.putQueryParam(fields, "object", value); Map> expectedFields = new HashMap<>(); - expectedFields.put("optName1", Arrays.asList("1.000000001s")); - expectedFields.put("optName2", Arrays.asList("1970-01-01T00:00:01.000000001Z")); - expectedFields.put("optName3", Arrays.asList("a.b,c.d")); - expectedFields.put("optName4", Arrays.asList("1")); - expectedFields.put("optName5", Arrays.asList("1.1")); - expectedFields.put("optName6.name", Arrays.asList("field_name1")); - expectedFields.put("optName6.number", Arrays.asList("2")); - expectedFields.put("optName6.options.name", Arrays.asList("opt_name1", "opt_name2")); - expectedFields.put("optName6.cardinality", Arrays.asList("1")); + expectedFields.put("object.name", Arrays.asList("well_known_container")); + expectedFields.put("object.number", Arrays.asList("2")); + expectedFields.put("object.options.name", Arrays.asList("duration", "duration", "mask")); + expectedFields.put( + "object.options.value.value", Arrays.asList("1.000000001s", "2.000000002s", "a.b.c,d.e.f")); + // used by JSON parser to obtain descriptors from this type url + expectedFields.put( + "object.options.value.@type", + Arrays.asList( + "type.googleapis.com/google.protobuf.Duration", + "type.googleapis.com/google.protobuf.Duration", + "type.googleapis.com/google.protobuf.FieldMask")); + expectedFields.put("object.cardinality", Arrays.asList("1")); Truth.assertThat(fields).isEqualTo(expectedFields); } + @Test + public void putQueryParamDuration() { + queryParamHelper(Duration.newBuilder().setSeconds(1).setNanos(1).build(), "1.000000001s"); + } + + @Test + public void putQueryParamTimestamp() { + queryParamHelper( + Timestamp.newBuilder().setSeconds(1).setNanos(1).build(), "1970-01-01T00:00:01.000000001Z"); + } + + @Test + public void putQueryParamFieldMask() { + queryParamHelper(FieldMask.newBuilder().addPaths("a.b").addPaths("c.d").build(), "a.b,c.d"); + } + + @Test + public void putQueryParamInt32Value() { + queryParamHelper(Int32Value.of(1), "1"); + } + + @Test + public void putQueryParamFloatValue() { + queryParamHelper(FloatValue.of(1.1f), "1.1"); + } + + private void queryParamHelper(Object value, String expected) { + Map> fields = new HashMap<>(); + requestSerializer.putQueryParam(fields, "value", value); + Map> expectedFields = new HashMap<>(); + expectedFields.put("value", Arrays.asList(expected)); + Truth.assertThat(fields).isEqualTo(expectedFields); + } + @Test public void toBody() { String body = requestSerializer.toBody("bodyField1", field, false); From f7443b9b756ebe0c65c5f875e3c6cf518f29b222 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Mon, 12 Sep 2022 21:12:30 +0000 Subject: [PATCH 9/9] test(queryparam): test objects w/ well-known types --- .../gax/httpjson/ProtoRestSerializerTest.java | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java index c5b84700b..3ff9ceaf6 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoRestSerializerTest.java @@ -31,7 +31,6 @@ package com.google.api.gax.httpjson; import com.google.common.truth.Truth; -import com.google.protobuf.Any; import com.google.protobuf.Duration; import com.google.protobuf.Field; import com.google.protobuf.Field.Cardinality; @@ -41,6 +40,8 @@ import com.google.protobuf.Option; import com.google.protobuf.Timestamp; import com.google.protobuf.TypeRegistry; +import com.google.rpc.RetryInfo; +import com.google.type.Interval; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; @@ -198,34 +199,42 @@ public void putQueryParamPrimitive() { @Test public void putQueryParamComplexObject() { Map> fields = new HashMap<>(); - Any fieldMask = Any.pack(FieldMask.newBuilder().addPaths("a.b.c").addPaths("d.e.f").build()); - Any duration1 = Any.pack(Duration.newBuilder().setSeconds(1).setNanos(1).build()); - Any duration2 = Any.pack(Duration.newBuilder().setSeconds(2).setNanos(2).build()); - Field value = - Field.newBuilder() - .setNumber(2) - .setName("well_known_container") - .addOptions(Option.newBuilder().setName("duration").setValue(duration1).build()) - .addOptions(Option.newBuilder().setName("duration").setValue(duration2).build()) - .addOptions(Option.newBuilder().setName("mask").setValue(fieldMask).build()) - .setCardinality(Cardinality.CARDINALITY_OPTIONAL) - .build(); - requestSerializer.putQueryParam(fields, "object", value); + requestSerializer.putQueryParam(fields, "object", field); Map> expectedFields = new HashMap<>(); - expectedFields.put("object.name", Arrays.asList("well_known_container")); - expectedFields.put("object.number", Arrays.asList("2")); - expectedFields.put("object.options.name", Arrays.asList("duration", "duration", "mask")); - expectedFields.put( - "object.options.value.value", Arrays.asList("1.000000001s", "2.000000002s", "a.b.c,d.e.f")); - // used by JSON parser to obtain descriptors from this type url - expectedFields.put( - "object.options.value.@type", - Arrays.asList( - "type.googleapis.com/google.protobuf.Duration", - "type.googleapis.com/google.protobuf.Duration", - "type.googleapis.com/google.protobuf.FieldMask")); expectedFields.put("object.cardinality", Arrays.asList("1")); + expectedFields.put("object.name", Arrays.asList("field_name1")); + expectedFields.put("object.number", Arrays.asList("2")); + expectedFields.put("object.options.name", Arrays.asList("opt_name1", "opt_name2")); + + Truth.assertThat(fields).isEqualTo(expectedFields); + } + + @Test + public void putQueryParamComplexObjectDuration() { + Map> fields = new HashMap<>(); + Duration duration = Duration.newBuilder().setSeconds(1).setNanos(1).build(); + RetryInfo input = RetryInfo.newBuilder().setRetryDelay(duration).build(); + requestSerializer.putQueryParam(fields, "retry_info", input); + + Map> expectedFields = new HashMap<>(); + expectedFields.put("retry_info.retryDelay", Arrays.asList("1.000000001s")); + + Truth.assertThat(fields).isEqualTo(expectedFields); + } + + @Test + public void putQueryParamComplexObjectTimestamp() { + Map> fields = new HashMap<>(); + Timestamp start = Timestamp.newBuilder().setSeconds(1).setNanos(1).build(); + Timestamp end = Timestamp.newBuilder().setSeconds(2).setNanos(2).build(); + Interval input = Interval.newBuilder().setStartTime(start).setEndTime(end).build(); + + requestSerializer.putQueryParam(fields, "object", input); + + Map> expectedFields = new HashMap<>(); + expectedFields.put("object.startTime", Arrays.asList("1970-01-01T00:00:01.000000001Z")); + expectedFields.put("object.endTime", Arrays.asList("1970-01-01T00:00:02.000000002Z")); Truth.assertThat(fields).isEqualTo(expectedFields); }