1 /*
2  *
3  * Copyright 2015 gRPC authors.
4  *
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
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  */
18 
19 #include "src/compiler/csharp_generator.h"
20 
21 #include <cctype>
22 #include <map>
23 #include <sstream>
24 #include <vector>
25 
26 #include "src/compiler/config.h"
27 #include "src/compiler/csharp_generator_helpers.h"
28 
29 using grpc::protobuf::Descriptor;
30 using grpc::protobuf::FileDescriptor;
31 using grpc::protobuf::MethodDescriptor;
32 using grpc::protobuf::ServiceDescriptor;
33 using grpc::protobuf::io::Printer;
34 using grpc::protobuf::io::StringOutputStream;
35 using grpc_generator::StringReplace;
36 using std::vector;
37 
38 namespace grpc_csharp_generator {
39 namespace {
40 
41 // This function is a massaged version of
42 // https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc
43 // Currently, we cannot easily reuse the functionality as
44 // google/protobuf/compiler/csharp/csharp_doc_comment.h is not a public header.
45 // TODO(jtattermusch): reuse the functionality from google/protobuf.
GenerateDocCommentBodyImpl(grpc::protobuf::io::Printer * printer,grpc::protobuf::SourceLocation location)46 bool GenerateDocCommentBodyImpl(grpc::protobuf::io::Printer* printer,
47                                 grpc::protobuf::SourceLocation location) {
48   std::string comments = location.leading_comments.empty()
49                              ? location.trailing_comments
50                              : location.leading_comments;
51   if (comments.empty()) {
52     return false;
53   }
54   // XML escaping... no need for apostrophes etc as the whole text is going to
55   // be a child
56   // node of a summary element, not part of an attribute.
57   comments = grpc_generator::StringReplace(comments, "&", "&amp;", true);
58   comments = grpc_generator::StringReplace(comments, "<", "&lt;", true);
59 
60   std::vector<std::string> lines;
61   grpc_generator::Split(comments, '\n', &lines);
62   // TODO: We really should work out which part to put in the summary and which
63   // to put in the remarks...
64   // but that needs to be part of a bigger effort to understand the markdown
65   // better anyway.
66   printer->Print("/// <summary>\n");
67   bool last_was_empty = false;
68   // We squash multiple blank lines down to one, and remove any trailing blank
69   // lines. We need
70   // to preserve the blank lines themselves, as this is relevant in the
71   // markdown.
72   // Note that we can't remove leading or trailing whitespace as *that's*
73   // relevant in markdown too.
74   // (We don't skip "just whitespace" lines, either.)
75   for (std::vector<std::string>::iterator it = lines.begin(); it != lines.end();
76        ++it) {
77     std::string line = *it;
78     if (line.empty()) {
79       last_was_empty = true;
80     } else {
81       if (last_was_empty) {
82         printer->Print("///\n");
83       }
84       last_was_empty = false;
85       printer->Print("///$line$\n", "line", *it);
86     }
87   }
88   printer->Print("/// </summary>\n");
89   return true;
90 }
91 
GenerateGeneratedCodeAttribute(grpc::protobuf::io::Printer * printer)92 void GenerateGeneratedCodeAttribute(grpc::protobuf::io::Printer* printer) {
93   // Mark the code as generated using the [GeneratedCode] attribute.
94   // We don't provide plugin version info in attribute the because:
95   // * the version information is not readily available from the plugin's code.
96   // * it would cause a lot of churn in the pre-generated code
97   //   in this repository every time the version is updated.
98   printer->Print(
99       "[global::System.CodeDom.Compiler.GeneratedCode(\"grpc_csharp_plugin\", "
100       "null)]\n");
101 }
102 
103 template <typename DescriptorType>
GenerateDocCommentBody(grpc::protobuf::io::Printer * printer,const DescriptorType * descriptor)104 bool GenerateDocCommentBody(grpc::protobuf::io::Printer* printer,
105                             const DescriptorType* descriptor) {
106   grpc::protobuf::SourceLocation location;
107   if (!descriptor->GetSourceLocation(&location)) {
108     return false;
109   }
110   return GenerateDocCommentBodyImpl(printer, location);
111 }
112 
GenerateDocCommentServerMethod(grpc::protobuf::io::Printer * printer,const MethodDescriptor * method)113 void GenerateDocCommentServerMethod(grpc::protobuf::io::Printer* printer,
114                                     const MethodDescriptor* method) {
115   if (GenerateDocCommentBody(printer, method)) {
116     if (method->client_streaming()) {
117       printer->Print(
118           "/// <param name=\"requestStream\">Used for reading requests from "
119           "the client.</param>\n");
120     } else {
121       printer->Print(
122           "/// <param name=\"request\">The request received from the "
123           "client.</param>\n");
124     }
125     if (method->server_streaming()) {
126       printer->Print(
127           "/// <param name=\"responseStream\">Used for sending responses back "
128           "to the client.</param>\n");
129     }
130     printer->Print(
131         "/// <param name=\"context\">The context of the server-side call "
132         "handler being invoked.</param>\n");
133     if (method->server_streaming()) {
134       printer->Print(
135           "/// <returns>A task indicating completion of the "
136           "handler.</returns>\n");
137     } else {
138       printer->Print(
139           "/// <returns>The response to send back to the client (wrapped by a "
140           "task).</returns>\n");
141     }
142   }
143 }
144 
GenerateDocCommentClientMethod(grpc::protobuf::io::Printer * printer,const MethodDescriptor * method,bool is_sync,bool use_call_options)145 void GenerateDocCommentClientMethod(grpc::protobuf::io::Printer* printer,
146                                     const MethodDescriptor* method,
147                                     bool is_sync, bool use_call_options) {
148   if (GenerateDocCommentBody(printer, method)) {
149     if (!method->client_streaming()) {
150       printer->Print(
151           "/// <param name=\"request\">The request to send to the "
152           "server.</param>\n");
153     }
154     if (!use_call_options) {
155       printer->Print(
156           "/// <param name=\"headers\">The initial metadata to send with the "
157           "call. This parameter is optional.</param>\n");
158       printer->Print(
159           "/// <param name=\"deadline\">An optional deadline for the call. The "
160           "call will be cancelled if deadline is hit.</param>\n");
161       printer->Print(
162           "/// <param name=\"cancellationToken\">An optional token for "
163           "canceling the call.</param>\n");
164     } else {
165       printer->Print(
166           "/// <param name=\"options\">The options for the call.</param>\n");
167     }
168     if (is_sync) {
169       printer->Print(
170           "/// <returns>The response received from the server.</returns>\n");
171     } else {
172       printer->Print("/// <returns>The call object.</returns>\n");
173     }
174   }
175 }
176 
GetServiceClassName(const ServiceDescriptor * service)177 std::string GetServiceClassName(const ServiceDescriptor* service) {
178   return service->name();
179 }
180 
GetClientClassName(const ServiceDescriptor * service)181 std::string GetClientClassName(const ServiceDescriptor* service) {
182   return service->name() + "Client";
183 }
184 
GetServerClassName(const ServiceDescriptor * service)185 std::string GetServerClassName(const ServiceDescriptor* service) {
186   return service->name() + "Base";
187 }
188 
GetCSharpMethodType(const MethodDescriptor * method)189 std::string GetCSharpMethodType(const MethodDescriptor* method) {
190   if (method->client_streaming()) {
191     if (method->server_streaming()) {
192       return "grpc::MethodType.DuplexStreaming";
193     } else {
194       return "grpc::MethodType.ClientStreaming";
195     }
196   } else {
197     if (method->server_streaming()) {
198       return "grpc::MethodType.ServerStreaming";
199     } else {
200       return "grpc::MethodType.Unary";
201     }
202   }
203 }
204 
GetCSharpServerMethodType(const MethodDescriptor * method)205 std::string GetCSharpServerMethodType(const MethodDescriptor* method) {
206   if (method->client_streaming()) {
207     if (method->server_streaming()) {
208       return "grpc::DuplexStreamingServerMethod";
209     } else {
210       return "grpc::ClientStreamingServerMethod";
211     }
212   } else {
213     if (method->server_streaming()) {
214       return "grpc::ServerStreamingServerMethod";
215     } else {
216       return "grpc::UnaryServerMethod";
217     }
218   }
219 }
220 
GetServiceNameFieldName()221 std::string GetServiceNameFieldName() { return "__ServiceName"; }
222 
GetMarshallerFieldName(const Descriptor * message)223 std::string GetMarshallerFieldName(const Descriptor* message) {
224   return "__Marshaller_" +
225          grpc_generator::StringReplace(message->full_name(), ".", "_", true);
226 }
227 
GetMethodFieldName(const MethodDescriptor * method)228 std::string GetMethodFieldName(const MethodDescriptor* method) {
229   return "__Method_" + method->name();
230 }
231 
GetMethodRequestParamMaybe(const MethodDescriptor * method,bool invocation_param=false)232 std::string GetMethodRequestParamMaybe(const MethodDescriptor* method,
233                                        bool invocation_param = false) {
234   if (method->client_streaming()) {
235     return "";
236   }
237   if (invocation_param) {
238     return "request, ";
239   }
240   return GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()) + " request, ";
241 }
242 
GetAccessLevel(bool internal_access)243 std::string GetAccessLevel(bool internal_access) {
244   return internal_access ? "internal" : "public";
245 }
246 
GetMethodReturnTypeClient(const MethodDescriptor * method)247 std::string GetMethodReturnTypeClient(const MethodDescriptor* method) {
248   if (method->client_streaming()) {
249     if (method->server_streaming()) {
250       return "grpc::AsyncDuplexStreamingCall<" +
251              GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()) + ", " +
252              GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()) + ">";
253     } else {
254       return "grpc::AsyncClientStreamingCall<" +
255              GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()) + ", " +
256              GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()) + ">";
257     }
258   } else {
259     if (method->server_streaming()) {
260       return "grpc::AsyncServerStreamingCall<" +
261              GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()) + ">";
262     } else {
263       return "grpc::AsyncUnaryCall<" +
264              GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()) + ">";
265     }
266   }
267 }
268 
GetMethodRequestParamServer(const MethodDescriptor * method)269 std::string GetMethodRequestParamServer(const MethodDescriptor* method) {
270   if (method->client_streaming()) {
271     return "grpc::IAsyncStreamReader<" +
272            GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()) +
273            "> requestStream";
274   }
275   return GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()) + " request";
276 }
277 
GetMethodReturnTypeServer(const MethodDescriptor * method)278 std::string GetMethodReturnTypeServer(const MethodDescriptor* method) {
279   if (method->server_streaming()) {
280     return "global::System.Threading.Tasks.Task";
281   }
282   return "global::System.Threading.Tasks.Task<" +
283          GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()) + ">";
284 }
285 
GetMethodResponseStreamMaybe(const MethodDescriptor * method)286 std::string GetMethodResponseStreamMaybe(const MethodDescriptor* method) {
287   if (method->server_streaming()) {
288     return ", grpc::IServerStreamWriter<" +
289            GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()) +
290            "> responseStream";
291   }
292   return "";
293 }
294 
295 // Gets vector of all messages used as input or output types.
GetUsedMessages(const ServiceDescriptor * service)296 std::vector<const Descriptor*> GetUsedMessages(
297     const ServiceDescriptor* service) {
298   std::set<const Descriptor*> descriptor_set;
299   std::vector<const Descriptor*>
300       result;  // vector is to maintain stable ordering
301   for (int i = 0; i < service->method_count(); i++) {
302     const MethodDescriptor* method = service->method(i);
303     if (descriptor_set.find(method->input_type()) == descriptor_set.end()) {
304       descriptor_set.insert(method->input_type());
305       result.push_back(method->input_type());
306     }
307     if (descriptor_set.find(method->output_type()) == descriptor_set.end()) {
308       descriptor_set.insert(method->output_type());
309       result.push_back(method->output_type());
310     }
311   }
312   return result;
313 }
314 
GenerateMarshallerFields(Printer * out,const ServiceDescriptor * service)315 void GenerateMarshallerFields(Printer* out, const ServiceDescriptor* service) {
316   std::vector<const Descriptor*> used_messages = GetUsedMessages(service);
317   if (used_messages.size() != 0) {
318     // Generate static helper methods for serialization/deserialization
319     GenerateGeneratedCodeAttribute(out);
320     out->Print(
321         "static void __Helper_SerializeMessage("
322         "global::Google.Protobuf.IMessage message, "
323         "grpc::SerializationContext context)\n"
324         "{\n");
325     out->Indent();
326     out->Print(
327         "#if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION\n"
328         "if (message is global::Google.Protobuf.IBufferMessage)\n"
329         "{\n");
330     out->Indent();
331     out->Print(
332         "context.SetPayloadLength(message.CalculateSize());\n"
333         "global::Google.Protobuf.MessageExtensions.WriteTo(message, "
334         "context.GetBufferWriter());\n"
335         "context.Complete();\n"
336         "return;\n");
337     out->Outdent();
338     out->Print(
339         "}\n"
340         "#endif\n");
341     out->Print(
342         "context.Complete("
343         "global::Google.Protobuf.MessageExtensions.ToByteArray(message));\n");
344     out->Outdent();
345     out->Print("}\n\n");
346 
347     GenerateGeneratedCodeAttribute(out);
348     out->Print(
349         "static class __Helper_MessageCache<T>\n"
350         "{\n");
351     out->Indent();
352     out->Print(
353         "public static readonly bool IsBufferMessage = "
354         "global::System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof("
355         "global::Google.Protobuf.IBufferMessage)).IsAssignableFrom(typeof(T));"
356         "\n");
357     out->Outdent();
358     out->Print("}\n\n");
359 
360     GenerateGeneratedCodeAttribute(out);
361     out->Print(
362         "static T __Helper_DeserializeMessage<T>("
363         "grpc::DeserializationContext context, "
364         "global::Google.Protobuf.MessageParser<T> parser) "
365         "where T : global::Google.Protobuf.IMessage<T>\n"
366         "{\n");
367     out->Indent();
368     out->Print(
369         "#if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION\n"
370         "if (__Helper_MessageCache<T>.IsBufferMessage)\n"
371         "{\n");
372     out->Indent();
373     out->Print(
374         "return parser.ParseFrom(context.PayloadAsReadOnlySequence());\n");
375     out->Outdent();
376     out->Print(
377         "}\n"
378         "#endif\n");
379     out->Print("return parser.ParseFrom(context.PayloadAsNewBuffer());\n");
380     out->Outdent();
381     out->Print("}\n\n");
382   }
383 
384   for (size_t i = 0; i < used_messages.size(); i++) {
385     const Descriptor* message = used_messages[i];
386     GenerateGeneratedCodeAttribute(out);
387     out->Print(
388         "static readonly grpc::Marshaller<$type$> $fieldname$ = "
389         "grpc::Marshallers.Create(__Helper_SerializeMessage, "
390         "context => __Helper_DeserializeMessage(context, $type$.Parser));\n",
391         "fieldname", GetMarshallerFieldName(message), "type",
392         GRPC_CUSTOM_CSHARP_GETCLASSNAME(message));
393   }
394   out->Print("\n");
395 }
396 
GenerateStaticMethodField(Printer * out,const MethodDescriptor * method)397 void GenerateStaticMethodField(Printer* out, const MethodDescriptor* method) {
398   GenerateGeneratedCodeAttribute(out);
399   out->Print(
400       "static readonly grpc::Method<$request$, $response$> $fieldname$ = new "
401       "grpc::Method<$request$, $response$>(\n",
402       "fieldname", GetMethodFieldName(method), "request",
403       GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()), "response",
404       GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()));
405   out->Indent();
406   out->Indent();
407   out->Print("$methodtype$,\n", "methodtype", GetCSharpMethodType(method));
408   out->Print("$servicenamefield$,\n", "servicenamefield",
409              GetServiceNameFieldName());
410   out->Print("\"$methodname$\",\n", "methodname", method->name());
411   out->Print("$requestmarshaller$,\n", "requestmarshaller",
412              GetMarshallerFieldName(method->input_type()));
413   out->Print("$responsemarshaller$);\n", "responsemarshaller",
414              GetMarshallerFieldName(method->output_type()));
415   out->Print("\n");
416   out->Outdent();
417   out->Outdent();
418 }
419 
GenerateServiceDescriptorProperty(Printer * out,const ServiceDescriptor * service)420 void GenerateServiceDescriptorProperty(Printer* out,
421                                        const ServiceDescriptor* service) {
422   std::ostringstream index;
423   index << service->index();
424   out->Print("/// <summary>Service descriptor</summary>\n");
425   out->Print(
426       "public static global::Google.Protobuf.Reflection.ServiceDescriptor "
427       "Descriptor\n");
428   out->Print("{\n");
429   out->Print("  get { return $umbrella$.Descriptor.Services[$index$]; }\n",
430              "umbrella",
431              GRPC_CUSTOM_CSHARP_GETREFLECTIONCLASSNAME(service->file()),
432              "index", index.str());
433   out->Print("}\n");
434   out->Print("\n");
435 }
436 
GenerateServerClass(Printer * out,const ServiceDescriptor * service)437 void GenerateServerClass(Printer* out, const ServiceDescriptor* service) {
438   out->Print(
439       "/// <summary>Base class for server-side implementations of "
440       "$servicename$</summary>\n",
441       "servicename", GetServiceClassName(service));
442   out->Print(
443       "[grpc::BindServiceMethod(typeof($classname$), "
444       "\"BindService\")]\n",
445       "classname", GetServiceClassName(service));
446   out->Print("public abstract partial class $name$\n", "name",
447              GetServerClassName(service));
448   out->Print("{\n");
449   out->Indent();
450   for (int i = 0; i < service->method_count(); i++) {
451     const MethodDescriptor* method = service->method(i);
452     GenerateDocCommentServerMethod(out, method);
453     GenerateGeneratedCodeAttribute(out);
454     out->Print(
455         "public virtual $returntype$ "
456         "$methodname$($request$$response_stream_maybe$, "
457         "grpc::ServerCallContext context)\n",
458         "methodname", method->name(), "returntype",
459         GetMethodReturnTypeServer(method), "request",
460         GetMethodRequestParamServer(method), "response_stream_maybe",
461         GetMethodResponseStreamMaybe(method));
462     out->Print("{\n");
463     out->Indent();
464     out->Print(
465         "throw new grpc::RpcException("
466         "new grpc::Status(grpc::StatusCode.Unimplemented, \"\"));\n");
467     out->Outdent();
468     out->Print("}\n\n");
469   }
470   out->Outdent();
471   out->Print("}\n");
472   out->Print("\n");
473 }
474 
GenerateClientStub(Printer * out,const ServiceDescriptor * service)475 void GenerateClientStub(Printer* out, const ServiceDescriptor* service) {
476   out->Print("/// <summary>Client for $servicename$</summary>\n", "servicename",
477              GetServiceClassName(service));
478   out->Print("public partial class $name$ : grpc::ClientBase<$name$>\n", "name",
479              GetClientClassName(service));
480   out->Print("{\n");
481   out->Indent();
482 
483   // constructors
484   out->Print(
485       "/// <summary>Creates a new client for $servicename$</summary>\n"
486       "/// <param name=\"channel\">The channel to use to make remote "
487       "calls.</param>\n",
488       "servicename", GetServiceClassName(service));
489   GenerateGeneratedCodeAttribute(out);
490   out->Print("public $name$(grpc::ChannelBase channel) : base(channel)\n",
491              "name", GetClientClassName(service));
492   out->Print("{\n");
493   out->Print("}\n");
494   out->Print(
495       "/// <summary>Creates a new client for $servicename$ that uses a custom "
496       "<c>CallInvoker</c>.</summary>\n"
497       "/// <param name=\"callInvoker\">The callInvoker to use to make remote "
498       "calls.</param>\n",
499       "servicename", GetServiceClassName(service));
500   GenerateGeneratedCodeAttribute(out);
501   out->Print(
502       "public $name$(grpc::CallInvoker callInvoker) : base(callInvoker)\n",
503       "name", GetClientClassName(service));
504   out->Print("{\n");
505   out->Print("}\n");
506   out->Print(
507       "/// <summary>Protected parameterless constructor to allow creation"
508       " of test doubles.</summary>\n");
509   GenerateGeneratedCodeAttribute(out);
510   out->Print("protected $name$() : base()\n", "name",
511              GetClientClassName(service));
512   out->Print("{\n");
513   out->Print("}\n");
514   out->Print(
515       "/// <summary>Protected constructor to allow creation of configured "
516       "clients.</summary>\n"
517       "/// <param name=\"configuration\">The client configuration.</param>\n");
518   GenerateGeneratedCodeAttribute(out);
519   out->Print(
520       "protected $name$(ClientBaseConfiguration configuration)"
521       " : base(configuration)\n",
522       "name", GetClientClassName(service));
523   out->Print("{\n");
524   out->Print("}\n\n");
525 
526   for (int i = 0; i < service->method_count(); i++) {
527     const MethodDescriptor* method = service->method(i);
528     if (!method->client_streaming() && !method->server_streaming()) {
529       // unary calls have an extra synchronous stub method
530       GenerateDocCommentClientMethod(out, method, true, false);
531       GenerateGeneratedCodeAttribute(out);
532       out->Print(
533           "public virtual $response$ $methodname$($request$ request, "
534           "grpc::Metadata "
535           "headers = null, global::System.DateTime? deadline = null, "
536           "global::System.Threading.CancellationToken "
537           "cancellationToken = "
538           "default(global::System.Threading.CancellationToken))\n",
539           "methodname", method->name(), "request",
540           GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()), "response",
541           GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()));
542       out->Print("{\n");
543       out->Indent();
544       out->Print(
545           "return $methodname$(request, new grpc::CallOptions(headers, "
546           "deadline, "
547           "cancellationToken));\n",
548           "methodname", method->name());
549       out->Outdent();
550       out->Print("}\n");
551 
552       // overload taking CallOptions as a param
553       GenerateDocCommentClientMethod(out, method, true, true);
554       GenerateGeneratedCodeAttribute(out);
555       out->Print(
556           "public virtual $response$ $methodname$($request$ request, "
557           "grpc::CallOptions options)\n",
558           "methodname", method->name(), "request",
559           GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()), "response",
560           GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()));
561       out->Print("{\n");
562       out->Indent();
563       out->Print(
564           "return CallInvoker.BlockingUnaryCall($methodfield$, null, options, "
565           "request);\n",
566           "methodfield", GetMethodFieldName(method));
567       out->Outdent();
568       out->Print("}\n");
569     }
570 
571     std::string method_name = method->name();
572     if (!method->client_streaming() && !method->server_streaming()) {
573       method_name += "Async";  // prevent name clash with synchronous method.
574     }
575     GenerateDocCommentClientMethod(out, method, false, false);
576     GenerateGeneratedCodeAttribute(out);
577     out->Print(
578         "public virtual $returntype$ "
579         "$methodname$($request_maybe$grpc::Metadata "
580         "headers = null, global::System.DateTime? deadline = null, "
581         "global::System.Threading.CancellationToken "
582         "cancellationToken = "
583         "default(global::System.Threading.CancellationToken))\n",
584         "methodname", method_name, "request_maybe",
585         GetMethodRequestParamMaybe(method), "returntype",
586         GetMethodReturnTypeClient(method));
587     out->Print("{\n");
588     out->Indent();
589 
590     out->Print(
591         "return $methodname$($request_maybe$new grpc::CallOptions(headers, "
592         "deadline, "
593         "cancellationToken));\n",
594         "methodname", method_name, "request_maybe",
595         GetMethodRequestParamMaybe(method, true));
596     out->Outdent();
597     out->Print("}\n");
598 
599     // overload taking CallOptions as a param
600     GenerateDocCommentClientMethod(out, method, false, true);
601     GenerateGeneratedCodeAttribute(out);
602     out->Print(
603         "public virtual $returntype$ "
604         "$methodname$($request_maybe$grpc::CallOptions "
605         "options)\n",
606         "methodname", method_name, "request_maybe",
607         GetMethodRequestParamMaybe(method), "returntype",
608         GetMethodReturnTypeClient(method));
609     out->Print("{\n");
610     out->Indent();
611     if (!method->client_streaming() && !method->server_streaming()) {
612       // Non-Streaming
613       out->Print(
614           "return CallInvoker.AsyncUnaryCall($methodfield$, null, options, "
615           "request);\n",
616           "methodfield", GetMethodFieldName(method));
617     } else if (method->client_streaming() && !method->server_streaming()) {
618       // Client Streaming Only
619       out->Print(
620           "return CallInvoker.AsyncClientStreamingCall($methodfield$, null, "
621           "options);\n",
622           "methodfield", GetMethodFieldName(method));
623     } else if (!method->client_streaming() && method->server_streaming()) {
624       // Server Streaming Only
625       out->Print(
626           "return CallInvoker.AsyncServerStreamingCall($methodfield$, null, "
627           "options, request);\n",
628           "methodfield", GetMethodFieldName(method));
629     } else {
630       // Bi-Directional Streaming
631       out->Print(
632           "return CallInvoker.AsyncDuplexStreamingCall($methodfield$, null, "
633           "options);\n",
634           "methodfield", GetMethodFieldName(method));
635     }
636     out->Outdent();
637     out->Print("}\n");
638   }
639 
640   // override NewInstance method
641   out->Print(
642       "/// <summary>Creates a new instance of client from given "
643       "<c>ClientBaseConfiguration</c>.</summary>\n");
644   GenerateGeneratedCodeAttribute(out);
645   out->Print(
646       "protected override $name$ NewInstance(ClientBaseConfiguration "
647       "configuration)\n",
648       "name", GetClientClassName(service));
649   out->Print("{\n");
650   out->Indent();
651   out->Print("return new $name$(configuration);\n", "name",
652              GetClientClassName(service));
653   out->Outdent();
654   out->Print("}\n");
655 
656   out->Outdent();
657   out->Print("}\n");
658   out->Print("\n");
659 }
660 
GenerateBindServiceMethod(Printer * out,const ServiceDescriptor * service)661 void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor* service) {
662   out->Print(
663       "/// <summary>Creates service definition that can be registered with a "
664       "server</summary>\n");
665   out->Print(
666       "/// <param name=\"serviceImpl\">An object implementing the server-side"
667       " handling logic.</param>\n");
668   GenerateGeneratedCodeAttribute(out);
669   out->Print(
670       "public static grpc::ServerServiceDefinition BindService($implclass$ "
671       "serviceImpl)\n",
672       "implclass", GetServerClassName(service));
673   out->Print("{\n");
674   out->Indent();
675 
676   out->Print("return grpc::ServerServiceDefinition.CreateBuilder()");
677   out->Indent();
678   out->Indent();
679   for (int i = 0; i < service->method_count(); i++) {
680     const MethodDescriptor* method = service->method(i);
681     out->Print("\n.AddMethod($methodfield$, serviceImpl.$methodname$)",
682                "methodfield", GetMethodFieldName(method), "methodname",
683                method->name());
684   }
685   out->Print(".Build();\n");
686   out->Outdent();
687   out->Outdent();
688 
689   out->Outdent();
690   out->Print("}\n");
691   out->Print("\n");
692 }
693 
GenerateBindServiceWithBinderMethod(Printer * out,const ServiceDescriptor * service)694 void GenerateBindServiceWithBinderMethod(Printer* out,
695                                          const ServiceDescriptor* service) {
696   out->Print(
697       "/// <summary>Register service method with a service "
698       "binder with or without implementation. Useful when customizing the  "
699       "service binding logic.\n"
700       "/// Note: this method is part of an experimental API that can change or "
701       "be "
702       "removed without any prior notice.</summary>\n");
703   out->Print(
704       "/// <param name=\"serviceBinder\">Service methods will be bound by "
705       "calling <c>AddMethod</c> on this object."
706       "</param>\n");
707   out->Print(
708       "/// <param name=\"serviceImpl\">An object implementing the server-side"
709       " handling logic.</param>\n");
710   GenerateGeneratedCodeAttribute(out);
711   out->Print(
712       "public static void BindService(grpc::ServiceBinderBase serviceBinder, "
713       "$implclass$ "
714       "serviceImpl)\n",
715       "implclass", GetServerClassName(service));
716   out->Print("{\n");
717   out->Indent();
718 
719   for (int i = 0; i < service->method_count(); i++) {
720     const MethodDescriptor* method = service->method(i);
721     out->Print(
722         "serviceBinder.AddMethod($methodfield$, serviceImpl == null ? null : "
723         "new $servermethodtype$<$inputtype$, $outputtype$>("
724         "serviceImpl.$methodname$));\n",
725         "methodfield", GetMethodFieldName(method), "servermethodtype",
726         GetCSharpServerMethodType(method), "inputtype",
727         GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->input_type()), "outputtype",
728         GRPC_CUSTOM_CSHARP_GETCLASSNAME(method->output_type()), "methodname",
729         method->name());
730   }
731 
732   out->Outdent();
733   out->Print("}\n");
734   out->Print("\n");
735 }
736 
GenerateService(Printer * out,const ServiceDescriptor * service,bool generate_client,bool generate_server,bool internal_access)737 void GenerateService(Printer* out, const ServiceDescriptor* service,
738                      bool generate_client, bool generate_server,
739                      bool internal_access) {
740   GenerateDocCommentBody(out, service);
741 
742   out->Print("$access_level$ static partial class $classname$\n",
743              "access_level", GetAccessLevel(internal_access), "classname",
744              GetServiceClassName(service));
745   out->Print("{\n");
746   out->Indent();
747   out->Print("static readonly string $servicenamefield$ = \"$servicename$\";\n",
748              "servicenamefield", GetServiceNameFieldName(), "servicename",
749              service->full_name());
750   out->Print("\n");
751 
752   GenerateMarshallerFields(out, service);
753   for (int i = 0; i < service->method_count(); i++) {
754     GenerateStaticMethodField(out, service->method(i));
755   }
756   GenerateServiceDescriptorProperty(out, service);
757 
758   if (generate_server) {
759     GenerateServerClass(out, service);
760   }
761   if (generate_client) {
762     GenerateClientStub(out, service);
763   }
764   if (generate_server) {
765     GenerateBindServiceMethod(out, service);
766     GenerateBindServiceWithBinderMethod(out, service);
767   }
768 
769   out->Outdent();
770   out->Print("}\n");
771 }
772 
773 }  // anonymous namespace
774 
GetServices(const FileDescriptor * file,bool generate_client,bool generate_server,bool internal_access)775 std::string GetServices(const FileDescriptor* file, bool generate_client,
776                         bool generate_server, bool internal_access) {
777   std::string output;
778   {
779     // Scope the output stream so it closes and finalizes output to the string.
780 
781     StringOutputStream output_stream(&output);
782     Printer out(&output_stream, '$');
783 
784     // Don't write out any output if there no services, to avoid empty service
785     // files being generated for proto files that don't declare any.
786     if (file->service_count() == 0) {
787       return output;
788     }
789 
790     // Write out a file header.
791     out.Print("// <auto-generated>\n");
792     out.Print(
793         "//     Generated by the protocol buffer compiler.  DO NOT EDIT!\n");
794     out.Print("//     source: $filename$\n", "filename", file->name());
795     out.Print("// </auto-generated>\n");
796 
797     // use C++ style as there are no file-level XML comments in .NET
798     std::string leading_comments = GetCsharpComments(file, true);
799     if (!leading_comments.empty()) {
800       out.Print("// Original file comments:\n");
801       out.PrintRaw(leading_comments.c_str());
802     }
803 
804     out.Print("#pragma warning disable 0414, 1591\n");
805 
806     out.Print("#region Designer generated code\n");
807     out.Print("\n");
808     out.Print("using grpc = global::Grpc.Core;\n");
809     out.Print("\n");
810 
811     std::string file_namespace = GRPC_CUSTOM_CSHARP_GETFILENAMESPACE(file);
812     if (file_namespace != "") {
813       out.Print("namespace $namespace$ {\n", "namespace", file_namespace);
814       out.Indent();
815     }
816     for (int i = 0; i < file->service_count(); i++) {
817       GenerateService(&out, file->service(i), generate_client, generate_server,
818                       internal_access);
819     }
820     if (file_namespace != "") {
821       out.Outdent();
822       out.Print("}\n");
823     }
824     out.Print("#endregion\n");
825   }
826   return output;
827 }
828 
829 }  // namespace grpc_csharp_generator
830