From 8f9d6ce431546fa58b5c1e32f2c44eed145528db Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Wed, 16 May 2018 13:17:28 -0700 Subject: [PATCH] Add fieldmask to JSON serialization functions (#537) --- .../google/api/gax/httpjson/ApiMessage.java | 10 +- .../ApiMessageHttpRequestFormatter.java | 73 +++--- .../gax/httpjson/FieldMaskedSerializer.java | 71 ++++++ .../httpjson/ApiMessageHttpRequestTest.java | 233 ++++++++++++++++++ .../api/gax/httpjson/FieldMaskTest.java | 111 +++++++++ .../gax/httpjson/HttpRequestRunnableTest.java | 33 ++- .../api/gax/httpjson/MockHttpServiceTest.java | 18 +- .../gax/httpjson/testing/FakeApiMessage.java | 31 +-- 8 files changed, 497 insertions(+), 83 deletions(-) create mode 100644 gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldMaskedSerializer.java create mode 100644 gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java create mode 100644 gax-httpjson/src/test/java/com/google/api/gax/httpjson/FieldMaskTest.java diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessage.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessage.java index 2ab087e8d..876fe86a8 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessage.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessage.java @@ -31,19 +31,19 @@ import com.google.api.core.BetaApi; import java.util.List; -import java.util.Map; -import java.util.Set; import javax.annotation.Nullable; /* An interface for message classes. */ @BetaApi public interface ApiMessage { - /* For each field name in fieldNames, fetch that field's List value. */ - Map> populateFieldsInMap(Set fieldNames); /* Get the String value of a field in this message. */ @Nullable - String getFieldStringValue(String fieldName); + Object getFieldValue(String fieldName); + + /* List of names of fields to include in the serialized request body. */ + @Nullable + List getFieldMask(); /* If this is a Request object, return the inner ApiMessage that represents the body * of the request; else return null. */ diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpRequestFormatter.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpRequestFormatter.java index 54ce44ed2..b1eda2a2b 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpRequestFormatter.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpRequestFormatter.java @@ -33,11 +33,9 @@ import com.google.api.pathtemplate.PathTemplate; import com.google.api.resourcenames.ResourceNameFactory; import com.google.auto.value.AutoValue; -import com.google.gson.Gson; +import com.google.common.collect.Lists; import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -62,37 +60,14 @@ public abstract class ApiMessageHttpRequestFormatter ApiMessageHttpRequestFormatter create( - final RequestT requestInstance, Set queryParams, String resourceNameField, ResourceNameFactory resourceNameFactory, PathTemplate pathTemplate) { - final Gson baseGson = new GsonBuilder().create(); - - TypeAdapter requestTypeAdapter = - new TypeAdapter() { - @Override - public void write(JsonWriter out, RequestT value) { - baseGson.toJson(value, requestInstance.getClass(), out); - } - - @Override - public RequestT read(JsonReader in) { - return null; - } - }; - - Gson requestMarshaller = - new GsonBuilder() - .registerTypeAdapter(requestInstance.getClass(), requestTypeAdapter) - .create(); - return new AutoValue_ApiMessageHttpRequestFormatter<>( - resourceNameField, resourceNameFactory, queryParams, pathTemplate, requestMarshaller); + resourceNameField, resourceNameFactory, queryParams, pathTemplate); } public static @@ -104,7 +79,23 @@ ApiMessageHttpRequestFormatter.Builder newBuilder() { public Map> getQueryParamNames(RequestT apiMessage) { Set paramNames = getQueryParamNames(); Map> queryParams = new HashMap<>(); - Map> nullableParams = apiMessage.populateFieldsInMap(paramNames); + Map> nullableParams = new HashMap<>(); + for (String paramName : paramNames) { + Object paramValue = apiMessage.getFieldValue(paramName); + List valueList; + if (paramValue == null) { + continue; + } + if (paramValue instanceof List) { + valueList = new ArrayList<>(); + for (Object val : (List) paramValue) { + valueList.add(val.toString()); + } + } else { + valueList = Lists.newArrayList(paramValue.toString()); + } + nullableParams.put(paramName, valueList); + } Iterator>> iterator = nullableParams.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> pair = iterator.next(); @@ -118,10 +109,15 @@ public Map> getQueryParamNames(RequestT apiMessage) { @Override public String getRequestBody(ApiMessage apiMessage) { ApiMessage body = apiMessage.getApiMessageRequestBody(); - if (body != null) { - return getRequestMarshaller().toJson(body); + if (body == null) { + return null; + } + GsonBuilder requestMarshaller = new GsonBuilder().serializeNulls(); + if (apiMessage.getFieldMask() != null) { + requestMarshaller.registerTypeAdapter( + body.getClass(), new FieldMaskedSerializer(apiMessage.getFieldMask())); } - return null; + return requestMarshaller.create().toJson(body); } @Override @@ -131,17 +127,17 @@ public String getPath(RequestT apiMessage) { } private Map getPathParams(RequestT apiMessage) { - String resourceNamePath = apiMessage.getFieldStringValue(getResourceNameField()); - if (resourceNamePath == null) { + Object fieldValue = apiMessage.getFieldValue(getResourceNameField()); + if (fieldValue == null) { throw new IllegalArgumentException( String.format( "Resource name field %s is null in message object.", getResourceNameField())); } + String resourceNamePath = fieldValue.toString(); return getResourceNameFactory().parse(resourceNamePath).getFieldValuesMap(); } public static class Builder { - private RequestT requestInstance; private String resourceNameField; private ResourceNameFactory resourceNameFactory; private Set queryParams; @@ -149,11 +145,6 @@ public static class Builder { private Builder() {} - public Builder setRequestInstance(RequestT requestInstance) { - this.requestInstance = requestInstance; - return this; - } - public Builder setResourceNameField(String resourceNameField) { this.resourceNameField = resourceNameField; return this; @@ -176,7 +167,7 @@ public Builder setQueryParams(Set queryParams) { public ApiMessageHttpRequestFormatter build() { return ApiMessageHttpRequestFormatter.create( - requestInstance, queryParams, resourceNameField, resourceNameFactory, pathTemplate); + queryParams, resourceNameField, resourceNameFactory, pathTemplate); } } } diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldMaskedSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldMaskedSerializer.java new file mode 100644 index 000000000..c76f37890 --- /dev/null +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldMaskedSerializer.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.httpjson; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.util.List; + +/** JSON Serializer for messages with a field mask to selectively serialize fields. */ +public class FieldMaskedSerializer implements JsonSerializer { + private final List fieldMask; + + public FieldMaskedSerializer(List fieldMask) { + this.fieldMask = fieldMask; + } + + @Override + public JsonElement serialize( + ApiMessage requestBody, Type typeOfSrc, JsonSerializationContext context) { + Gson gson = new GsonBuilder().serializeNulls().create(); + if (fieldMask == null) { + return gson.toJsonTree(requestBody, typeOfSrc); + } + JsonObject jsonObject = new JsonObject(); + for (String fieldName : fieldMask) { + Object fieldValue = requestBody.getFieldValue(fieldName); + if (fieldValue != null) { + jsonObject.add(fieldName, gson.toJsonTree(fieldValue, fieldValue.getClass())); + } else { + // TODO(andrealin): This doesn't distinguish between the non-existence of a field + // and a field value being null. + jsonObject.add(fieldName, JsonNull.INSTANCE); + } + } + + return jsonObject; + } +} diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java new file mode 100644 index 000000000..acca3e3f6 --- /dev/null +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 2018 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.httpjson; + +import com.google.api.client.http.HttpRequest; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.pathtemplate.PathTemplate; +import com.google.api.resourcenames.ResourceName; +import com.google.api.resourcenames.ResourceNameFactory; +import com.google.auth.Credentials; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.truth.Truth; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Test; +import org.threeten.bp.Instant; + +public class ApiMessageHttpRequestTest { + private static final String ENDPOINT = "https://www.googleapis.com/animals/v1/projects/"; + private static PathTemplate nameTemplate = PathTemplate.create("name/{name}"); + + private static HttpJsonCallOptions fakeCallOptions = + new HttpJsonCallOptions() { + @Override + public Instant getDeadline() { + return null; + } + + @Override + public Credentials getCredentials() { + return null; + } + }; + + @Test + public void testFieldMask() throws IOException { + List fieldMask = Lists.newArrayList("name", "limbs", "poisonous"); + FrogMessage frogMessage = new FrogMessage("tree_frog", 4, Lists.newArrayList("legs"), null); + + InsertFrogRequest insertFrogRequest = + new InsertFrogRequest("name/tree_frog", "request57", frogMessage, fieldMask); + + ApiMessageHttpRequestFormatter frogFormatter = + ApiMessageHttpRequestFormatter.newBuilder() + .setResourceNameField("name") + .setPathTemplate(nameTemplate) + .setResourceNameFactory( + new ResourceNameFactory() { + public ResourceName parse(final String formattedString) { + return new ResourceName() { + @Override + public Map getFieldValuesMap() { + Map fieldValues = new HashMap<>(); + fieldValues.put("name", nameTemplate.parse(formattedString).get("name")); + return fieldValues; + } + + @Override + public String getFieldValue(String s) { + return getFieldValuesMap().get(s); + } + }; + } + }) + .setQueryParams(Sets.newHashSet("requestId")) + .build(); + + ApiMethodDescriptor apiMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("house.details.get") + .setHttpMethod(null) + .setRequestFormatter(frogFormatter) + .build(); + + HttpRequestRunnable httpRequestRunnable = + HttpRequestRunnable.newBuilder() + .setHttpJsonCallOptions(fakeCallOptions) + .setEndpoint(ENDPOINT) + .setRequest(insertFrogRequest) + .setApiMethodDescriptor(apiMethodDescriptor) + .setHttpTransport(new MockHttpTransport()) + .setJsonFactory(new JacksonFactory()) + .build(); + + HttpRequest httpRequest = httpRequestRunnable.createHttpRequest(); + String expectedUrl = ENDPOINT + "name/tree_frog" + "?requestId=request57"; + Truth.assertThat(httpRequest.getUrl().toString()).isEqualTo(expectedUrl); + + OutputStream outputStream = new PrintableOutputStream(); + httpRequest.getContent().writeTo(outputStream); + + // JSON content string must contain all fields in fieldMask, even if the value is null. + Truth.assertThat(outputStream.toString()) + .isEqualTo("{\"name\":\"tree_frog\",\"limbs\":[\"legs\"],\"poisonous\":null}"); + } + + // Example of a Request object that contains an inner request body message. + private static class InsertFrogRequest implements ApiMessage { + private final String name; + private final String requestId; + private final FrogMessage requestBody; + + private final transient List fieldMask; + + InsertFrogRequest(String name, String requestId, FrogMessage frog, List fieldMask) { + this.name = name; + this.requestId = requestId; + this.requestBody = frog; + this.fieldMask = fieldMask; + } + + /* If this is a Request object, return the inner ApiMessage that represents the body + * of the request; else return null. */ + @Nullable + @Override + public ApiMessage getApiMessageRequestBody() { + return requestBody; + } + + @Nullable + @Override + public Object getFieldValue(String fieldName) { + if ("name".equals(fieldName)) { + return name; + } + if ("requestId".equals(fieldName)) { + return requestId; + } + if ("requestBody".equals(fieldName)) { + return requestBody; + } + return null; + } + + @Nullable + @Override + public List getFieldMask() { + return fieldMask; + } + } + + private static class FrogMessage implements ApiMessage { + private final String name; + private final Integer legs; + private final List limbs; + private final Boolean poisonous; + + FrogMessage(String name, Integer legs, List limbs, Boolean poisonous) { + this.name = name; + this.legs = legs; + this.limbs = limbs; + this.poisonous = poisonous; + } + + @Nullable + @Override + public Object getFieldValue(String fieldName) { + if ("name".equals(fieldName)) { + return name; + } + if ("legs".equals(fieldName)) { + return legs; + } + if ("limbs".equals(fieldName)) { + return limbs; + } + if ("poisonous".equals(fieldName)) { + return poisonous; + } + return null; + } + + @Nullable + @Override + public List getFieldMask() { + return null; + } + + /* If this is a Request object, return the inner ApiMessage that represents the body + * of the request; else return null. */ + @Nullable + @Override + public ApiMessage getApiMessageRequestBody() { + return null; + } + } + + public static class PrintableOutputStream extends OutputStream { + private StringBuilder string = new StringBuilder(); + + @Override + public void write(int x) { + this.string.append((char) x); + } + + public String toString() { + return this.string.toString(); + } + } +} diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/FieldMaskTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/FieldMaskTest.java new file mode 100644 index 000000000..96743577c --- /dev/null +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/FieldMaskTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 2018 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.httpjson; + +import com.google.api.gax.httpjson.testing.FakeApiMessage; +import com.google.common.collect.Lists; +import com.google.common.truth.Truth; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSerializer; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Test; + +public class FieldMaskTest { + + private static final TreeMessage treeMessage = + new TreeMessage("Cedrus", Lists.newArrayList(2, 0, 6)); + + @Test + public void testFieldMaskGenus() { + List fieldMask = Lists.newArrayList("genus"); + + JsonSerializer jsonSerializer = new FieldMaskedSerializer(fieldMask); + Gson gson = new GsonBuilder().registerTypeAdapter(TreeMessage.class, jsonSerializer).create(); + Truth.assertThat(gson.toJson(treeMessage)).isEqualTo("{\"genus\":\"Cedrus\"}"); + } + + @Test + public void testFieldMaskBranches() { + List fieldMask = Lists.newArrayList("branchLengths"); + + JsonSerializer jsonSerializer = new FieldMaskedSerializer(fieldMask); + Gson gson = new GsonBuilder().registerTypeAdapter(TreeMessage.class, jsonSerializer).create(); + Truth.assertThat(gson.toJson(treeMessage)).isEqualTo("{\"branchLengths\":[2,0,6]}"); + } + + @Test + public void testEmptyFieldMask() { + List fieldMask = null; + + JsonSerializer jsonSerializer = new FieldMaskedSerializer(fieldMask); + Gson gson = + new GsonBuilder().registerTypeAdapter(FakeApiMessage.class, jsonSerializer).create(); + Truth.assertThat(gson.toJson(treeMessage)) + .isEqualTo("{\"genus\":\"Cedrus\",\"branchLengths\":[2,0,6]}"); + } + + // Represents a resource message type. + private static class TreeMessage implements ApiMessage { + + private String genus; + private List branchLengths; + + TreeMessage(String genus, List branchLengths) { + this.genus = genus; + this.branchLengths = branchLengths; + } + + @Nullable + @Override + public Object getFieldValue(String fieldName) { + if (fieldName.equals("genus")) { + return genus; + } + if (fieldName.equals("branchLengths")) { + return branchLengths; + } + return null; + } + + @Nullable + @Override + public List getFieldMask() { + return null; + } + + @Nullable + @Override + public ApiMessage getApiMessageRequestBody() { + return null; + } + } +} diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java index f79cda898..31e8f0f8a 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java @@ -36,6 +36,7 @@ import com.google.api.pathtemplate.PathTemplate; import com.google.auth.Credentials; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.truth.Truth; import java.io.IOException; @@ -44,6 +45,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import org.junit.BeforeClass; import org.junit.Test; import org.threeten.bp.Instant; @@ -56,6 +58,9 @@ public class HttpRequestRunnableTest { private static HttpRequestFormatter catFormatter; private static HttpResponseParser catParser; private static ApiMethodDescriptor methodDescriptor; + private static PathTemplate nameTemplate = PathTemplate.create("name/{name}"); + private static Set queryParams = + Sets.newTreeSet(Lists.newArrayList("food", "size", "gibberish")); @BeforeClass public static void setUp() { @@ -75,9 +80,10 @@ public Credentials getCredentials() { catMessage = new CatMessage( ImmutableMap.of( - "name", Arrays.asList("feline"), + "name", "feline", "size", Arrays.asList("small"), "food", Arrays.asList("bird", "mouse")), + null, null); catFormatter = @@ -86,11 +92,19 @@ public Credentials getCredentials() { @Override public Map> getQueryParamNames(CatMessage apiMessage) { - Set orderedParams = Sets.newTreeSet(); - orderedParams.add("food"); - orderedParams.add("size"); - orderedParams.add("gibberish"); - return apiMessage.populateFieldsInMap(orderedParams); + Map> values = new TreeMap<>(); + for (String queryParam : queryParams) { + Object fieldValue = apiMessage.getFieldValue(queryParam); + if (fieldValue == null) { + continue; + } + if (fieldValue instanceof List) { + values.put(queryParam, (List) fieldValue); + } else { + values.put(queryParam, Lists.newArrayList(fieldValue.toString())); + } + } + return values; } @Override @@ -100,7 +114,8 @@ public String getRequestBody(CatMessage apiMessage) { @Override public String getPath(CatMessage apiMessage) { - return namePattern.instantiate("name", apiMessage.getFieldStringValue("name")); + String name = apiMessage.getFieldValue("name").toString(); + return nameTemplate.instantiate("name", name); } @Override @@ -151,8 +166,8 @@ public void testRequestUrl() throws IOException { // TODO(andrealin): test request body private static class CatMessage extends FakeApiMessage { - public CatMessage(Map> fieldValues, ApiMessage messageBody) { - super(fieldValues, messageBody); + CatMessage(Map fieldValues, ApiMessage messageBody, List fieldMask) { + super(fieldValues, messageBody, fieldMask); } } } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java index 8be933211..1efc8466f 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java @@ -63,13 +63,13 @@ public class MockHttpServiceTest { private static PetMessage gerbilMessage = new PetMessage( - ImmutableMap.>of("type", Lists.newArrayList("rodent")), null); + ImmutableMap.of("type", Lists.newArrayList("rodent")), null, null); private static PetMessage ospreyMessage = new PetMessage( - ImmutableMap.>of("type", Lists.newArrayList("raptor")), null); + ImmutableMap.of("type", Lists.newArrayList("raptor")), null, null); private static HumanMessage humanMessage = new HumanMessage( - ImmutableMap.>of("type", Lists.newArrayList("toddler")), null); + ImmutableMap.of("type", Lists.newArrayList("toddler")), null, null); private static final String RESPONSE_EXCEPTION_STRING = "[Expected exception]"; private static final ApiException PARSE_EXCEPTION = @@ -77,14 +77,16 @@ public class MockHttpServiceTest { "Unknown object type.", null, HttpJsonStatusCode.of(Code.INVALID_ARGUMENT), false); private static class PetMessage extends FakeApiMessage { - public PetMessage(Map> fieldValues, ApiMessage requestBodyMessage) { - super(fieldValues, requestBodyMessage); + public PetMessage( + Map fieldValues, ApiMessage requestBodyMessage, List fieldMask) { + super(fieldValues, requestBodyMessage, fieldMask); } } private static class HumanMessage extends FakeApiMessage { - public HumanMessage(Map> fieldValues, ApiMessage requestBodyMessage) { - super(fieldValues, requestBodyMessage); + public HumanMessage( + Map fieldValues, ApiMessage requestBodyMessage, List fieldMask) { + super(fieldValues, requestBodyMessage, fieldMask); } } @@ -97,7 +99,7 @@ public PetMessage parse(InputStream httpContent) { @Override public String serialize(PetMessage response) { - return response.getFieldStringValue("type"); + return ((List) response.getFieldValue("type")).get(0); } }; diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/testing/FakeApiMessage.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/testing/FakeApiMessage.java index f5e703187..51d7816f2 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/testing/FakeApiMessage.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/testing/FakeApiMessage.java @@ -31,45 +31,36 @@ import com.google.api.core.InternalApi; import com.google.api.gax.httpjson.ApiMessage; -import com.google.common.collect.ImmutableMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; import javax.annotation.Nullable; /** Simple implementation of ApiMessage. */ @InternalApi("for testing") public class FakeApiMessage implements ApiMessage { - private final Map> fieldValues; + private final Map fieldValues; private final ApiMessage messageBody; + private List fieldMask; /** Instantiate a FakeApiMessage with a message body and a map of field names and their values. */ - public FakeApiMessage(Map> fieldValues, ApiMessage messageBody) { - this.fieldValues = ImmutableMap.copyOf(fieldValues); + public FakeApiMessage( + Map fieldValues, ApiMessage messageBody, List fieldMask) { + this.fieldValues = new TreeMap<>(fieldValues); this.messageBody = messageBody; + this.fieldMask = fieldMask; } + @Nullable @Override - public Map> populateFieldsInMap(Set fieldNames) { - Map> fieldMap = new TreeMap<>(); - for (String key : fieldNames) { - if (fieldValues.containsKey(key)) { - fieldMap.put(key, fieldValues.get(key)); - } - } - return fieldMap; + public Object getFieldValue(String fieldName) { + return fieldValues.get(fieldName); } - /* Get the first value of a field in this message. */ @Nullable @Override - public String getFieldStringValue(String fieldName) { - List fieldValue = fieldValues.get(fieldName); - if (fieldValue == null || fieldValue.size() == 0) { - return null; - } - return fieldValue.get(0); + public List getFieldMask() { + return fieldMask; } /* If this is a Request object, return the inner ApiMessage that represents the body