diff --git a/examples/helloworld/static_codegen_es/README.md b/examples/helloworld/static_codegen_es/README.md new file mode 100644 index 000000000..c3a4289e5 --- /dev/null +++ b/examples/helloworld/static_codegen_es/README.md @@ -0,0 +1,7 @@ +This is the static code generation variant of the Hello World. Code in these examples is pre-generated using protoc and the Node gRPC protoc plugin, and the generated code can be found in various `*_pb.js` files. The command line sequence for generating those files is as follows (assuming that `protoc` and `grpc_node_plugin` are present, and starting in the directory which contains this README.md file): + +```sh +cd ../protos +npm install -g grpc-tools @bufbuild/protoc-gen-es +grpc_tools_node_protoc --es_out=target=js,js_import_style=legacy_commonjs:../helloworld/static_codegen_es/ --grpc_out=grpc_js,runtime=bufbuild-protobuf:../helloworld/static_codegen_es/ helloworld.proto +``` diff --git a/examples/helloworld/static_codegen_es/greeter_client.js b/examples/helloworld/static_codegen_es/greeter_client.js new file mode 100644 index 000000000..70a7dfc3b --- /dev/null +++ b/examples/helloworld/static_codegen_es/greeter_client.js @@ -0,0 +1,52 @@ +/* + * + * Copyright 2015 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +var parseArgs = require('minimist'); +var messages = require('./helloworld_pb'); +var services = require('./helloworld_grpc_pb'); + +var grpc = require('@grpc/grpc-js'); +var { create } = require('@bufbuild/protobuf'); + +function main() { + var argv = parseArgs(process.argv.slice(2), { + string: 'target' + }); + var target; + if (argv.target) { + target = argv.target; + } else { + target = 'localhost:50051'; + } + var client = new services.GreeterClient(target, + grpc.credentials.createInsecure()); + var user; + if (argv._.length > 0) { + user = argv._[0]; + } else { + user = 'world'; + } + var request = create(messages.HelloRequestSchema, { + name: user, + }); + client.sayHello(request, function(err, response) { + console.log('Greeting:', response.message); + }); +} + +main(); diff --git a/examples/helloworld/static_codegen_es/greeter_server.js b/examples/helloworld/static_codegen_es/greeter_server.js new file mode 100644 index 000000000..0cf8422ff --- /dev/null +++ b/examples/helloworld/static_codegen_es/greeter_server.js @@ -0,0 +1,50 @@ +/* + * + * Copyright 2015 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +var messages = require('./helloworld_pb'); +var services = require('./helloworld_grpc_pb'); + +var grpc = require('@grpc/grpc-js'); +var { create } = require('@bufbuild/protobuf'); + +/** + * Implements the SayHello RPC method. + */ +function sayHello(call, callback) { + var reply = create(messages.HelloReplySchema, { + message: 'Hello ' + call.request.name, + }); + callback(null, reply); +} + +/** + * Starts an RPC server that receives requests for the Greeter service at the + * sample server port + */ +function main() { + var server = new grpc.Server(); + server.addService(services.GreeterService, {sayHello: sayHello}); + server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), (err, port) => { + if (err != null) { + return console.error(err); + } + console.log(`gRPC listening on ${port}`) + }); +} + +main(); diff --git a/examples/helloworld/static_codegen_es/helloworld_grpc_pb.js b/examples/helloworld/static_codegen_es/helloworld_grpc_pb.js new file mode 100644 index 000000000..c95a9a0af --- /dev/null +++ b/examples/helloworld/static_codegen_es/helloworld_grpc_pb.js @@ -0,0 +1,73 @@ +// GENERATED CODE -- DO NOT EDIT! + +// Original file comments: +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +'use strict'; +var grpc = require('@grpc/grpc-js'); +var proto = require('@bufbuild/protobuf'); +var helloworld_pb = require('./helloworld_pb.js'); + +function serialize_helloworld_HelloReply(arg) { + if (!proto.isMessage(arg, helloworld_pb.HelloReplySchema)) { + throw new Error('Expected argument of type helloworld.HelloReply'); + } + return Buffer.from(proto.toBinary(helloworld_pb.HelloReplySchema, arg)); +} + +function deserialize_helloworld_HelloReply(buffer_arg) { + return proto.fromBinary(helloworld_pb.HelloReplySchema, new Uint8Array(buffer_arg)); +} + +function serialize_helloworld_HelloRequest(arg) { + if (!proto.isMessage(arg, helloworld_pb.HelloRequestSchema)) { + throw new Error('Expected argument of type helloworld.HelloRequest'); + } + return Buffer.from(proto.toBinary(helloworld_pb.HelloRequestSchema, arg)); +} + +function deserialize_helloworld_HelloRequest(buffer_arg) { + return proto.fromBinary(helloworld_pb.HelloRequestSchema, new Uint8Array(buffer_arg)); +} + + +// The greeting service definition. +var GreeterService = exports.GreeterService = { + // Sends a greeting +sayHello: { + path: '/helloworld.Greeter/SayHello', + requestStream: false, + responseStream: false, + requestType: helloworld_pb.HelloRequestSchema, + responseType: helloworld_pb.HelloReplySchema, + requestSerialize: serialize_helloworld_HelloRequest, + requestDeserialize: deserialize_helloworld_HelloRequest, + responseSerialize: serialize_helloworld_HelloReply, + responseDeserialize: deserialize_helloworld_HelloReply, + }, + sayHelloStreamReply: { + path: '/helloworld.Greeter/SayHelloStreamReply', + requestStream: false, + responseStream: true, + requestType: helloworld_pb.HelloRequestSchema, + responseType: helloworld_pb.HelloReplySchema, + requestSerialize: serialize_helloworld_HelloRequest, + requestDeserialize: deserialize_helloworld_HelloRequest, + responseSerialize: serialize_helloworld_HelloReply, + responseDeserialize: deserialize_helloworld_HelloReply, + }, +}; + +exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService, 'Greeter'); diff --git a/examples/helloworld/static_codegen_es/helloworld_pb.js b/examples/helloworld/static_codegen_es/helloworld_pb.js new file mode 100644 index 000000000..ac6db1324 --- /dev/null +++ b/examples/helloworld/static_codegen_es/helloworld_pb.js @@ -0,0 +1,56 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// @generated by protoc-gen-es v2.7.0 with parameter "target=js,js_import_style=legacy_commonjs" +// @generated from file helloworld.proto (package helloworld, syntax proto3) +/* eslint-disable */ + +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +const { fileDesc, messageDesc, serviceDesc } = require("@bufbuild/protobuf/codegenv2"); + +/** + * Describes the file helloworld.proto. + */ +const file_helloworld = /*@__PURE__*/ + fileDesc("ChBoZWxsb3dvcmxkLnByb3RvEgpoZWxsb3dvcmxkIhwKDEhlbGxvUmVxdWVzdBIMCgRuYW1lGAEgASgJIh0KCkhlbGxvUmVwbHkSDwoHbWVzc2FnZRgBIAEoCTKWAQoHR3JlZXRlchI+CghTYXlIZWxsbxIYLmhlbGxvd29ybGQuSGVsbG9SZXF1ZXN0GhYuaGVsbG93b3JsZC5IZWxsb1JlcGx5IgASSwoTU2F5SGVsbG9TdHJlYW1SZXBseRIYLmhlbGxvd29ybGQuSGVsbG9SZXF1ZXN0GhYuaGVsbG93b3JsZC5IZWxsb1JlcGx5IgAwAUI2Chtpby5ncnBjLmV4YW1wbGVzLmhlbGxvd29ybGRCD0hlbGxvV29ybGRQcm90b1ABogIDSExXYgZwcm90bzM"); + +/** + * Describes the message helloworld.HelloRequest. + * Use `create(HelloRequestSchema)` to create a new message. + */ +const HelloRequestSchema = /*@__PURE__*/ + messageDesc(file_helloworld, 0); + +/** + * Describes the message helloworld.HelloReply. + * Use `create(HelloReplySchema)` to create a new message. + */ +const HelloReplySchema = /*@__PURE__*/ + messageDesc(file_helloworld, 1); + +/** + * The greeting service definition. + * + * @generated from service helloworld.Greeter + */ +const Greeter = /*@__PURE__*/ + serviceDesc(file_helloworld, 0); + + +exports.file_helloworld = file_helloworld; +exports.HelloRequestSchema = HelloRequestSchema; +exports.HelloReplySchema = HelloReplySchema; +exports.Greeter = Greeter; diff --git a/examples/package.json b/examples/package.json index 6e717dadb..78ac1fa34 100644 --- a/examples/package.json +++ b/examples/package.json @@ -5,6 +5,7 @@ "@grpc/proto-loader": "^0.6.0", "async": "^1.5.2", "google-protobuf": "^3.0.0", + "@bufbuild/protobuf": "^2.7.0", "@grpc/grpc-js": "^1.10.2", "@grpc/grpc-js-xds": "^1.10.0", "@grpc/reflection": "^1.0.0", diff --git a/packages/grpc-tools/src/node_generator.cc b/packages/grpc-tools/src/node_generator.cc index 2bea5b504..daf8b78fa 100644 --- a/packages/grpc-tools/src/node_generator.cc +++ b/packages/grpc-tools/src/node_generator.cc @@ -112,21 +112,27 @@ grpc::string MessageIdentifierName(const grpc::string& name) { return grpc_generator::StringReplace(name, ".", "_"); } -grpc::string NodeObjectPath(const Descriptor* descriptor) { +grpc::string NodeObjectPath(const Descriptor* descriptor, const grpc::string& runtime) { grpc::string module_alias = ModuleAlias(descriptor->file()->name()); + if (runtime == "bufbuild-protobuf" && descriptor->file()->name().find("google/protobuf") == 0) { + module_alias = "wkt"; + } grpc::string name = descriptor->full_name(); grpc_generator::StripPrefix(&name, descriptor->file()->package() + "."); + if (runtime == "bufbuild-protobuf") { + name += "Schema"; + } return module_alias + "." + name; } -// Prints out the message serializer and deserializer functions -void PrintMessageTransformer(const Descriptor* descriptor, Printer* out, +// Prints out the message serializer and deserializer functions for google-protobuf. +void PrintGoogleProtobufMessageTransformer(const Descriptor* descriptor, Printer* out, const Parameters& params) { map template_vars; grpc::string full_name = descriptor->full_name(); template_vars["identifier_name"] = MessageIdentifierName(full_name); template_vars["name"] = full_name; - template_vars["node_name"] = NodeObjectPath(descriptor); + template_vars["node_name"] = NodeObjectPath(descriptor, params.runtime); // Print the serializer out->Print(template_vars, "function serialize_$identifier_name$(arg) {\n"); out->Indent(); @@ -153,15 +159,59 @@ void PrintMessageTransformer(const Descriptor* descriptor, Printer* out, out->Print("}\n\n"); } -void PrintMethod(const MethodDescriptor* method, Printer* out) { +// Prints out the message serializer and deserializer functions for bufbuild-protobuf. +void PrintBufbuildProtobufMessageTransformer(const Descriptor* descriptor, Printer* out, + const Parameters& params) { + map template_vars; + grpc::string full_name = descriptor->full_name(); + template_vars["identifier_name"] = MessageIdentifierName(full_name); + template_vars["name"] = full_name; + template_vars["node_name"] = NodeObjectPath(descriptor, params.runtime); + // Print the serializer + out->Print(template_vars, "function serialize_$identifier_name$(arg) {\n"); + out->Indent(); + if (!params.omit_serialize_instanceof) { + out->Print(template_vars, "if (!proto.isMessage(arg, $node_name$)) {\n"); + out->Indent(); + out->Print(template_vars, + "throw new Error('Expected argument of type $name$');\n"); + out->Outdent(); + out->Print("}\n"); + } + out->Print(template_vars, "return Buffer.from(proto.toBinary($node_name$, arg));\n"); + out->Outdent(); + out->Print("}\n\n"); + + // Print the deserializer + out->Print(template_vars, + "function deserialize_$identifier_name$(buffer_arg) {\n"); + out->Indent(); + out->Print( + template_vars, + "return proto.fromBinary($node_name$, new Uint8Array(buffer_arg));\n"); + out->Outdent(); + out->Print("}\n\n"); +} + +// Prints out the message serializer and deserializer functions +void PrintMessageTransformer(const Descriptor* descriptor, Printer* out, + const Parameters& params) { + if (params.runtime == "bufbuild-protobuf") { + PrintBufbuildProtobufMessageTransformer(descriptor, out, params); + } else { + PrintGoogleProtobufMessageTransformer(descriptor, out, params); + } +} + +void PrintMethod(const MethodDescriptor* method, Printer* out, const Parameters& params) { const Descriptor* input_type = method->input_type(); const Descriptor* output_type = method->output_type(); map vars; vars["service_name"] = method->service()->full_name(); vars["name"] = method->name(); - vars["input_type"] = NodeObjectPath(input_type); + vars["input_type"] = NodeObjectPath(input_type, params.runtime); vars["input_type_id"] = MessageIdentifierName(input_type->full_name()); - vars["output_type"] = NodeObjectPath(output_type); + vars["output_type"] = NodeObjectPath(output_type, params.runtime); vars["output_type_id"] = MessageIdentifierName(output_type->full_name()); vars["client_stream"] = method->client_streaming() ? "true" : "false"; vars["server_stream"] = method->server_streaming() ? "true" : "false"; @@ -198,7 +248,7 @@ void PrintService(const ServiceDescriptor* service, Printer* out, grpc_generator::LowercaseFirstLetter(service->method(i)->name()); out->PrintRaw(GetNodeComments(service->method(i), true).c_str()); out->Print("$method_name$: ", "method_name", method_name); - PrintMethod(service->method(i), out); + PrintMethod(service->method(i), out, params); out->Print(",\n"); out->PrintRaw(GetNodeComments(service->method(i), false).c_str()); } @@ -218,14 +268,25 @@ void PrintImports(const FileDescriptor* file, Printer* out, grpc::string package = params.grpc_js ? "@grpc/grpc-js" : "grpc"; out->Print("var grpc = require('$package$');\n", "package", package); } + if (params.runtime == "bufbuild-protobuf") { + out->Print("var proto = require('@bufbuild/protobuf');\n"); + } if (file->message_type_count() > 0) { grpc::string file_path = GetRelativePath(file->name(), GetJSMessageFilename(file->name())); out->Print("var $module_alias$ = require('$file_path$');\n", "module_alias", ModuleAlias(file->name()), "file_path", file_path); } - + bool imports_wkt = false; for (int i = 0; i < file->dependency_count(); i++) { + if (params.runtime == "bufbuild-protobuf" && file->dependency(i)->name().find("google/protobuf") == 0) { + // WKTs are provided by the runtime from a single location. + if (!imports_wkt) { + out->Print("var wkt = require('@bufbuild/protobuf/wkt');"); + imports_wkt = true; + } + continue; + } grpc::string file_path = GetRelativePath( file->name(), GetJSMessageFilename(file->dependency(i)->name())); out->Print("var $module_alias$ = require('$file_path$');\n", "module_alias", diff --git a/packages/grpc-tools/src/node_generator.h b/packages/grpc-tools/src/node_generator.h index 389fe0274..a294c4626 100644 --- a/packages/grpc-tools/src/node_generator.h +++ b/packages/grpc-tools/src/node_generator.h @@ -30,6 +30,8 @@ struct Parameters { bool grpc_js; // Omit instanceof check for messages in serialize methods bool omit_serialize_instanceof; + // Runtime to use for protobuf serialization (default: "google-protobuf", "bufbuild-protobuf" for @bufbuild/protobuf) + grpc::string runtime; }; grpc::string GenerateFile(const grpc::protobuf::FileDescriptor* file, diff --git a/packages/grpc-tools/src/node_plugin.cc b/packages/grpc-tools/src/node_plugin.cc index f703ba5eb..0981e5625 100644 --- a/packages/grpc-tools/src/node_plugin.cc +++ b/packages/grpc-tools/src/node_plugin.cc @@ -40,6 +40,7 @@ class NodeGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { generator_parameters.generate_package_definition = false; generator_parameters.grpc_js = false; generator_parameters.omit_serialize_instanceof = false; + generator_parameters.runtime = "google-protobuf"; if (!parameter.empty()) { std::vector parameters_list = grpc_generator::tokenize(parameter, ","); @@ -51,6 +52,8 @@ class NodeGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { generator_parameters.grpc_js = true; } else if (*parameter_string == "omit_serialize_instanceof") { generator_parameters.omit_serialize_instanceof = true; + } else if (parameter_string->find("runtime=") == 0) { + generator_parameters.runtime = parameter_string->substr(8); } } }