3 * Copyright 2015 gRPC authors.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
24 #include "src/compiler/config.h"
25 #include "src/compiler/csharp_generator.h"
26 #include "src/compiler/csharp_generator_helpers.h"
28 using google::protobuf::compiler::csharp::GetClassName;
29 using google::protobuf::compiler::csharp::GetFileNamespace;
30 using google::protobuf::compiler::csharp::GetReflectionClassName;
31 using grpc::protobuf::Descriptor;
32 using grpc::protobuf::FileDescriptor;
33 using grpc::protobuf::MethodDescriptor;
34 using grpc::protobuf::ServiceDescriptor;
35 using grpc::protobuf::io::Printer;
36 using grpc::protobuf::io::StringOutputStream;
37 using grpc_generator::GetMethodType;
38 using grpc_generator::METHODTYPE_BIDI_STREAMING;
39 using grpc_generator::METHODTYPE_CLIENT_STREAMING;
40 using grpc_generator::METHODTYPE_NO_STREAMING;
41 using grpc_generator::METHODTYPE_SERVER_STREAMING;
42 using grpc_generator::MethodType;
43 using grpc_generator::StringReplace;
47 namespace grpc_csharp_generator {
50 // This function is a massaged version of
51 // https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc
52 // Currently, we cannot easily reuse the functionality as
53 // google/protobuf/compiler/csharp/csharp_doc_comment.h is not a public header.
54 // TODO(jtattermusch): reuse the functionality from google/protobuf.
55 bool GenerateDocCommentBodyImpl(grpc::protobuf::io::Printer* printer,
56 grpc::protobuf::SourceLocation location) {
57 grpc::string comments = location.leading_comments.empty()
58 ? location.trailing_comments
59 : location.leading_comments;
60 if (comments.empty()) {
63 // XML escaping... no need for apostrophes etc as the whole text is going to
65 // node of a summary element, not part of an attribute.
66 comments = grpc_generator::StringReplace(comments, "&", "&", true);
67 comments = grpc_generator::StringReplace(comments, "<", "<", true);
69 std::vector<grpc::string> lines;
70 grpc_generator::Split(comments, '\n', &lines);
71 // TODO: We really should work out which part to put in the summary and which
72 // to put in the remarks...
73 // but that needs to be part of a bigger effort to understand the markdown
75 printer->Print("/// <summary>\n");
76 bool last_was_empty = false;
77 // We squash multiple blank lines down to one, and remove any trailing blank
79 // to preserve the blank lines themselves, as this is relevant in the
81 // Note that we can't remove leading or trailing whitespace as *that's*
82 // relevant in markdown too.
83 // (We don't skip "just whitespace" lines, either.)
84 for (std::vector<grpc::string>::iterator it = lines.begin();
85 it != lines.end(); ++it) {
86 grpc::string line = *it;
88 last_was_empty = true;
91 printer->Print("///\n");
93 last_was_empty = false;
94 printer->Print("///$line$\n", "line", *it);
97 printer->Print("/// </summary>\n");
101 template <typename DescriptorType>
102 bool GenerateDocCommentBody(grpc::protobuf::io::Printer* printer,
103 const DescriptorType* descriptor) {
104 grpc::protobuf::SourceLocation location;
105 if (!descriptor->GetSourceLocation(&location)) {
108 return GenerateDocCommentBodyImpl(printer, location);
111 void GenerateDocCommentServerMethod(grpc::protobuf::io::Printer* printer,
112 const MethodDescriptor* method) {
113 if (GenerateDocCommentBody(printer, method)) {
114 if (method->client_streaming()) {
116 "/// <param name=\"requestStream\">Used for reading requests from "
117 "the client.</param>\n");
120 "/// <param name=\"request\">The request received from the "
121 "client.</param>\n");
123 if (method->server_streaming()) {
125 "/// <param name=\"responseStream\">Used for sending responses back "
126 "to the client.</param>\n");
129 "/// <param name=\"context\">The context of the server-side call "
130 "handler being invoked.</param>\n");
131 if (method->server_streaming()) {
133 "/// <returns>A task indicating completion of the "
134 "handler.</returns>\n");
137 "/// <returns>The response to send back to the client (wrapped by a "
138 "task).</returns>\n");
143 void GenerateDocCommentClientMethod(grpc::protobuf::io::Printer* printer,
144 const MethodDescriptor* method,
145 bool is_sync, bool use_call_options) {
146 if (GenerateDocCommentBody(printer, method)) {
147 if (!method->client_streaming()) {
149 "/// <param name=\"request\">The request to send to the "
150 "server.</param>\n");
152 if (!use_call_options) {
154 "/// <param name=\"headers\">The initial metadata to send with the "
155 "call. This parameter is optional.</param>\n");
157 "/// <param name=\"deadline\">An optional deadline for the call. The "
158 "call will be cancelled if deadline is hit.</param>\n");
160 "/// <param name=\"cancellationToken\">An optional token for "
161 "canceling the call.</param>\n");
164 "/// <param name=\"options\">The options for the call.</param>\n");
168 "/// <returns>The response received from the server.</returns>\n");
170 printer->Print("/// <returns>The call object.</returns>\n");
175 std::string GetServiceClassName(const ServiceDescriptor* service) {
176 return service->name();
179 std::string GetClientClassName(const ServiceDescriptor* service) {
180 return service->name() + "Client";
183 std::string GetServerClassName(const ServiceDescriptor* service) {
184 return service->name() + "Base";
187 std::string GetCSharpMethodType(MethodType method_type) {
188 switch (method_type) {
189 case METHODTYPE_NO_STREAMING:
190 return "grpc::MethodType.Unary";
191 case METHODTYPE_CLIENT_STREAMING:
192 return "grpc::MethodType.ClientStreaming";
193 case METHODTYPE_SERVER_STREAMING:
194 return "grpc::MethodType.ServerStreaming";
195 case METHODTYPE_BIDI_STREAMING:
196 return "grpc::MethodType.DuplexStreaming";
198 GOOGLE_LOG(FATAL) << "Can't get here.";
202 std::string GetCSharpServerMethodType(MethodType method_type) {
203 switch (method_type) {
204 case METHODTYPE_NO_STREAMING:
205 return "grpc::UnaryServerMethod";
206 case METHODTYPE_CLIENT_STREAMING:
207 return "grpc::ClientStreamingServerMethod";
208 case METHODTYPE_SERVER_STREAMING:
209 return "grpc::ServerStreamingServerMethod";
210 case METHODTYPE_BIDI_STREAMING:
211 return "grpc::DuplexStreamingServerMethod";
213 GOOGLE_LOG(FATAL) << "Can't get here.";
217 std::string GetServiceNameFieldName() { return "__ServiceName"; }
219 std::string GetMarshallerFieldName(const Descriptor* message) {
220 return "__Marshaller_" +
221 grpc_generator::StringReplace(message->full_name(), ".", "_", true);
224 std::string GetMethodFieldName(const MethodDescriptor* method) {
225 return "__Method_" + method->name();
228 std::string GetMethodRequestParamMaybe(const MethodDescriptor* method,
229 bool invocation_param = false) {
230 if (method->client_streaming()) {
233 if (invocation_param) {
236 return GetClassName(method->input_type()) + " request, ";
239 std::string GetAccessLevel(bool internal_access) {
240 return internal_access ? "internal" : "public";
243 std::string GetMethodReturnTypeClient(const MethodDescriptor* method) {
244 switch (GetMethodType(method)) {
245 case METHODTYPE_NO_STREAMING:
246 return "grpc::AsyncUnaryCall<" + GetClassName(method->output_type()) +
248 case METHODTYPE_CLIENT_STREAMING:
249 return "grpc::AsyncClientStreamingCall<" +
250 GetClassName(method->input_type()) + ", " +
251 GetClassName(method->output_type()) + ">";
252 case METHODTYPE_SERVER_STREAMING:
253 return "grpc::AsyncServerStreamingCall<" +
254 GetClassName(method->output_type()) + ">";
255 case METHODTYPE_BIDI_STREAMING:
256 return "grpc::AsyncDuplexStreamingCall<" +
257 GetClassName(method->input_type()) + ", " +
258 GetClassName(method->output_type()) + ">";
260 GOOGLE_LOG(FATAL) << "Can't get here.";
264 std::string GetMethodRequestParamServer(const MethodDescriptor* method) {
265 switch (GetMethodType(method)) {
266 case METHODTYPE_NO_STREAMING:
267 case METHODTYPE_SERVER_STREAMING:
268 return GetClassName(method->input_type()) + " request";
269 case METHODTYPE_CLIENT_STREAMING:
270 case METHODTYPE_BIDI_STREAMING:
271 return "grpc::IAsyncStreamReader<" + GetClassName(method->input_type()) +
274 GOOGLE_LOG(FATAL) << "Can't get here.";
278 std::string GetMethodReturnTypeServer(const MethodDescriptor* method) {
279 switch (GetMethodType(method)) {
280 case METHODTYPE_NO_STREAMING:
281 case METHODTYPE_CLIENT_STREAMING:
282 return "global::System.Threading.Tasks.Task<" +
283 GetClassName(method->output_type()) + ">";
284 case METHODTYPE_SERVER_STREAMING:
285 case METHODTYPE_BIDI_STREAMING:
286 return "global::System.Threading.Tasks.Task";
288 GOOGLE_LOG(FATAL) << "Can't get here.";
292 std::string GetMethodResponseStreamMaybe(const MethodDescriptor* method) {
293 switch (GetMethodType(method)) {
294 case METHODTYPE_NO_STREAMING:
295 case METHODTYPE_CLIENT_STREAMING:
297 case METHODTYPE_SERVER_STREAMING:
298 case METHODTYPE_BIDI_STREAMING:
299 return ", grpc::IServerStreamWriter<" +
300 GetClassName(method->output_type()) + "> responseStream";
302 GOOGLE_LOG(FATAL) << "Can't get here.";
306 // Gets vector of all messages used as input or output types.
307 std::vector<const Descriptor*> GetUsedMessages(
308 const ServiceDescriptor* service) {
309 std::set<const Descriptor*> descriptor_set;
310 std::vector<const Descriptor*>
311 result; // vector is to maintain stable ordering
312 for (int i = 0; i < service->method_count(); i++) {
313 const MethodDescriptor* method = service->method(i);
314 if (descriptor_set.find(method->input_type()) == descriptor_set.end()) {
315 descriptor_set.insert(method->input_type());
316 result.push_back(method->input_type());
318 if (descriptor_set.find(method->output_type()) == descriptor_set.end()) {
319 descriptor_set.insert(method->output_type());
320 result.push_back(method->output_type());
326 void GenerateMarshallerFields(Printer* out, const ServiceDescriptor* service) {
327 std::vector<const Descriptor*> used_messages = GetUsedMessages(service);
328 for (size_t i = 0; i < used_messages.size(); i++) {
329 const Descriptor* message = used_messages[i];
331 "static readonly grpc::Marshaller<$type$> $fieldname$ = "
332 "grpc::Marshallers.Create((arg) => "
333 "global::Google.Protobuf.MessageExtensions.ToByteArray(arg), "
334 "$type$.Parser.ParseFrom);\n",
335 "fieldname", GetMarshallerFieldName(message), "type",
336 GetClassName(message));
341 void GenerateStaticMethodField(Printer* out, const MethodDescriptor* method) {
343 "static readonly grpc::Method<$request$, $response$> $fieldname$ = new "
344 "grpc::Method<$request$, $response$>(\n",
345 "fieldname", GetMethodFieldName(method), "request",
346 GetClassName(method->input_type()), "response",
347 GetClassName(method->output_type()));
350 out->Print("$methodtype$,\n", "methodtype",
351 GetCSharpMethodType(GetMethodType(method)));
352 out->Print("$servicenamefield$,\n", "servicenamefield",
353 GetServiceNameFieldName());
354 out->Print("\"$methodname$\",\n", "methodname", method->name());
355 out->Print("$requestmarshaller$,\n", "requestmarshaller",
356 GetMarshallerFieldName(method->input_type()));
357 out->Print("$responsemarshaller$);\n", "responsemarshaller",
358 GetMarshallerFieldName(method->output_type()));
364 void GenerateServiceDescriptorProperty(Printer* out,
365 const ServiceDescriptor* service) {
366 std::ostringstream index;
367 index << service->index();
368 out->Print("/// <summary>Service descriptor</summary>\n");
370 "public static global::Google.Protobuf.Reflection.ServiceDescriptor "
373 out->Print(" get { return $umbrella$.Descriptor.Services[$index$]; }\n",
374 "umbrella", GetReflectionClassName(service->file()), "index",
380 void GenerateServerClass(Printer* out, const ServiceDescriptor* service) {
382 "/// <summary>Base class for server-side implementations of "
383 "$servicename$</summary>\n",
384 "servicename", GetServiceClassName(service));
386 "[grpc::BindServiceMethod(typeof($classname$), "
387 "\"BindService\")]\n",
388 "classname", GetServiceClassName(service));
389 out->Print("public abstract partial class $name$\n", "name",
390 GetServerClassName(service));
393 for (int i = 0; i < service->method_count(); i++) {
394 const MethodDescriptor* method = service->method(i);
395 GenerateDocCommentServerMethod(out, method);
397 "public virtual $returntype$ "
398 "$methodname$($request$$response_stream_maybe$, "
399 "grpc::ServerCallContext context)\n",
400 "methodname", method->name(), "returntype",
401 GetMethodReturnTypeServer(method), "request",
402 GetMethodRequestParamServer(method), "response_stream_maybe",
403 GetMethodResponseStreamMaybe(method));
407 "throw new grpc::RpcException("
408 "new grpc::Status(grpc::StatusCode.Unimplemented, \"\"));\n");
417 void GenerateClientStub(Printer* out, const ServiceDescriptor* service,
420 out->Print("/// <summary>Client for $servicename$</summary>\n",
421 "servicename", GetServiceClassName(service));
422 out->Print("public partial class $name$ : grpc::ClientBase<$name$>\n",
423 "name", GetClientClassName(service));
425 out->Print("/// <summary>Lite client for $servicename$</summary>\n",
426 "servicename", GetServiceClassName(service));
427 out->Print("public partial class $name$ : grpc::LiteClientBase\n", "name",
428 GetClientClassName(service));
436 "/// <summary>Creates a new client for $servicename$</summary>\n"
437 "/// <param name=\"channel\">The channel to use to make remote "
439 "servicename", GetServiceClassName(service));
440 out->Print("public $name$(grpc::Channel channel) : base(channel)\n", "name",
441 GetClientClassName(service));
446 "/// <summary>Creates a new client for $servicename$ that uses a custom "
447 "<c>CallInvoker</c>.</summary>\n"
448 "/// <param name=\"callInvoker\">The callInvoker to use to make remote "
450 "servicename", GetServiceClassName(service));
452 "public $name$(grpc::CallInvoker callInvoker) : base(callInvoker)\n",
453 "name", GetClientClassName(service));
457 "/// <summary>Protected parameterless constructor to allow creation"
458 " of test doubles.</summary>\n");
459 out->Print("protected $name$() : base()\n", "name",
460 GetClientClassName(service));
465 "/// <summary>Protected constructor to allow creation of configured "
466 "clients.</summary>\n"
467 "/// <param name=\"configuration\">The client "
468 "configuration.</param>\n");
470 "protected $name$(ClientBaseConfiguration configuration)"
471 " : base(configuration)\n",
472 "name", GetClientClassName(service));
478 for (int i = 0; i < service->method_count(); i++) {
479 const MethodDescriptor* method = service->method(i);
480 MethodType method_type = GetMethodType(method);
482 if (method_type == METHODTYPE_NO_STREAMING) {
483 // unary calls have an extra synchronous stub method
484 GenerateDocCommentClientMethod(out, method, true, false);
486 "public virtual $response$ $methodname$($request$ request, "
488 "headers = null, global::System.DateTime? deadline = null, "
489 "global::System.Threading.CancellationToken "
490 "cancellationToken = "
491 "default(global::System.Threading.CancellationToken))\n",
492 "methodname", method->name(), "request",
493 GetClassName(method->input_type()), "response",
494 GetClassName(method->output_type()));
498 "return $methodname$(request, new grpc::CallOptions(headers, "
500 "cancellationToken));\n",
501 "methodname", method->name());
505 // overload taking CallOptions as a param
506 GenerateDocCommentClientMethod(out, method, true, true);
508 "public virtual $response$ $methodname$($request$ request, "
509 "grpc::CallOptions options)\n",
510 "methodname", method->name(), "request",
511 GetClassName(method->input_type()), "response",
512 GetClassName(method->output_type()));
516 "return CallInvoker.BlockingUnaryCall($methodfield$, null, options, "
518 "methodfield", GetMethodFieldName(method));
523 std::string method_name = method->name();
524 if (method_type == METHODTYPE_NO_STREAMING) {
525 method_name += "Async"; // prevent name clash with synchronous method.
527 GenerateDocCommentClientMethod(out, method, false, false);
529 "public virtual $returntype$ "
530 "$methodname$($request_maybe$grpc::Metadata "
531 "headers = null, global::System.DateTime? deadline = null, "
532 "global::System.Threading.CancellationToken "
533 "cancellationToken = "
534 "default(global::System.Threading.CancellationToken))\n",
535 "methodname", method_name, "request_maybe",
536 GetMethodRequestParamMaybe(method), "returntype",
537 GetMethodReturnTypeClient(method));
542 "return $methodname$($request_maybe$new grpc::CallOptions(headers, "
544 "cancellationToken));\n",
545 "methodname", method_name, "request_maybe",
546 GetMethodRequestParamMaybe(method, true));
550 // overload taking CallOptions as a param
551 GenerateDocCommentClientMethod(out, method, false, true);
553 "public virtual $returntype$ "
554 "$methodname$($request_maybe$grpc::CallOptions "
556 "methodname", method_name, "request_maybe",
557 GetMethodRequestParamMaybe(method), "returntype",
558 GetMethodReturnTypeClient(method));
561 switch (GetMethodType(method)) {
562 case METHODTYPE_NO_STREAMING:
564 "return CallInvoker.AsyncUnaryCall($methodfield$, null, options, "
566 "methodfield", GetMethodFieldName(method));
568 case METHODTYPE_CLIENT_STREAMING:
570 "return CallInvoker.AsyncClientStreamingCall($methodfield$, null, "
572 "methodfield", GetMethodFieldName(method));
574 case METHODTYPE_SERVER_STREAMING:
576 "return CallInvoker.AsyncServerStreamingCall($methodfield$, null, "
577 "options, request);\n",
578 "methodfield", GetMethodFieldName(method));
580 case METHODTYPE_BIDI_STREAMING:
582 "return CallInvoker.AsyncDuplexStreamingCall($methodfield$, null, "
584 "methodfield", GetMethodFieldName(method));
587 GOOGLE_LOG(FATAL) << "Can't get here.";
593 // override NewInstance method
596 "/// <summary>Creates a new instance of client from given "
597 "<c>ClientBaseConfiguration</c>.</summary>\n");
599 "protected override $name$ NewInstance(ClientBaseConfiguration "
601 "name", GetClientClassName(service));
604 out->Print("return new $name$(configuration);\n", "name",
605 GetClientClassName(service));
615 void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor* service) {
617 "/// <summary>Creates service definition that can be registered with a "
618 "server</summary>\n");
620 "/// <param name=\"serviceImpl\">An object implementing the server-side"
621 " handling logic.</param>\n");
623 "public static grpc::ServerServiceDefinition BindService($implclass$ "
625 "implclass", GetServerClassName(service));
629 out->Print("return grpc::ServerServiceDefinition.CreateBuilder()");
632 for (int i = 0; i < service->method_count(); i++) {
633 const MethodDescriptor* method = service->method(i);
634 out->Print("\n.AddMethod($methodfield$, serviceImpl.$methodname$)",
635 "methodfield", GetMethodFieldName(method), "methodname",
638 out->Print(".Build();\n");
647 void GenerateBindServiceWithBinderMethod(Printer* out,
648 const ServiceDescriptor* service) {
650 "/// <summary>Register service method with a service "
651 "binder with or without implementation. Useful when customizing the "
652 "service binding logic.\n"
653 "/// Note: this method is part of an experimental API that can change or "
655 "removed without any prior notice.</summary>\n");
657 "/// <param name=\"serviceBinder\">Service methods will be bound by "
658 "calling <c>AddMethod</c> on this object."
661 "/// <param name=\"serviceImpl\">An object implementing the server-side"
662 " handling logic.</param>\n");
664 "public static void BindService(grpc::ServiceBinderBase serviceBinder, "
667 "implclass", GetServerClassName(service));
671 for (int i = 0; i < service->method_count(); i++) {
672 const MethodDescriptor* method = service->method(i);
674 "serviceBinder.AddMethod($methodfield$, serviceImpl == null ? null : "
675 "new $servermethodtype$<$inputtype$, $outputtype$>("
676 "serviceImpl.$methodname$));\n",
677 "methodfield", GetMethodFieldName(method), "servermethodtype",
678 GetCSharpServerMethodType(GetMethodType(method)), "inputtype",
679 GetClassName(method->input_type()), "outputtype",
680 GetClassName(method->output_type()), "methodname", method->name());
688 void GenerateService(Printer* out, const ServiceDescriptor* service,
689 bool generate_client, bool generate_server,
690 bool internal_access, bool lite_client) {
691 GenerateDocCommentBody(out, service);
692 out->Print("$access_level$ static partial class $classname$\n",
693 "access_level", GetAccessLevel(internal_access), "classname",
694 GetServiceClassName(service));
697 out->Print("static readonly string $servicenamefield$ = \"$servicename$\";\n",
698 "servicenamefield", GetServiceNameFieldName(), "servicename",
699 service->full_name());
702 GenerateMarshallerFields(out, service);
703 for (int i = 0; i < service->method_count(); i++) {
704 GenerateStaticMethodField(out, service->method(i));
706 GenerateServiceDescriptorProperty(out, service);
708 if (generate_server) {
709 GenerateServerClass(out, service);
711 if (generate_client) {
712 GenerateClientStub(out, service, lite_client);
715 if (generate_server) {
716 GenerateBindServiceMethod(out, service);
717 GenerateBindServiceWithBinderMethod(out, service);
724 } // anonymous namespace
726 grpc::string GetServices(const FileDescriptor* file, bool generate_client,
727 bool generate_server, bool internal_access,
731 // Scope the output stream so it closes and finalizes output to the string.
733 StringOutputStream output_stream(&output);
734 Printer out(&output_stream, '$');
736 // Don't write out any output if there no services, to avoid empty service
737 // files being generated for proto files that don't declare any.
738 if (file->service_count() == 0) {
742 // Write out a file header.
743 out.Print("// <auto-generated>\n");
745 "// Generated by the protocol buffer compiler. DO NOT EDIT!\n");
746 out.Print("// source: $filename$\n", "filename", file->name());
747 out.Print("// </auto-generated>\n");
749 // use C++ style as there are no file-level XML comments in .NET
750 grpc::string leading_comments = GetCsharpComments(file, true);
751 if (!leading_comments.empty()) {
752 out.Print("// Original file comments:\n");
753 out.PrintRaw(leading_comments.c_str());
756 out.Print("#pragma warning disable 0414, 1591\n");
758 out.Print("#region Designer generated code\n");
760 out.Print("using grpc = global::Grpc.Core;\n");
763 grpc::string file_namespace = GetFileNamespace(file);
764 if (file_namespace != "") {
765 out.Print("namespace $namespace$ {\n", "namespace", file_namespace);
768 for (int i = 0; i < file->service_count(); i++) {
769 GenerateService(&out, file->service(i), generate_client, generate_server,
770 internal_access, lite_client);
772 if (file_namespace != "") {
776 out.Print("#endregion\n");
781 } // namespace grpc_csharp_generator