Skip to content

Commit

Permalink
fix: Remove null check for paged Protobuf messages (#3164)
Browse files Browse the repository at this point in the history
## Background 
This was caught from ErrorProne flagging an
[ImpossibleNullComparison](https://errorprone.info/bugpattern/ImpossibleNullComparison)
when upgrading to Protobuf 4.27.4 runtime. Protobuf messages should be
non-null (with type specific default values) when they are serialized.
Removing the null check that is being flagged from ErrorProne.

## Testing
We have some issues with testing this inside Showcase. Protobuf
serializes messages with default values and does not allow for null
(will throw a NPE). Since showcase is configured to receive and send
Protobuf Messages, we cannot configure the response to be return null
values (at best we can return something like an empty string or an empty
map).

Instead, we have opted to create a sample Spring server to return a JSON
payload with certain fields set to null. Create a GAPIC client and
manually set the endpoint to point to the local server (i.e.
`localhost:8080`). The Spring application will have a endpoint that
matches the path of the RPC (i.e.
`@GetMapping("/compute/v1/projects/{project}/zones/{zone}/instances")`)
and returns the JSON. This proves that a null fields sent from the
server is serialized properly into a Protobuf message and the message's
fields are non-null.

```
@RestController
public class ComputeController {

  @GetMapping("/compute/v1/projects/{project}/zones/{zone}/instances")
  String listInstances(@PathVariable String project, String zone) {
    return "{\"items\": null, \"id\": 5}";
  }
}
```
In the example above, the `items` field is a list and the printing it
out in the client library will result in `[]`.

Additionally, we have added some small, local tests against two repeated
Protobuf messages:
1. BackendBucketList's items is a non-null List
```
JsonFormat.Parser parser = JsonFormat.parser().ignoringUnknownFields()
        .usingTypeRegistry(TypeRegistry.newBuilder().add(BackendBucketList.getDescriptor()).build());
BackendBucketList.Builder builder = BackendBucketList.newBuilder();
parser.merge("{\"items\": null}", builder);
BackendBucketList list = builder.build();
System.out.println(list.getItemsList());
```

2. PredictResponse's metadata is a non-null Map
```
JsonFormat.Parser parser = JsonFormat.parser().ignoringUnknownFields()
        .usingTypeRegistry(TypeRegistry.newBuilder().add(PredictResponse.getDescriptor()).build());;
PredictResponse.Builder builder = PredictResponse.newBuilder();
parser.merge("{\"recommendation_token\": \"343\"}", builder);
System.out.println(builder.build().getMetadataMap());
```

---------

Co-authored-by: cloud-java-bot <cloud-java-bot@google.com>
Co-authored-by: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 5, 2024
1 parent 8fe3a2d commit f3890aa
Show file tree
Hide file tree
Showing 25 changed files with 77 additions and 220 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,11 @@
import com.google.api.generator.engine.ast.PrimitiveValue;
import com.google.api.generator.engine.ast.Reference;
import com.google.api.generator.engine.ast.ReferenceConstructorExpr;
import com.google.api.generator.engine.ast.RelationalOperationExpr;
import com.google.api.generator.engine.ast.ReturnExpr;
import com.google.api.generator.engine.ast.ScopeNode;
import com.google.api.generator.engine.ast.Statement;
import com.google.api.generator.engine.ast.StringObjectValue;
import com.google.api.generator.engine.ast.SuperObjectValue;
import com.google.api.generator.engine.ast.TernaryExpr;
import com.google.api.generator.engine.ast.ThisObjectValue;
import com.google.api.generator.engine.ast.ThrowExpr;
import com.google.api.generator.engine.ast.TypeNode;
Expand Down Expand Up @@ -788,27 +786,9 @@ private static Expr createPagedListDescriptorAssignExpr(
.setGenerics(Arrays.asList(repeatedResponseType.reference()))
.build());

Expr getResponsesExpr;
Expr elseExpr;
Expr thenExpr;
if (repeatedResponseType.reference() != null
&& "java.util.Map.Entry".equals(repeatedResponseType.reference().fullName())) {
getResponsesExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(payloadVarExpr)
.setMethodName(
String.format("get%sMap", JavaStyle.toUpperCamelCase(repeatedFieldName)))
.setReturnType(returnType)
.build();
thenExpr =
MethodInvocationExpr.builder()
.setStaticReferenceType(
TypeNode.withReference(ConcreteReference.withClazz(Collections.class)))
.setGenerics(Arrays.asList(repeatedResponseType.reference()))
.setMethodName("emptySet")
.setReturnType(returnType)
.build();
elseExpr =
returnExpr =
MethodInvocationExpr.builder()
.setMethodName("entrySet")
.setExprReferenceExpr(
Expand All @@ -820,39 +800,14 @@ private static Expr createPagedListDescriptorAssignExpr(
.setReturnType(returnType)
.build();
} else {
getResponsesExpr =
returnExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(payloadVarExpr)
.setMethodName(
String.format("get%sList", JavaStyle.toUpperCamelCase(repeatedFieldName)))
.setReturnType(returnType)
.build();
thenExpr =
MethodInvocationExpr.builder()
.setStaticReferenceType(
TypeNode.withReference(ConcreteReference.withClazz(ImmutableList.class)))
.setGenerics(Arrays.asList(repeatedResponseType.reference()))
.setMethodName("of")
.setReturnType(returnType)
.build();
elseExpr = getResponsesExpr;
}
// While protobufs should not be null, this null-check is needed to protect against NPEs
// in paged iteration on clients that use legacy HTTP/JSON types, as these clients can
// actually return null instead of an empty list.
// Context:
// Original issue: /~https://github.com/googleapis/google-cloud-java/issues/3736
// Relevant discussion where this check was first added:
// /~https://github.com/googleapis/google-cloud-java/pull/4499#discussion_r257057409
Expr conditionExpr =
RelationalOperationExpr.equalToWithExprs(getResponsesExpr, ValueExpr.createNullExpr());

returnExpr =
TernaryExpr.builder()
.setConditionExpr(conditionExpr)
.setThenExpr(thenExpr)
.setElseExpr(elseExpr)
.build();
anonClassMethods.add(
methodStarterBuilder
.setReturnType(returnType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,7 @@ public class EchoStubSettings extends StubSettings<EchoStubSettings> {

@Override
public Iterable<EchoResponse> extractResources(PagedExpandResponse payload) {
return payload.getResponsesList() == null
? ImmutableList.<EchoResponse>of()
: payload.getResponsesList();
return payload.getResponsesList();
}
};

Expand Down Expand Up @@ -216,9 +214,7 @@ public class EchoStubSettings extends StubSettings<EchoStubSettings> {

@Override
public Iterable<EchoResponse> extractResources(PagedExpandResponse payload) {
return payload.getResponsesList() == null
? ImmutableList.<EchoResponse>of()
: payload.getResponsesList();
return payload.getResponsesList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,7 @@ public class LoggingServiceV2StubSettings extends StubSettings<LoggingServiceV2S

@Override
public Iterable<LogEntry> extractResources(ListLogEntriesResponse payload) {
return payload.getEntriesList() == null
? ImmutableList.<LogEntry>of()
: payload.getEntriesList();
return payload.getEntriesList();
}
};

Expand Down Expand Up @@ -217,9 +215,7 @@ public class LoggingServiceV2StubSettings extends StubSettings<LoggingServiceV2S
@Override
public Iterable<MonitoredResourceDescriptor> extractResources(
ListMonitoredResourceDescriptorsResponse payload) {
return payload.getResourceDescriptorsList() == null
? ImmutableList.<MonitoredResourceDescriptor>of()
: payload.getResourceDescriptorsList();
return payload.getResourceDescriptorsList();
}
};

Expand Down Expand Up @@ -253,9 +249,7 @@ public class LoggingServiceV2StubSettings extends StubSettings<LoggingServiceV2S

@Override
public Iterable<String> extractResources(ListLogsResponse payload) {
return payload.getLogNamesList() == null
? ImmutableList.<String>of()
: payload.getLogNamesList();
return payload.getLogNamesList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,7 @@ public class PublisherStubSettings extends StubSettings<PublisherStubSettings> {

@Override
public Iterable<Topic> extractResources(ListTopicsResponse payload) {
return payload.getTopicsList() == null
? ImmutableList.<Topic>of()
: payload.getTopicsList();
return payload.getTopicsList();
}
};

Expand Down Expand Up @@ -208,9 +206,7 @@ public class PublisherStubSettings extends StubSettings<PublisherStubSettings> {

@Override
public Iterable<String> extractResources(ListTopicSubscriptionsResponse payload) {
return payload.getSubscriptionsList() == null
? ImmutableList.<String>of()
: payload.getSubscriptionsList();
return payload.getSubscriptionsList();
}
};

Expand Down Expand Up @@ -247,9 +243,7 @@ public class PublisherStubSettings extends StubSettings<PublisherStubSettings> {

@Override
public Iterable<String> extractResources(ListTopicSnapshotsResponse payload) {
return payload.getSnapshotsList() == null
? ImmutableList.<String>of()
: payload.getSnapshotsList();
return payload.getSnapshotsList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,7 @@ public class EchoStubSettings extends StubSettings<EchoStubSettings> {

@Override
public Iterable<EchoResponse> extractResources(PagedExpandResponse payload) {
return payload.getResponsesList() == null
? ImmutableList.<EchoResponse>of()
: payload.getResponsesList();
return payload.getResponsesList();
}
};

Expand Down Expand Up @@ -222,9 +220,7 @@ public class EchoStubSettings extends StubSettings<EchoStubSettings> {

@Override
public Iterable<EchoResponse> extractResources(PagedExpandResponse payload) {
return payload.getResponsesList() == null
? ImmutableList.<EchoResponse>of()
: payload.getResponsesList();
return payload.getResponsesList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,7 @@ public String extractNextToken(ListLocationsResponse payload) {

@Override
public Iterable<Location> extractResources(ListLocationsResponse payload) {
return payload.getLocationsList() == null
? ImmutableList.<Location>of()
: payload.getLocationsList();
return payload.getLocationsList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@
import com.google.showcase.v1beta1.WaitRequest;
import com.google.showcase.v1beta1.WaitResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Generated;
Expand Down Expand Up @@ -225,9 +224,7 @@ public String extractNextToken(PagedExpandResponse payload) {

@Override
public Iterable<EchoResponse> extractResources(PagedExpandResponse payload) {
return payload.getResponsesList() == null
? ImmutableList.<EchoResponse>of()
: payload.getResponsesList();
return payload.getResponsesList();
}
};

Expand Down Expand Up @@ -268,9 +265,7 @@ public String extractNextToken(PagedExpandLegacyMappedResponse payload) {
@Override
public Iterable<Map.Entry<String, PagedExpandResponseList>> extractResources(
PagedExpandLegacyMappedResponse payload) {
return payload.getAlphabetizedMap() == null
? Collections.<Map.Entry<String, PagedExpandResponseList>>emptySet()
: payload.getAlphabetizedMap().entrySet();
return payload.getAlphabetizedMap().entrySet();
}
};

Expand Down Expand Up @@ -304,9 +299,7 @@ public String extractNextToken(ListLocationsResponse payload) {

@Override
public Iterable<Location> extractResources(ListLocationsResponse payload) {
return payload.getLocationsList() == null
? ImmutableList.<Location>of()
: payload.getLocationsList();
return payload.getLocationsList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,7 @@ public String extractNextToken(ListUsersResponse payload) {

@Override
public Iterable<User> extractResources(ListUsersResponse payload) {
return payload.getUsersList() == null
? ImmutableList.<User>of()
: payload.getUsersList();
return payload.getUsersList();
}
};

Expand Down Expand Up @@ -208,9 +206,7 @@ public String extractNextToken(ListLocationsResponse payload) {

@Override
public Iterable<Location> extractResources(ListLocationsResponse payload) {
return payload.getLocationsList() == null
? ImmutableList.<Location>of()
: payload.getLocationsList();
return payload.getLocationsList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,7 @@ public String extractNextToken(ListRoomsResponse payload) {

@Override
public Iterable<Room> extractResources(ListRoomsResponse payload) {
return payload.getRoomsList() == null
? ImmutableList.<Room>of()
: payload.getRoomsList();
return payload.getRoomsList();
}
};

Expand Down Expand Up @@ -269,9 +267,7 @@ public String extractNextToken(ListBlurbsResponse payload) {

@Override
public Iterable<Blurb> extractResources(ListBlurbsResponse payload) {
return payload.getBlurbsList() == null
? ImmutableList.<Blurb>of()
: payload.getBlurbsList();
return payload.getBlurbsList();
}
};

Expand Down Expand Up @@ -305,9 +301,7 @@ public String extractNextToken(ListLocationsResponse payload) {

@Override
public Iterable<Location> extractResources(ListLocationsResponse payload) {
return payload.getLocationsList() == null
? ImmutableList.<Location>of()
: payload.getLocationsList();
return payload.getLocationsList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ public String extractNextToken(ListLocationsResponse payload) {

@Override
public Iterable<Location> extractResources(ListLocationsResponse payload) {
return payload.getLocationsList() == null
? ImmutableList.<Location>of()
: payload.getLocationsList();
return payload.getLocationsList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,7 @@ public String extractNextToken(ListSessionsResponse payload) {

@Override
public Iterable<Session> extractResources(ListSessionsResponse payload) {
return payload.getSessionsList() == null
? ImmutableList.<Session>of()
: payload.getSessionsList();
return payload.getSessionsList();
}
};

Expand Down Expand Up @@ -221,9 +219,7 @@ public String extractNextToken(ListTestsResponse payload) {

@Override
public Iterable<Test> extractResources(ListTestsResponse payload) {
return payload.getTestsList() == null
? ImmutableList.<Test>of()
: payload.getTestsList();
return payload.getTestsList();
}
};

Expand Down Expand Up @@ -257,9 +253,7 @@ public String extractNextToken(ListLocationsResponse payload) {

@Override
public Iterable<Location> extractResources(ListLocationsResponse payload) {
return payload.getLocationsList() == null
? ImmutableList.<Location>of()
: payload.getLocationsList();
return payload.getLocationsList();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,24 @@
"allDeclaredClasses": true,
"allPublicClasses": true
},
{
"name": "com.google.api.PythonSettings$ExperimentalFeatures",
"queryAllDeclaredConstructors": true,
"queryAllPublicConstructors": true,
"queryAllDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredClasses": true,
"allPublicClasses": true
},
{
"name": "com.google.api.PythonSettings$ExperimentalFeatures$Builder",
"queryAllDeclaredConstructors": true,
"queryAllPublicConstructors": true,
"queryAllDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredClasses": true,
"allPublicClasses": true
},
{
"name": "com.google.api.ResourceDescriptor",
"queryAllDeclaredConstructors": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,7 @@ public String extractNextToken(ListConnectionsResponse payload) {

@Override
public Iterable<Connection> extractResources(ListConnectionsResponse payload) {
return payload.getConnectionsList() == null
? ImmutableList.<Connection>of()
: payload.getConnectionsList();
return payload.getConnectionsList();
}
};

Expand Down
Loading

0 comments on commit f3890aa

Please sign in to comment.