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 | |
29 | using ::google::protobuf::compiler::objectivec::ClassName; |
30 | using ::grpc::protobuf::FileDescriptor; |
31 | using ::grpc::protobuf::FileDescriptor; |
32 | using ::grpc::protobuf::MethodDescriptor; |
33 | using ::grpc::protobuf::ServiceDescriptor; |
34 | using ::grpc::protobuf::io::Printer; |
35 | using ::std::map; |
36 | using ::std::set; |
37 | |
38 | namespace grpc_objective_c_generator { |
39 | namespace { |
40 | |
41 | void 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 | |
52 | template <typename DescriptorType> |
53 | static void (const DescriptorType* desc, Printer* printer, |
54 | bool deprecated = false) { |
55 | std::vector<grpc::string> ; |
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 | |
84 | void 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 | |
109 | void 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 | |
117 | void 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 | |
124 | void 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 | |
147 | inline 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 | |
158 | void 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 | |
169 | void 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 | |
179 | void 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 | |
196 | void 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 | |
221 | void 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 | |
240 | void 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 | |