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));
385 out->Print("public abstract partial class $name$\n", "name",
386 GetServerClassName(service));
389 for (int i = 0; i < service->method_count(); i++) {
390 const MethodDescriptor* method = service->method(i);
391 GenerateDocCommentServerMethod(out, method);
393 "public virtual $returntype$ "
394 "$methodname$($request$$response_stream_maybe$, "
395 "grpc::ServerCallContext context)\n",
396 "methodname", method->name(), "returntype",
397 GetMethodReturnTypeServer(method), "request",
398 GetMethodRequestParamServer(method), "response_stream_maybe",
399 GetMethodResponseStreamMaybe(method));
403 "throw new grpc::RpcException("
404 "new grpc::Status(grpc::StatusCode.Unimplemented, \"\"));\n");
413 void GenerateClientStub(Printer* out, const ServiceDescriptor* service) {
414 out->Print("/// <summary>Client for $servicename$</summary>\n", "servicename",
415 GetServiceClassName(service));
416 out->Print("public partial class $name$ : grpc::ClientBase<$name$>\n", "name",
417 GetClientClassName(service));
423 "/// <summary>Creates a new client for $servicename$</summary>\n"
424 "/// <param name=\"channel\">The channel to use to make remote "
426 "servicename", GetServiceClassName(service));
427 out->Print("public $name$(grpc::Channel channel) : base(channel)\n", "name",
428 GetClientClassName(service));
432 "/// <summary>Creates a new client for $servicename$ that uses a custom "
433 "<c>CallInvoker</c>.</summary>\n"
434 "/// <param name=\"callInvoker\">The callInvoker to use to make remote "
436 "servicename", GetServiceClassName(service));
438 "public $name$(grpc::CallInvoker callInvoker) : base(callInvoker)\n",
439 "name", GetClientClassName(service));
443 "/// <summary>Protected parameterless constructor to allow creation"
444 " of test doubles.</summary>\n");
445 out->Print("protected $name$() : base()\n", "name",
446 GetClientClassName(service));
450 "/// <summary>Protected constructor to allow creation of configured "
451 "clients.</summary>\n"
452 "/// <param name=\"configuration\">The client configuration.</param>\n");
454 "protected $name$(ClientBaseConfiguration configuration)"
455 " : base(configuration)\n",
456 "name", GetClientClassName(service));
460 for (int i = 0; i < service->method_count(); i++) {
461 const MethodDescriptor* method = service->method(i);
462 MethodType method_type = GetMethodType(method);
464 if (method_type == METHODTYPE_NO_STREAMING) {
465 // unary calls have an extra synchronous stub method
466 GenerateDocCommentClientMethod(out, method, true, false);
468 "public virtual $response$ $methodname$($request$ request, "
470 "headers = null, global::System.DateTime? deadline = null, "
471 "global::System.Threading.CancellationToken "
472 "cancellationToken = "
473 "default(global::System.Threading.CancellationToken))\n",
474 "methodname", method->name(), "request",
475 GetClassName(method->input_type()), "response",
476 GetClassName(method->output_type()));
480 "return $methodname$(request, new grpc::CallOptions(headers, "
482 "cancellationToken));\n",
483 "methodname", method->name());
487 // overload taking CallOptions as a param
488 GenerateDocCommentClientMethod(out, method, true, true);
490 "public virtual $response$ $methodname$($request$ request, "
491 "grpc::CallOptions options)\n",
492 "methodname", method->name(), "request",
493 GetClassName(method->input_type()), "response",
494 GetClassName(method->output_type()));
498 "return CallInvoker.BlockingUnaryCall($methodfield$, null, options, "
500 "methodfield", GetMethodFieldName(method));
505 std::string method_name = method->name();
506 if (method_type == METHODTYPE_NO_STREAMING) {
507 method_name += "Async"; // prevent name clash with synchronous method.
509 GenerateDocCommentClientMethod(out, method, false, false);
511 "public virtual $returntype$ "
512 "$methodname$($request_maybe$grpc::Metadata "
513 "headers = null, global::System.DateTime? deadline = null, "
514 "global::System.Threading.CancellationToken "
515 "cancellationToken = "
516 "default(global::System.Threading.CancellationToken))\n",
517 "methodname", method_name, "request_maybe",
518 GetMethodRequestParamMaybe(method), "returntype",
519 GetMethodReturnTypeClient(method));
524 "return $methodname$($request_maybe$new grpc::CallOptions(headers, "
526 "cancellationToken));\n",
527 "methodname", method_name, "request_maybe",
528 GetMethodRequestParamMaybe(method, true));
532 // overload taking CallOptions as a param
533 GenerateDocCommentClientMethod(out, method, false, true);
535 "public virtual $returntype$ "
536 "$methodname$($request_maybe$grpc::CallOptions "
538 "methodname", method_name, "request_maybe",
539 GetMethodRequestParamMaybe(method), "returntype",
540 GetMethodReturnTypeClient(method));
543 switch (GetMethodType(method)) {
544 case METHODTYPE_NO_STREAMING:
546 "return CallInvoker.AsyncUnaryCall($methodfield$, null, options, "
548 "methodfield", GetMethodFieldName(method));
550 case METHODTYPE_CLIENT_STREAMING:
552 "return CallInvoker.AsyncClientStreamingCall($methodfield$, null, "
554 "methodfield", GetMethodFieldName(method));
556 case METHODTYPE_SERVER_STREAMING:
558 "return CallInvoker.AsyncServerStreamingCall($methodfield$, null, "
559 "options, request);\n",
560 "methodfield", GetMethodFieldName(method));
562 case METHODTYPE_BIDI_STREAMING:
564 "return CallInvoker.AsyncDuplexStreamingCall($methodfield$, null, "
566 "methodfield", GetMethodFieldName(method));
569 GOOGLE_LOG(FATAL) << "Can't get here.";
575 // override NewInstance method
577 "/// <summary>Creates a new instance of client from given "
578 "<c>ClientBaseConfiguration</c>.</summary>\n");
580 "protected override $name$ NewInstance(ClientBaseConfiguration "
582 "name", GetClientClassName(service));
585 out->Print("return new $name$(configuration);\n", "name",
586 GetClientClassName(service));
595 void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor* service) {
597 "/// <summary>Creates service definition that can be registered with a "
598 "server</summary>\n");
600 "/// <param name=\"serviceImpl\">An object implementing the server-side"
601 " handling logic.</param>\n");
603 "public static grpc::ServerServiceDefinition BindService($implclass$ "
605 "implclass", GetServerClassName(service));
609 out->Print("return grpc::ServerServiceDefinition.CreateBuilder()");
612 for (int i = 0; i < service->method_count(); i++) {
613 const MethodDescriptor* method = service->method(i);
614 out->Print("\n.AddMethod($methodfield$, serviceImpl.$methodname$)",
615 "methodfield", GetMethodFieldName(method), "methodname",
618 out->Print(".Build();\n");
627 void GenerateBindServiceWithBinderMethod(Printer* out,
628 const ServiceDescriptor* service) {
630 "/// <summary>Register service method with a service "
631 "binder with or without implementation. Useful when customizing the "
632 "service binding logic.\n"
633 "/// Note: this method is part of an experimental API that can change or "
635 "removed without any prior notice.</summary>\n");
637 "/// <param name=\"serviceBinder\">Service methods will be bound by "
638 "calling <c>AddMethod</c> on this object."
641 "/// <param name=\"serviceImpl\">An object implementing the server-side"
642 " handling logic.</param>\n");
644 "public static void BindService(grpc::ServiceBinderBase serviceBinder, "
647 "implclass", GetServerClassName(service));
651 for (int i = 0; i < service->method_count(); i++) {
652 const MethodDescriptor* method = service->method(i);
654 "serviceBinder.AddMethod($methodfield$, serviceImpl == null ? null : "
655 "new $servermethodtype$<$inputtype$, $outputtype$>("
656 "serviceImpl.$methodname$));\n",
657 "methodfield", GetMethodFieldName(method), "servermethodtype",
658 GetCSharpServerMethodType(GetMethodType(method)), "inputtype",
659 GetClassName(method->input_type()), "outputtype",
660 GetClassName(method->output_type()), "methodname", method->name());
668 void GenerateService(Printer* out, const ServiceDescriptor* service,
669 bool generate_client, bool generate_server,
670 bool internal_access) {
671 GenerateDocCommentBody(out, service);
672 out->Print("$access_level$ static partial class $classname$\n",
673 "access_level", GetAccessLevel(internal_access), "classname",
674 GetServiceClassName(service));
677 out->Print("static readonly string $servicenamefield$ = \"$servicename$\";\n",
678 "servicenamefield", GetServiceNameFieldName(), "servicename",
679 service->full_name());
682 GenerateMarshallerFields(out, service);
683 for (int i = 0; i < service->method_count(); i++) {
684 GenerateStaticMethodField(out, service->method(i));
686 GenerateServiceDescriptorProperty(out, service);
688 if (generate_server) {
689 GenerateServerClass(out, service);
691 if (generate_client) {
692 GenerateClientStub(out, service);
694 if (generate_server) {
695 GenerateBindServiceMethod(out, service);
696 GenerateBindServiceWithBinderMethod(out, service);
703 } // anonymous namespace
705 grpc::string GetServices(const FileDescriptor* file, bool generate_client,
706 bool generate_server, bool internal_access) {
709 // Scope the output stream so it closes and finalizes output to the string.
711 StringOutputStream output_stream(&output);
712 Printer out(&output_stream, '$');
714 // Don't write out any output if there no services, to avoid empty service
715 // files being generated for proto files that don't declare any.
716 if (file->service_count() == 0) {
720 // Write out a file header.
721 out.Print("// <auto-generated>\n");
723 "// Generated by the protocol buffer compiler. DO NOT EDIT!\n");
724 out.Print("// source: $filename$\n", "filename", file->name());
725 out.Print("// </auto-generated>\n");
727 // use C++ style as there are no file-level XML comments in .NET
728 grpc::string leading_comments = GetCsharpComments(file, true);
729 if (!leading_comments.empty()) {
730 out.Print("// Original file comments:\n");
731 out.PrintRaw(leading_comments.c_str());
734 out.Print("#pragma warning disable 0414, 1591\n");
736 out.Print("#region Designer generated code\n");
738 out.Print("using grpc = global::Grpc.Core;\n");
741 grpc::string file_namespace = GetFileNamespace(file);
742 if (file_namespace != "") {
743 out.Print("namespace $namespace$ {\n", "namespace", file_namespace);
746 for (int i = 0; i < file->service_count(); i++) {
747 GenerateService(&out, file->service(i), generate_client, generate_server,
750 if (file_namespace != "") {
754 out.Print("#endregion\n");
759 } // namespace grpc_csharp_generator