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