From f1fe57473f15aa995bdf7935ebe714ed8c3bf05b Mon Sep 17 00:00:00 2001 From: Eryu Xia Date: Tue, 28 Sep 2021 10:48:20 -0700 Subject: [PATCH] Internal code sync (#1140) --- Makefile | 6 +- .../net/grpc/web/{ => generator}/BUILD.bazel | 0 .../net/grpc/web/{ => generator}/Makefile | 0 .../web/{ => generator}/grpc_generator.cc | 289 +++++++----------- javascript/net/grpc/web/genericclient.js | 41 --- javascript/net/grpc/web/grpcwebclientbase.js | 14 +- .../net/grpc/web/grpcwebclientbase_test.js | 51 +++- .../grpc/web/grpcwebclientreadablestream.js | 38 +-- javascript/net/grpc/web/rpcerror.js | 9 +- javascript/net/grpc/web/status.js | 2 - javascript/net/grpc/web/statuscode.js | 51 +++- javascript/net/grpc/web/statuscode_test.js | 41 +++ net/grpc/gateway/docker/prereqs/Dockerfile | 7 +- .../gateway/docker/protoc_plugin/Dockerfile | 2 +- net/grpc/gateway/examples/echo/package.json | 2 +- packages/grpc-web/test/generated_code_test.js | 4 +- scripts/README.md | 2 +- scripts/docker-run-build-tests.sh | 4 +- 18 files changed, 281 insertions(+), 282 deletions(-) rename javascript/net/grpc/web/{ => generator}/BUILD.bazel (100%) rename javascript/net/grpc/web/{ => generator}/Makefile (100%) rename javascript/net/grpc/web/{ => generator}/grpc_generator.cc (89%) delete mode 100644 javascript/net/grpc/web/genericclient.js create mode 100644 javascript/net/grpc/web/statuscode_test.js diff --git a/Makefile b/Makefile index 3e90ece..4be53e5 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,11 @@ ROOT_DIR := $(shell pwd) all: clean plugin: - cd "$(ROOT_DIR)"/javascript/net/grpc/web && make + cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make install-plugin: - cd "$(ROOT_DIR)"/javascript/net/grpc/web && make install + cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make install clean: - cd "$(ROOT_DIR)"/javascript/net/grpc/web && make clean + cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make clean cd "$(ROOT_DIR)" diff --git a/javascript/net/grpc/web/BUILD.bazel b/javascript/net/grpc/web/generator/BUILD.bazel similarity index 100% rename from javascript/net/grpc/web/BUILD.bazel rename to javascript/net/grpc/web/generator/BUILD.bazel diff --git a/javascript/net/grpc/web/Makefile b/javascript/net/grpc/web/generator/Makefile similarity index 100% rename from javascript/net/grpc/web/Makefile rename to javascript/net/grpc/web/generator/Makefile diff --git a/javascript/net/grpc/web/grpc_generator.cc b/javascript/net/grpc/web/generator/grpc_generator.cc similarity index 89% rename from javascript/net/grpc/web/grpc_generator.cc rename to javascript/net/grpc/web/generator/grpc_generator.cc index 955a22a..bf77582 100644 --- a/javascript/net/grpc/web/grpc_generator.cc +++ b/javascript/net/grpc/web/generator/grpc_generator.cc @@ -63,64 +63,18 @@ enum ImportStyle { const char GRPC_PROMISE[] = "grpc.web.promise.GrpcWebPromise"; const char* kKeyword[] = { - "abstract", - "boolean", - "break", - "byte", - "case", - "catch", - "char", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "double", - "else", - "enum", - "export", - "extends", - "false", - "final", - "finally", - "float", - "for", - "function", - "goto", - "if", - "implements", - "import", - "in", - "instanceof", - "int", - "interface", - "long", - "native", - "new", - "null", - "package", - "private", - "protected", - "public", - "return", - "short", - "static", - "super", - "switch", - "synchronized", - "this", - "throw", - "throws", - "transient", - "try", - "typeof", - "var", - "void", - "volatile", - "while", - "with", + "abstract", "boolean", "break", "byte", "case", + "catch", "char", "class", "const", "continue", + "debugger", "default", "delete", "do", "double", + "else", "enum", "export", "extends", "false", + "final", "finally", "float", "for", "function", + "goto", "if", "implements", "import", "in", + "instanceof", "int", "interface", "long", "native", + "new", "null", "package", "private", "protected", + "public", "return", "short", "static", "super", + "switch", "synchronized", "this", "throw", "throws", + "transient", "try", "typeof", "var", "void", + "volatile", "while", "with", }; bool IsReserved(const string& ident) { @@ -204,10 +158,9 @@ string Uppercase(string s) { // The following 5 functions were copied from // google/protobuf/src/google/protobuf/stubs/strutil.h -inline bool HasPrefixString(const string& str, - const string& prefix) { +inline bool HasPrefixString(const string& str, const string& prefix) { return str.size() >= prefix.size() && - str.compare(0, prefix.size(), prefix) == 0; + str.compare(0, prefix.size(), prefix) == 0; } inline string StripPrefixString(const string& str, const string& prefix) { @@ -218,10 +171,9 @@ inline string StripPrefixString(const string& str, const string& prefix) { } } -inline bool HasSuffixString(const string& str, - const string& suffix) { +inline bool HasSuffixString(const string& str, const string& suffix) { return str.size() >= suffix.size() && - str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; } inline string StripSuffixString(const string& str, const string& suffix) { @@ -232,17 +184,15 @@ inline string StripSuffixString(const string& str, const string& suffix) { } } -void ReplaceCharacters(string *s, const char *remove, char replacewith) { - const char *str_start = s->c_str(); - const char *str = str_start; - for (str = strpbrk(str, remove); - str != nullptr; +void ReplaceCharacters(string* s, const char* remove, char replacewith) { + const char* str_start = s->c_str(); + const char* str = str_start; + for (str = strpbrk(str, remove); str != nullptr; str = strpbrk(str + 1, remove)) { (*s)[str - str_start] = replacewith; } } - // The following function was copied from // google/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc @@ -313,7 +263,7 @@ string ModuleAlias(const string& filename) { return basename + "_pb"; } -string JSMessageType(const Descriptor *desc, const FileDescriptor *file) { +string JSMessageType(const Descriptor* desc, const FileDescriptor* file) { string class_name; class_name = StripPrefixString(desc->full_name(), desc->file()->package()); if (!class_name.empty() && class_name[0] == '.') { @@ -327,11 +277,11 @@ string JSMessageType(const Descriptor *desc, const FileDescriptor *file) { return ModuleAlias(desc->file()->name()) + "." + class_name; } -string JSMessageType(const Descriptor *desc) { +string JSMessageType(const Descriptor* desc) { return JSMessageType(desc, nullptr); } -string JSElementType(const FieldDescriptor *desc, const FileDescriptor *file) { +string JSElementType(const FieldDescriptor* desc, const FileDescriptor* file) { switch (desc->type()) { case FieldDescriptor::TYPE_DOUBLE: case FieldDescriptor::TYPE_FLOAT: @@ -367,15 +317,13 @@ string JSElementType(const FieldDescriptor *desc, const FileDescriptor *file) { // [for protobuf .d.ts files only] Do not add the module prefix for // local messages. string enum_name = - StripPrefixString( - desc->enum_type()->full_name(), - desc->enum_type()->file()->package()); + StripPrefixString(desc->enum_type()->full_name(), + desc->enum_type()->file()->package()); return StripPrefixString(enum_name, "."); } - return ModuleAlias(desc->enum_type()->file()->name()) - + StripPrefixString( - desc->enum_type()->full_name(), - desc->enum_type()->file()->package()); + return ModuleAlias(desc->enum_type()->file()->name()) + + StripPrefixString(desc->enum_type()->full_name(), + desc->enum_type()->file()->package()); case FieldDescriptor::TYPE_MESSAGE: return JSMessageType(desc->message_type(), file); @@ -385,22 +333,21 @@ string JSElementType(const FieldDescriptor *desc, const FileDescriptor *file) { } } -string JSFieldType(const FieldDescriptor *desc, const FileDescriptor *file) { +string JSFieldType(const FieldDescriptor* desc, const FileDescriptor* file) { string js_field_type = JSElementType(desc, file); if (desc->is_map()) { string key_type = JSFieldType(desc->message_type()->field(0), file); string value_type = JSFieldType(desc->message_type()->field(1), file); return "jspb.Map<" + key_type + ", " + value_type + ">"; } - if (desc->is_repeated()) - { + if (desc->is_repeated()) { return "Array<" + js_field_type + ">"; } return js_field_type; } -string AsObjectFieldType( - const FieldDescriptor *desc, const FileDescriptor *file) { +string AsObjectFieldType(const FieldDescriptor* desc, + const FileDescriptor* file) { if (desc->type() != FieldDescriptor::TYPE_MESSAGE) { return JSFieldType(desc, file); } @@ -417,11 +364,11 @@ string AsObjectFieldType( return field_type; } -string JSElementName(const FieldDescriptor *desc) { +string JSElementName(const FieldDescriptor* desc) { return ToUpperCamel(ParseLowerUnderscore(desc->name())); } -string JSFieldName(const FieldDescriptor *desc) { +string JSFieldName(const FieldDescriptor* desc) { string js_field_name = JSElementName(desc); if (desc->is_map()) { js_field_name += "Map"; @@ -432,20 +379,17 @@ string JSFieldName(const FieldDescriptor *desc) { } // Like ToUpperCamel except the first letter is not converted. -string ToCamelCase(const std::vector& words) -{ +string ToCamelCase(const std::vector& words) { if (words.empty()) { - return ""; + return ""; } string result = words[0]; return result + ToUpperCamel(std::vector( - words.begin()+1, - words.begin()+words.size())); + words.begin() + 1, words.begin() + words.size())); } // Like JSFieldName, but with first letter not uppercased -string CamelCaseJSFieldName(const FieldDescriptor *desc) -{ +string CamelCaseJSFieldName(const FieldDescriptor* desc) { string js_field_name = ToCamelCase(ParseLowerUnderscore(desc->name())); if (desc->is_map()) { js_field_name += "Map"; @@ -463,8 +407,8 @@ string GetNestedMessageName(const Descriptor* descriptor) { if (descriptor == nullptr) { return ""; } - string result = StripPrefixString(descriptor->full_name(), - descriptor->file()->package()); + string result = + StripPrefixString(descriptor->full_name(), descriptor->file()->package()); // Add a leading dot if one is not already present. if (!result.empty() && result[0] != '.') { result = "." + result; @@ -534,7 +478,7 @@ std::map GetAllMessages(const FileDescriptor* file) { for (int s = 0; s < file->service_count(); ++s) { const ServiceDescriptor* service = file->service(s); for (int m = 0; m < service->method_count(); ++m) { - const MethodDescriptor *method = service->method(m); + const MethodDescriptor* method = service->method(m); messages[method->input_type()->full_name()] = method->input_type(); messages[method->output_type()->full_name()] = method->output_type(); } @@ -544,12 +488,10 @@ std::map GetAllMessages(const FileDescriptor* file) { } void PrintClosureDependencies(Printer* printer, const FileDescriptor* file) { - for (const auto &entry : GetAllMessages(file)) { - printer->Print( - "goog.require('proto.$full_name$');\n", - "full_name", entry.second->full_name()); + for (const auto& entry : GetAllMessages(file)) { + printer->Print("goog.require('proto.$full_name$');\n", "full_name", + entry.second->full_name()); } - printer->Print("\n\n\n"); } void PrintCommonJsMessagesDeps(Printer* printer, const FileDescriptor* file) { @@ -560,9 +502,7 @@ void PrintCommonJsMessagesDeps(Printer* printer, const FileDescriptor* file) { vars["alias"] = ModuleAlias(name); vars["dep_filename"] = GetRootPath(file->name(), name) + StripProto(name); // we need to give each cross-file import an alias - printer->Print( - vars, - "\nvar $alias$ = require('$dep_filename$_pb.js')\n"); + printer->Print(vars, "\nvar $alias$ = require('$dep_filename$_pb.js')\n"); } const string& package = file->package(); @@ -587,13 +527,10 @@ void PrintCommonJsMessagesDeps(Printer* printer, const FileDescriptor* file) { vars["filename"] = GetBasename(StripProto(file->name())); if (!package.empty()) { - printer->Print( - vars, - "proto.$package_name$ = require('./$filename$_pb.js');\n\n"); + printer->Print(vars, + "proto.$package_name$ = require('./$filename$_pb.js');\n\n"); } else { - printer->Print( - vars, - "const proto = require('./$filename$_pb.js');\n\n"); + printer->Print(vars, "const proto = require('./$filename$_pb.js');\n\n"); } } @@ -603,7 +540,7 @@ void PrintES6Imports(Printer* printer, const FileDescriptor* file) { printer->Print("import * as grpcWeb from 'grpc-web';\n\n"); std::set imports; - for (const auto &entry : GetAllMessages(file)) { + for (const auto& entry : GetAllMessages(file)) { const string& name = entry.second->file()->name(); string dep_filename = GetRootPath(file->name(), name) + StripProto(name); if (imports.find(dep_filename) != imports.end()) { @@ -611,10 +548,8 @@ void PrintES6Imports(Printer* printer, const FileDescriptor* file) { } imports.insert(dep_filename); // We need to give each cross-file import an alias. - printer->Print( - "import * as $alias$ from '$dep_filename$_pb';\n", - "alias", ModuleAlias(name), - "dep_filename", dep_filename); + printer->Print("import * as $alias$ from '$dep_filename$_pb';\n", "alias", + ModuleAlias(name), "dep_filename", dep_filename); } printer->Print("\n\n"); } @@ -687,13 +622,12 @@ void PrintTypescriptFile(Printer* printer, const FileDescriptor* file, "metadata?: grpcWeb.Metadata) {\n"); printer->Print(vars, "return this.client_.serverStreaming(\n"); printer->Indent(); - printer->Print( - vars, - "this.hostname_ +\n" - " '/$package_dot$$service_name$/$method_name$',\n" - "request,\n" - "metadata || {},\n" - "this.methodInfo$method_name$);\n"); + printer->Print(vars, + "this.hostname_ +\n" + " '/$package_dot$$service_name$/$method_name$',\n" + "request,\n" + "metadata || {},\n" + "this.methodInfo$method_name$);\n"); printer->Outdent(); printer->Outdent(); printer->Print("}\n\n"); @@ -727,14 +661,13 @@ void PrintTypescriptFile(Printer* printer, const FileDescriptor* file, printer->Indent(); printer->Print(vars, "return this.client_.rpcCall(\n"); printer->Indent(); - printer->Print( - vars, - "this.hostname_ +\n" - " '/$package_dot$$service_name$/$method_name$',\n" - "request,\n" - "metadata || {},\n" - "this.methodInfo$method_name$,\n" - "callback);\n"); + printer->Print(vars, + "this.hostname_ +\n" + " '/$package_dot$$service_name$/$method_name$',\n" + "request,\n" + "metadata || {},\n" + "this.methodInfo$method_name$,\n" + "callback);\n"); printer->Outdent(); printer->Outdent(); printer->Print(vars, @@ -757,7 +690,7 @@ void PrintTypescriptFile(Printer* printer, const FileDescriptor* file, } void PrintGrpcWebDtsClientClass(Printer* printer, const FileDescriptor* file, - const string &client_type) { + const string& client_type) { std::map vars; vars["client_type"] = client_type; vars["promise"] = "Promise"; @@ -824,15 +757,13 @@ void PrintGrpcWebDtsFile(Printer* printer, const FileDescriptor* file) { PrintGrpcWebDtsClientClass(printer, file, "PromiseClient"); } -void PrintProtoDtsEnum(Printer *printer, const EnumDescriptor *desc) -{ +void PrintProtoDtsEnum(Printer* printer, const EnumDescriptor* desc) { std::map vars; vars["enum_name"] = desc->name(); printer->Print(vars, "export enum $enum_name$ { \n"); printer->Indent(); - for (int i = 0; i < desc->value_count(); i++) - { + for (int i = 0; i < desc->value_count(); i++) { vars["value_name"] = Uppercase(desc->value(i)->name()); vars["value_number"] = std::to_string(desc->value(i)->number()); printer->Print(vars, "$value_name$ = $value_number$,\n"); @@ -841,8 +772,7 @@ void PrintProtoDtsEnum(Printer *printer, const EnumDescriptor *desc) printer->Print("}\n"); } -void PrintProtoDtsOneofCase(Printer *printer, const OneofDescriptor *desc) -{ +void PrintProtoDtsOneofCase(Printer* printer, const OneofDescriptor* desc) { std::map vars; vars["oneof_name"] = ToUpperCamel(ParseLowerUnderscore(desc->name())); vars["oneof_name_upper"] = Uppercase(desc->name()); @@ -851,7 +781,7 @@ void PrintProtoDtsOneofCase(Printer *printer, const OneofDescriptor *desc) printer->Indent(); printer->Print(vars, "$oneof_name_upper$_NOT_SET = 0,\n"); for (int i = 0; i < desc->field_count(); i++) { - const FieldDescriptor *field = desc->field(i); + const FieldDescriptor* field = desc->field(i); vars["field_name"] = Uppercase(field->name()); vars["field_number"] = std::to_string(field->number()); printer->Print(vars, "$field_name$ = $field_number$,\n"); @@ -860,8 +790,8 @@ void PrintProtoDtsOneofCase(Printer *printer, const OneofDescriptor *desc) printer->Print("}\n"); } -void PrintProtoDtsMessage(Printer *printer, const Descriptor *desc, - const FileDescriptor *file) { +void PrintProtoDtsMessage(Printer* printer, const Descriptor* desc, + const FileDescriptor* file) { const string& class_name = desc->name(); std::map vars; vars["class_name"] = class_name; @@ -874,8 +804,7 @@ void PrintProtoDtsMessage(Printer *printer, const Descriptor *desc, vars["js_field_type"] = JSFieldType(field, file); if (field->type() != FieldDescriptor::TYPE_MESSAGE || field->is_repeated()) { - printer->Print(vars, - "get$js_field_name$(): $js_field_type$;\n"); + printer->Print(vars, "get$js_field_name$(): $js_field_type$;\n"); } else { printer->Print(vars, "get$js_field_name$(): $js_field_type$ | undefined;\n"); @@ -895,8 +824,8 @@ void PrintProtoDtsMessage(Printer *printer, const Descriptor *desc, "set$js_field_name$(value?: $js_field_type$): " "$class_name$;\n"); } - if (field->type() == FieldDescriptor::TYPE_MESSAGE && !field->is_repeated() - && !field->is_map()) { + if (field->type() == FieldDescriptor::TYPE_MESSAGE && + !field->is_repeated() && !field->is_map()) { printer->Print(vars, "has$js_field_name$(): boolean;\n"); } if (field->type() == FieldDescriptor::TYPE_MESSAGE || @@ -924,8 +853,7 @@ void PrintProtoDtsMessage(Printer *printer, const Descriptor *desc, const OneofDescriptor* oneof = desc->oneof_decl(i); vars["js_oneof_name"] = ToUpperCamel(ParseLowerUnderscore(oneof->name())); printer->Print( - vars, - "get$js_oneof_name$Case(): $class_name$.$js_oneof_name$Case;\n"); + vars, "get$js_oneof_name$Case(): $class_name$.$js_oneof_name$Case;\n"); printer->Print("\n"); } @@ -988,17 +916,15 @@ void PrintProtoDtsMessage(Printer *printer, const Descriptor *desc, printer->Print("}\n\n"); } -void PrintProtoDtsFile(Printer *printer, const FileDescriptor *file) -{ +void PrintProtoDtsFile(Printer* printer, const FileDescriptor* file) { printer->Print("import * as jspb from 'google-protobuf'\n\n"); for (int i = 0; i < file->dependency_count(); i++) { const string& name = file->dependency(i)->name(); // We need to give each cross-file import an alias. - printer->Print( - "import * as $alias$ from '$dep_filename$_pb';\n", - "alias", ModuleAlias(name), - "dep_filename", GetRootPath(file->name(), name) + StripProto(name)); + printer->Print("import * as $alias$ from '$dep_filename$_pb';\n", "alias", + ModuleAlias(name), "dep_filename", + GetRootPath(file->name(), name) + StripProto(name)); } printer->Print("\n\n"); @@ -1080,8 +1006,9 @@ void PrintMethodDescriptorFile(Printer* printer, " * @param {!proto.$in$} request\n"); printer->Print( (" * @return {" + GetSerializeMethodReturnType(vars) + "}\n").c_str()); - printer->Print(" */\n" - "function(request) {\n"); + printer->Print( + " */\n" + "function(request) {\n"); printer->Print( (" return request." + GetSerializeMethodName(vars) + "();\n").c_str()); printer->Print("},\n"); @@ -1102,7 +1029,7 @@ void PrintServiceConstructor(Printer* printer, std::map vars, "/**\n" " * @param {string} hostname\n" " * @param {?Object} credentials\n" - " * @param {?Object} options\n" + " * @param {?grpc.web.ClientOptions} options\n" " * @constructor\n" " * @struct\n" " * @final\n" @@ -1111,7 +1038,7 @@ void PrintServiceConstructor(Printer* printer, std::map vars, " function(hostname, credentials, options) {\n" " if (!options) options = {};\n"); if (vars["mode"] == GetModeVar(Mode::GRPCWEB)) { - printer->Print(vars, " options['format'] = '$format$';\n\n"); + printer->Print(vars, " options.format = '$format$';\n\n"); } if (vars["mode"] == GetModeVar(Mode::OP)) { printer->Print( @@ -1213,7 +1140,7 @@ void PrintPromiseUnaryCall(Printer* printer, std::map vars) { "/**\n" " * @param {!proto.$in$} request The\n" " * request proto\n" - " * @param {?Object} metadata User defined\n" + " * @param {?Object=} metadata User defined\n" " * call metadata\n" " * @return {!$promise$}\n" " * Promise that resolves to the response\n" @@ -1243,17 +1170,16 @@ void PrintPromiseUnaryCall(Printer* printer, std::map vars) { } void PrintServerStreamingCall(Printer* printer, std::map vars) { - printer->Print( - vars, - "/**\n" - " * @param {!proto.$in$} request The request proto\n" - " * @param {?Object} metadata User defined\n" - " * call metadata\n" - " * @return {!grpc.web.ClientReadableStream}\n" - " * The XHR Node Readable Stream\n" - " */\n" - "proto.$package_dot$$service_name$$client_type$.prototype." - "$js_method_name$ =\n"); + printer->Print(vars, + "/**\n" + " * @param {!proto.$in$} request The request proto\n" + " * @param {?Object=} metadata User defined\n" + " * call metadata\n" + " * @return {!grpc.web.ClientReadableStream}\n" + " * The XHR Node Readable Stream\n" + " */\n" + "proto.$package_dot$$service_name$$client_type$.prototype." + "$js_method_name$ =\n"); printer->Indent(); printer->Print( " function(request, metadata) {\n" @@ -1359,6 +1285,11 @@ void PrintMultipleFilesMode(const FileDescriptor* file, string file_name, PrintClosureDependencies(&printer1, file); PrintClosureDependencies(&printer2, file); + printer1.Print(vars, "\ngoog.requireType('grpc.web.ClientOptions');\n"); + printer2.Print(vars, "\ngoog.requireType('grpc.web.ClientOptions');\n"); + + printer1.Print("\n\n\n"); + printer2.Print("\n\n\n"); printer1.Print("goog.scope(function() {\n\n"); printer2.Print("goog.scope(function() {\n\n"); @@ -1403,20 +1334,20 @@ void PrintMultipleFilesMode(const FileDescriptor* file, string file_name, printer2.Print("}); // goog.scope\n\n"); } -void PrintClosureES6Imports( - Printer* printer, const FileDescriptor* file, string package_dot) { +void PrintClosureES6Imports(Printer* printer, const FileDescriptor* file, + string package_dot) { for (int i = 0; i < file->service_count(); ++i) { const ServiceDescriptor* service = file->service(i); string service_namespace = "proto." + package_dot + service->name(); printer->Print( "import $service_name$Client_import from 'goog:$namespace$';\n", - "service_name", service->name(), - "namespace", service_namespace + "Client"); + "service_name", service->name(), "namespace", + service_namespace + "Client"); printer->Print( "import $service_name$PromiseClient_import from 'goog:$namespace$';\n", - "service_name", service->name(), - "namespace", service_namespace + "PromiseClient"); + "service_name", service->name(), "namespace", + service_namespace + "PromiseClient"); } printer->Print("\n\n\n"); @@ -1441,9 +1372,8 @@ void PrintGrpcWebClosureES6File(Printer* printer, const FileDescriptor* file) { const ServiceDescriptor* service = file->service(i); string service_namespace = "proto." + package_dot + service->name(); - printer->Print( - "export const $name$Client = $name$Client_import;\n", - "name", service->name()); + printer->Print("export const $name$Client = $name$Client_import;\n", "name", + service->name()); printer->Print( "export const $name$PromiseClient = $name$PromiseClient_import;\n", "name", service->name()); @@ -1664,7 +1594,8 @@ class GrpcCodeGenerator : public CodeGenerator { printer.Print(vars, "goog.require('grpc.web.RpcError');\n"); PrintClosureDependencies(&printer, file); - + printer.Print(vars, "\ngoog.requireType('grpc.web.ClientOptions');\n"); + printer.Print("\n\n\n"); printer.Print("goog.scope(function() {\n\n"); break; case ImportStyle::COMMONJS: diff --git a/javascript/net/grpc/web/genericclient.js b/javascript/net/grpc/web/genericclient.js deleted file mode 100644 index efbe5c6..0000000 --- a/javascript/net/grpc/web/genericclient.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @fileoverview base interface for grpc web GenericClient. - */ - -goog.module('grpc.web.GenericClient'); -goog.module.declareLegacyNamespace(); - -const MethodDescriptor = goog.require('grpc.web.MethodDescriptor'); -const Request = goog.require('grpc.web.Request'); -const UnaryResponse = goog.require('grpc.web.UnaryResponse'); - -/** - * @interface - */ -const GenericClient = function() {}; - - -/** - * @param {!Request} request The wrapped gRPC-Web request - * @return {!Promise>} A promise that resolves to the - * response message and metadata - * @template REQUEST, RESPONSE - * @abstract - */ -GenericClient.prototype.unaryCall = function(request) {}; - -/** - * Simplified version of GenericClient.prototype.unaryCall. Users are expected - * to use this method if they don't have the need to customize metadata and - * callOptions . - * @param {!REQUEST} requestMessage The request message - * @param {!MethodDescriptor} methodDescriptor Information of - * this RPC method - * @return {!Promise} A promise that resolves to the - * response message - * @template REQUEST, RESPONSE - * @abstract - */ -GenericClient.prototype.call = function(requestMessage, methodDescriptor) {}; - -exports = GenericClient; diff --git a/javascript/net/grpc/web/grpcwebclientbase.js b/javascript/net/grpc/web/grpcwebclientbase.js index b1458f8..6e4c27a 100644 --- a/javascript/net/grpc/web/grpcwebclientbase.js +++ b/javascript/net/grpc/web/grpcwebclientbase.js @@ -42,6 +42,7 @@ const XhrIo = goog.require('goog.net.XhrIo'); const googCrypt = goog.require('goog.crypt.base64'); const {Status} = goog.require('grpc.web.Status'); const {StreamInterceptor, UnaryInterceptor} = goog.require('grpc.web.Interceptor'); +const {toObject} = goog.require('goog.collections.maps'); @@ -193,10 +194,13 @@ class GrpcWebClientBase { stream.setResponseDeserializeFn( methodDescriptor.getResponseDeserializeFn()); - xhr.headers.addAll(request.getMetadata()); + const metadata = request.getMetadata(); + for(const key in metadata) { + xhr.headers.set(key, metadata[key]); + } this.processHeaders_(xhr); if (this.suppressCorsPreflight_) { - const headerObject = xhr.headers.toObject(); + const headerObject = toObject(xhr.headers); xhr.headers.clear(); path = GrpcWebClientBase.setCorsOverride_(path, headerObject); } @@ -310,11 +314,11 @@ class GrpcWebClientBase { } xhr.headers.set('X-User-Agent', 'grpc-web-javascript/0.1'); xhr.headers.set('X-Grpc-Web', '1'); - if (xhr.headers.containsKey('deadline')) { - const deadline = xhr.headers.get('deadline'); // in ms + if (xhr.headers.has('deadline')) { + const deadline = Number(xhr.headers.get('deadline')); // in ms const currentTime = (new Date()).getTime(); let timeout = Math.round(deadline - currentTime); - xhr.headers.remove('deadline'); + xhr.headers.delete('deadline'); if (timeout === Infinity) { // grpc-timeout header defaults to infinity if not set. timeout = 0; diff --git a/javascript/net/grpc/web/grpcwebclientbase_test.js b/javascript/net/grpc/web/grpcwebclientbase_test.js index fb4f93b..e962d49 100644 --- a/javascript/net/grpc/web/grpcwebclientbase_test.js +++ b/javascript/net/grpc/web/grpcwebclientbase_test.js @@ -24,12 +24,17 @@ const MethodDescriptor = goog.require('grpc.web.MethodDescriptor'); const ReadyState = goog.require('goog.net.XmlHttp.ReadyState'); const Request = goog.requireType('grpc.web.Request'); const RpcError = goog.require('grpc.web.RpcError'); +const StatusCode = goog.require('grpc.web.StatusCode'); const XhrIo = goog.require('goog.testing.net.XhrIo'); const googCrypt = goog.require('goog.crypt.base64'); const testSuite = goog.require('goog.testing.testSuite'); const {StreamInterceptor} = goog.require('grpc.web.Interceptor'); goog.require('goog.testing.jsunit'); +// This parses to [ { DATA: [4, 5, 6] }, { TRAILER: "a: b" } ] +const DEFAULT_RPC_RESPONSE = + new Uint8Array([0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98]); +const DEFAULT_RPC_RESPONSE_DATA = [4, 5, 6]; const DEFAULT_UNARY_HEADERS = ['Content-Type', 'Accept', 'X-User-Agent', 'X-Grpc-Web']; const DEFAULT_UNARY_HEADER_VALUES = [ @@ -47,7 +52,7 @@ testSuite({ const xhr = new XhrIo(); const client = new GrpcWebClientBase(/* options= */ {}, xhr); const methodDescriptor = createMethodDescriptor((bytes) => { - assertElementsEquals([4, 5, 6], [].slice.call(bytes)); + assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes)); return new MockReply('value'); }); @@ -58,10 +63,8 @@ testSuite({ assertNull(error); resolve(response); }); - // This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ] xhr.simulatePartialResponse( - googCrypt.encodeByteArray(new Uint8Array( - [0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98])), + googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), DEFAULT_RESPONSE_HEADERS); xhr.simulateReadyStateChange(ReadyState.COMPLETE); }); @@ -86,10 +89,8 @@ testSuite({ assertNull(error); resolve(); }); - // This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ] xhr.simulatePartialResponse( - googCrypt.encodeByteArray(new Uint8Array( - [0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98])), + googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), DEFAULT_RESPONSE_HEADERS); xhr.simulateReadyStateChange(ReadyState.COMPLETE); }); @@ -122,11 +123,34 @@ testSuite({ assertEquals(3, error.code); }, + async testRpcDeserializationError() { + const xhr = new XhrIo(); + const client = new GrpcWebClientBase(/* options= */ {}, xhr); + + const responseDeserializeFn = () => { + throw new Error('Decoding error :)'); + }; + const methodDescriptor = createMethodDescriptor(responseDeserializeFn); + const error = await new Promise((resolve, reject) => { + client.rpcCall( + 'urlurl', new MockRequest(), /* metadata= */ {}, methodDescriptor, + (error, response) => { + assertNull(response); + resolve(error); + }); + xhr.simulatePartialResponse( + googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), + DEFAULT_RESPONSE_HEADERS); + }); + assertTrue(error instanceof RpcError); + assertEquals(StatusCode.INTERNAL, error.code); + }, + async testRpcResponseHeader() { const xhr = new XhrIo(); const client = new GrpcWebClientBase(/* options= */ {}, xhr); const methodDescriptor = createMethodDescriptor((bytes) => { - assertElementsEquals([4, 5, 6], [].slice.call(bytes)); + assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes)); return new MockReply('value'); }); @@ -139,11 +163,8 @@ testSuite({ call.on('metadata', (metadata) => { resolve(metadata); }); - // This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ] xhr.simulatePartialResponse( - googCrypt.encodeByteArray(new Uint8Array( - [0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98])), - { + googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), { 'Content-Type': 'application/grpc-web-text', 'initial-metadata-key': 'initial-metadata-value', }); @@ -156,7 +177,7 @@ testSuite({ const xhr = new XhrIo(); const interceptor = new StreamResponseInterceptor(); const methodDescriptor = createMethodDescriptor((bytes) => { - assertElementsEquals([4, 5, 6], [].slice.call(bytes)); + assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes)); return new MockReply('value'); }); const client = @@ -169,10 +190,8 @@ testSuite({ assertNull(error); resolve(response); }); - // This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ] xhr.simulatePartialResponse( - googCrypt.encodeByteArray(new Uint8Array( - [0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98])), + googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), DEFAULT_RESPONSE_HEADERS); xhr.simulateReadyStateChange(ReadyState.COMPLETE); }); diff --git a/javascript/net/grpc/web/grpcwebclientreadablestream.js b/javascript/net/grpc/web/grpcwebclientreadablestream.js index a4a0d5a..f8c6e6f 100644 --- a/javascript/net/grpc/web/grpcwebclientreadablestream.js +++ b/javascript/net/grpc/web/grpcwebclientreadablestream.js @@ -152,16 +152,16 @@ class GrpcWebClientReadableStream { var byteSource = new Uint8Array( /** @type {!ArrayBuffer} */ (self.xhr_.getResponse())); } else { - self.handleError_(new RpcError( - StatusCode.UNKNOWN, 'Unknown Content-type received.')); + self.handleError_( + new RpcError(StatusCode.UNKNOWN, 'Unknown Content-type received.')); return; } var messages = null; try { messages = self.parser_.parse(byteSource); } catch (err) { - self.handleError_(new RpcError( - StatusCode.UNKNOWN, 'Error in parsing response body')); + self.handleError_( + new RpcError(StatusCode.UNKNOWN, 'Error in parsing response body')); } if (messages) { var FrameType = GrpcWebStreamParser.FrameType; @@ -169,15 +169,16 @@ class GrpcWebClientReadableStream { if (FrameType.DATA in messages[i]) { var data = messages[i][FrameType.DATA]; if (data) { + let response; try { - var response = self.responseDeserializeFn_(data); - if (response) { - self.sendDataCallbacks_(response); - } + response = self.responseDeserializeFn_(data); } catch (err) { self.handleError_(new RpcError( - StatusCode.UNKNOWN, - 'Error in response deserializer function.')); + StatusCode.INTERNAL, + `Error when deserializing response data: ${response}`)); + } + if (response) { + self.sendDataCallbacks_(response); } } } @@ -193,15 +194,16 @@ class GrpcWebClientReadableStream { var grpcStatusCode = StatusCode.OK; var grpcStatusMessage = ''; if (GRPC_STATUS in trailers) { - grpcStatusCode = trailers[GRPC_STATUS]; + grpcStatusCode = + /** @type {!StatusCode} */ (Number(trailers[GRPC_STATUS])); delete trailers[GRPC_STATUS]; } if (GRPC_STATUS_MESSAGE in trailers) { grpcStatusMessage = trailers[GRPC_STATUS_MESSAGE]; delete trailers[GRPC_STATUS_MESSAGE]; } - self.handleError_(new RpcError( - Number(grpcStatusCode), grpcStatusMessage, trailers)); + self.handleError_( + new RpcError(grpcStatusCode, grpcStatusMessage, trailers)); } } } @@ -210,7 +212,7 @@ class GrpcWebClientReadableStream { events.listen(this.xhr_, EventType.COMPLETE, function(e) { var lastErrorCode = self.xhr_.getLastErrorCode(); - var grpcStatusCode; + var grpcStatusCode = StatusCode.UNKNOWN; var grpcStatusMessage = ''; var initialMetadata = /** @type {!Metadata} */ ({}); @@ -249,14 +251,14 @@ class GrpcWebClientReadableStream { // Check whethere there are grpc specific response headers if (GRPC_STATUS in responseHeaders) { - grpcStatusCode = self.xhr_.getResponseHeader(GRPC_STATUS); + grpcStatusCode = /** @type {!StatusCode} */ ( + Number(self.xhr_.getResponseHeader(GRPC_STATUS))); if (GRPC_STATUS_MESSAGE in responseHeaders) { grpcStatusMessage = self.xhr_.getResponseHeader(GRPC_STATUS_MESSAGE); } - if (Number(grpcStatusCode) != StatusCode.OK) { + if (grpcStatusCode != StatusCode.OK) { self.handleError_(new RpcError( - Number(grpcStatusCode), grpcStatusMessage || '', - responseHeaders)); + grpcStatusCode, grpcStatusMessage || '', responseHeaders)); errorEmitted = true; } } diff --git a/javascript/net/grpc/web/rpcerror.js b/javascript/net/grpc/web/rpcerror.js index a2ce8eb..39a12d3 100644 --- a/javascript/net/grpc/web/rpcerror.js +++ b/javascript/net/grpc/web/rpcerror.js @@ -26,16 +26,21 @@ goog.module('grpc.web.RpcError'); const Metadata = goog.require('grpc.web.Metadata'); +const StatusCode = goog.require('grpc.web.StatusCode'); +/** + * gRPC-Web Error object, contains the {@link StatusCode}, a string message + * and {@link Metadata} contained in the error response. + */ class RpcError extends Error { /** - * @param {number} code + * @param {!StatusCode} code * @param {string} message * @param {!Metadata=} metadata */ constructor(code, message, metadata = {}) { super(message); - /** @type {number} */ + /** @type {!StatusCode} */ this.code = code; /** @type {!Metadata} */ this.metadata = metadata; diff --git a/javascript/net/grpc/web/status.js b/javascript/net/grpc/web/status.js index 26cfcbe..2ccc4c3 100644 --- a/javascript/net/grpc/web/status.js +++ b/javascript/net/grpc/web/status.js @@ -23,11 +23,9 @@ * @author stanleycheung@google.com (Stanley Cheung) */ goog.module('grpc.web.Status'); - goog.module.declareLegacyNamespace(); - /** @record */ function Status() {} diff --git a/javascript/net/grpc/web/statuscode.js b/javascript/net/grpc/web/statuscode.js index 0046b12..ef5f2b0 100644 --- a/javascript/net/grpc/web/statuscode.js +++ b/javascript/net/grpc/web/statuscode.js @@ -26,7 +26,8 @@ goog.module('grpc.web.StatusCode'); /** * gRPC Status Codes - * See: https://github.com/grpc/grpc/blob/master/include/grpc%2B%2B/impl/codegen/status_code_enum.h + * See: + * https://github.com/grpc/grpc/blob/master/include/grpcpp/impl/codegen/status_code_enum.h * @enum {number} */ const StatusCode = { @@ -140,11 +141,11 @@ const StatusCode = { /** * Convert HTTP Status code to gRPC Status code - * @param {number} http_status HTTP Status Code - * @return {number} gRPC Status Code + * @param {number} httpStatus HTTP Status Code + * @return {!StatusCode} gRPC Status Code */ -StatusCode.fromHttpStatus = function(http_status) { - switch (http_status) { +StatusCode.fromHttpStatus = function(httpStatus) { + switch (httpStatus) { case 200: return StatusCode.OK; case 400: @@ -178,4 +179,44 @@ StatusCode.fromHttpStatus = function(http_status) { }; +/** + * Convert a {@link StatusCode} to an HTTP Status code + * @param {!StatusCode} statusCode GRPC Status Code + * @return {number} HTTP Status code + */ +StatusCode.getHttpStatus = function(statusCode) { + switch (statusCode) { + case StatusCode.OK: + return 200; + case StatusCode.INVALID_ARGUMENT: + return 400; + case StatusCode.UNAUTHENTICATED: + return 401; + case StatusCode.PERMISSION_DENIED: + return 403; + case StatusCode.NOT_FOUND: + return 404; + case StatusCode.ABORTED: + return 409; + case StatusCode.FAILED_PRECONDITION: + return 412; + case StatusCode.RESOURCE_EXHAUSTED: + return 429; + case StatusCode.CANCELLED: + return 499; + case StatusCode.UNKNOWN: + return 500; + case StatusCode.UNIMPLEMENTED: + return 501; + case StatusCode.UNAVAILABLE: + return 503; + case StatusCode.DEADLINE_EXCEEDED: + return 504; + /* everything else is unknown */ + default: + return 0; + } +}; + + exports = StatusCode; diff --git a/javascript/net/grpc/web/statuscode_test.js b/javascript/net/grpc/web/statuscode_test.js new file mode 100644 index 0000000..d52824b --- /dev/null +++ b/javascript/net/grpc/web/statuscode_test.js @@ -0,0 +1,41 @@ +goog.module('grpc.web.StatusCodeTest'); +goog.setTestOnly('grpc.web.StatusCodeTest'); + +const StatusCode = goog.require('grpc.web.StatusCode'); +const testSuite = goog.require('goog.testing.testSuite'); + + +/** @type {!Map} */ +const statusMap = new Map([ + [200, StatusCode.OK], + [400, StatusCode.INVALID_ARGUMENT], + [401, StatusCode.UNAUTHENTICATED], + [403, StatusCode.PERMISSION_DENIED], + [404, StatusCode.NOT_FOUND], + [409, StatusCode.ABORTED], + [412, StatusCode.FAILED_PRECONDITION], + [429, StatusCode.RESOURCE_EXHAUSTED], + [500, StatusCode.UNKNOWN], + [501, StatusCode.UNIMPLEMENTED], + [503, StatusCode.UNAVAILABLE], + [504, StatusCode.DEADLINE_EXCEEDED], +]); + +testSuite({ + testFromHttpStatus() { + statusMap.forEach((statusCode, httpStatus) => { + assertEquals(StatusCode.fromHttpStatus(httpStatus), statusCode); + }); + }, + + testGetHttpStatus() { + statusMap.forEach((statusCode, httpStatus) => { + assertEquals(StatusCode.getHttpStatus(statusCode), httpStatus); + }); + }, + + testUnknown() { + assertEquals(StatusCode.getHttpStatus(StatusCode.UNKNOWN), 500); + assertEquals(StatusCode.fromHttpStatus(511), StatusCode.UNKNOWN); + } +}); \ No newline at end of file diff --git a/net/grpc/gateway/docker/prereqs/Dockerfile b/net/grpc/gateway/docker/prereqs/Dockerfile index bbc4af9..6f19e51 100644 --- a/net/grpc/gateway/docker/prereqs/Dockerfile +++ b/net/grpc/gateway/docker/prereqs/Dockerfile @@ -73,11 +73,10 @@ WORKDIR /github/grpc-web # Copy only files necessary to build the protoc-gen-grpc-web first as an optimization because they # are rarely updated compared with the javascript files. COPY ./WORKSPACE ./WORKSPACE -COPY ./javascript/net/grpc/web/grpc_generator.cc javascript/net/grpc/web/grpc_generator.cc -COPY ./javascript/net/grpc/web/BUILD.bazel javascript/net/grpc/web/BUILD.bazel +COPY ./javascript/net/grpc/web/generator javascript/net/grpc/web/generator -RUN bazel build javascript/net/grpc/web:protoc-gen-grpc-web && \ - cp $(bazel info bazel-genfiles)/javascript/net/grpc/web/protoc-gen-grpc-web \ +RUN bazel build javascript/net/grpc/web/generator:protoc-gen-grpc-web && \ + cp $(bazel info bazel-genfiles)/javascript/net/grpc/web/generator/protoc-gen-grpc-web \ /usr/local/bin/protoc-gen-grpc-web COPY ./javascript ./javascript diff --git a/net/grpc/gateway/docker/protoc_plugin/Dockerfile b/net/grpc/gateway/docker/protoc_plugin/Dockerfile index 09a43cf..69ba677 100644 --- a/net/grpc/gateway/docker/protoc_plugin/Dockerfile +++ b/net/grpc/gateway/docker/protoc_plugin/Dockerfile @@ -32,5 +32,5 @@ WORKDIR /github/grpc-web RUN cd ./third_party/protobuf && \ ./autogen.sh && ./configure && make && make install && ldconfig -RUN cd ./javascript/net/grpc/web && \ +RUN cd ./javascript/net/grpc/web/generator && \ make protoc-gen-grpc-web diff --git a/net/grpc/gateway/examples/echo/package.json b/net/grpc/gateway/examples/echo/package.json index 6ef4662..0e07806 100644 --- a/net/grpc/gateway/examples/echo/package.json +++ b/net/grpc/gateway/examples/echo/package.json @@ -6,6 +6,6 @@ "dependencies": {}, "devDependencies": { "google-closure-compiler": "~20200224.0.0", - "google-closure-library": "~20201102.0.1" + "google-closure-library": "~20210808.0.0" } } diff --git a/packages/grpc-web/test/generated_code_test.js b/packages/grpc-web/test/generated_code_test.js index 37b6136..b7622be 100644 --- a/packages/grpc-web/test/generated_code_test.js +++ b/packages/grpc-web/test/generated_code_test.js @@ -376,8 +376,8 @@ describe('grpc-web generated code (commonjs+grpcwebtext)', function() { if (response) { assert.fail('should not receive response'); } - assert.equal(2, err.code); - assert.equal(true, err.message.toLowerCase().includes('deserialize')); + assert.equal(13 /* StatusCode.INTERNAL */, err.code); + assert.equal(true, err.message.toLowerCase().includes('deserializing')); assert.equal(true, err.message.toLowerCase().includes('error')); done(); }); diff --git a/scripts/README.md b/scripts/README.md index 1bb22df..e9ec0cb 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -16,7 +16,7 @@ in Docker Desktop for Mac (e.g. to 4 - 6GB) if you see the following errors: ``` -$ bazel test --cache_test_results=no //javascript/net/grpc/web/... //net/grpc/gateway/examples/... +$ bazel build //javascript/net/grpc/web/generator/... //net/grpc/gateway/examples/echo/... ... diff --git a/scripts/docker-run-build-tests.sh b/scripts/docker-run-build-tests.sh index 6d48bed..f1fb1b3 100755 --- a/scripts/docker-run-build-tests.sh +++ b/scripts/docker-run-build-tests.sh @@ -21,5 +21,5 @@ set -ex cd /github/grpc-web && \ bazel clean && \ bazel build \ - //javascript/net/grpc/web/... \ - //net/grpc/gateway/examples/... + //javascript/net/grpc/web/generator/... \ + //net/grpc/gateway/examples/echo/...