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