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