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