Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to use google.protobuf.Struct with @grpc/grpc-js #2747

Open
Zoddo opened this issue May 12, 2024 · 1 comment
Open

Unable to use google.protobuf.Struct with @grpc/grpc-js #2747

Zoddo opened this issue May 12, 2024 · 1 comment

Comments

@Zoddo
Copy link

Zoddo commented May 12, 2024

Problem description

When trying to respond with a google.protobuf.Struct, the call errors with status 13 INTERNAL Error serializing response: Expected argument of type google.protobuf.Struct.

Reproduction steps

Use this proto file:

syntax = "proto3";
import "google/protobuf/struct.proto";

package test;

message Empty {}

service Test {
  rpc getStruct(Empty) returns (google.protobuf.Struct) {}
}

Compile it with:

node_modules/.bin/grpc_tools_node_protoc \
  --grpc_out="grpc_js:./" \
  --js_out="import_style=commonjs,binary:./" \
  test.proto

Then create the following server:

const grpc = require('@grpc/grpc-js');
const { Struct } = require('google-protobuf/google/protobuf/struct_pb');
const { TestService } = require('./test_grpc_pb');

const server = new grpc.Server();
server.addService(TestService, {
  getStruct: (call, callback) => {
    callback(null, Struct.fromJavaScript({ test: 1, b: 'c' }));
  },
});

server.bindAsync('0.0.0.0:4000', grpc.ServerCredentials.createInsecure());

Run it, and try to call getStruct (using Postman, for example).
The server replies with an error: Error serializing response: Expected argument of type google.protobuf.Struct

I also tried with a plain object:

callback(null, {                                                                                                                                                                        
  fields: {                                                                                                                                                              
    test: { kind: 'numberValue', numberValue: 1 },                                                                                                                       
    b: { kind: 'stringValue', stringValue: 'c' }                                                                                                                         
  }
});

I get the same error.

Maybe I'm missing something obvious, but I've been trying a lot of things for the past hours and I still can't figure how to send a Struct...

Environment

  • OS name, version and architecture: Arch Linux x64
  • Node version: 20.12.2
  • Node installation method: nvm
  • Package name and version: @grpc/[email protected]
@Zoddo
Copy link
Author

Zoddo commented May 12, 2024

As we say, things are clearer in the morning! I found out what's my issue.

This error is coming from this check in the compiled proto file:

if (!(arg instanceof google_protobuf_struct_pb.Struct)) {
  throw new Error('Expected argument of type google.protobuf.Struct');
}

Now, why this cause issues in my case?
In my project, compiled proto files are put in a separate package that is installed with npm (to prevent code duplication).

Basically:

my repo
|─ server
|   |─ package.json
|   `─ server.js
`─ lib/testproto
    |─ package.json
    |─ test.proto
    |─ test_grpc_pb.js
    `─ test_pb.js

So when in server, I npm I -S ../lib/testproto, npm generates a symlink like this:
node_modules/testproto -> ../../lib/cache

Because it's a symlink, module deduplication between server and testproto doesn't work, as shown by npm ls google-protobuf in server:

zoddo@arch:~/git/myproject/server$ npm ls google-protobuf
[email protected] /home/zoddo/git/myproject/server
├─┬ [email protected] -> ./../lib/testproto
│ ├── [email protected]
│ └─┬ [email protected]
│   └── [email protected] deduped
└── [email protected]

As such, compiled files in testproto don't use the same files than server when doing require('google-protobuf'), breaking the instanceof check done in the compiled proto file.

In other words, that means that in the server, I generated an instance of Struct coming from:

server/node_modules/google-protobuf/google/protobuf/struct_pb.js

...while in testproto, it is checking if it's an instance of Struct coming from:

lib/cache/node_modules/google-protobuf/google/protobuf/struct_pb.js

(while it's the same code in the 2 files, they are different objects for the JS engine)

I'm not sure how to properly fix this issue. We can't even check arg.constructor.name because Struct isn't a class, but an anonymous function.

Right now I have a workaround by using TestService.getStruct.responseType.fromJavaScript(myObj) instead of Struct.fromJavaScript(myObj), but that's a hacky solution I don't like at all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants