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 // 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, "/");
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