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 {}