Search code examples
javascriptnode.jsprotocol-buffersgrpc

gRPC fails to serialize object with arbitrary properties


The context is that I am working on a configurable mock gRPC server which will run in a Docker instance and serve as a mock instance of backend services. I initialize each instance with a proto file and a configuration that tells it what functions to listen to and what data to respond with.

I am using @grpc/grpc-js and @grpc/proto-loader. I've already set it up to return arbitrary scalar values as well as arrays and objects with a known schema. Everything was working until I got to the part where I wanted to generate arbitrary objects (i.e. maps). For some reason, the gRPC server doesn't recognize the object that I pass to it as an object and complains.

Error as reported from the client:

Error: 13 INTERNAL: Error serializing response: .Result.testObject: object expected
    at callErrorFromStatus (***/test/grpc/node_modules/@grpc/grpc-js/build/src/call.js:31:19)
    at Object.onReceiveStatus (***/test/grpc/node_modules/@grpc/grpc-js/build/src/client.js:192:76)
    at Object.onReceiveStatus (***/test/grpc/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141)
    at Object.onReceiveStatus (***/test/grpc/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:323:181)
    at ***/test/grpc/node_modules/@grpc/grpc-js/build/src/resolving-call.js:99:78
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
for call at
    at ServiceClientImpl.makeUnaryRequest (***/test/grpc/node_modules/@grpc/grpc-js/build/src/client.js:160:32)
    at ServiceClientImpl.<anonymous> (***/test/grpc/node_modules/@grpc/grpc-js/build/src/make-client.js:105:19)
    at file://***/test/grpc/index.mjs:14:10
    at file://***/test/grpc/index.mjs:18:3
    at ModuleJob.run (node:internal/modules/esm/module_job:217:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
    at async loadESM (node:internal/process/esm_loader:34:7)
    at async handleMainPromise (node:internal/modules/run_main:66:12) {
  code: 13,
  details: 'Error serializing response: .Result.testObject: object expected',
  metadata: Metadata {
    internalRepr: Map(2) { 'content-type' => [Array], 'date' => [Array] },
    options: {}
  }
}

This is my proto file (identical for both client and server):

syntax = "proto3";

service TestApi {
  rpc Execute(None) returns (Result);
}

message None {}

message Result {
  map<string, Value> testObject = 1;
}

message Value {
  oneof kind {
    bool boolValue = 1;
    int32 intValue = 2;
    double doubleValue = 3;
    string stringValue = 4;
  }
}

The server handler function:

export function handler(call, callback) {
  try {
    const response = generateValues('$', outputs); // Generate random response
    console.log(response);
    callback(null, response);
  } catch (e) {
    console.error(e);
    callback(e, null);
  }
}

The console.error never fires. An example of the console.log output:

{
  testObject: {
    VmZWD: false,
    VXiRwi: 28.087891844342792,
    cjCTDFD: false,
    inYZS: 6,
    AYKahk: ' xsyflf szsnwb ush',
    pgUT: 23.19160119367565
  }
}

Things I've tried which resulted in the same error:

  • Changing Value to google.protobuf.Any (with associated import)
  • Setting the json, object, and oneOf options of the proto-loader load function to true or false.

Note that changing map to another structure isn't an option as the production protos that this image is meant to be able to mock already use map in their definitions.


Solution

  • The object you are generating does not match the message types you have defined. The testObject map field has the value type Value, which is defined as a message containing one of several fields. In your example response object, the key VmZWD has the value false, but a Value message that represents the value false would be represented as { boolValue: false }. All of the map entries have the same problem.