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