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 <map>
20#include <set>
21#include <sstream>
22
23#include "src/compiler/config.h"
24#include "src/compiler/objective_c_generator.h"
25#include "src/compiler/objective_c_generator_helpers.h"
26
27#include <google/protobuf/compiler/objectivec/objectivec_helpers.h>
28
29using ::google::protobuf::compiler::objectivec::ClassName;
30using ::grpc::protobuf::FileDescriptor;
31using ::grpc::protobuf::FileDescriptor;
32using ::grpc::protobuf::MethodDescriptor;
33using ::grpc::protobuf::ServiceDescriptor;
34using ::grpc::protobuf::io::Printer;
35using ::std::map;
36using ::std::set;
37
38namespace grpc_objective_c_generator {
39namespace {
40
41void PrintProtoRpcDeclarationAsPragma(
42 Printer* printer, const MethodDescriptor* method,
43 map< ::grpc::string, ::grpc::string> vars) {
44 vars["client_stream"] = method->client_streaming() ? "stream " : "";
45 vars["server_stream"] = method->server_streaming() ? "stream " : "";
46
47 printer->Print(vars,
48 "#pragma mark $method_name$($client_stream$$request_type$)"
49 " returns ($server_stream$$response_type$)\n\n");
50}
51
52template <typename DescriptorType>
53static void PrintAllComments(const DescriptorType* desc, Printer* printer,
54 bool deprecated = false) {
55 std::vector<grpc::string> comments;
56 grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_LEADING_DETACHED,
57 &comments);
58 grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_LEADING,
59 &comments);
60 grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_TRAILING,
61 &comments);
62 if (comments.empty()) {
63 return;
64 }
65 printer->Print("/**\n");
66 for (auto it = comments.begin(); it != comments.end(); ++it) {
67 printer->Print(" * ");
68 size_t start_pos = it->find_first_not_of(' ');
69 if (start_pos != grpc::string::npos) {
70 printer->PrintRaw(it->c_str() + start_pos);
71 }
72 printer->Print("\n");
73 }
74 if (deprecated) {
75 printer->Print(" *\n");
76 printer->Print(
77 " * This method belongs to a set of APIs that have been deprecated. "
78 "Using"
79 " the v2 API is recommended.\n");
80 }
81 printer->Print(" */\n");
82}
83
84void PrintMethodSignature(Printer* printer, const MethodDescriptor* method,
85 const map< ::grpc::string, ::grpc::string>& vars) {
86 // Print comment
87 PrintAllComments(method, printer, true);
88
89 printer->Print(vars, "- ($return_type$)$method_name$With");
90 if (method->client_streaming()) {
91 printer->Print("RequestsWriter:(GRXWriter *)requestWriter");
92 } else {
93 printer->Print(vars, "Request:($request_class$ *)request");
94 }
95
96 // TODO(jcanizales): Put this on a new line and align colons.
97 if (method->server_streaming()) {
98 printer->Print(vars,
99 " eventHandler:(void(^)(BOOL done, "
100 "$response_class$ *_Nullable response, NSError *_Nullable "
101 "error))eventHandler");
102 } else {
103 printer->Print(vars,
104 " handler:(void(^)($response_class$ *_Nullable response, "
105 "NSError *_Nullable error))handler");
106 }
107}
108
109void PrintSimpleSignature(Printer* printer, const MethodDescriptor* method,
110 map< ::grpc::string, ::grpc::string> vars) {
111 vars["method_name"] =
112 grpc_generator::LowercaseFirstLetter(vars["method_name"]);
113 vars["return_type"] = "void";
114 PrintMethodSignature(printer, method, vars);
115}
116
117void PrintAdvancedSignature(Printer* printer, const MethodDescriptor* method,
118 map< ::grpc::string, ::grpc::string> vars) {
119 vars["method_name"] = "RPCTo" + vars["method_name"];
120 vars["return_type"] = "GRPCProtoCall *";
121 PrintMethodSignature(printer, method, vars);
122}
123
124void PrintV2Signature(Printer* printer, const MethodDescriptor* method,
125 map< ::grpc::string, ::grpc::string> vars) {
126 if (method->client_streaming()) {
127 vars["return_type"] = "GRPCStreamingProtoCall *";
128 } else {
129 vars["return_type"] = "GRPCUnaryProtoCall *";
130 }
131 vars["method_name"] =
132 grpc_generator::LowercaseFirstLetter(vars["method_name"]);
133
134 PrintAllComments(method, printer);
135
136 printer->Print(vars, "- ($return_type$)$method_name$With");
137 if (method->client_streaming()) {
138 printer->Print("ResponseHandler:(id<GRPCProtoResponseHandler>)handler");
139 } else {
140 printer->Print(vars,
141 "Message:($request_class$ *)message "
142 "responseHandler:(id<GRPCProtoResponseHandler>)handler");
143 }
144 printer->Print(" callOptions:(GRPCCallOptions *_Nullable)callOptions");
145}
146
147inline map< ::grpc::string, ::grpc::string> GetMethodVars(
148 const MethodDescriptor* method) {
149 map< ::grpc::string, ::grpc::string> res;
150 res["method_name"] = method->name();
151 res["request_type"] = method->input_type()->name();
152 res["response_type"] = method->output_type()->name();
153 res["request_class"] = ClassName(method->input_type());
154 res["response_class"] = ClassName(method->output_type());
155 return res;
156}
157
158void PrintMethodDeclarations(Printer* printer, const MethodDescriptor* method) {
159 map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method);
160
161 PrintProtoRpcDeclarationAsPragma(printer, method, vars);
162
163 PrintSimpleSignature(printer, method, vars);
164 printer->Print(";\n\n");
165 PrintAdvancedSignature(printer, method, vars);
166 printer->Print(";\n\n\n");
167}
168
169void PrintV2MethodDeclarations(Printer* printer,
170 const MethodDescriptor* method) {
171 map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method);
172
173 PrintProtoRpcDeclarationAsPragma(printer, method, vars);
174
175 PrintV2Signature(printer, method, vars);
176 printer->Print(";\n\n");
177}
178
179void PrintSimpleImplementation(Printer* printer, const MethodDescriptor* method,
180 map< ::grpc::string, ::grpc::string> vars) {
181 printer->Print("{\n");
182 printer->Print(vars, " [[self RPCTo$method_name$With");
183 if (method->client_streaming()) {
184 printer->Print("RequestsWriter:requestWriter");
185 } else {
186 printer->Print("Request:request");
187 }
188 if (method->server_streaming()) {
189 printer->Print(" eventHandler:eventHandler] start];\n");
190 } else {
191 printer->Print(" handler:handler] start];\n");
192 }
193 printer->Print("}\n");
194}
195
196void PrintAdvancedImplementation(Printer* printer,
197 const MethodDescriptor* method,
198 map< ::grpc::string, ::grpc::string> vars) {
199 printer->Print("{\n");
200 printer->Print(vars, " return [self RPCToMethod:@\"$method_name$\"\n");
201
202 printer->Print(" requestsWriter:");
203 if (method->client_streaming()) {
204 printer->Print("requestWriter\n");
205 } else {
206 printer->Print("[GRXWriter writerWithValue:request]\n");
207 }
208
209 printer->Print(vars, " responseClass:[$response_class$ class]\n");
210
211 printer->Print(" responsesWriteable:[GRXWriteable ");
212 if (method->server_streaming()) {
213 printer->Print("writeableWithEventHandler:eventHandler]];\n");
214 } else {
215 printer->Print("writeableWithSingleHandler:handler]];\n");
216 }
217
218 printer->Print("}\n");
219}
220
221void PrintV2Implementation(Printer* printer, const MethodDescriptor* method,
222 map< ::grpc::string, ::grpc::string> vars) {
223 printer->Print(" {\n");
224 if (method->client_streaming()) {
225 printer->Print(vars, " return [self RPCToMethod:@\"$method_name$\"\n");
226 printer->Print(" responseHandler:handler\n");
227 printer->Print(" callOptions:callOptions\n");
228 printer->Print(
229 vars, " responseClass:[$response_class$ class]];\n}\n\n");
230 } else {
231 printer->Print(vars, " return [self RPCToMethod:@\"$method_name$\"\n");
232 printer->Print(" message:message\n");
233 printer->Print(" responseHandler:handler\n");
234 printer->Print(" callOptions:callOptions\n");
235 printer->Print(
236 vars, " responseClass:[$response_class$ class]];\n}\n\n");
237 }
238}
239
240void PrintMethodImplementations(Printer* printer,
241 const MethodDescriptor* method,
242 const Parameters& generator_params) {
243 map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method);
244
245 PrintProtoRpcDeclarationAsPragma(printer, method, vars);
246
247 if (!generator_params.no_v1_compatibility) {
248 // TODO(jcanizales): Print documentation from the method.
249 PrintSimpleSignature(printer, method, vars);
250 PrintSimpleImplementation(printer, method, vars);
251
252 printer->Print("// Returns a not-yet-started RPC object.\n");
253 PrintAdvancedSignature(printer, method, vars);
254 PrintAdvancedImplementation(printer, method, vars);
255 }
256
257 PrintV2Signature(printer, method, vars);
258 PrintV2Implementation(printer, method, vars);
259}
260
261} // namespace
262
263::grpc::string GetAllMessageClasses(const FileDescriptor* file) {
264 ::grpc::string output;
265 set< ::grpc::string> classes;
266 for (int i = 0; i < file->service_count(); i++) {
267 const auto service = file->service(i);
268 for (int i = 0; i < service->method_count(); i++) {
269 const auto method = service->method(i);
270 classes.insert(ClassName(method->input_type()));
271 classes.insert(ClassName(method->output_type()));
272 }
273 }
274 for (auto one_class : classes) {
275 output += "@class " + one_class + ";\n";
276 }
277
278 return output;
279}
280
281::grpc::string GetProtocol(const ServiceDescriptor* service,
282 const Parameters& generator_params) {
283 ::grpc::string output;
284
285 if (generator_params.no_v1_compatibility) return output;
286
287 // Scope the output stream so it closes and finalizes output to the string.
288 grpc::protobuf::io::StringOutputStream output_stream(&output);
289 Printer printer(&output_stream, '$');
290
291 map< ::grpc::string, ::grpc::string> vars = {
292 {"service_class", ServiceClassName(service)}};
293
294 printer.Print(vars,
295 "/**\n"
296 " * The methods in this protocol belong to a set of old APIs "
297 "that have been deprecated. They do not\n"
298 " * recognize call options provided in the initializer. Using "
299 "the v2 protocol is recommended.\n"
300 " */\n");
301 printer.Print(vars, "@protocol $service_class$ <NSObject>\n\n");
302 for (int i = 0; i < service->method_count(); i++) {
303 PrintMethodDeclarations(&printer, service->method(i));
304 }
305 printer.Print("@end\n\n");
306
307 return output;
308}
309
310::grpc::string GetV2Protocol(const ServiceDescriptor* service) {
311 ::grpc::string output;
312
313 // Scope the output stream so it closes and finalizes output to the string.
314 grpc::protobuf::io::StringOutputStream output_stream(&output);
315 Printer printer(&output_stream, '$');
316
317 map< ::grpc::string, ::grpc::string> vars = {
318 {"service_class", ServiceClassName(service) + "2"}};
319
320 printer.Print(vars, "@protocol $service_class$ <NSObject>\n\n");
321 for (int i = 0; i < service->method_count(); i++) {
322 PrintV2MethodDeclarations(&printer, service->method(i));
323 }
324 printer.Print("@end\n\n");
325
326 return output;
327}
328
329::grpc::string GetInterface(const ServiceDescriptor* service,
330 const Parameters& generator_params) {
331 ::grpc::string output;
332
333 // Scope the output stream so it closes and finalizes output to the string.
334 grpc::protobuf::io::StringOutputStream output_stream(&output);
335 Printer printer(&output_stream, '$');
336
337 map< ::grpc::string, ::grpc::string> vars = {
338 {"service_class", ServiceClassName(service)}};
339
340 printer.Print(vars,
341 "/**\n"
342 " * Basic service implementation, over gRPC, that only does\n"
343 " * marshalling and parsing.\n"
344 " */\n");
345 printer.Print(vars,
346 "@interface $service_class$ :"
347 " GRPCProtoService<$service_class$2");
348 if (!generator_params.no_v1_compatibility) {
349 printer.Print(vars, ", $service_class$");
350 }
351 printer.Print(">\n");
352 printer.Print(
353 "- (instancetype)initWithHost:(NSString *)host "
354 "callOptions:(GRPCCallOptions "
355 "*_Nullable)callOptions"
356 " NS_DESIGNATED_INITIALIZER;\n");
357 printer.Print(
358 "+ (instancetype)serviceWithHost:(NSString *)host "
359 "callOptions:(GRPCCallOptions *_Nullable)callOptions;\n");
360 if (!generator_params.no_v1_compatibility) {
361 printer.Print(
362 "// The following methods belong to a set of old APIs that have been "
363 "deprecated.\n");
364 printer.Print("- (instancetype)initWithHost:(NSString *)host;\n");
365 printer.Print("+ (instancetype)serviceWithHost:(NSString *)host;\n");
366 }
367 printer.Print("@end\n");
368
369 return output;
370}
371
372::grpc::string GetSource(const ServiceDescriptor* service,
373 const Parameters& generator_params) {
374 ::grpc::string output;
375 {
376 // Scope the output stream so it closes and finalizes output to the string.
377 grpc::protobuf::io::StringOutputStream output_stream(&output);
378 Printer printer(&output_stream, '$');
379
380 map< ::grpc::string, ::grpc::string> vars = {
381 {"service_name", service->name()},
382 {"service_class", ServiceClassName(service)},
383 {"package", service->file()->package()}};
384
385 printer.Print(vars,
386 "@implementation $service_class$\n\n"
387 "#pragma clang diagnostic push\n"
388 "#pragma clang diagnostic ignored "
389 "\"-Wobjc-designated-initializers\"\n\n"
390 "// Designated initializer\n"
391 "- (instancetype)initWithHost:(NSString *)host "
392 "callOptions:(GRPCCallOptions *_Nullable)callOptions {\n"
393 " return [super initWithHost:host\n"
394 " packageName:@\"$package$\"\n"
395 " serviceName:@\"$service_name$\"\n"
396 " callOptions:callOptions];\n"
397 "}\n\n");
398 if (!generator_params.no_v1_compatibility) {
399 printer.Print(vars,
400 "- (instancetype)initWithHost:(NSString *)host {\n"
401 " return [super initWithHost:host\n"
402 " packageName:@\"$package$\"\n"
403 " serviceName:@\"$service_name$\"];\n"
404 "}\n\n");
405 }
406 printer.Print("#pragma clang diagnostic pop\n\n");
407
408 if (!generator_params.no_v1_compatibility) {
409 printer.Print(
410 "// Override superclass initializer to disallow different"
411 " package and service names.\n"
412 "- (instancetype)initWithHost:(NSString *)host\n"
413 " packageName:(NSString *)packageName\n"
414 " serviceName:(NSString *)serviceName {\n"
415 " return [self initWithHost:host];\n"
416 "}\n\n");
417 }
418 printer.Print(
419 "- (instancetype)initWithHost:(NSString *)host\n"
420 " packageName:(NSString *)packageName\n"
421 " serviceName:(NSString *)serviceName\n"
422 " callOptions:(GRPCCallOptions *)callOptions {\n"
423 " return [self initWithHost:host callOptions:callOptions];\n"
424 "}\n\n");
425
426 printer.Print("#pragma mark - Class Methods\n\n");
427 if (!generator_params.no_v1_compatibility) {
428 printer.Print(
429 "+ (instancetype)serviceWithHost:(NSString *)host {\n"
430 " return [[self alloc] initWithHost:host];\n"
431 "}\n\n");
432 }
433 printer.Print(
434 "+ (instancetype)serviceWithHost:(NSString *)host "
435 "callOptions:(GRPCCallOptions *_Nullable)callOptions {\n"
436 " return [[self alloc] initWithHost:host callOptions:callOptions];\n"
437 "}\n\n");
438
439 printer.Print("#pragma mark - Method Implementations\n\n");
440
441 for (int i = 0; i < service->method_count(); i++) {
442 PrintMethodImplementations(&printer, service->method(i),
443 generator_params);
444 }
445
446 printer.Print("@end\n");
447 }
448 return output;
449}
450
451} // namespace grpc_objective_c_generator
452