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