3 * Copyright 2015 gRPC authors.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
24 #include "src/compiler/config.h"
25 #include "src/compiler/csharp_generator.h"
26 #include "src/compiler/csharp_generator_helpers.h"
28 using google::protobuf::compiler::csharp::GetClassName;
29 using google::protobuf::compiler::csharp::GetFileNamespace;
30 using google::protobuf::compiler::csharp::GetReflectionClassName;
31 using grpc::protobuf::Descriptor;
32 using grpc::protobuf::FileDescriptor;
33 using grpc::protobuf::MethodDescriptor;
34 using grpc::protobuf::ServiceDescriptor;
35 using grpc::protobuf::io::Printer;
36 using grpc::protobuf::io::StringOutputStream;
37 using grpc_generator::GetMethodType;
38 using grpc_generator::METHODTYPE_BIDI_STREAMING;
39 using grpc_generator::METHODTYPE_CLIENT_STREAMING;
40 using grpc_generator::METHODTYPE_NO_STREAMING;
41 using grpc_generator::METHODTYPE_SERVER_STREAMING;
42 using grpc_generator::MethodType;
43 using grpc_generator::StringReplace;
47 namespace grpc_csharp_generator {
50 // This function is a massaged version of
51 // https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc
52 // Currently, we cannot easily reuse the functionality as
53 // google/protobuf/compiler/csharp/csharp_doc_comment.h is not a public header.
54 // TODO(jtattermusch): reuse the functionality from google/protobuf.
55 bool GenerateDocCommentBodyImpl(grpc::protobuf::io::Printer* printer,
56 grpc::protobuf::SourceLocation location) {
57 grpc::string comments = location.leading_comments.empty()
58 ? location.trailing_comments
59 : location.leading_comments;
60 if (comments.empty()) {
63 // XML escaping... no need for apostrophes etc as the whole text is going to
65 // node of a summary element, not part of an attribute.
66 comments = grpc_generator::StringReplace(comments, "&", "&", true);
67 comments = grpc_generator::StringReplace(comments, "<", "<", true);
69 std::vector<grpc::string> lines;
70 grpc_generator::Split(comments, '\n', &lines);
71 // TODO: We really should work out which part to put in the summary and which
72 // to put in the remarks...
73 // but that needs to be part of a bigger effort to understand the markdown
75 printer->Print("/// <summary>\n");
76 bool last_was_empty = false;
77 // We squash multiple blank lines down to one, and remove any trailing blank
79 // to preserve the blank lines themselves, as this is relevant in the
81 // Note that we can't remove leading or trailing whitespace as *that's*
82 // relevant in markdown too.
83 // (We don't skip "just whitespace" lines, either.)
84 for (std::vector<grpc::string>::iterator it = lines.begin();
85 it != lines.end(); ++it) {
86 grpc::string line = *it;
88 last_was_empty = true;
91 printer->Print("///\n");
93 last_was_empty = false;
94 printer->Print("///$line$\n", "line", *it);
97 printer->Print("/// </summary>\n");
101 template <typename DescriptorType>
102 bool GenerateDocCommentBody(grpc::protobuf::io::Printer* printer,
103 const DescriptorType* descriptor) {
104 grpc::protobuf::SourceLocation location;
105 if (!descriptor->GetSourceLocation(&location)) {
108 return GenerateDocCommentBodyImpl(printer, location);
111 void GenerateDocCommentServerMethod(grpc::protobuf::io::Printer* printer,
112 const MethodDescriptor* method) {
113 if (GenerateDocCommentBody(printer, method)) {
114 if (method->client_streaming()) {
116 "/// <param name=\"requestStream\">Used for reading requests from "
117 "the client.</param>\n");
120 "/// <param name=\"request\">The request received from the "
121 "client.</param>\n");
123 if (method->server_streaming()) {
125 "/// <param name=\"responseStream\">Used for sending responses back "
126 "to the client.</param>\n");
129 "/// <param name=\"context\">The context of the server-side call "
130 "handler being invoked.</param>\n");
131 if (method->server_streaming()) {
133 "/// <returns>A task indicating completion of the "
134 "handler.</returns>\n");
137 "/// <returns>The response to send back to the client (wrapped by a "
138 "task).</returns>\n");
143 void GenerateDocCommentClientMethod(grpc::protobuf::io::Printer* printer,
144 const MethodDescriptor* method,
145 bool is_sync, bool use_call_options) {
146 if (GenerateDocCommentBody(printer, method)) {
147 if (!method->client_streaming()) {
149 "/// <param name=\"request\">The request to send to the "
150 "server.</param>\n");
152 if (!use_call_options) {
154 "/// <param name=\"headers\">The initial metadata to send with the "
155 "call. This parameter is optional.</param>\n");
157 "/// <param name=\"deadline\">An optional deadline for the call. The "
158 "call will be cancelled if deadline is hit.</param>\n");
160 "/// <param name=\"cancellationToken\">An optional token for "
161 "canceling the call.</param>\n");
164 "/// <param name=\"options\">The options for the call.</param>\n");
168 "/// <returns>The response received from the server.</returns>\n");
170 printer->Print("/// <returns>The call object.</returns>\n");
175 std::string GetServiceClassName(const ServiceDescriptor* service) {
176 return service->name();
179 std::string GetClientClassName(const ServiceDescriptor* service) {
180 return service->name() + "Client";
183 std::string GetServerClassName(const ServiceDescriptor* service) {
184 return service->name() + "Base";
187 std::string GetCSharpMethodType(MethodType method_type) {
188 switch (method_type) {
189 case METHODTYPE_NO_STREAMING:
190 return "grpc::MethodType.Unary";
191 case METHODTYPE_CLIENT_STREAMING:
192 return "grpc::MethodType.ClientStreaming";
193 case METHODTYPE_SERVER_STREAMING:
194 return "grpc::MethodType.ServerStreaming";
195 case METHODTYPE_BIDI_STREAMING:
196 return "grpc::MethodType.DuplexStreaming";
198 GOOGLE_LOG(FATAL) << "Can't get here.";
202 std::string GetServiceNameFieldName() { return "__ServiceName"; }
204 std::string GetMarshallerFieldName(const Descriptor* message) {
205 return "__Marshaller_" +
206 grpc_generator::StringReplace(message->full_name(), ".", "_", true);
209 std::string GetMethodFieldName(const MethodDescriptor* method) {
210 return "__Method_" + method->name();
213 std::string GetMethodRequestParamMaybe(const MethodDescriptor* method,
214 bool invocation_param = false) {
215 if (method->client_streaming()) {
218 if (invocation_param) {
221 return GetClassName(method->input_type()) + " request, ";
224 std::string GetAccessLevel(bool internal_access) {
225 return internal_access ? "internal" : "public";
228 std::string GetMethodReturnTypeClient(const MethodDescriptor* method) {
229 switch (GetMethodType(method)) {
230 case METHODTYPE_NO_STREAMING:
231 return "grpc::AsyncUnaryCall<" + GetClassName(method->output_type()) +
233 case METHODTYPE_CLIENT_STREAMING:
234 return "grpc::AsyncClientStreamingCall<" +
235 GetClassName(method->input_type()) + ", " +
236 GetClassName(method->output_type()) + ">";
237 case METHODTYPE_SERVER_STREAMING:
238 return "grpc::AsyncServerStreamingCall<" +
239 GetClassName(method->output_type()) + ">";
240 case METHODTYPE_BIDI_STREAMING:
241 return "grpc::AsyncDuplexStreamingCall<" +
242 GetClassName(method->input_type()) + ", " +
243 GetClassName(method->output_type()) + ">";
245 GOOGLE_LOG(FATAL) << "Can't get here.";
249 std::string GetMethodRequestParamServer(const MethodDescriptor* method) {
250 switch (GetMethodType(method)) {
251 case METHODTYPE_NO_STREAMING:
252 case METHODTYPE_SERVER_STREAMING:
253 return GetClassName(method->input_type()) + " request";
254 case METHODTYPE_CLIENT_STREAMING:
255 case METHODTYPE_BIDI_STREAMING:
256 return "grpc::IAsyncStreamReader<" + GetClassName(method->input_type()) +
259 GOOGLE_LOG(FATAL) << "Can't get here.";
263 std::string GetMethodReturnTypeServer(const MethodDescriptor* method) {
264 switch (GetMethodType(method)) {
265 case METHODTYPE_NO_STREAMING:
266 case METHODTYPE_CLIENT_STREAMING:
267 return "global::System.Threading.Tasks.Task<" +
268 GetClassName(method->output_type()) + ">";
269 case METHODTYPE_SERVER_STREAMING:
270 case METHODTYPE_BIDI_STREAMING:
271 return "global::System.Threading.Tasks.Task";
273 GOOGLE_LOG(FATAL) << "Can't get here.";
277 std::string GetMethodResponseStreamMaybe(const MethodDescriptor* method) {
278 switch (GetMethodType(method)) {
279 case METHODTYPE_NO_STREAMING:
280 case METHODTYPE_CLIENT_STREAMING:
282 case METHODTYPE_SERVER_STREAMING:
283 case METHODTYPE_BIDI_STREAMING:
284 return ", grpc::IServerStreamWriter<" +
285 GetClassName(method->output_type()) + "> responseStream";
287 GOOGLE_LOG(FATAL) << "Can't get here.";
291 // Gets vector of all messages used as input or output types.
292 std::vector<const Descriptor*> GetUsedMessages(
293 const ServiceDescriptor* service) {
294 std::set<const Descriptor*> descriptor_set;
295 std::vector<const Descriptor*>
296 result; // vector is to maintain stable ordering
297 for (int i = 0; i < service->method_count(); i++) {
298 const MethodDescriptor* method = service->method(i);
299 if (descriptor_set.find(method->input_type()) == descriptor_set.end()) {
300 descriptor_set.insert(method->input_type());
301 result.push_back(method->input_type());
303 if (descriptor_set.find(method->output_type()) == descriptor_set.end()) {
304 descriptor_set.insert(method->output_type());
305 result.push_back(method->output_type());
311 void GenerateMarshallerFields(Printer* out, const ServiceDescriptor* service) {
312 std::vector<const Descriptor*> used_messages = GetUsedMessages(service);
313 for (size_t i = 0; i < used_messages.size(); i++) {
314 const Descriptor* message = used_messages[i];
316 "static readonly grpc::Marshaller<$type$> $fieldname$ = "
317 "grpc::Marshallers.Create((arg) => "
318 "global::Google.Protobuf.MessageExtensions.ToByteArray(arg), "
319 "$type$.Parser.ParseFrom);\n",
320 "fieldname", GetMarshallerFieldName(message), "type",
321 GetClassName(message));
326 void GenerateStaticMethodField(Printer* out, const MethodDescriptor* method) {
328 "static readonly grpc::Method<$request$, $response$> $fieldname$ = new "
329 "grpc::Method<$request$, $response$>(\n",
330 "fieldname", GetMethodFieldName(method), "request",
331 GetClassName(method->input_type()), "response",
332 GetClassName(method->output_type()));
335 out->Print("$methodtype$,\n", "methodtype",
336 GetCSharpMethodType(GetMethodType(method)));
337 out->Print("$servicenamefield$,\n", "servicenamefield",
338 GetServiceNameFieldName());
339 out->Print("\"$methodname$\",\n", "methodname", method->name());
340 out->Print("$requestmarshaller$,\n", "requestmarshaller",
341 GetMarshallerFieldName(method->input_type()));
342 out->Print("$responsemarshaller$);\n", "responsemarshaller",
343 GetMarshallerFieldName(method->output_type()));
349 void GenerateServiceDescriptorProperty(Printer* out,
350 const ServiceDescriptor* service) {
351 std::ostringstream index;
352 index << service->index();
353 out->Print("/// <summary>Service descriptor</summary>\n");
355 "public static global::Google.Protobuf.Reflection.ServiceDescriptor "
358 out->Print(" get { return $umbrella$.Descriptor.Services[$index$]; }\n",
359 "umbrella", GetReflectionClassName(service->file()), "index",
365 void GenerateServerClass(Printer* out, const ServiceDescriptor* service) {
367 "/// <summary>Base class for server-side implementations of "
368 "$servicename$</summary>\n",
369 "servicename", GetServiceClassName(service));
370 out->Print("public abstract partial class $name$\n", "name",
371 GetServerClassName(service));
374 for (int i = 0; i < service->method_count(); i++) {
375 const MethodDescriptor* method = service->method(i);
376 GenerateDocCommentServerMethod(out, method);
378 "public virtual $returntype$ "
379 "$methodname$($request$$response_stream_maybe$, "
380 "grpc::ServerCallContext context)\n",
381 "methodname", method->name(), "returntype",
382 GetMethodReturnTypeServer(method), "request",
383 GetMethodRequestParamServer(method), "response_stream_maybe",
384 GetMethodResponseStreamMaybe(method));
388 "throw new grpc::RpcException("
389 "new grpc::Status(grpc::StatusCode.Unimplemented, \"\"));\n");
398 void GenerateClientStub(Printer* out, const ServiceDescriptor* service) {
399 out->Print("/// <summary>Client for $servicename$</summary>\n", "servicename",
400 GetServiceClassName(service));
401 out->Print("public partial class $name$ : grpc::ClientBase<$name$>\n", "name",
402 GetClientClassName(service));
408 "/// <summary>Creates a new client for $servicename$</summary>\n"
409 "/// <param name=\"channel\">The channel to use to make remote "
411 "servicename", GetServiceClassName(service));
412 out->Print("public $name$(grpc::Channel channel) : base(channel)\n", "name",
413 GetClientClassName(service));
417 "/// <summary>Creates a new client for $servicename$ that uses a custom "
418 "<c>CallInvoker</c>.</summary>\n"
419 "/// <param name=\"callInvoker\">The callInvoker to use to make remote "
421 "servicename", GetServiceClassName(service));
423 "public $name$(grpc::CallInvoker callInvoker) : base(callInvoker)\n",
424 "name", GetClientClassName(service));
428 "/// <summary>Protected parameterless constructor to allow creation"
429 " of test doubles.</summary>\n");
430 out->Print("protected $name$() : base()\n", "name",
431 GetClientClassName(service));
435 "/// <summary>Protected constructor to allow creation of configured "
436 "clients.</summary>\n"
437 "/// <param name=\"configuration\">The client configuration.</param>\n");
439 "protected $name$(ClientBaseConfiguration configuration)"
440 " : base(configuration)\n",
441 "name", GetClientClassName(service));
445 for (int i = 0; i < service->method_count(); i++) {
446 const MethodDescriptor* method = service->method(i);
447 MethodType method_type = GetMethodType(method);
449 if (method_type == METHODTYPE_NO_STREAMING) {
450 // unary calls have an extra synchronous stub method
451 GenerateDocCommentClientMethod(out, method, true, false);
453 "public virtual $response$ $methodname$($request$ request, "
455 "headers = null, global::System.DateTime? deadline = null, "
456 "global::System.Threading.CancellationToken "
457 "cancellationToken = "
458 "default(global::System.Threading.CancellationToken))\n",
459 "methodname", method->name(), "request",
460 GetClassName(method->input_type()), "response",
461 GetClassName(method->output_type()));
465 "return $methodname$(request, new grpc::CallOptions(headers, "
467 "cancellationToken));\n",
468 "methodname", method->name());
472 // overload taking CallOptions as a param
473 GenerateDocCommentClientMethod(out, method, true, true);
475 "public virtual $response$ $methodname$($request$ request, "
476 "grpc::CallOptions options)\n",
477 "methodname", method->name(), "request",
478 GetClassName(method->input_type()), "response",
479 GetClassName(method->output_type()));
483 "return CallInvoker.BlockingUnaryCall($methodfield$, null, options, "
485 "methodfield", GetMethodFieldName(method));
490 std::string method_name = method->name();
491 if (method_type == METHODTYPE_NO_STREAMING) {
492 method_name += "Async"; // prevent name clash with synchronous method.
494 GenerateDocCommentClientMethod(out, method, false, false);
496 "public virtual $returntype$ "
497 "$methodname$($request_maybe$grpc::Metadata "
498 "headers = null, global::System.DateTime? deadline = null, "
499 "global::System.Threading.CancellationToken "
500 "cancellationToken = "
501 "default(global::System.Threading.CancellationToken))\n",
502 "methodname", method_name, "request_maybe",
503 GetMethodRequestParamMaybe(method), "returntype",
504 GetMethodReturnTypeClient(method));
509 "return $methodname$($request_maybe$new grpc::CallOptions(headers, "
511 "cancellationToken));\n",
512 "methodname", method_name, "request_maybe",
513 GetMethodRequestParamMaybe(method, true));
517 // overload taking CallOptions as a param
518 GenerateDocCommentClientMethod(out, method, false, true);
520 "public virtual $returntype$ "
521 "$methodname$($request_maybe$grpc::CallOptions "
523 "methodname", method_name, "request_maybe",
524 GetMethodRequestParamMaybe(method), "returntype",
525 GetMethodReturnTypeClient(method));
528 switch (GetMethodType(method)) {
529 case METHODTYPE_NO_STREAMING:
531 "return CallInvoker.AsyncUnaryCall($methodfield$, null, options, "
533 "methodfield", GetMethodFieldName(method));
535 case METHODTYPE_CLIENT_STREAMING:
537 "return CallInvoker.AsyncClientStreamingCall($methodfield$, null, "
539 "methodfield", GetMethodFieldName(method));
541 case METHODTYPE_SERVER_STREAMING:
543 "return CallInvoker.AsyncServerStreamingCall($methodfield$, null, "
544 "options, request);\n",
545 "methodfield", GetMethodFieldName(method));
547 case METHODTYPE_BIDI_STREAMING:
549 "return CallInvoker.AsyncDuplexStreamingCall($methodfield$, null, "
551 "methodfield", GetMethodFieldName(method));
554 GOOGLE_LOG(FATAL) << "Can't get here.";
560 // override NewInstance method
562 "/// <summary>Creates a new instance of client from given "
563 "<c>ClientBaseConfiguration</c>.</summary>\n");
565 "protected override $name$ NewInstance(ClientBaseConfiguration "
567 "name", GetClientClassName(service));
570 out->Print("return new $name$(configuration);\n", "name",
571 GetClientClassName(service));
580 void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor* service) {
582 "/// <summary>Creates service definition that can be registered with a "
583 "server</summary>\n");
585 "/// <param name=\"serviceImpl\">An object implementing the server-side"
586 " handling logic.</param>\n");
588 "public static grpc::ServerServiceDefinition BindService($implclass$ "
590 "implclass", GetServerClassName(service));
594 out->Print("return grpc::ServerServiceDefinition.CreateBuilder()");
597 for (int i = 0; i < service->method_count(); i++) {
598 const MethodDescriptor* method = service->method(i);
599 out->Print("\n.AddMethod($methodfield$, serviceImpl.$methodname$)",
600 "methodfield", GetMethodFieldName(method), "methodname",
603 out->Print(".Build();\n");
612 void GenerateBindServiceWithBinderMethod(Printer* out,
613 const ServiceDescriptor* service) {
615 "/// <summary>Register service method implementations with a service "
616 "binder. Useful when customizing the service binding logic.\n"
617 "/// Note: this method is part of an experimental API that can change or "
619 "removed without any prior notice.</summary>\n");
621 "/// <param name=\"serviceBinder\">Service methods will be bound by "
622 "calling <c>AddMethod</c> on this object."
625 "/// <param name=\"serviceImpl\">An object implementing the server-side"
626 " handling logic.</param>\n");
628 "public static void BindService(grpc::ServiceBinderBase serviceBinder, "
631 "implclass", GetServerClassName(service));
635 for (int i = 0; i < service->method_count(); i++) {
636 const MethodDescriptor* method = service->method(i);
638 "serviceBinder.AddMethod($methodfield$, serviceImpl.$methodname$);\n",
639 "methodfield", GetMethodFieldName(method), "methodname",
648 void GenerateService(Printer* out, const ServiceDescriptor* service,
649 bool generate_client, bool generate_server,
650 bool internal_access) {
651 GenerateDocCommentBody(out, service);
652 out->Print("$access_level$ static partial class $classname$\n",
653 "access_level", GetAccessLevel(internal_access), "classname",
654 GetServiceClassName(service));
657 out->Print("static readonly string $servicenamefield$ = \"$servicename$\";\n",
658 "servicenamefield", GetServiceNameFieldName(), "servicename",
659 service->full_name());
662 GenerateMarshallerFields(out, service);
663 for (int i = 0; i < service->method_count(); i++) {
664 GenerateStaticMethodField(out, service->method(i));
666 GenerateServiceDescriptorProperty(out, service);
668 if (generate_server) {
669 GenerateServerClass(out, service);
671 if (generate_client) {
672 GenerateClientStub(out, service);
674 if (generate_server) {
675 GenerateBindServiceMethod(out, service);
676 GenerateBindServiceWithBinderMethod(out, service);
683 } // anonymous namespace
685 grpc::string GetServices(const FileDescriptor* file, bool generate_client,
686 bool generate_server, bool internal_access) {
689 // Scope the output stream so it closes and finalizes output to the string.
691 StringOutputStream output_stream(&output);
692 Printer out(&output_stream, '$');
694 // Don't write out any output if there no services, to avoid empty service
695 // files being generated for proto files that don't declare any.
696 if (file->service_count() == 0) {
700 // Write out a file header.
701 out.Print("// <auto-generated>\n");
703 "// Generated by the protocol buffer compiler. DO NOT EDIT!\n");
704 out.Print("// source: $filename$\n", "filename", file->name());
705 out.Print("// </auto-generated>\n");
707 // use C++ style as there are no file-level XML comments in .NET
708 grpc::string leading_comments = GetCsharpComments(file, true);
709 if (!leading_comments.empty()) {
710 out.Print("// Original file comments:\n");
711 out.PrintRaw(leading_comments.c_str());
714 out.Print("#pragma warning disable 0414, 1591\n");
716 out.Print("#region Designer generated code\n");
718 out.Print("using grpc = global::Grpc.Core;\n");
721 grpc::string file_namespace = GetFileNamespace(file);
722 if (file_namespace != "") {
723 out.Print("namespace $namespace$ {\n", "namespace", file_namespace);
726 for (int i = 0; i < file->service_count(); i++) {
727 GenerateService(&out, file->service(i), generate_client, generate_server,
730 if (file_namespace != "") {
734 out.Print("#endregion\n");
739 } // namespace grpc_csharp_generator