Can @grpc/grpc-js dynamically unpack google.protobuf.Any type?

235 views Asked by At

I work on a gRPC client using @grpc/grpc-js, and I have to handle application-specific errors by reading the details array of the google.rpc.Status object. Is it possible to dynamically unpack the error object without knowing the possible errors upfront? The elements of the details array are google.protobuf.Any.

I am calling a server-streaming Init method:

  // Initializes an existing Arduino Core instance by loading platforms and
  // libraries
  rpc Init(InitRequest) returns (stream InitResponse) {}

The InitResponse has the following definition:

message InitResponse {
  message Progress {
    // Progress of the downloads of platforms and libraries index files.
    DownloadProgress download_progress = 1;
    // Describes the current stage of the initialization.
    TaskProgress task_progress = 2;
  }
  oneof message {
    Progress init_progress = 1;
    google.rpc.Status error = 2;
    // Selected profile information
    Profile profile = 3;
  }
}

As a client, I do not know what errors can be in the details array. Here are a few examples:

{
  "typeUrl": "type.googleapis.com/cc.arduino.cli.commands.v1.PlatformLoadingError",
  "value": ""
}
{
  "typeUrl": "type.googleapis.com/cc.arduino.cli.commands.v1.FailedInstanceInitError",
  "value": "CAIStAFMb2FkaW5nIGluZGV4IGZpbGU6IGxvYWRpbmcganNvbiBpbmRleCBmaWxlIC9Vc2Vycy9hLmtpdHRhL0xpYnJhcnkvQXJkdWlubzE1L3BhY2thZ2VfaW5kZXguanNvbjogb3BlbiAvVXNlcnMvYS5raXR0YS9MaWJyYXJ5L0FyZHVpbm8xNS9wYWNrYWdlX2luZGV4Lmpzb246IG5vIHN1Y2ggZmlsZSBvciBkaXJlY3Rvcnk="
}

From this SO answer, I learned that I could use Any#unpack to get the message object. It works but is not dynamic, and I need to know the possible errors to deserialize it. See an example:

const error: Status | undefined = resp.getError();
if (error) {
  const details: Any[] = error.getDetailsList();
  if (details.length) {
    const any = details[0];
    const typeName = any.getTypeName();
    switch (typeName) {
      case 'cc.arduino.cli.commands.v1.FailedInstanceInitError': {
        const initError = any.unpack(
          FailedInstanceInitError.deserializeBinary,
          typeName
        );
        console.log('Handle the init error', initError);
        break;
      }
      case 'cc.arduino.cli.commands.v1.PlatformLoadingError': {
        const loadError = any.unpack(
          PlatformLoadingError.deserializeBinary,
          typeName
        );
        console.log('Handle the load error', loadError);
        break;
      }
      default: {
        console.log('Unexpected error');
      }
    }
  }
}

Can I dynamically acquire the deserialize method of the corresponding type from the typeName? According to the protocolbuffers/protobuf-javascript#68 (comment), it's not feasible. How do others do it? What's the best practice if dynamic deserialization is not possible?

Thank you!


Update

I found a hack, but it works. I leave it here.

I looked into the JS modules generated from the proto files and noticed that the deserializeBinary is available from the global object. They're under the 'proto' key.

If I write a tiny function with the help of get-value:

import { Message } from 'google-protobuf';
import get = require('get-value');

export function globalDeserializeBinary<T extends Message = Message>(
  typeName: string
): ((data: Uint8Array) => T) | undefined {
  return get(global, ['proto', typeName, 'deserializeBinary'].join('.'));
}

I can modify my code to this:

const error: Status | undefined = resp.getError();
if (error) {
  const details: Any[] = error.getDetailsList();
  if (details.length) {
    const any = details[0];
    const typeName = any.getTypeName();
    const deserialize = globalDeserializeBinary(typeName);
    if (deserialize) {
      const error = any.unpack(deserialize, typeName);
      if (error) {
        console.log('BLABLA', JSON.stringify(error.toObject(false)));
      }
    }
  }
}

This will produce the desired objects:

BLABLA {"reason":2,"message":"Loading index file: loading json index file /Users/a.kitta/Library/Arduino15/package_index.json: open /Users/a.kitta/Library/Arduino15/package_index.json: no such file or directory"}
BLABLA {"reason":2,"message":"Loading index file: loading json index file /Users/a.kitta/Library/Arduino15/package_teensy_index.json: open /Users/a.kitta/Library/Arduino15/package_teensy_index.json: no such file or directory"}
BLABLA {}
BLABLA {}
0

There are 0 answers