]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | /* |
2 | * Licensed to the Apache Software Foundation (ASF) under one | |
3 | * or more contributor license agreements. See the NOTICE file | |
4 | * distributed with this work for additional information | |
5 | * regarding copyright ownership. The ASF licenses this file | |
6 | * to you under the Apache License, Version 2.0 (the | |
7 | * "License"); you may not use this file except in compliance | |
8 | * with the License. You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
18 | */ | |
19 | ||
20 | #include <map> | |
21 | #include <string> | |
22 | #include <fstream> | |
23 | #include <iomanip> | |
24 | #include <iostream> | |
25 | #include <limits> | |
26 | #include <vector> | |
27 | #include <list> | |
28 | #include <cassert> | |
29 | #include <unordered_map> | |
30 | ||
31 | #include <stdlib.h> | |
32 | #include <sys/stat.h> | |
33 | #include <sstream> | |
34 | #include "thrift/platform.h" | |
35 | #include "thrift/version.h" | |
36 | ||
37 | using std::map; | |
38 | using std::ostream; | |
39 | using std::ostringstream; | |
40 | using std::string; | |
41 | using std::stringstream; | |
42 | using std::unordered_map; | |
43 | using std::vector; | |
44 | ||
45 | static const string endl = "\n"; // avoid ostream << std::endl flushes | |
46 | static const string episode_file_name = "thrift.js.episode"; | |
47 | // largest consecutive integer representable by a double (2 ^ 53 - 1) | |
48 | static const int64_t max_safe_integer = 0x1fffffffffffff; | |
49 | // smallest consecutive number representable by a double (-2 ^ 53 + 1) | |
50 | static const int64_t min_safe_integer = -max_safe_integer; | |
51 | ||
52 | #include "thrift/generate/t_oop_generator.h" | |
53 | ||
54 | ||
55 | /** | |
56 | * JS code generator. | |
57 | */ | |
58 | class t_js_generator : public t_oop_generator { | |
59 | public: | |
60 | t_js_generator(t_program* program, | |
61 | const std::map<std::string, std::string>& parsed_options, | |
62 | const std::string& option_string) | |
63 | : t_oop_generator(program) { | |
64 | (void)option_string; | |
65 | std::map<std::string, std::string>::const_iterator iter; | |
66 | ||
67 | gen_node_ = false; | |
68 | gen_jquery_ = false; | |
69 | gen_ts_ = false; | |
70 | gen_es6_ = false; | |
71 | gen_episode_file_ = false; | |
72 | ||
73 | bool with_ns_ = false; | |
74 | ||
75 | for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { | |
76 | if( iter->first.compare("node") == 0) { | |
77 | gen_node_ = true; | |
78 | } else if( iter->first.compare("jquery") == 0) { | |
79 | gen_jquery_ = true; | |
80 | } else if( iter->first.compare("ts") == 0) { | |
81 | gen_ts_ = true; | |
82 | } else if( iter->first.compare("with_ns") == 0) { | |
83 | with_ns_ = true; | |
84 | } else if( iter->first.compare("es6") == 0) { | |
85 | gen_es6_ = true; | |
86 | } else if( iter->first.compare("imports") == 0) { | |
87 | parse_imports(program, iter->second); | |
88 | } else if (iter->first.compare("thrift_package_output_directory") == 0) { | |
89 | parse_thrift_package_output_directory(iter->second); | |
90 | } else { | |
91 | throw std::invalid_argument("unknown option js:" + iter->first); | |
92 | } | |
93 | } | |
94 | ||
95 | if (gen_es6_ && gen_jquery_) { | |
96 | throw std::invalid_argument("invalid switch: [-gen js:es6,jquery] options not compatible"); | |
97 | } | |
98 | ||
99 | if (gen_node_ && gen_jquery_) { | |
100 | throw std::invalid_argument("invalid switch: [-gen js:node,jquery] options not compatible, try: [-gen js:node -gen " | |
101 | "js:jquery]"); | |
102 | } | |
103 | ||
104 | if (!gen_node_ && with_ns_) { | |
105 | throw std::invalid_argument("invalid switch: [-gen js:with_ns] is only valid when using node.js"); | |
106 | } | |
107 | ||
108 | // Depending on the processing flags, we will update these to be ES6 compatible | |
109 | js_const_type_ = "var "; | |
110 | js_let_type_ = "var "; | |
111 | js_var_type_ = "var "; | |
112 | if (gen_es6_) { | |
113 | js_const_type_ = "const "; | |
114 | js_let_type_ = "let "; | |
115 | } | |
116 | ||
117 | if (gen_node_) { | |
118 | out_dir_base_ = "gen-nodejs"; | |
119 | no_ns_ = !with_ns_; | |
120 | } else { | |
121 | out_dir_base_ = "gen-js"; | |
122 | no_ns_ = false; | |
123 | } | |
124 | ||
125 | escape_['\''] = "\\'"; | |
126 | } | |
127 | ||
128 | /** | |
129 | * Init and close methods | |
130 | */ | |
131 | ||
132 | void init_generator() override; | |
133 | void close_generator() override; | |
134 | ||
135 | /** | |
136 | * Program-level generation functions | |
137 | */ | |
138 | ||
139 | void generate_typedef(t_typedef* ttypedef) override; | |
140 | void generate_enum(t_enum* tenum) override; | |
141 | void generate_const(t_const* tconst) override; | |
142 | void generate_struct(t_struct* tstruct) override; | |
143 | void generate_xception(t_struct* txception) override; | |
144 | void generate_service(t_service* tservice) override; | |
145 | ||
146 | std::string render_recv_throw(std::string var); | |
147 | std::string render_recv_return(std::string var); | |
148 | ||
149 | std::string render_const_value(t_type* type, t_const_value* value); | |
150 | ||
151 | /** | |
152 | * Structs! | |
153 | */ | |
154 | void generate_js_struct(t_struct* tstruct, bool is_exception); | |
155 | void generate_js_struct_definition(std::ostream& out, | |
156 | t_struct* tstruct, | |
157 | bool is_xception = false, | |
158 | bool is_exported = true); | |
159 | void generate_js_struct_reader(std::ostream& out, t_struct* tstruct); | |
160 | void generate_js_struct_writer(std::ostream& out, t_struct* tstruct); | |
161 | void generate_js_function_helpers(t_function* tfunction); | |
162 | ||
163 | /** | |
164 | * Service-level generation functions | |
165 | */ | |
166 | void generate_service_helpers(t_service* tservice); | |
167 | void generate_service_interface(t_service* tservice); | |
168 | void generate_service_rest(t_service* tservice); | |
169 | void generate_service_client(t_service* tservice); | |
170 | void generate_service_processor(t_service* tservice); | |
171 | void generate_process_function(t_service* tservice, t_function* tfunction); | |
172 | ||
173 | /** | |
174 | * Serialization constructs | |
175 | */ | |
176 | ||
177 | void generate_deserialize_field(std::ostream& out, | |
178 | t_field* tfield, | |
179 | std::string prefix = "", | |
180 | bool inclass = false); | |
181 | ||
182 | void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); | |
183 | ||
184 | void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); | |
185 | ||
186 | void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); | |
187 | ||
188 | void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); | |
189 | ||
190 | void generate_deserialize_list_element(std::ostream& out, | |
191 | t_list* tlist, | |
192 | std::string prefix = ""); | |
193 | ||
194 | void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); | |
195 | ||
196 | void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); | |
197 | ||
198 | void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); | |
199 | ||
200 | void generate_serialize_map_element(std::ostream& out, | |
201 | t_map* tmap, | |
202 | std::string kiter, | |
203 | std::string viter); | |
204 | ||
205 | void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); | |
206 | ||
207 | void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); | |
208 | ||
209 | /** | |
210 | * Helper rendering functions | |
211 | */ | |
212 | ||
213 | std::string js_includes(); | |
214 | std::string ts_includes(); | |
215 | std::string ts_service_includes(); | |
216 | std::string render_includes(); | |
217 | std::string render_ts_includes(); | |
218 | std::string get_import_path(t_program* program); | |
219 | std::string declare_field(t_field* tfield, bool init = false, bool obj = false); | |
220 | std::string function_signature(t_function* tfunction, | |
221 | std::string prefix = "", | |
222 | bool include_callback = false); | |
223 | std::string argument_list(t_struct* tstruct, bool include_callback = false); | |
224 | std::string type_to_enum(t_type* ttype); | |
225 | std::string make_valid_nodeJs_identifier(std::string const& name); | |
226 | ||
227 | /** | |
228 | * Helper parser functions | |
229 | */ | |
230 | ||
231 | void parse_imports(t_program* program, const std::string& imports_string); | |
232 | void parse_thrift_package_output_directory(const std::string& thrift_package_output_directory); | |
233 | ||
234 | std::string autogen_comment() override { | |
235 | return std::string("//\n") + "// Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" | |
236 | + "//\n" + "// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" | |
237 | + "//\n"; | |
238 | } | |
239 | ||
240 | t_type* get_contained_type(t_type* t); | |
241 | ||
242 | std::vector<std::string> js_namespace_pieces(t_program* p) { | |
243 | std::string ns = p->get_namespace("js"); | |
244 | ||
245 | std::string::size_type loc; | |
246 | std::vector<std::string> pieces; | |
247 | ||
248 | if (no_ns_) { | |
249 | return pieces; | |
250 | } | |
251 | ||
252 | if (ns.size() > 0) { | |
253 | while ((loc = ns.find(".")) != std::string::npos) { | |
254 | pieces.push_back(ns.substr(0, loc)); | |
255 | ns = ns.substr(loc + 1); | |
256 | } | |
257 | } | |
258 | ||
259 | if (ns.size() > 0) { | |
260 | pieces.push_back(ns); | |
261 | } | |
262 | ||
263 | return pieces; | |
264 | } | |
265 | ||
266 | std::string js_type_namespace(t_program* p) { | |
267 | if (gen_node_) { | |
268 | if (p != NULL && p != program_) { | |
269 | return make_valid_nodeJs_identifier(p->get_name()) + "_ttypes."; | |
270 | } | |
271 | return "ttypes."; | |
272 | } | |
273 | return js_namespace(p); | |
274 | } | |
275 | ||
276 | std::string js_export_namespace(t_program* p) { | |
277 | if (gen_node_) { | |
278 | return "exports."; | |
279 | } | |
280 | return js_namespace(p); | |
281 | } | |
282 | ||
283 | bool has_js_namespace(t_program* p) { | |
284 | if (no_ns_) { | |
285 | return false; | |
286 | } | |
287 | std::string ns = p->get_namespace("js"); | |
288 | return (ns.size() > 0); | |
289 | } | |
290 | ||
291 | std::string js_namespace(t_program* p) { | |
292 | if (no_ns_) { | |
293 | return ""; | |
294 | } | |
295 | std::string ns = p->get_namespace("js"); | |
296 | if (ns.size() > 0) { | |
297 | ns += "."; | |
298 | } | |
299 | ||
300 | return ns; | |
301 | } | |
302 | ||
303 | /** | |
304 | * TypeScript Definition File helper functions | |
305 | */ | |
306 | ||
307 | string ts_function_signature(t_function* tfunction, bool include_callback); | |
308 | string ts_get_type(t_type* type); | |
309 | ||
310 | /** | |
311 | * Special indentation for TypeScript Definitions because of the module. | |
312 | * Returns the normal indentation + " " if a module was defined. | |
313 | * @return string | |
314 | */ | |
315 | string ts_indent() { return indent() + (!ts_module_.empty() ? " " : ""); } | |
316 | ||
317 | /** | |
318 | * Returns "declare " if no module was defined. | |
319 | * @return string | |
320 | */ | |
321 | string ts_declare() { return (ts_module_.empty() ? (gen_node_ ? "declare " : "export declare ") : ""); } | |
322 | ||
323 | /** | |
324 | * Returns "?" if the given field is optional or has a default value. | |
325 | * @param t_field The field to check | |
326 | * @return string | |
327 | */ | |
328 | string ts_get_req(t_field* field) {return (field->get_req() == t_field::T_OPTIONAL || field->get_value() != NULL ? "?" : ""); } | |
329 | ||
330 | /** | |
331 | * Returns the documentation, if the provided documentable object has one. | |
332 | * @param t_doc The object to get the documentation from | |
333 | * @return string The documentation | |
334 | */ | |
335 | string ts_print_doc(t_doc* tdoc) { | |
336 | string result = endl; | |
337 | ||
338 | if (tdoc->has_doc()) { | |
339 | std::stringstream doc(tdoc->get_doc()); | |
340 | string item; | |
341 | ||
342 | result += ts_indent() + "/**" + endl; | |
343 | while (std::getline(doc, item)) { | |
344 | result += ts_indent() + " * " + item + endl; | |
345 | } | |
346 | result += ts_indent() + " */" + endl; | |
347 | } | |
348 | return result; | |
349 | } | |
350 | ||
351 | private: | |
352 | /** | |
353 | * True if we should generate NodeJS-friendly RPC services. | |
354 | */ | |
355 | bool gen_node_; | |
356 | ||
357 | /** | |
358 | * True if we should generate services that use jQuery ajax (async/sync). | |
359 | */ | |
360 | bool gen_jquery_; | |
361 | ||
362 | /** | |
363 | * True if we should generate a TypeScript Definition File for each service. | |
364 | */ | |
365 | bool gen_ts_; | |
366 | ||
367 | /** | |
368 | * True if we should generate ES6 code, i.e. with Promises | |
369 | */ | |
370 | bool gen_es6_; | |
371 | ||
372 | /** | |
373 | * True if we will generate an episode file. | |
374 | */ | |
375 | bool gen_episode_file_; | |
376 | ||
377 | /** | |
378 | * The name of the defined module(s), for TypeScript Definition Files. | |
379 | */ | |
380 | string ts_module_; | |
381 | ||
382 | /** | |
383 | * True if we should not generate namespace objects for node. | |
384 | */ | |
385 | bool no_ns_; | |
386 | ||
387 | /** | |
388 | * The node modules to use when importing the previously generated files. | |
389 | */ | |
390 | vector<string> imports; | |
391 | ||
392 | /** | |
393 | * Cache for imported modules. | |
394 | */ | |
395 | unordered_map<string, string> module_name_2_import_path; | |
396 | ||
397 | /** | |
398 | * Cache for TypeScript includes to generated import name. | |
399 | */ | |
400 | unordered_map<t_program*, string> include_2_import_name; | |
401 | ||
402 | /** | |
403 | * The prefix to use when generating the episode file. | |
404 | */ | |
405 | string thrift_package_output_directory_; | |
406 | ||
407 | /** | |
408 | * The variable decorator for "const" variables. Will default to "var" if in an incompatible language. | |
409 | */ | |
410 | string js_const_type_; | |
411 | ||
412 | /** | |
413 | * The variable decorator for "let" variables. Will default to "var" if in an incompatible language. | |
414 | */ | |
415 | string js_let_type_; | |
416 | ||
417 | /** | |
418 | * The default variable decorator. Supports all javascript languages, but is not scoped to functions or closures. | |
419 | */ | |
420 | string js_var_type_; | |
421 | ||
422 | /** | |
423 | * File streams | |
424 | */ | |
425 | ofstream_with_content_based_conditional_update f_episode_; | |
426 | ofstream_with_content_based_conditional_update f_types_; | |
427 | ofstream_with_content_based_conditional_update f_service_; | |
428 | ofstream_with_content_based_conditional_update f_types_ts_; | |
429 | ofstream_with_content_based_conditional_update f_service_ts_; | |
430 | }; | |
431 | ||
432 | /** | |
433 | * Prepares for file generation by opening up the necessary file output | |
434 | * streams. | |
435 | * | |
436 | * @param tprogram The program to generate | |
437 | */ | |
438 | void t_js_generator::init_generator() { | |
439 | // Make output directory | |
440 | MKDIR(get_out_dir().c_str()); | |
441 | ||
442 | const auto outdir = get_out_dir(); | |
443 | ||
444 | // Make output file(s) | |
445 | if (gen_episode_file_) { | |
446 | const auto f_episode_file_path = outdir + episode_file_name; | |
447 | f_episode_.open(f_episode_file_path); | |
448 | } | |
449 | ||
450 | const auto f_types_name = outdir + program_->get_name() + "_types.js"; | |
451 | f_types_.open(f_types_name.c_str()); | |
452 | if (gen_episode_file_) { | |
453 | const auto types_module = program_->get_name() + "_types"; | |
454 | f_episode_ << types_module << ":" << thrift_package_output_directory_ << "/" << types_module << endl; | |
455 | } | |
456 | ||
457 | if (gen_ts_) { | |
458 | const auto f_types_ts_name = outdir + program_->get_name() + "_types.d.ts"; | |
459 | f_types_ts_.open(f_types_ts_name.c_str()); | |
460 | } | |
461 | ||
462 | // Print header | |
463 | f_types_ << autogen_comment(); | |
464 | ||
465 | if ((gen_node_ || gen_es6_) && no_ns_) { | |
466 | f_types_ << "\"use strict\";" << endl << endl; | |
467 | } | |
468 | ||
469 | f_types_ << js_includes() << endl << render_includes() << endl; | |
470 | ||
471 | if (gen_ts_) { | |
472 | f_types_ts_ << autogen_comment() << ts_includes() << endl << render_ts_includes() << endl; | |
473 | } | |
474 | ||
475 | if (gen_node_) { | |
476 | f_types_ << js_const_type_ << "ttypes = module.exports = {};" << endl; | |
477 | } | |
478 | ||
479 | string pns; | |
480 | ||
481 | // setup the namespace | |
482 | // TODO should the namespace just be in the directory structure for node? | |
483 | vector<string> ns_pieces = js_namespace_pieces(program_); | |
484 | if (ns_pieces.size() > 0) { | |
485 | for (size_t i = 0; i < ns_pieces.size(); ++i) { | |
486 | pns += ((i == 0) ? "" : ".") + ns_pieces[i]; | |
487 | f_types_ << "if (typeof " << pns << " === 'undefined') {" << endl; | |
488 | f_types_ << " " << pns << " = {};" << endl; | |
489 | f_types_ << "}" << endl; | |
490 | f_types_ << "" << "if (typeof module !== 'undefined' && module.exports) {" << endl; | |
491 | f_types_ << " module.exports." << pns << " = " << pns << ";" << endl << "}" << endl; | |
492 | } | |
493 | if (gen_ts_) { | |
494 | ts_module_ = pns; | |
495 | f_types_ts_ << "declare module " << ts_module_ << " {"; | |
496 | } | |
497 | } | |
498 | } | |
499 | ||
500 | /** | |
501 | * Prints standard js imports | |
502 | */ | |
503 | string t_js_generator::js_includes() { | |
504 | if (gen_node_) { | |
505 | string result = js_const_type_ + "thrift = require('thrift');\n" | |
506 | + js_const_type_ + "Thrift = thrift.Thrift;\n"; | |
507 | if (!gen_es6_) { | |
508 | result += js_const_type_ + "Q = thrift.Q;\n"; | |
509 | } | |
510 | result += js_const_type_ + "Int64 = require('node-int64');\n"; | |
511 | return result; | |
512 | } | |
513 | string result = "if (typeof Int64 === 'undefined' && typeof require === 'function') {\n " + js_const_type_ + "Int64 = require('node-int64');\n}\n"; | |
514 | return result; | |
515 | } | |
516 | ||
517 | /** | |
518 | * Prints standard ts imports | |
519 | */ | |
520 | string t_js_generator::ts_includes() { | |
521 | if (gen_node_) { | |
522 | return string( | |
523 | "import thrift = require('thrift');\n" | |
524 | "import Thrift = thrift.Thrift;\n" | |
525 | "import Q = thrift.Q;\n" | |
526 | "import Int64 = require('node-int64');"); | |
527 | } | |
528 | return string("import Int64 = require('node-int64');"); | |
529 | } | |
530 | ||
531 | /** | |
532 | * Prints service ts imports | |
533 | */ | |
534 | string t_js_generator::ts_service_includes() { | |
535 | if (gen_node_) { | |
536 | return string( | |
537 | "import thrift = require('thrift');\n" | |
538 | "import Thrift = thrift.Thrift;\n" | |
539 | "import Q = thrift.Q;\n" | |
540 | "import Int64 = require('node-int64');"); | |
541 | } | |
542 | return string("import Int64 = require('node-int64');"); | |
543 | } | |
544 | ||
545 | /** | |
546 | * Renders all the imports necessary for including another Thrift program | |
547 | */ | |
548 | string t_js_generator::render_includes() { | |
549 | string result = ""; | |
550 | ||
551 | if (gen_node_) { | |
552 | const vector<t_program*>& includes = program_->get_includes(); | |
553 | for (auto include : includes) { | |
554 | result += js_const_type_ + make_valid_nodeJs_identifier(include->get_name()) + "_ttypes = require('" + get_import_path(include) + "');\n"; | |
555 | } | |
556 | if (includes.size() > 0) { | |
557 | result += "\n"; | |
558 | } | |
559 | } | |
560 | ||
561 | return result; | |
562 | } | |
563 | ||
564 | /** | |
565 | * Renders all the imports necessary for including another Thrift program | |
566 | */ | |
567 | string t_js_generator::render_ts_includes() { | |
568 | string result; | |
569 | ||
570 | if (!gen_node_) { | |
571 | return result; | |
572 | } | |
573 | const vector<t_program*>& includes = program_->get_includes(); | |
574 | for (auto include : includes) { | |
575 | string include_name = make_valid_nodeJs_identifier(include->get_name()) + "_ttypes"; | |
576 | include_2_import_name.insert({include, include_name}); | |
577 | result += "import " + include_name + " = require('" + get_import_path(include) + "');\n"; | |
578 | } | |
579 | if (includes.size() > 0) { | |
580 | result += "\n"; | |
581 | } | |
582 | ||
583 | return result; | |
584 | } | |
585 | ||
586 | string t_js_generator::get_import_path(t_program* program) { | |
587 | const string import_file_name(program->get_name() + "_types"); | |
588 | if (program->get_recursive()) { | |
589 | return "./" + import_file_name; | |
590 | } | |
591 | ||
592 | const string import_file_name_with_extension = import_file_name + ".js"; | |
593 | ||
594 | auto module_name_and_import_path_iterator = module_name_2_import_path.find(import_file_name); | |
595 | if (module_name_and_import_path_iterator != module_name_2_import_path.end()) { | |
596 | return module_name_and_import_path_iterator->second; | |
597 | } | |
598 | return "./" + import_file_name; | |
599 | } | |
600 | ||
601 | /** | |
602 | * Close up (or down) some filez. | |
603 | */ | |
604 | void t_js_generator::close_generator() { | |
605 | // Close types file(s) | |
606 | ||
607 | f_types_.close(); | |
608 | ||
609 | if (gen_ts_) { | |
610 | if (!ts_module_.empty()) { | |
611 | f_types_ts_ << "}"; | |
612 | } | |
613 | f_types_ts_.close(); | |
614 | } | |
615 | if (gen_episode_file_){ | |
616 | f_episode_.close(); | |
617 | } | |
618 | } | |
619 | ||
620 | /** | |
621 | * Generates a typedef. This is not done in JS, types are all implicit. | |
622 | * | |
623 | * @param ttypedef The type definition | |
624 | */ | |
625 | void t_js_generator::generate_typedef(t_typedef* ttypedef) { | |
626 | (void)ttypedef; | |
627 | } | |
628 | ||
629 | /** | |
630 | * Generates code for an enumerated type. Since define is expensive to lookup | |
631 | * in JS, we use a global array for this. | |
632 | * | |
633 | * @param tenum The enumeration | |
634 | */ | |
635 | void t_js_generator::generate_enum(t_enum* tenum) { | |
636 | f_types_ << js_type_namespace(tenum->get_program()) << tenum->get_name() << " = {" << endl; | |
637 | ||
638 | if (gen_ts_) { | |
639 | f_types_ts_ << ts_print_doc(tenum) << ts_indent() << ts_declare() << "enum " | |
640 | << tenum->get_name() << " {" << endl; | |
641 | } | |
642 | ||
643 | indent_up(); | |
644 | ||
645 | vector<t_enum_value*> const& constants = tenum->get_constants(); | |
646 | vector<t_enum_value*>::const_iterator c_iter; | |
647 | for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { | |
648 | int value = (*c_iter)->get_value(); | |
649 | if (gen_ts_) { | |
650 | f_types_ts_ << ts_indent() << (*c_iter)->get_name() << " = " << value << "," << endl; | |
651 | // add 'value: key' in addition to 'key: value' for TypeScript enums | |
652 | f_types_ << indent() << "'" << value << "' : '" << (*c_iter)->get_name() << "'," << endl; | |
653 | } | |
654 | f_types_ << indent() << "'" << (*c_iter)->get_name() << "' : " << value; | |
655 | if (c_iter != constants.end() - 1) { | |
656 | f_types_ << ","; | |
657 | } | |
658 | f_types_ << endl; | |
659 | } | |
660 | ||
661 | indent_down(); | |
662 | ||
663 | f_types_ << "};" << endl; | |
664 | ||
665 | if (gen_ts_) { | |
666 | f_types_ts_ << ts_indent() << "}" << endl; | |
667 | } | |
668 | } | |
669 | ||
670 | /** | |
671 | * Generate a constant value | |
672 | */ | |
673 | void t_js_generator::generate_const(t_const* tconst) { | |
674 | t_type* type = tconst->get_type(); | |
675 | string name = tconst->get_name(); | |
676 | t_const_value* value = tconst->get_value(); | |
677 | ||
678 | f_types_ << js_type_namespace(program_) << name << " = "; | |
679 | f_types_ << render_const_value(type, value) << ";" << endl; | |
680 | ||
681 | if (gen_ts_) { | |
682 | f_types_ts_ << ts_print_doc(tconst) << ts_indent() << ts_declare() << js_const_type_ << name << ": " | |
683 | << ts_get_type(type) << ";" << endl; | |
684 | } | |
685 | } | |
686 | ||
687 | /** | |
688 | * Prints the value of a constant with the given type. Note that type checking | |
689 | * is NOT performed in this function as it is always run beforehand using the | |
690 | * validate_types method in main.cc | |
691 | */ | |
692 | string t_js_generator::render_const_value(t_type* type, t_const_value* value) { | |
693 | std::ostringstream out; | |
694 | ||
695 | type = get_true_type(type); | |
696 | ||
697 | if (type->is_base_type()) { | |
698 | t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); | |
699 | switch (tbase) { | |
700 | case t_base_type::TYPE_STRING: | |
701 | out << "'" << get_escaped_string(value) << "'"; | |
702 | break; | |
703 | case t_base_type::TYPE_BOOL: | |
704 | out << (value->get_integer() > 0 ? "true" : "false"); | |
705 | break; | |
706 | case t_base_type::TYPE_I8: | |
707 | case t_base_type::TYPE_I16: | |
708 | case t_base_type::TYPE_I32: | |
709 | out << value->get_integer(); | |
710 | break; | |
711 | case t_base_type::TYPE_I64: | |
712 | { | |
713 | int64_t const& integer_value = value->get_integer(); | |
714 | if (integer_value <= max_safe_integer && integer_value >= min_safe_integer) { | |
715 | out << "new Int64(" << integer_value << ")"; | |
716 | } else { | |
717 | out << "new Int64('" << std::hex << integer_value << std::dec << "')"; | |
718 | } | |
719 | } | |
720 | break; | |
721 | case t_base_type::TYPE_DOUBLE: | |
722 | if (value->get_type() == t_const_value::CV_INTEGER) { | |
723 | out << value->get_integer(); | |
724 | } else { | |
725 | out << emit_double_as_string(value->get_double()); | |
726 | } | |
727 | break; | |
728 | default: | |
729 | throw std::runtime_error("compiler error: no const of base type " + t_base_type::t_base_name(tbase)); | |
730 | } | |
731 | } else if (type->is_enum()) { | |
732 | out << value->get_integer(); | |
733 | } else if (type->is_struct() || type->is_xception()) { | |
734 | out << "new " << js_type_namespace(type->get_program()) << type->get_name() << "({"; | |
735 | indent_up(); | |
736 | const vector<t_field*>& fields = ((t_struct*)type)->get_members(); | |
737 | vector<t_field*>::const_iterator f_iter; | |
738 | const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); | |
739 | map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; | |
740 | for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { | |
741 | t_type* field_type = NULL; | |
742 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
743 | if ((*f_iter)->get_name() == v_iter->first->get_string()) { | |
744 | field_type = (*f_iter)->get_type(); | |
745 | } | |
746 | } | |
747 | if (field_type == NULL) { | |
748 | throw std::runtime_error("type error: " + type->get_name() + " has no field " + v_iter->first->get_string()); | |
749 | } | |
750 | if (v_iter != val.begin()) | |
751 | out << ","; | |
752 | out << endl << indent() << render_const_value(g_type_string, v_iter->first); | |
753 | out << " : "; | |
754 | out << render_const_value(field_type, v_iter->second); | |
755 | } | |
756 | indent_down(); | |
757 | out << endl << indent() << "})"; | |
758 | } else if (type->is_map()) { | |
759 | t_type* ktype = ((t_map*)type)->get_key_type(); | |
760 | ||
761 | t_type* vtype = ((t_map*)type)->get_val_type(); | |
762 | out << "{" << endl; | |
763 | indent_up(); | |
764 | ||
765 | const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); | |
766 | map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; | |
767 | for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { | |
768 | if (v_iter != val.begin()) | |
769 | out << "," << endl; | |
770 | ||
771 | if (ktype->is_base_type() && ((t_base_type*)get_true_type(ktype))->get_base() == t_base_type::TYPE_I64){ | |
772 | out << indent() << "\"" << v_iter->first->get_integer() << "\""; | |
773 | } else { | |
774 | out << indent() << render_const_value(ktype, v_iter->first); | |
775 | } | |
776 | ||
777 | out << " : "; | |
778 | out << render_const_value(vtype, v_iter->second); | |
779 | } | |
780 | indent_down(); | |
781 | out << endl << indent() << "}"; | |
782 | } else if (type->is_list() || type->is_set()) { | |
783 | t_type* etype; | |
784 | if (type->is_list()) { | |
785 | etype = ((t_list*)type)->get_elem_type(); | |
786 | } else { | |
787 | etype = ((t_set*)type)->get_elem_type(); | |
788 | } | |
789 | out << "["; | |
790 | const vector<t_const_value*>& val = value->get_list(); | |
791 | vector<t_const_value*>::const_iterator v_iter; | |
792 | for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { | |
793 | if (v_iter != val.begin()) | |
794 | out << ","; | |
795 | out << render_const_value(etype, *v_iter); | |
796 | } | |
797 | out << "]"; | |
798 | } | |
799 | return out.str(); | |
800 | } | |
801 | ||
802 | /** | |
803 | * Make a struct | |
804 | */ | |
805 | void t_js_generator::generate_struct(t_struct* tstruct) { | |
806 | generate_js_struct(tstruct, false); | |
807 | } | |
808 | ||
809 | /** | |
810 | * Generates a struct definition for a thrift exception. Basically the same | |
811 | * as a struct but extends the Exception class. | |
812 | * | |
813 | * @param txception The struct definition | |
814 | */ | |
815 | void t_js_generator::generate_xception(t_struct* txception) { | |
816 | generate_js_struct(txception, true); | |
817 | } | |
818 | ||
819 | /** | |
820 | * Structs can be normal or exceptions. | |
821 | */ | |
822 | void t_js_generator::generate_js_struct(t_struct* tstruct, bool is_exception) { | |
823 | generate_js_struct_definition(f_types_, tstruct, is_exception); | |
824 | } | |
825 | ||
826 | /** | |
827 | * Return type of contained elements for a container type. For maps | |
828 | * this is type of value (keys are always strings in js) | |
829 | */ | |
830 | t_type* t_js_generator::get_contained_type(t_type* t) { | |
831 | t_type* etype; | |
832 | if (t->is_list()) { | |
833 | etype = ((t_list*)t)->get_elem_type(); | |
834 | } else if (t->is_set()) { | |
835 | etype = ((t_set*)t)->get_elem_type(); | |
836 | } else { | |
837 | etype = ((t_map*)t)->get_val_type(); | |
838 | } | |
839 | return etype; | |
840 | } | |
841 | ||
842 | /** | |
843 | * Generates a struct definition for a thrift data type. This is nothing in JS | |
844 | * where the objects are all just associative arrays (unless of course we | |
845 | * decide to start using objects for them...) | |
846 | * | |
847 | * @param tstruct The struct definition | |
848 | */ | |
849 | void t_js_generator::generate_js_struct_definition(ostream& out, | |
850 | t_struct* tstruct, | |
851 | bool is_exception, | |
852 | bool is_exported) { | |
853 | const vector<t_field*>& members = tstruct->get_members(); | |
854 | vector<t_field*>::const_iterator m_iter; | |
855 | ||
856 | if (gen_node_) { | |
857 | string prefix = has_js_namespace(tstruct->get_program()) ? js_namespace(tstruct->get_program()) : js_const_type_; | |
858 | out << prefix << tstruct->get_name() << | |
859 | (is_exported ? " = module.exports." + tstruct->get_name() : ""); | |
860 | if (gen_ts_) { | |
861 | f_types_ts_ << ts_print_doc(tstruct) << ts_indent() << ts_declare() << "class " | |
862 | << tstruct->get_name() << (is_exception ? " extends Thrift.TException" : "") | |
863 | << " {" << endl; | |
864 | } | |
865 | } else { | |
866 | out << js_namespace(tstruct->get_program()) << tstruct->get_name(); | |
867 | if (gen_ts_) { | |
868 | f_types_ts_ << ts_print_doc(tstruct) << ts_indent() << ts_declare() << "class " | |
869 | << tstruct->get_name() << (is_exception ? " extends Thrift.TException" : "") | |
870 | << " {" << endl; | |
871 | } | |
872 | } | |
873 | ||
874 | if (gen_es6_) { | |
875 | if (gen_node_ && is_exception) { | |
876 | out << " = class extends Thrift.TException {" << endl; | |
877 | } else { | |
878 | out << " = class {" << endl; | |
879 | } | |
880 | indent_up(); | |
881 | indent(out) << "constructor(args) {" << endl; | |
882 | } else { | |
883 | out << " = function(args) {" << endl; | |
884 | } | |
885 | ||
886 | indent_up(); | |
887 | ||
888 | // Call super() method on inherited Error class | |
889 | if (gen_node_ && is_exception) { | |
890 | if (gen_es6_) { | |
891 | indent(out) << "super(args);" << endl; | |
892 | } else { | |
893 | indent(out) << "Thrift.TException.call(this, \"" << js_namespace(tstruct->get_program()) | |
894 | << tstruct->get_name() << "\");" << endl; | |
895 | } | |
896 | out << indent() << "this.name = \"" << js_namespace(tstruct->get_program()) | |
897 | << tstruct->get_name() << "\";" << endl; | |
898 | } | |
899 | ||
900 | // members with arguments | |
901 | for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { | |
902 | string dval = declare_field(*m_iter, false, true); | |
903 | t_type* t = get_true_type((*m_iter)->get_type()); | |
904 | if ((*m_iter)->get_value() != NULL && !(t->is_struct() || t->is_xception())) { | |
905 | dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value()); | |
906 | out << indent() << "this." << (*m_iter)->get_name() << " = " << dval << ";" << endl; | |
907 | } else { | |
908 | out << indent() << dval << ";" << endl; | |
909 | } | |
910 | if (gen_ts_) { | |
911 | if (gen_node_) { | |
912 | f_types_ts_ << ts_indent() << "public " << (*m_iter)->get_name() << ": " | |
913 | << ts_get_type((*m_iter)->get_type()) << ";" << endl; | |
914 | } else { | |
915 | f_types_ts_ << ts_indent() << (*m_iter)->get_name() << ": " | |
916 | << ts_get_type((*m_iter)->get_type()) << ";" << endl; | |
917 | } | |
918 | } | |
919 | } | |
920 | ||
921 | // Generate constructor from array | |
922 | if (members.size() > 0) { | |
923 | ||
924 | for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { | |
925 | t_type* t = get_true_type((*m_iter)->get_type()); | |
926 | if ((*m_iter)->get_value() != NULL && (t->is_struct() || t->is_xception())) { | |
927 | indent(out) << "this." << (*m_iter)->get_name() << " = " | |
928 | << render_const_value(t, (*m_iter)->get_value()) << ";" << endl; | |
929 | } | |
930 | } | |
931 | ||
932 | // Early returns for exceptions | |
933 | for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { | |
934 | t_type* t = get_true_type((*m_iter)->get_type()); | |
935 | if (t->is_xception()) { | |
936 | out << indent() << "if (args instanceof " << js_type_namespace(t->get_program()) | |
937 | << t->get_name() << ") {" << endl << indent() << indent() << "this." | |
938 | << (*m_iter)->get_name() << " = args;" << endl << indent() << indent() << "return;" | |
939 | << endl << indent() << "}" << endl; | |
940 | } | |
941 | } | |
942 | ||
943 | indent(out) << "if (args) {" << endl; | |
944 | indent_up(); | |
945 | if (gen_ts_) { | |
946 | f_types_ts_ << endl << ts_indent() << "constructor(args?: { "; | |
947 | } | |
948 | ||
949 | for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { | |
950 | t_type* t = get_true_type((*m_iter)->get_type()); | |
951 | indent(out) << "if (args." << (*m_iter)->get_name() << " !== undefined && args." << (*m_iter)->get_name() << " !== null) {" << endl; | |
952 | indent_up(); | |
953 | indent(out) << "this." << (*m_iter)->get_name(); | |
954 | ||
955 | if (t->is_struct()) { | |
956 | out << (" = new " + js_type_namespace(t->get_program()) + t->get_name() + | |
957 | "(args."+(*m_iter)->get_name() +");"); | |
958 | out << endl; | |
959 | } else if (t->is_container()) { | |
960 | t_type* etype = get_contained_type(t); | |
961 | string copyFunc = t->is_map() ? "Thrift.copyMap" : "Thrift.copyList"; | |
962 | string type_list = ""; | |
963 | ||
964 | while (etype->is_container()) { | |
965 | if (type_list.length() > 0) { | |
966 | type_list += ", "; | |
967 | } | |
968 | type_list += etype->is_map() ? "Thrift.copyMap" : "Thrift.copyList"; | |
969 | etype = get_contained_type(etype); | |
970 | } | |
971 | ||
972 | if (etype->is_struct()) { | |
973 | if (type_list.length() > 0) { | |
974 | type_list += ", "; | |
975 | } | |
976 | type_list += js_type_namespace(etype->get_program()) + etype->get_name(); | |
977 | } | |
978 | else { | |
979 | if (type_list.length() > 0) { | |
980 | type_list += ", "; | |
981 | } | |
982 | type_list += "null"; | |
983 | } | |
984 | ||
985 | out << (" = " + copyFunc + "(args." + (*m_iter)->get_name() + | |
986 | ", [" + type_list + "]);"); | |
987 | out << endl; | |
988 | } else { | |
989 | out << " = args." << (*m_iter)->get_name() << ";" << endl; | |
990 | } | |
991 | ||
992 | indent_down(); | |
993 | if (!(*m_iter)->get_req()) { | |
994 | indent(out) << "} else {" << endl; | |
995 | indent(out) | |
996 | << " throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.UNKNOWN, " | |
997 | "'Required field " << (*m_iter)->get_name() << " is unset!');" << endl; | |
998 | } | |
999 | indent(out) << "}" << endl; | |
1000 | if (gen_ts_) { | |
1001 | f_types_ts_ << (*m_iter)->get_name() << ts_get_req(*m_iter) << ": " | |
1002 | << ts_get_type((*m_iter)->get_type()) << "; "; | |
1003 | } | |
1004 | } | |
1005 | indent_down(); | |
1006 | out << indent() << "}" << endl; | |
1007 | if (gen_ts_) { | |
1008 | f_types_ts_ << "});" << endl; | |
1009 | } | |
1010 | } | |
1011 | ||
1012 | // Done with constructor | |
1013 | indent_down(); | |
1014 | if (gen_es6_) { | |
1015 | indent(out) << "}" << endl << endl; | |
1016 | } else { | |
1017 | indent(out) << "};" << endl; | |
1018 | } | |
1019 | ||
1020 | if (gen_ts_) { | |
1021 | f_types_ts_ << ts_indent() << "}" << endl; | |
1022 | } | |
1023 | ||
1024 | if (!gen_es6_) { | |
1025 | if (is_exception) { | |
1026 | out << "Thrift.inherits(" << js_namespace(tstruct->get_program()) << tstruct->get_name() | |
1027 | << ", Thrift.TException);" << endl; | |
1028 | out << js_namespace(tstruct->get_program()) << tstruct->get_name() << ".prototype.name = '" | |
1029 | << tstruct->get_name() << "';" << endl; | |
1030 | } else { | |
1031 | // init prototype manually if we aren't using es6 | |
1032 | out << js_namespace(tstruct->get_program()) << tstruct->get_name() << ".prototype = {};" | |
1033 | << endl; | |
1034 | } | |
1035 | ||
1036 | } | |
1037 | ||
1038 | generate_js_struct_reader(out, tstruct); | |
1039 | generate_js_struct_writer(out, tstruct); | |
1040 | ||
1041 | // Close out the class definition | |
1042 | if (gen_es6_) { | |
1043 | indent_down(); | |
1044 | indent(out) << "};" << endl; | |
1045 | } | |
1046 | } | |
1047 | ||
1048 | /** | |
1049 | * Generates the read() method for a struct | |
1050 | */ | |
1051 | void t_js_generator::generate_js_struct_reader(ostream& out, t_struct* tstruct) { | |
1052 | const vector<t_field*>& fields = tstruct->get_members(); | |
1053 | vector<t_field*>::const_iterator f_iter; | |
1054 | ||
1055 | if (gen_es6_) { | |
1056 | indent(out) << "read (input) {" << endl; | |
1057 | } else { | |
1058 | indent(out) << js_namespace(tstruct->get_program()) << tstruct->get_name() | |
1059 | << ".prototype.read = function(input) {" << endl; | |
1060 | } | |
1061 | ||
1062 | indent_up(); | |
1063 | ||
1064 | indent(out) << "input.readStructBegin();" << endl; | |
1065 | ||
1066 | // Loop over reading in fields | |
1067 | indent(out) << "while (true) {" << endl; | |
1068 | ||
1069 | indent_up(); | |
1070 | ||
1071 | indent(out) << js_const_type_ << "ret = input.readFieldBegin();" << endl; | |
1072 | indent(out) << js_const_type_ << "ftype = ret.ftype;" << endl; | |
1073 | if (!fields.empty()) { | |
1074 | indent(out) << js_const_type_ << "fid = ret.fid;" << endl; | |
1075 | } | |
1076 | ||
1077 | // Check for field STOP marker and break | |
1078 | indent(out) << "if (ftype == Thrift.Type.STOP) {" << endl; | |
1079 | indent_up(); | |
1080 | indent(out) << "break;" << endl; | |
1081 | indent_down(); | |
1082 | indent(out) << "}" << endl; | |
1083 | if (!fields.empty()) { | |
1084 | // Switch statement on the field we are reading | |
1085 | indent(out) << "switch (fid) {" << endl; | |
1086 | ||
1087 | indent_up(); | |
1088 | ||
1089 | // Generate deserialization code for known cases | |
1090 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
1091 | ||
1092 | indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; | |
1093 | indent(out) << "if (ftype == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; | |
1094 | ||
1095 | indent_up(); | |
1096 | generate_deserialize_field(out, *f_iter, "this."); | |
1097 | indent_down(); | |
1098 | ||
1099 | indent(out) << "} else {" << endl; | |
1100 | ||
1101 | indent(out) << " input.skip(ftype);" << endl; | |
1102 | ||
1103 | out << indent() << "}" << endl << indent() << "break;" << endl; | |
1104 | } | |
1105 | if (fields.size() == 1) { | |
1106 | // pseudo case to make jslint happy | |
1107 | indent(out) << "case 0:" << endl; | |
1108 | indent(out) << " input.skip(ftype);" << endl; | |
1109 | indent(out) << " break;" << endl; | |
1110 | } | |
1111 | // In the default case we skip the field | |
1112 | indent(out) << "default:" << endl; | |
1113 | indent(out) << " input.skip(ftype);" << endl; | |
1114 | ||
1115 | scope_down(out); | |
1116 | } else { | |
1117 | indent(out) << "input.skip(ftype);" << endl; | |
1118 | } | |
1119 | ||
1120 | indent(out) << "input.readFieldEnd();" << endl; | |
1121 | ||
1122 | scope_down(out); | |
1123 | ||
1124 | indent(out) << "input.readStructEnd();" << endl; | |
1125 | ||
1126 | indent(out) << "return;" << endl; | |
1127 | ||
1128 | indent_down(); | |
1129 | ||
1130 | if (gen_es6_) { | |
1131 | indent(out) << "}" << endl << endl; | |
1132 | } else { | |
1133 | indent(out) << "};" << endl << endl; | |
1134 | } | |
1135 | } | |
1136 | ||
1137 | /** | |
1138 | * Generates the write() method for a struct | |
1139 | */ | |
1140 | void t_js_generator::generate_js_struct_writer(ostream& out, t_struct* tstruct) { | |
1141 | string name = tstruct->get_name(); | |
1142 | const vector<t_field*>& fields = tstruct->get_members(); | |
1143 | vector<t_field*>::const_iterator f_iter; | |
1144 | ||
1145 | if (gen_es6_) { | |
1146 | indent(out) << "write (output) {" << endl; | |
1147 | } else { | |
1148 | indent(out) << js_namespace(tstruct->get_program()) << tstruct->get_name() | |
1149 | << ".prototype.write = function(output) {" << endl; | |
1150 | } | |
1151 | ||
1152 | indent_up(); | |
1153 | ||
1154 | indent(out) << "output.writeStructBegin('" << name << "');" << endl; | |
1155 | ||
1156 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
1157 | out << indent() << "if (this." << (*f_iter)->get_name() << " !== null && this." | |
1158 | << (*f_iter)->get_name() << " !== undefined) {" << endl; | |
1159 | indent_up(); | |
1160 | ||
1161 | indent(out) << "output.writeFieldBegin(" | |
1162 | << "'" << (*f_iter)->get_name() << "', " << type_to_enum((*f_iter)->get_type()) | |
1163 | << ", " << (*f_iter)->get_key() << ");" << endl; | |
1164 | ||
1165 | // Write field contents | |
1166 | generate_serialize_field(out, *f_iter, "this."); | |
1167 | ||
1168 | indent(out) << "output.writeFieldEnd();" << endl; | |
1169 | ||
1170 | indent_down(); | |
1171 | indent(out) << "}" << endl; | |
1172 | } | |
1173 | ||
1174 | out << indent() << "output.writeFieldStop();" << endl << indent() << "output.writeStructEnd();" | |
1175 | << endl; | |
1176 | ||
1177 | out << indent() << "return;" << endl; | |
1178 | ||
1179 | indent_down(); | |
1180 | if (gen_es6_) { | |
1181 | out << indent() << "}" << endl << endl; | |
1182 | } else { | |
1183 | out << indent() << "};" << endl << endl; | |
1184 | } | |
1185 | } | |
1186 | ||
1187 | /** | |
1188 | * Generates a thrift service. | |
1189 | * | |
1190 | * @param tservice The service definition | |
1191 | */ | |
1192 | void t_js_generator::generate_service(t_service* tservice) { | |
1193 | string f_service_name = get_out_dir() + service_name_ + ".js"; | |
1194 | f_service_.open(f_service_name.c_str()); | |
1195 | if (gen_episode_file_) { | |
1196 | f_episode_ << service_name_ << ":" << thrift_package_output_directory_ << "/" << service_name_ << endl; | |
1197 | } | |
1198 | ||
1199 | if (gen_ts_) { | |
1200 | string f_service_ts_name = get_out_dir() + service_name_ + ".d.ts"; | |
1201 | f_service_ts_.open(f_service_ts_name.c_str()); | |
1202 | } | |
1203 | ||
1204 | f_service_ << autogen_comment(); | |
1205 | ||
1206 | if ((gen_node_ || gen_es6_) && no_ns_) { | |
1207 | f_service_ << "\"use strict\";" << endl << endl; | |
1208 | } | |
1209 | ||
1210 | f_service_ << js_includes() << endl << render_includes() << endl; | |
1211 | ||
1212 | if (gen_ts_) { | |
1213 | if (tservice->get_extends() != NULL) { | |
1214 | f_service_ts_ << "/// <reference path=\"" << tservice->get_extends()->get_name() | |
1215 | << ".d.ts\" />" << endl; | |
1216 | } | |
1217 | f_service_ts_ << autogen_comment() << endl << ts_includes() << endl << render_ts_includes() << endl; | |
1218 | if (gen_node_) { | |
1219 | f_service_ts_ << "import ttypes = require('./" + program_->get_name() + "_types');" << endl; | |
1220 | // Generate type aliases | |
1221 | // enum | |
1222 | vector<t_enum*> const& enums = program_->get_enums(); | |
1223 | vector<t_enum*>::const_iterator e_iter; | |
1224 | for (e_iter = enums.begin(); e_iter != enums.end(); ++e_iter) { | |
1225 | f_service_ts_ << "import " << (*e_iter)->get_name() << " = ttypes." | |
1226 | << js_namespace(program_) << (*e_iter)->get_name() << endl; | |
1227 | } | |
1228 | // const | |
1229 | vector<t_const*> const& consts = program_->get_consts(); | |
1230 | vector<t_const*>::const_iterator c_iter; | |
1231 | for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { | |
1232 | f_service_ts_ << "import " << (*c_iter)->get_name() << " = ttypes." | |
1233 | << js_namespace(program_) << (*c_iter)->get_name() << endl; | |
1234 | } | |
1235 | // exception | |
1236 | vector<t_struct*> const& exceptions = program_->get_xceptions(); | |
1237 | vector<t_struct*>::const_iterator x_iter; | |
1238 | for (x_iter = exceptions.begin(); x_iter != exceptions.end(); ++x_iter) { | |
1239 | f_service_ts_ << "import " << (*x_iter)->get_name() << " = ttypes." | |
1240 | << js_namespace(program_) << (*x_iter)->get_name() << endl; | |
1241 | } | |
1242 | // structs | |
1243 | vector<t_struct*> const& structs = program_->get_structs(); | |
1244 | vector<t_struct*>::const_iterator s_iter; | |
1245 | for (s_iter = structs.begin(); s_iter != structs.end(); ++s_iter) { | |
1246 | f_service_ts_ << "import " << (*s_iter)->get_name() << " = ttypes." | |
1247 | << js_namespace(program_) << (*s_iter)->get_name() << endl; | |
1248 | } | |
1249 | } else { | |
1250 | f_service_ts_ << "import { " << program_->get_name() << " } from \"./" << program_->get_name() << "_types\";" << endl << endl; | |
1251 | } | |
1252 | if (!ts_module_.empty()) { | |
1253 | if (gen_node_) { | |
1254 | f_service_ts_ << "declare module " << ts_module_ << " {"; | |
1255 | } else { | |
1256 | f_service_ts_ << "declare module \"./" << program_->get_name() << "_types\" {" << endl; | |
1257 | indent_up(); | |
1258 | f_service_ts_ << ts_indent() << "module " << program_->get_name() << " {" << endl; | |
1259 | indent_up(); | |
1260 | } | |
1261 | } | |
1262 | } | |
1263 | ||
1264 | if (gen_node_) { | |
1265 | if (tservice->get_extends() != NULL) { | |
1266 | f_service_ << js_const_type_ << tservice->get_extends()->get_name() << " = require('./" | |
1267 | << tservice->get_extends()->get_name() << "');" << endl << js_const_type_ | |
1268 | << tservice->get_extends()->get_name() | |
1269 | << "Client = " << tservice->get_extends()->get_name() << ".Client;" << endl | |
1270 | << js_const_type_ << tservice->get_extends()->get_name() | |
1271 | << "Processor = " << tservice->get_extends()->get_name() << ".Processor;" << endl; | |
1272 | } | |
1273 | ||
1274 | f_service_ << js_const_type_ << "ttypes = require('./" + program_->get_name() + "_types');" << endl; | |
1275 | } | |
1276 | ||
1277 | generate_service_helpers(tservice); | |
1278 | generate_service_interface(tservice); | |
1279 | generate_service_client(tservice); | |
1280 | ||
1281 | if (gen_node_) { | |
1282 | generate_service_processor(tservice); | |
1283 | } | |
1284 | ||
1285 | f_service_.close(); | |
1286 | if (gen_ts_) { | |
1287 | if (!ts_module_.empty()) { | |
1288 | if (gen_node_) { | |
1289 | f_service_ts_ << "}" << endl; | |
1290 | } else { | |
1291 | indent_down(); | |
1292 | f_service_ts_ << ts_indent() << "}" << endl; | |
1293 | f_service_ts_ << "}" << endl; | |
1294 | } | |
1295 | } | |
1296 | f_service_ts_.close(); | |
1297 | } | |
1298 | } | |
1299 | ||
1300 | /** | |
1301 | * Generates a service server definition. | |
1302 | * | |
1303 | * @param tservice The service to generate a server for. | |
1304 | */ | |
1305 | void t_js_generator::generate_service_processor(t_service* tservice) { | |
1306 | vector<t_function*> functions = tservice->get_functions(); | |
1307 | vector<t_function*>::iterator f_iter; | |
1308 | ||
1309 | if (gen_node_) { | |
1310 | string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_; | |
1311 | f_service_ << prefix << service_name_ << "Processor = " << "exports.Processor"; | |
1312 | if (gen_ts_) { | |
1313 | f_service_ts_ << endl << "declare class Processor "; | |
1314 | if (tservice->get_extends() != NULL) { | |
1315 | f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Processor "; | |
1316 | } | |
1317 | f_service_ts_ << "{" << endl; | |
1318 | indent_up(); | |
1319 | f_service_ts_ << ts_indent() << "private _handler: object;" << endl << endl; | |
1320 | f_service_ts_ << ts_indent() << "constructor(handler: object);" << endl; | |
1321 | f_service_ts_ << ts_indent() << "process(input: thrift.TProtocol, output: thrift.TProtocol): void;" << endl; | |
1322 | indent_down(); | |
1323 | } | |
1324 | } else { | |
1325 | f_service_ << js_namespace(tservice->get_program()) << service_name_ << "Processor = " | |
1326 | << "exports.Processor"; | |
1327 | } | |
1328 | ||
1329 | bool is_subclass_service = tservice->get_extends() != NULL; | |
1330 | ||
1331 | // ES6 Constructor | |
1332 | if (gen_es6_) { | |
1333 | if (is_subclass_service) { | |
1334 | f_service_ << " = class extends " << tservice->get_extends()->get_name() << "Processor {" << endl; | |
1335 | } else { | |
1336 | f_service_ << " = class {" << endl; | |
1337 | } | |
1338 | indent_up(); | |
1339 | indent(f_service_) << "constructor(handler) {" << endl; | |
1340 | } else { | |
1341 | f_service_ << " = function(handler) {" << endl; | |
1342 | } | |
1343 | ||
1344 | indent_up(); | |
1345 | if (gen_es6_ && is_subclass_service) { | |
1346 | indent(f_service_) << "super(handler);" << endl; | |
1347 | } | |
1348 | indent(f_service_) << "this._handler = handler;" << endl; | |
1349 | indent_down(); | |
1350 | ||
1351 | // Done with constructor | |
1352 | if (gen_es6_) { | |
1353 | indent(f_service_) << "}" << endl; | |
1354 | } else { | |
1355 | indent(f_service_) << "};" << endl; | |
1356 | } | |
1357 | ||
1358 | // ES5 service inheritance | |
1359 | if (!gen_es6_ && is_subclass_service) { | |
1360 | indent(f_service_) << "Thrift.inherits(" << js_namespace(tservice->get_program()) | |
1361 | << service_name_ << "Processor, " << tservice->get_extends()->get_name() | |
1362 | << "Processor);" << endl; | |
1363 | } | |
1364 | ||
1365 | // Generate the server implementation | |
1366 | if (gen_es6_) { | |
1367 | indent(f_service_) << "process (input, output) {" << endl; | |
1368 | } else { | |
1369 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ | |
1370 | << "Processor.prototype.process = function(input, output) {" << endl; | |
1371 | } | |
1372 | ||
1373 | indent_up(); | |
1374 | ||
1375 | indent(f_service_) << js_const_type_ << "r = input.readMessageBegin();" << endl << indent() | |
1376 | << "if (this['process_' + r.fname]) {" << endl << indent() | |
1377 | << " return this['process_' + r.fname].call(this, r.rseqid, input, output);" << endl | |
1378 | << indent() << "} else {" << endl << indent() << " input.skip(Thrift.Type.STRUCT);" | |
1379 | << endl << indent() << " input.readMessageEnd();" << endl << indent() | |
1380 | << " " << js_const_type_ << "x = new " | |
1381 | "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN_METHOD, " | |
1382 | "'Unknown function ' + r.fname);" << endl << indent() | |
1383 | << " output.writeMessageBegin(r.fname, Thrift.MessageType.EXCEPTION, r.rseqid);" | |
1384 | << endl << indent() << " x.write(output);" << endl << indent() | |
1385 | << " output.writeMessageEnd();" << endl << indent() << " output.flush();" << endl | |
1386 | << indent() << "}" << endl; | |
1387 | ||
1388 | indent_down(); | |
1389 | if (gen_es6_) { | |
1390 | indent(f_service_) << "}" << endl; | |
1391 | } else { | |
1392 | indent(f_service_) << "};" << endl; | |
1393 | } | |
1394 | ||
1395 | // Generate the process subfunctions | |
1396 | for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { | |
1397 | generate_process_function(tservice, *f_iter); | |
1398 | } | |
1399 | ||
1400 | // Close off the processor class definition | |
1401 | if (gen_es6_) { | |
1402 | indent_down(); | |
1403 | indent(f_service_) << "};" << endl; | |
1404 | } | |
1405 | if (gen_node_ && gen_ts_) { | |
1406 | f_service_ts_ << "}" << endl; | |
1407 | } | |
1408 | } | |
1409 | ||
1410 | /** | |
1411 | * Generates a process function definition. | |
1412 | * | |
1413 | * @param tfunction The function to write a dispatcher for | |
1414 | */ | |
1415 | void t_js_generator::generate_process_function(t_service* tservice, t_function* tfunction) { | |
1416 | if (gen_es6_) { | |
1417 | indent(f_service_) << "process_" + tfunction->get_name() + " (seqid, input, output) {" << endl; | |
1418 | } else { | |
1419 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ | |
1420 | << "Processor.prototype.process_" + tfunction->get_name() | |
1421 | + " = function(seqid, input, output) {" << endl; | |
1422 | } | |
1423 | if (gen_ts_) { | |
1424 | indent_up(); | |
1425 | f_service_ts_ << ts_indent() << "process_" << tfunction->get_name() << "(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;" << endl; | |
1426 | indent_down(); | |
1427 | } | |
1428 | ||
1429 | indent_up(); | |
1430 | ||
1431 | string argsname = js_namespace(program_) + service_name_ + "_" + tfunction->get_name() + "_args"; | |
1432 | string resultname = js_namespace(program_) + service_name_ + "_" + tfunction->get_name() | |
1433 | + "_result"; | |
1434 | ||
1435 | indent(f_service_) << js_const_type_ << "args = new " << argsname << "();" << endl << indent() | |
1436 | << "args.read(input);" << endl << indent() << "input.readMessageEnd();" << endl; | |
1437 | ||
1438 | // Generate the function call | |
1439 | t_struct* arg_struct = tfunction->get_arglist(); | |
1440 | const std::vector<t_field*>& fields = arg_struct->get_members(); | |
1441 | vector<t_field*>::const_iterator f_iter; | |
1442 | ||
1443 | // Shortcut out here for oneway functions | |
1444 | if (tfunction->is_oneway()) { | |
1445 | indent(f_service_) << "this._handler." << tfunction->get_name() << "("; | |
1446 | ||
1447 | bool first = true; | |
1448 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
1449 | if (first) { | |
1450 | first = false; | |
1451 | } else { | |
1452 | f_service_ << ", "; | |
1453 | } | |
1454 | f_service_ << "args." << (*f_iter)->get_name(); | |
1455 | } | |
1456 | ||
1457 | f_service_ << ");" << endl; | |
1458 | indent_down(); | |
1459 | ||
1460 | if (gen_es6_) { | |
1461 | indent(f_service_) << "}" << endl; | |
1462 | } else { | |
1463 | indent(f_service_) << "};" << endl; | |
1464 | } | |
1465 | return; | |
1466 | } | |
1467 | ||
1468 | // Promise style invocation | |
1469 | indent(f_service_) << "if (this._handler." << tfunction->get_name() | |
1470 | << ".length === " << fields.size() << ") {" << endl; | |
1471 | indent_up(); | |
1472 | ||
1473 | if (gen_es6_) { | |
1474 | indent(f_service_) << "Promise.resolve(this._handler." << tfunction->get_name() << ".bind(this._handler)(" << endl; | |
1475 | } else { | |
1476 | string maybeComma = (fields.size() > 0 ? "," : ""); | |
1477 | indent(f_service_) << "Q.fcall(this._handler." << tfunction->get_name() << ".bind(this._handler)" | |
1478 | << maybeComma << endl; | |
1479 | } | |
1480 | ||
1481 | indent_up(); | |
1482 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
1483 | string maybeComma = (f_iter != fields.end() - 1 ? "," : ""); | |
1484 | indent(f_service_) << "args." << (*f_iter)->get_name() << maybeComma << endl; | |
1485 | } | |
1486 | indent_down(); | |
1487 | ||
1488 | if (gen_es6_) { | |
1489 | indent(f_service_) << ")).then(result => {" << endl; | |
1490 | } else { | |
1491 | indent(f_service_) << ").then(function(result) {" << endl; | |
1492 | } | |
1493 | ||
1494 | indent_up(); | |
1495 | f_service_ << indent() << js_const_type_ << "result_obj = new " << resultname << "({success: result});" << endl | |
1496 | << indent() << "output.writeMessageBegin(\"" << tfunction->get_name() | |
1497 | << "\", Thrift.MessageType.REPLY, seqid);" << endl << indent() | |
1498 | << "result_obj.write(output);" << endl << indent() << "output.writeMessageEnd();" << endl | |
1499 | << indent() << "output.flush();" << endl; | |
1500 | indent_down(); | |
1501 | ||
1502 | if (gen_es6_) { | |
1503 | indent(f_service_) << "}).catch(err => {" << endl; | |
1504 | } else { | |
1505 | indent(f_service_) << "}).catch(function (err) {" << endl; | |
1506 | } | |
1507 | indent_up(); | |
1508 | indent(f_service_) << js_let_type_ << "result;" << endl; | |
1509 | ||
1510 | bool has_exception = false; | |
1511 | t_struct* exceptions = tfunction->get_xceptions(); | |
1512 | if (exceptions) { | |
1513 | const vector<t_field*>& members = exceptions->get_members(); | |
1514 | for (auto member : members) { | |
1515 | t_type* t = get_true_type(member->get_type()); | |
1516 | if (t->is_xception()) { | |
1517 | if (!has_exception) { | |
1518 | has_exception = true; | |
1519 | indent(f_service_) << "if (err instanceof " << js_type_namespace(t->get_program()) | |
1520 | << t->get_name(); | |
1521 | } else { | |
1522 | f_service_ << " || err instanceof " << js_type_namespace(t->get_program()) | |
1523 | << t->get_name(); | |
1524 | } | |
1525 | } | |
1526 | } | |
1527 | } | |
1528 | ||
1529 | if (has_exception) { | |
1530 | f_service_ << ") {" << endl; | |
1531 | indent_up(); | |
1532 | f_service_ << indent() << "result = new " << resultname << "(err);" << endl << indent() | |
1533 | << "output.writeMessageBegin(\"" << tfunction->get_name() | |
1534 | << "\", Thrift.MessageType.REPLY, seqid);" << endl; | |
1535 | ||
1536 | indent_down(); | |
1537 | indent(f_service_) << "} else {" << endl; | |
1538 | indent_up(); | |
1539 | } | |
1540 | ||
1541 | f_service_ << indent() << "result = new " | |
1542 | "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN," | |
1543 | " err.message);" << endl << indent() << "output.writeMessageBegin(\"" | |
1544 | << tfunction->get_name() << "\", Thrift.MessageType.EXCEPTION, seqid);" << endl; | |
1545 | ||
1546 | if (has_exception) { | |
1547 | indent_down(); | |
1548 | indent(f_service_) << "}" << endl; | |
1549 | } | |
1550 | ||
1551 | f_service_ << indent() << "result.write(output);" << endl << indent() | |
1552 | << "output.writeMessageEnd();" << endl << indent() << "output.flush();" << endl; | |
1553 | indent_down(); | |
1554 | indent(f_service_) << "});" << endl; | |
1555 | indent_down(); | |
1556 | // End promise style invocation | |
1557 | ||
1558 | // Callback style invocation | |
1559 | indent(f_service_) << "} else {" << endl; | |
1560 | indent_up(); | |
1561 | indent(f_service_) << "this._handler." << tfunction->get_name() << "("; | |
1562 | ||
1563 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
1564 | f_service_ << "args." << (*f_iter)->get_name() << ", "; | |
1565 | } | |
1566 | ||
1567 | if (gen_es6_) { | |
1568 | f_service_ << "(err, result) => {" << endl; | |
1569 | } else { | |
1570 | f_service_ << "function (err, result) {" << endl; | |
1571 | } | |
1572 | indent_up(); | |
1573 | indent(f_service_) << js_let_type_ << "result_obj;" << endl; | |
1574 | ||
1575 | indent(f_service_) << "if ((err === null || typeof err === 'undefined')"; | |
1576 | if (has_exception) { | |
1577 | const vector<t_field*>& members = exceptions->get_members(); | |
1578 | for (auto member : members) { | |
1579 | t_type* t = get_true_type(member->get_type()); | |
1580 | if (t->is_xception()) { | |
1581 | f_service_ << " || err instanceof " << js_type_namespace(t->get_program()) << t->get_name(); | |
1582 | } | |
1583 | } | |
1584 | } | |
1585 | f_service_ << ") {" << endl; | |
1586 | indent_up(); | |
1587 | f_service_ << indent() << "result_obj = new " << resultname | |
1588 | << "((err !== null || typeof err === 'undefined') ? err : {success: result});" << endl << indent() | |
1589 | << "output.writeMessageBegin(\"" << tfunction->get_name() | |
1590 | << "\", Thrift.MessageType.REPLY, seqid);" << endl; | |
1591 | indent_down(); | |
1592 | indent(f_service_) << "} else {" << endl; | |
1593 | indent_up(); | |
1594 | f_service_ << indent() << "result_obj = new " | |
1595 | "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN," | |
1596 | " err.message);" << endl << indent() << "output.writeMessageBegin(\"" | |
1597 | << tfunction->get_name() << "\", Thrift.MessageType.EXCEPTION, seqid);" << endl; | |
1598 | indent_down(); | |
1599 | f_service_ << indent() << "}" << endl << indent() << "result_obj.write(output);" << endl << indent() | |
1600 | << "output.writeMessageEnd();" << endl << indent() << "output.flush();" << endl; | |
1601 | ||
1602 | indent_down(); | |
1603 | indent(f_service_) << "});" << endl; | |
1604 | indent_down(); | |
1605 | indent(f_service_) << "}" << endl; | |
1606 | // End callback style invocation | |
1607 | ||
1608 | indent_down(); | |
1609 | ||
1610 | if (gen_es6_) { | |
1611 | indent(f_service_) << "}" << endl; | |
1612 | } else { | |
1613 | indent(f_service_) << "};" << endl; | |
1614 | } | |
1615 | } | |
1616 | ||
1617 | /** | |
1618 | * Generates helper functions for a service. | |
1619 | * | |
1620 | * @param tservice The service to generate a header definition for | |
1621 | */ | |
1622 | void t_js_generator::generate_service_helpers(t_service* tservice) { | |
1623 | // Do not generate TS definitions for helper functions | |
1624 | bool gen_ts_tmp = gen_ts_; | |
1625 | gen_ts_ = false; | |
1626 | ||
1627 | vector<t_function*> functions = tservice->get_functions(); | |
1628 | vector<t_function*>::iterator f_iter; | |
1629 | ||
1630 | f_service_ << "//HELPER FUNCTIONS AND STRUCTURES" << endl << endl; | |
1631 | ||
1632 | for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { | |
1633 | t_struct* ts = (*f_iter)->get_arglist(); | |
1634 | string name = ts->get_name(); | |
1635 | ts->set_name(service_name_ + "_" + name); | |
1636 | generate_js_struct_definition(f_service_, ts, false, false); | |
1637 | generate_js_function_helpers(*f_iter); | |
1638 | ts->set_name(name); | |
1639 | } | |
1640 | ||
1641 | gen_ts_ = gen_ts_tmp; | |
1642 | } | |
1643 | ||
1644 | /** | |
1645 | * Generates a struct and helpers for a function. | |
1646 | * | |
1647 | * @param tfunction The function | |
1648 | */ | |
1649 | void t_js_generator::generate_js_function_helpers(t_function* tfunction) { | |
1650 | t_struct result(program_, service_name_ + "_" + tfunction->get_name() + "_result"); | |
1651 | t_field success(tfunction->get_returntype(), "success", 0); | |
1652 | if (!tfunction->get_returntype()->is_void()) { | |
1653 | result.append(&success); | |
1654 | } | |
1655 | ||
1656 | t_struct* xs = tfunction->get_xceptions(); | |
1657 | const vector<t_field*>& fields = xs->get_members(); | |
1658 | vector<t_field*>::const_iterator f_iter; | |
1659 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
1660 | result.append(*f_iter); | |
1661 | } | |
1662 | ||
1663 | generate_js_struct_definition(f_service_, &result, false, false); | |
1664 | } | |
1665 | ||
1666 | /** | |
1667 | * Generates a service interface definition. | |
1668 | * | |
1669 | * @param tservice The service to generate a header definition for | |
1670 | */ | |
1671 | void t_js_generator::generate_service_interface(t_service* tservice) { | |
1672 | (void)tservice; | |
1673 | } | |
1674 | ||
1675 | /** | |
1676 | * Generates a REST interface | |
1677 | */ | |
1678 | void t_js_generator::generate_service_rest(t_service* tservice) { | |
1679 | (void)tservice; | |
1680 | } | |
1681 | ||
1682 | /** | |
1683 | * Generates a service client definition. | |
1684 | * | |
1685 | * @param tservice The service to generate a server for. | |
1686 | */ | |
1687 | void t_js_generator::generate_service_client(t_service* tservice) { | |
1688 | ||
1689 | bool is_subclass_service = tservice->get_extends() != NULL; | |
1690 | ||
1691 | if (gen_node_) { | |
1692 | string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_; | |
1693 | f_service_ << prefix << service_name_ << "Client = " << "exports.Client"; | |
1694 | if (gen_ts_) { | |
1695 | f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class " | |
1696 | << "Client "; | |
1697 | if (tservice->get_extends() != NULL) { | |
1698 | f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Client "; | |
1699 | } | |
1700 | f_service_ts_ << "{" << endl; | |
1701 | } | |
1702 | } else { | |
1703 | f_service_ << js_namespace(tservice->get_program()) << service_name_ | |
1704 | << "Client"; | |
1705 | if (gen_ts_) { | |
1706 | f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class " | |
1707 | << service_name_ << "Client "; | |
1708 | if (is_subclass_service) { | |
1709 | f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Client "; | |
1710 | } | |
1711 | f_service_ts_ << "{" << endl; | |
1712 | } | |
1713 | } | |
1714 | ||
1715 | // ES6 Constructor | |
1716 | if (gen_es6_) { | |
1717 | if (is_subclass_service) { | |
1718 | f_service_ << " = class extends " << js_namespace(tservice->get_extends()->get_program()) | |
1719 | << tservice->get_extends()->get_name() << "Client {" << endl; | |
1720 | } else { | |
1721 | f_service_ << " = class {" << endl; | |
1722 | } | |
1723 | indent_up(); | |
1724 | if (gen_node_) { | |
1725 | indent(f_service_) << "constructor(output, pClass) {" << endl; | |
1726 | } else { | |
1727 | indent(f_service_) << "constructor(input, output) {" << endl; | |
1728 | } | |
1729 | } else { | |
1730 | if (gen_node_) { | |
1731 | f_service_ << " = function(output, pClass) {" << endl; | |
1732 | } else { | |
1733 | f_service_ << " = function(input, output) {" << endl; | |
1734 | } | |
1735 | } | |
1736 | ||
1737 | indent_up(); | |
1738 | ||
1739 | if (gen_node_) { | |
1740 | indent(f_service_) << "this.output = output;" << endl; | |
1741 | indent(f_service_) << "this.pClass = pClass;" << endl; | |
1742 | indent(f_service_) << "this._seqid = 0;" << endl; | |
1743 | indent(f_service_) << "this._reqs = {};" << endl; | |
1744 | if (gen_ts_) { | |
1745 | f_service_ts_ << ts_indent() << "private output: thrift.TTransport;" << endl | |
1746 | << ts_indent() << "private pClass: thrift.TProtocol;" << endl | |
1747 | << ts_indent() << "private _seqid: number;" << endl | |
1748 | << endl | |
1749 | << ts_indent() << "constructor(output: thrift.TTransport, pClass: { new(trans: thrift.TTransport): thrift.TProtocol });" | |
1750 | << endl; | |
1751 | } | |
1752 | } else { | |
1753 | indent(f_service_) << "this.input = input;" << endl; | |
1754 | indent(f_service_) << "this.output = (!output) ? input : output;" << endl; | |
1755 | indent(f_service_) << "this.seqid = 0;" << endl; | |
1756 | if (gen_ts_) { | |
1757 | f_service_ts_ << ts_indent() << "input: Thrift.TJSONProtocol;" << endl << ts_indent() | |
1758 | << "output: Thrift.TJSONProtocol;" << endl << ts_indent() << "seqid: number;" | |
1759 | << endl << endl << ts_indent() | |
1760 | << "constructor(input: Thrift.TJSONProtocol, output?: Thrift.TJSONProtocol);" | |
1761 | << endl; | |
1762 | } | |
1763 | } | |
1764 | ||
1765 | indent_down(); | |
1766 | ||
1767 | if (gen_es6_) { | |
1768 | indent(f_service_) << "}" << endl; | |
1769 | } else { | |
1770 | indent(f_service_) << "};" << endl; | |
1771 | if (is_subclass_service) { | |
1772 | indent(f_service_) << "Thrift.inherits(" << js_namespace(tservice->get_program()) | |
1773 | << service_name_ << "Client, " | |
1774 | << js_namespace(tservice->get_extends()->get_program()) | |
1775 | << tservice->get_extends()->get_name() << "Client);" << endl; | |
1776 | } else { | |
1777 | // init prototype | |
1778 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ | |
1779 | << "Client.prototype = {};" << endl; | |
1780 | } | |
1781 | } | |
1782 | ||
1783 | // utils for multiplexed services | |
1784 | if (gen_node_) { | |
1785 | if (gen_es6_) { | |
1786 | indent(f_service_) << "seqid () { return this._seqid; }" << endl; | |
1787 | indent(f_service_) << "new_seqid () { return this._seqid += 1; }" << endl; | |
1788 | } else { | |
1789 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ | |
1790 | << "Client.prototype.seqid = function() { return this._seqid; };" << endl | |
1791 | << js_namespace(tservice->get_program()) << service_name_ | |
1792 | << "Client.prototype.new_seqid = function() { return this._seqid += 1; };" | |
1793 | << endl; | |
1794 | } | |
1795 | } | |
1796 | ||
1797 | // Generate client method implementations | |
1798 | vector<t_function*> functions = tservice->get_functions(); | |
1799 | vector<t_function*>::const_iterator f_iter; | |
1800 | for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { | |
1801 | t_struct* arg_struct = (*f_iter)->get_arglist(); | |
1802 | const vector<t_field*>& fields = arg_struct->get_members(); | |
1803 | vector<t_field*>::const_iterator fld_iter; | |
1804 | string funname = (*f_iter)->get_name(); | |
1805 | string arglist = argument_list(arg_struct); | |
1806 | ||
1807 | // Open function | |
1808 | f_service_ << endl; | |
1809 | if (gen_es6_) { | |
1810 | indent(f_service_) << funname << " (" << arglist << ") {" << endl; | |
1811 | } else { | |
1812 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype." | |
1813 | << function_signature(*f_iter, "", !gen_es6_) << " {" << endl; | |
1814 | } | |
1815 | ||
1816 | indent_up(); | |
1817 | ||
1818 | if (gen_ts_) { | |
1819 | // function definition without callback | |
1820 | f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, false) << endl; | |
1821 | if (!gen_es6_) { | |
1822 | // overload with callback | |
1823 | f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << endl; | |
1824 | } else { | |
1825 | // overload with callback | |
1826 | f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << endl; | |
1827 | } | |
1828 | } | |
1829 | ||
1830 | if (gen_es6_ && gen_node_) { | |
1831 | indent(f_service_) << "this._seqid = this.new_seqid();" << endl; | |
1832 | indent(f_service_) << js_const_type_ << "self = this;" << endl << indent() | |
1833 | << "return new Promise((resolve, reject) => {" << endl; | |
1834 | indent_up(); | |
1835 | indent(f_service_) << "self._reqs[self.seqid()] = (error, result) => {" << endl; | |
1836 | indent_up(); | |
1837 | indent(f_service_) << "return error ? reject(error) : resolve(result);" << endl; | |
1838 | indent_down(); | |
1839 | indent(f_service_) << "};" << endl; | |
1840 | indent(f_service_) << "self.send_" << funname << "(" << arglist << ");" << endl; | |
1841 | indent_down(); | |
1842 | indent(f_service_) << "});" << endl; | |
1843 | } else if (gen_node_) { // Node.js output ./gen-nodejs | |
1844 | f_service_ << indent() << "this._seqid = this.new_seqid();" << endl << indent() | |
1845 | << "if (callback === undefined) {" << endl; | |
1846 | indent_up(); | |
1847 | f_service_ << indent() << js_const_type_ << "_defer = Q.defer();" << endl << indent() | |
1848 | << "this._reqs[this.seqid()] = function(error, result) {" << endl; | |
1849 | indent_up(); | |
1850 | indent(f_service_) << "if (error) {" << endl; | |
1851 | indent_up(); | |
1852 | indent(f_service_) << "_defer.reject(error);" << endl; | |
1853 | indent_down(); | |
1854 | indent(f_service_) << "} else {" << endl; | |
1855 | indent_up(); | |
1856 | indent(f_service_) << "_defer.resolve(result);" << endl; | |
1857 | indent_down(); | |
1858 | indent(f_service_) << "}" << endl; | |
1859 | indent_down(); | |
1860 | indent(f_service_) << "};" << endl; | |
1861 | f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << endl | |
1862 | << indent() << "return _defer.promise;" << endl; | |
1863 | indent_down(); | |
1864 | indent(f_service_) << "} else {" << endl; | |
1865 | indent_up(); | |
1866 | f_service_ << indent() << "this._reqs[this.seqid()] = callback;" << endl << indent() | |
1867 | << "this.send_" << funname << "(" << arglist << ");" << endl; | |
1868 | indent_down(); | |
1869 | indent(f_service_) << "}" << endl; | |
1870 | } else if (gen_es6_) { | |
1871 | f_service_ << indent() << js_const_type_ << "self = this;" << endl << indent() | |
1872 | << "return new Promise((resolve, reject) => {" << endl; | |
1873 | indent_up(); | |
1874 | f_service_ << indent() << "self.send_" << funname << "(" << arglist | |
1875 | << (arglist.empty() ? "" : ", ") << "(error, result) => {" << endl; | |
1876 | indent_up(); | |
1877 | indent(f_service_) << "return error ? reject(error) : resolve(result);" << endl; | |
1878 | indent_down(); | |
1879 | f_service_ << indent() << "});" << endl; | |
1880 | indent_down(); | |
1881 | f_service_ << indent() << "});" << endl; | |
1882 | ||
1883 | } else if (gen_jquery_) { // jQuery output ./gen-js | |
1884 | f_service_ << indent() << "if (callback === undefined) {" << endl; | |
1885 | indent_up(); | |
1886 | f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << endl; | |
1887 | if (!(*f_iter)->is_oneway()) { | |
1888 | f_service_ << indent(); | |
1889 | if (!(*f_iter)->get_returntype()->is_void()) { | |
1890 | f_service_ << "return "; | |
1891 | } | |
1892 | f_service_ << "this.recv_" << funname << "();" << endl; | |
1893 | } | |
1894 | indent_down(); | |
1895 | f_service_ << indent() << "} else {" << endl; | |
1896 | indent_up(); | |
1897 | f_service_ << indent() << js_const_type_ << "postData = this.send_" << funname << "(" << arglist | |
1898 | << (arglist.empty() ? "" : ", ") << "true);" << endl; | |
1899 | f_service_ << indent() << "return this.output.getTransport()" << endl; | |
1900 | indent_up(); | |
1901 | f_service_ << indent() << ".jqRequest(this, postData, arguments, this.recv_" << funname | |
1902 | << ");" << endl; | |
1903 | indent_down(); | |
1904 | indent_down(); | |
1905 | f_service_ << indent() << "}" << endl; | |
1906 | } else { // Standard JavaScript ./gen-js | |
1907 | f_service_ << indent() << "this.send_" << funname << "(" << arglist | |
1908 | << (arglist.empty() ? "" : ", ") << "callback); " << endl; | |
1909 | if (!(*f_iter)->is_oneway()) { | |
1910 | f_service_ << indent() << "if (!callback) {" << endl; | |
1911 | f_service_ << indent(); | |
1912 | if (!(*f_iter)->get_returntype()->is_void()) { | |
1913 | f_service_ << " return "; | |
1914 | } | |
1915 | f_service_ << "this.recv_" << funname << "();" << endl; | |
1916 | f_service_ << indent() << "}" << endl; | |
1917 | } | |
1918 | } | |
1919 | ||
1920 | indent_down(); | |
1921 | ||
1922 | if (gen_es6_) { | |
1923 | indent(f_service_) << "}" << endl << endl; | |
1924 | } else { | |
1925 | indent(f_service_) << "};" << endl << endl; | |
1926 | } | |
1927 | ||
1928 | // Send function | |
1929 | if (gen_es6_) { | |
1930 | if (gen_node_) { | |
1931 | indent(f_service_) << "send_" << funname << " (" << arglist << ") {" << endl; | |
1932 | } else { | |
1933 | // ES6 js still uses callbacks here. Should refactor this to promise style later.. | |
1934 | indent(f_service_) << "send_" << funname << " (" << argument_list(arg_struct, true) << ") {" << endl; | |
1935 | } | |
1936 | } else { | |
1937 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype.send_" | |
1938 | << function_signature(*f_iter, "", !gen_node_) << " {" << endl; | |
1939 | } | |
1940 | ||
1941 | indent_up(); | |
1942 | ||
1943 | std::string outputVar; | |
1944 | if (gen_node_) { | |
1945 | f_service_ << indent() << js_const_type_ << "output = new this.pClass(this.output);" << endl; | |
1946 | outputVar = "output"; | |
1947 | } else { | |
1948 | outputVar = "this.output"; | |
1949 | } | |
1950 | ||
1951 | std::string argsname = js_namespace(program_) + service_name_ + "_" + (*f_iter)->get_name() | |
1952 | + "_args"; | |
1953 | ||
1954 | std::string messageType = (*f_iter)->is_oneway() ? "Thrift.MessageType.ONEWAY" | |
1955 | : "Thrift.MessageType.CALL"; | |
1956 | // Build args | |
1957 | if (fields.size() > 0){ | |
1958 | f_service_ << indent() << js_const_type_ << "params = {" << endl; | |
1959 | indent_up(); | |
1960 | for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { | |
1961 | indent(f_service_) << (*fld_iter)->get_name() << ": " << (*fld_iter)->get_name(); | |
1962 | if (fld_iter != fields.end()-1) { | |
1963 | f_service_ << "," << endl; | |
1964 | } else { | |
1965 | f_service_ << endl; | |
1966 | } | |
1967 | } | |
1968 | indent_down(); | |
1969 | indent(f_service_) << "};" << endl; | |
1970 | indent(f_service_) << js_const_type_ << "args = new " << argsname << "(params);" << endl; | |
1971 | } else { | |
1972 | indent(f_service_) << js_const_type_ << "args = new " << argsname << "();" << endl; | |
1973 | } | |
1974 | ||
1975 | ||
1976 | // Serialize the request header within try/catch | |
1977 | indent(f_service_) << "try {" << endl; | |
1978 | indent_up(); | |
1979 | ||
1980 | if (gen_node_) { | |
1981 | f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name() | |
1982 | << "', " << messageType << ", this.seqid());" << endl; | |
1983 | } else { | |
1984 | f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name() | |
1985 | << "', " << messageType << ", this.seqid);" << endl; | |
1986 | } | |
1987 | ||
1988 | ||
1989 | // Write to the stream | |
1990 | f_service_ << indent() << "args.write(" << outputVar << ");" << endl << indent() << outputVar | |
1991 | << ".writeMessageEnd();" << endl; | |
1992 | ||
1993 | if (gen_node_) { | |
1994 | if((*f_iter)->is_oneway()) { | |
1995 | f_service_ << indent() << "this.output.flush();" << endl; | |
1996 | f_service_ << indent() << js_const_type_ << "callback = this._reqs[this.seqid()] || function() {};" << endl; | |
1997 | f_service_ << indent() << "delete this._reqs[this.seqid()];" << endl; | |
1998 | f_service_ << indent() << "callback(null);" << endl; | |
1999 | } else { | |
2000 | f_service_ << indent() << "return this.output.flush();" << endl; | |
2001 | } | |
2002 | } else { | |
2003 | if (gen_jquery_) { | |
2004 | f_service_ << indent() << "return this.output.getTransport().flush(callback);" << endl; | |
2005 | } else if (gen_es6_) { | |
2006 | f_service_ << indent() << js_const_type_ << "self = this;" << endl; | |
2007 | if((*f_iter)->is_oneway()) { | |
2008 | f_service_ << indent() << "this.output.getTransport().flush(true, null);" << endl; | |
2009 | f_service_ << indent() << "callback();" << endl; | |
2010 | } else { | |
2011 | f_service_ << indent() << "this.output.getTransport().flush(true, () => {" << endl; | |
2012 | indent_up(); | |
2013 | f_service_ << indent() << js_let_type_ << "error = null, result = null;" << endl; | |
2014 | f_service_ << indent() << "try {" << endl; | |
2015 | f_service_ << indent() << " result = self.recv_" << funname << "();" << endl; | |
2016 | f_service_ << indent() << "} catch (e) {" << endl; | |
2017 | f_service_ << indent() << " error = e;" << endl; | |
2018 | f_service_ << indent() << "}" << endl; | |
2019 | f_service_ << indent() << "callback(error, result);" << endl; | |
2020 | indent_down(); | |
2021 | f_service_ << indent() << "});" << endl; | |
2022 | } | |
2023 | } else { | |
2024 | f_service_ << indent() << "if (callback) {" << endl; | |
2025 | indent_up(); | |
2026 | if((*f_iter)->is_oneway()) { | |
2027 | f_service_ << indent() << "this.output.getTransport().flush(true, null);" << endl; | |
2028 | f_service_ << indent() << "callback();" << endl; | |
2029 | } else { | |
2030 | f_service_ << indent() << js_const_type_ << "self = this;" << endl; | |
2031 | f_service_ << indent() << "this.output.getTransport().flush(true, function() {" << endl; | |
2032 | indent_up(); | |
2033 | f_service_ << indent() << js_let_type_ << "result = null;" << endl; | |
2034 | f_service_ << indent() << "try {" << endl; | |
2035 | f_service_ << indent() << " result = self.recv_" << funname << "();" << endl; | |
2036 | f_service_ << indent() << "} catch (e) {" << endl; | |
2037 | f_service_ << indent() << " result = e;" << endl; | |
2038 | f_service_ << indent() << "}" << endl; | |
2039 | f_service_ << indent() << "callback(result);" << endl; | |
2040 | indent_down(); | |
2041 | f_service_ << indent() << "});" << endl; | |
2042 | } | |
2043 | indent_down(); | |
2044 | f_service_ << indent() << "} else {" << endl; | |
2045 | f_service_ << indent() << " return this.output.getTransport().flush();" << endl; | |
2046 | f_service_ << indent() << "}" << endl; | |
2047 | } | |
2048 | } | |
2049 | ||
2050 | indent_down(); | |
2051 | f_service_ << indent() << "}" << endl; | |
2052 | ||
2053 | // Reset the transport and delete registered callback if there was a serialization error | |
2054 | f_service_ << indent() << "catch (e) {" << endl; | |
2055 | indent_up(); | |
2056 | if (gen_node_) { | |
2057 | f_service_ << indent() << "delete this._reqs[this.seqid()];" << endl; | |
2058 | f_service_ << indent() << "if (typeof " << outputVar << ".reset === 'function') {" << endl; | |
2059 | f_service_ << indent() << " " << outputVar << ".reset();" << endl; | |
2060 | f_service_ << indent() << "}" << endl; | |
2061 | } else { | |
2062 | f_service_ << indent() << "if (typeof " << outputVar << ".getTransport().reset === 'function') {" << endl; | |
2063 | f_service_ << indent() << " " << outputVar << ".getTransport().reset();" << endl; | |
2064 | f_service_ << indent() << "}" << endl; | |
2065 | } | |
2066 | f_service_ << indent() << "throw e;" << endl; | |
2067 | indent_down(); | |
2068 | f_service_ << indent() << "}" << endl; | |
2069 | ||
2070 | indent_down(); | |
2071 | ||
2072 | // Close send function | |
2073 | if (gen_es6_) { | |
2074 | indent(f_service_) << "}" << endl; | |
2075 | } else { | |
2076 | indent(f_service_) << "};" << endl; | |
2077 | } | |
2078 | ||
2079 | // Receive function | |
2080 | if (!(*f_iter)->is_oneway()) { | |
2081 | std::string resultname = js_namespace(tservice->get_program()) + service_name_ + "_" | |
2082 | + (*f_iter)->get_name() + "_result"; | |
2083 | ||
2084 | f_service_ << endl; | |
2085 | // Open receive function | |
2086 | if (gen_node_) { | |
2087 | if (gen_es6_) { | |
2088 | indent(f_service_) << "recv_" << (*f_iter)->get_name() << " (input, mtype, rseqid) {" << endl; | |
2089 | } else { | |
2090 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ | |
2091 | << "Client.prototype.recv_" << (*f_iter)->get_name() | |
2092 | << " = function(input,mtype,rseqid) {" << endl; | |
2093 | } | |
2094 | } else { | |
2095 | if (gen_es6_) { | |
2096 | indent(f_service_) << "recv_" << (*f_iter)->get_name() << " () {" << endl; | |
2097 | } else { | |
2098 | t_struct noargs(program_); | |
2099 | ||
2100 | t_function recv_function((*f_iter)->get_returntype(), | |
2101 | string("recv_") + (*f_iter)->get_name(), | |
2102 | &noargs); | |
2103 | indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ | |
2104 | << "Client.prototype." << function_signature(&recv_function) << " {" << endl; | |
2105 | } | |
2106 | } | |
2107 | ||
2108 | indent_up(); | |
2109 | ||
2110 | std::string inputVar; | |
2111 | if (gen_node_) { | |
2112 | inputVar = "input"; | |
2113 | } else { | |
2114 | inputVar = "this.input"; | |
2115 | } | |
2116 | ||
2117 | if (gen_node_) { | |
2118 | f_service_ << indent() << js_const_type_ << "callback = this._reqs[rseqid] || function() {};" << endl | |
2119 | << indent() << "delete this._reqs[rseqid];" << endl; | |
2120 | } else { | |
2121 | f_service_ << indent() << js_const_type_ << "ret = this.input.readMessageBegin();" << endl | |
2122 | << indent() << js_const_type_ << "mtype = ret.mtype;" << endl; | |
2123 | } | |
2124 | ||
2125 | f_service_ << indent() << "if (mtype == Thrift.MessageType.EXCEPTION) {" << endl; | |
2126 | ||
2127 | indent_up(); | |
2128 | f_service_ << indent() << js_const_type_ << "x = new Thrift.TApplicationException();" << endl | |
2129 | << indent() << "x.read(" << inputVar << ");" << endl | |
2130 | << indent() << inputVar << ".readMessageEnd();" << endl | |
2131 | << indent() << render_recv_throw("x") << endl; | |
2132 | scope_down(f_service_); | |
2133 | ||
2134 | f_service_ << indent() << js_const_type_ << "result = new " << resultname << "();" << endl << indent() | |
2135 | << "result.read(" << inputVar << ");" << endl; | |
2136 | ||
2137 | f_service_ << indent() << inputVar << ".readMessageEnd();" << endl << endl; | |
2138 | ||
2139 | t_struct* xs = (*f_iter)->get_xceptions(); | |
2140 | const std::vector<t_field*>& xceptions = xs->get_members(); | |
2141 | vector<t_field*>::const_iterator x_iter; | |
2142 | for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { | |
2143 | f_service_ << indent() << "if (null !== result." << (*x_iter)->get_name() << ") {" << endl | |
2144 | << indent() << " " << render_recv_throw("result." + (*x_iter)->get_name()) | |
2145 | << endl << indent() << "}" << endl; | |
2146 | } | |
2147 | ||
2148 | // Careful, only return result if not a void function | |
2149 | if (!(*f_iter)->get_returntype()->is_void()) { | |
2150 | f_service_ << indent() << "if (null !== result.success) {" << endl << indent() << " " | |
2151 | << render_recv_return("result.success") << endl << indent() << "}" << endl; | |
2152 | f_service_ << indent() | |
2153 | << render_recv_throw("'" + (*f_iter)->get_name() + " failed: unknown result'") | |
2154 | << endl; | |
2155 | } else { | |
2156 | if (gen_node_) { | |
2157 | indent(f_service_) << "callback(null);" << endl; | |
2158 | } else { | |
2159 | indent(f_service_) << "return;" << endl; | |
2160 | } | |
2161 | } | |
2162 | ||
2163 | // Close receive function | |
2164 | indent_down(); | |
2165 | if (gen_es6_) { | |
2166 | indent(f_service_) << "}" << endl; | |
2167 | } else { | |
2168 | indent(f_service_) << "};" << endl; | |
2169 | } | |
2170 | } | |
2171 | } | |
2172 | ||
2173 | // Finish class definitions | |
2174 | if (gen_ts_) { | |
2175 | f_service_ts_ << ts_indent() << "}" << endl; | |
2176 | } | |
2177 | if (gen_es6_) { | |
2178 | indent_down(); | |
2179 | f_service_ << "};" << endl; | |
2180 | } | |
2181 | } | |
2182 | ||
2183 | std::string t_js_generator::render_recv_throw(std::string var) { | |
2184 | if (gen_node_) { | |
2185 | return "return callback(" + var + ");"; | |
2186 | } else { | |
2187 | return "throw " + var + ";"; | |
2188 | } | |
2189 | } | |
2190 | ||
2191 | std::string t_js_generator::render_recv_return(std::string var) { | |
2192 | if (gen_node_) { | |
2193 | return "return callback(null, " + var + ");"; | |
2194 | } else { | |
2195 | return "return " + var + ";"; | |
2196 | } | |
2197 | } | |
2198 | ||
2199 | /** | |
2200 | * Deserializes a field of any type. | |
2201 | */ | |
2202 | void t_js_generator::generate_deserialize_field(ostream& out, | |
2203 | t_field* tfield, | |
2204 | string prefix, | |
2205 | bool inclass) { | |
2206 | (void)inclass; | |
2207 | t_type* type = get_true_type(tfield->get_type()); | |
2208 | ||
2209 | if (type->is_void()) { | |
2210 | throw std::runtime_error("CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name()); | |
2211 | } | |
2212 | ||
2213 | string name = prefix + tfield->get_name(); | |
2214 | ||
2215 | if (type->is_struct() || type->is_xception()) { | |
2216 | generate_deserialize_struct(out, (t_struct*)type, name); | |
2217 | } else if (type->is_container()) { | |
2218 | generate_deserialize_container(out, type, name); | |
2219 | } else if (type->is_base_type() || type->is_enum()) { | |
2220 | indent(out) << name << " = input."; | |
2221 | ||
2222 | if (type->is_base_type()) { | |
2223 | t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); | |
2224 | switch (tbase) { | |
2225 | case t_base_type::TYPE_VOID: | |
2226 | throw std::runtime_error("compiler error: cannot serialize void field in a struct: " + name); | |
2227 | break; | |
2228 | case t_base_type::TYPE_STRING: | |
2229 | out << (type->is_binary() ? "readBinary()" : "readString()"); | |
2230 | break; | |
2231 | case t_base_type::TYPE_BOOL: | |
2232 | out << "readBool()"; | |
2233 | break; | |
2234 | case t_base_type::TYPE_I8: | |
2235 | out << "readByte()"; | |
2236 | break; | |
2237 | case t_base_type::TYPE_I16: | |
2238 | out << "readI16()"; | |
2239 | break; | |
2240 | case t_base_type::TYPE_I32: | |
2241 | out << "readI32()"; | |
2242 | break; | |
2243 | case t_base_type::TYPE_I64: | |
2244 | out << "readI64()"; | |
2245 | break; | |
2246 | case t_base_type::TYPE_DOUBLE: | |
2247 | out << "readDouble()"; | |
2248 | break; | |
2249 | default: | |
2250 | throw std::runtime_error("compiler error: no JS name for base type " + t_base_type::t_base_name(tbase)); | |
2251 | } | |
2252 | } else if (type->is_enum()) { | |
2253 | out << "readI32()"; | |
2254 | } | |
2255 | ||
2256 | if (!gen_node_) { | |
2257 | out << ".value"; | |
2258 | } | |
2259 | ||
2260 | out << ";" << endl; | |
2261 | } else { | |
2262 | printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", | |
2263 | tfield->get_name().c_str(), | |
2264 | type->get_name().c_str()); | |
2265 | } | |
2266 | } | |
2267 | ||
2268 | /** | |
2269 | * Generates an unserializer for a variable. This makes two key assumptions, | |
2270 | * first that there is a const char* variable named data that points to the | |
2271 | * buffer for deserialization, and that there is a variable protocol which | |
2272 | * is a reference to a TProtocol serialization object. | |
2273 | */ | |
2274 | void t_js_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) { | |
2275 | out << indent() << prefix << " = new " << js_type_namespace(tstruct->get_program()) | |
2276 | << tstruct->get_name() << "();" << endl << indent() << prefix << ".read(input);" << endl; | |
2277 | } | |
2278 | ||
2279 | void t_js_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { | |
2280 | string size = tmp("_size"); | |
2281 | string rtmp3 = tmp("_rtmp3"); | |
2282 | ||
2283 | t_field fsize(g_type_i32, size); | |
2284 | ||
2285 | // Declare variables, read header | |
2286 | if (ttype->is_map()) { | |
2287 | out << indent() << prefix << " = {};" << endl; | |
2288 | ||
2289 | out << indent() << js_const_type_ << rtmp3 << " = input.readMapBegin();" << endl; | |
2290 | out << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl; | |
2291 | ||
2292 | } else if (ttype->is_set()) { | |
2293 | ||
2294 | out << indent() << prefix << " = [];" << endl | |
2295 | << indent() << js_const_type_ << rtmp3 << " = input.readSetBegin();" << endl | |
2296 | << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl; | |
2297 | ||
2298 | } else if (ttype->is_list()) { | |
2299 | ||
2300 | out << indent() << prefix << " = [];" << endl | |
2301 | << indent() << js_const_type_ << rtmp3 << " = input.readListBegin();" << endl | |
2302 | << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl; | |
2303 | } | |
2304 | ||
2305 | // For loop iterates over elements | |
2306 | string i = tmp("_i"); | |
2307 | indent(out) << "for (" << js_let_type_ << i << " = 0; " << i << " < " << size << "; ++" << i << ") {" << endl; | |
2308 | ||
2309 | indent_up(); | |
2310 | ||
2311 | if (ttype->is_map()) { | |
2312 | if (!gen_node_) { | |
2313 | out << indent() << "if (" << i << " > 0 ) {" << endl << indent() | |
2314 | << " if (input.rstack.length > input.rpos[input.rpos.length -1] + 1) {" << endl | |
2315 | << indent() << " input.rstack.pop();" << endl << indent() << " }" << endl << indent() | |
2316 | << "}" << endl; | |
2317 | } | |
2318 | ||
2319 | generate_deserialize_map_element(out, (t_map*)ttype, prefix); | |
2320 | } else if (ttype->is_set()) { | |
2321 | generate_deserialize_set_element(out, (t_set*)ttype, prefix); | |
2322 | } else if (ttype->is_list()) { | |
2323 | generate_deserialize_list_element(out, (t_list*)ttype, prefix); | |
2324 | } | |
2325 | ||
2326 | scope_down(out); | |
2327 | ||
2328 | // Read container end | |
2329 | if (ttype->is_map()) { | |
2330 | indent(out) << "input.readMapEnd();" << endl; | |
2331 | } else if (ttype->is_set()) { | |
2332 | indent(out) << "input.readSetEnd();" << endl; | |
2333 | } else if (ttype->is_list()) { | |
2334 | indent(out) << "input.readListEnd();" << endl; | |
2335 | } | |
2336 | } | |
2337 | ||
2338 | /** | |
2339 | * Generates code to deserialize a map | |
2340 | */ | |
2341 | void t_js_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { | |
2342 | string key = tmp("key"); | |
2343 | string val = tmp("val"); | |
2344 | t_field fkey(tmap->get_key_type(), key); | |
2345 | t_field fval(tmap->get_val_type(), val); | |
2346 | ||
2347 | indent(out) << declare_field(&fkey, false, false) << ";" << endl; | |
2348 | indent(out) << declare_field(&fval, false, false) << ";" << endl; | |
2349 | ||
2350 | generate_deserialize_field(out, &fkey); | |
2351 | generate_deserialize_field(out, &fval); | |
2352 | ||
2353 | indent(out) << prefix << "[" << key << "] = " << val << ";" << endl; | |
2354 | } | |
2355 | ||
2356 | void t_js_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { | |
2357 | string elem = tmp("elem"); | |
2358 | t_field felem(tset->get_elem_type(), elem); | |
2359 | ||
2360 | indent(out) << js_let_type_ << elem << " = null;" << endl; | |
2361 | ||
2362 | generate_deserialize_field(out, &felem); | |
2363 | ||
2364 | indent(out) << prefix << ".push(" << elem << ");" << endl; | |
2365 | } | |
2366 | ||
2367 | void t_js_generator::generate_deserialize_list_element(ostream& out, | |
2368 | t_list* tlist, | |
2369 | string prefix) { | |
2370 | string elem = tmp("elem"); | |
2371 | t_field felem(tlist->get_elem_type(), elem); | |
2372 | ||
2373 | indent(out) << js_let_type_ << elem << " = null;" << endl; | |
2374 | ||
2375 | generate_deserialize_field(out, &felem); | |
2376 | ||
2377 | indent(out) << prefix << ".push(" << elem << ");" << endl; | |
2378 | } | |
2379 | ||
2380 | /** | |
2381 | * Serializes a field of any type. | |
2382 | * | |
2383 | * @param tfield The field to serialize | |
2384 | * @param prefix Name to prepend to field name | |
2385 | */ | |
2386 | void t_js_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { | |
2387 | t_type* type = get_true_type(tfield->get_type()); | |
2388 | ||
2389 | // Do nothing for void types | |
2390 | if (type->is_void()) { | |
2391 | throw std::runtime_error("CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name()); | |
2392 | } | |
2393 | ||
2394 | if (type->is_struct() || type->is_xception()) { | |
2395 | generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); | |
2396 | } else if (type->is_container()) { | |
2397 | generate_serialize_container(out, type, prefix + tfield->get_name()); | |
2398 | } else if (type->is_base_type() || type->is_enum()) { | |
2399 | ||
2400 | string name = tfield->get_name(); | |
2401 | ||
2402 | // Hack for when prefix is defined (always a hash ref) | |
2403 | if (!prefix.empty()) | |
2404 | name = prefix + tfield->get_name(); | |
2405 | ||
2406 | indent(out) << "output."; | |
2407 | ||
2408 | if (type->is_base_type()) { | |
2409 | t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); | |
2410 | switch (tbase) { | |
2411 | case t_base_type::TYPE_VOID: | |
2412 | throw std::runtime_error("compiler error: cannot serialize void field in a struct: " + name); | |
2413 | break; | |
2414 | case t_base_type::TYPE_STRING: | |
2415 | out << (type->is_binary() ? "writeBinary(" : "writeString(") << name << ")"; | |
2416 | break; | |
2417 | case t_base_type::TYPE_BOOL: | |
2418 | out << "writeBool(" << name << ")"; | |
2419 | break; | |
2420 | case t_base_type::TYPE_I8: | |
2421 | out << "writeByte(" << name << ")"; | |
2422 | break; | |
2423 | case t_base_type::TYPE_I16: | |
2424 | out << "writeI16(" << name << ")"; | |
2425 | break; | |
2426 | case t_base_type::TYPE_I32: | |
2427 | out << "writeI32(" << name << ")"; | |
2428 | break; | |
2429 | case t_base_type::TYPE_I64: | |
2430 | out << "writeI64(" << name << ")"; | |
2431 | break; | |
2432 | case t_base_type::TYPE_DOUBLE: | |
2433 | out << "writeDouble(" << name << ")"; | |
2434 | break; | |
2435 | default: | |
2436 | throw std::runtime_error("compiler error: no JS name for base type " + t_base_type::t_base_name(tbase)); | |
2437 | } | |
2438 | } else if (type->is_enum()) { | |
2439 | out << "writeI32(" << name << ")"; | |
2440 | } | |
2441 | out << ";" << endl; | |
2442 | ||
2443 | } else { | |
2444 | printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", | |
2445 | prefix.c_str(), | |
2446 | tfield->get_name().c_str(), | |
2447 | type->get_name().c_str()); | |
2448 | } | |
2449 | } | |
2450 | ||
2451 | /** | |
2452 | * Serializes all the members of a struct. | |
2453 | * | |
2454 | * @param tstruct The struct to serialize | |
2455 | * @param prefix String prefix to attach to all fields | |
2456 | */ | |
2457 | void t_js_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { | |
2458 | (void)tstruct; | |
2459 | indent(out) << prefix << ".write(output);" << endl; | |
2460 | } | |
2461 | ||
2462 | /** | |
2463 | * Writes out a container | |
2464 | */ | |
2465 | void t_js_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { | |
2466 | if (ttype->is_map()) { | |
2467 | indent(out) << "output.writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " | |
2468 | << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " | |
2469 | << "Thrift.objectLength(" << prefix << "));" << endl; | |
2470 | } else if (ttype->is_set()) { | |
2471 | indent(out) << "output.writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " | |
2472 | << prefix << ".length);" << endl; | |
2473 | ||
2474 | } else if (ttype->is_list()) { | |
2475 | ||
2476 | indent(out) << "output.writeListBegin(" << type_to_enum(((t_list*)ttype)->get_elem_type()) | |
2477 | << ", " << prefix << ".length);" << endl; | |
2478 | } | |
2479 | ||
2480 | if (ttype->is_map()) { | |
2481 | string kiter = tmp("kiter"); | |
2482 | string viter = tmp("viter"); | |
2483 | indent(out) << "for (" << js_let_type_ << kiter << " in " << prefix << ") {" << endl; | |
2484 | indent_up(); | |
2485 | indent(out) << "if (" << prefix << ".hasOwnProperty(" << kiter << ")) {" << endl; | |
2486 | indent_up(); | |
2487 | indent(out) << js_let_type_ << viter << " = " << prefix << "[" << kiter << "];" << endl; | |
2488 | generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); | |
2489 | scope_down(out); | |
2490 | scope_down(out); | |
2491 | ||
2492 | } else if (ttype->is_set()) { | |
2493 | string iter = tmp("iter"); | |
2494 | indent(out) << "for (" << js_let_type_ << iter << " in " << prefix << ") {" << endl; | |
2495 | indent_up(); | |
2496 | indent(out) << "if (" << prefix << ".hasOwnProperty(" << iter << ")) {" << endl; | |
2497 | indent_up(); | |
2498 | indent(out) << iter << " = " << prefix << "[" << iter << "];" << endl; | |
2499 | generate_serialize_set_element(out, (t_set*)ttype, iter); | |
2500 | scope_down(out); | |
2501 | scope_down(out); | |
2502 | ||
2503 | } else if (ttype->is_list()) { | |
2504 | string iter = tmp("iter"); | |
2505 | indent(out) << "for (" << js_let_type_ << iter << " in " << prefix << ") {" << endl; | |
2506 | indent_up(); | |
2507 | indent(out) << "if (" << prefix << ".hasOwnProperty(" << iter << ")) {" << endl; | |
2508 | indent_up(); | |
2509 | indent(out) << iter << " = " << prefix << "[" << iter << "];" << endl; | |
2510 | generate_serialize_list_element(out, (t_list*)ttype, iter); | |
2511 | scope_down(out); | |
2512 | scope_down(out); | |
2513 | } | |
2514 | ||
2515 | if (ttype->is_map()) { | |
2516 | indent(out) << "output.writeMapEnd();" << endl; | |
2517 | } else if (ttype->is_set()) { | |
2518 | indent(out) << "output.writeSetEnd();" << endl; | |
2519 | } else if (ttype->is_list()) { | |
2520 | indent(out) << "output.writeListEnd();" << endl; | |
2521 | } | |
2522 | } | |
2523 | ||
2524 | /** | |
2525 | * Serializes the members of a map. | |
2526 | * | |
2527 | */ | |
2528 | void t_js_generator::generate_serialize_map_element(ostream& out, | |
2529 | t_map* tmap, | |
2530 | string kiter, | |
2531 | string viter) { | |
2532 | t_field kfield(tmap->get_key_type(), kiter); | |
2533 | generate_serialize_field(out, &kfield); | |
2534 | ||
2535 | t_field vfield(tmap->get_val_type(), viter); | |
2536 | generate_serialize_field(out, &vfield); | |
2537 | } | |
2538 | ||
2539 | /** | |
2540 | * Serializes the members of a set. | |
2541 | */ | |
2542 | void t_js_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { | |
2543 | t_field efield(tset->get_elem_type(), iter); | |
2544 | generate_serialize_field(out, &efield); | |
2545 | } | |
2546 | ||
2547 | /** | |
2548 | * Serializes the members of a list. | |
2549 | */ | |
2550 | void t_js_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { | |
2551 | t_field efield(tlist->get_elem_type(), iter); | |
2552 | generate_serialize_field(out, &efield); | |
2553 | } | |
2554 | ||
2555 | /** | |
2556 | * Declares a field, which may include initialization as necessary. | |
2557 | * | |
2558 | * @param ttype The type | |
2559 | */ | |
2560 | string t_js_generator::declare_field(t_field* tfield, bool init, bool obj) { | |
2561 | string result = "this." + tfield->get_name(); | |
2562 | ||
2563 | if (!obj) { | |
2564 | result = js_let_type_ + tfield->get_name(); | |
2565 | } | |
2566 | ||
2567 | if (init) { | |
2568 | t_type* type = get_true_type(tfield->get_type()); | |
2569 | if (type->is_base_type()) { | |
2570 | t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); | |
2571 | switch (tbase) { | |
2572 | case t_base_type::TYPE_VOID: | |
2573 | break; | |
2574 | case t_base_type::TYPE_STRING: | |
2575 | case t_base_type::TYPE_BOOL: | |
2576 | case t_base_type::TYPE_I8: | |
2577 | case t_base_type::TYPE_I16: | |
2578 | case t_base_type::TYPE_I32: | |
2579 | case t_base_type::TYPE_I64: | |
2580 | case t_base_type::TYPE_DOUBLE: | |
2581 | result += " = null"; | |
2582 | break; | |
2583 | default: | |
2584 | throw std::runtime_error("compiler error: no JS initializer for base type " + t_base_type::t_base_name(tbase)); | |
2585 | } | |
2586 | } else if (type->is_enum()) { | |
2587 | result += " = null"; | |
2588 | } else if (type->is_map()) { | |
2589 | result += " = null"; | |
2590 | } else if (type->is_container()) { | |
2591 | result += " = null"; | |
2592 | } else if (type->is_struct() || type->is_xception()) { | |
2593 | if (obj) { | |
2594 | result += " = new " + js_type_namespace(type->get_program()) + type->get_name() + "()"; | |
2595 | } else { | |
2596 | result += " = null"; | |
2597 | } | |
2598 | } | |
2599 | } else { | |
2600 | result += " = null"; | |
2601 | } | |
2602 | return result; | |
2603 | } | |
2604 | ||
2605 | /** | |
2606 | * Renders a function signature of the form 'type name(args)' | |
2607 | * | |
2608 | * @param tfunction Function definition | |
2609 | * @return String of rendered function definition | |
2610 | */ | |
2611 | string t_js_generator::function_signature(t_function* tfunction, | |
2612 | string prefix, | |
2613 | bool include_callback) { | |
2614 | ||
2615 | string str; | |
2616 | ||
2617 | str = prefix + tfunction->get_name() + " = function("; | |
2618 | ||
2619 | str += argument_list(tfunction->get_arglist(), include_callback); | |
2620 | ||
2621 | str += ")"; | |
2622 | return str; | |
2623 | } | |
2624 | ||
2625 | /** | |
2626 | * Renders a field list | |
2627 | */ | |
2628 | string t_js_generator::argument_list(t_struct* tstruct, bool include_callback) { | |
2629 | string result = ""; | |
2630 | ||
2631 | const vector<t_field*>& fields = tstruct->get_members(); | |
2632 | vector<t_field*>::const_iterator f_iter; | |
2633 | bool first = true; | |
2634 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
2635 | if (first) { | |
2636 | first = false; | |
2637 | } else { | |
2638 | result += ", "; | |
2639 | } | |
2640 | result += (*f_iter)->get_name(); | |
2641 | } | |
2642 | ||
2643 | if (include_callback) { | |
2644 | if (!fields.empty()) { | |
2645 | result += ", "; | |
2646 | } | |
2647 | result += "callback"; | |
2648 | } | |
2649 | ||
2650 | return result; | |
2651 | } | |
2652 | ||
2653 | /** | |
2654 | * Converts the parse type to a C++ enum string for the given type. | |
2655 | */ | |
2656 | string t_js_generator::type_to_enum(t_type* type) { | |
2657 | type = get_true_type(type); | |
2658 | ||
2659 | if (type->is_base_type()) { | |
2660 | t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); | |
2661 | switch (tbase) { | |
2662 | case t_base_type::TYPE_VOID: | |
2663 | throw std::runtime_error("NO T_VOID CONSTRUCT"); | |
2664 | case t_base_type::TYPE_STRING: | |
2665 | return "Thrift.Type.STRING"; | |
2666 | case t_base_type::TYPE_BOOL: | |
2667 | return "Thrift.Type.BOOL"; | |
2668 | case t_base_type::TYPE_I8: | |
2669 | return "Thrift.Type.BYTE"; | |
2670 | case t_base_type::TYPE_I16: | |
2671 | return "Thrift.Type.I16"; | |
2672 | case t_base_type::TYPE_I32: | |
2673 | return "Thrift.Type.I32"; | |
2674 | case t_base_type::TYPE_I64: | |
2675 | return "Thrift.Type.I64"; | |
2676 | case t_base_type::TYPE_DOUBLE: | |
2677 | return "Thrift.Type.DOUBLE"; | |
2678 | } | |
2679 | } else if (type->is_enum()) { | |
2680 | return "Thrift.Type.I32"; | |
2681 | } else if (type->is_struct() || type->is_xception()) { | |
2682 | return "Thrift.Type.STRUCT"; | |
2683 | } else if (type->is_map()) { | |
2684 | return "Thrift.Type.MAP"; | |
2685 | } else if (type->is_set()) { | |
2686 | return "Thrift.Type.SET"; | |
2687 | } else if (type->is_list()) { | |
2688 | return "Thrift.Type.LIST"; | |
2689 | } | |
2690 | ||
2691 | throw std::runtime_error("INVALID TYPE IN type_to_enum: " + type->get_name()); | |
2692 | } | |
2693 | ||
2694 | /** | |
2695 | * Converts a t_type to a TypeScript type (string). | |
2696 | * @param t_type Type to convert to TypeScript | |
2697 | * @return String TypeScript type | |
2698 | */ | |
2699 | string t_js_generator::ts_get_type(t_type* type) { | |
2700 | std::string ts_type; | |
2701 | ||
2702 | type = get_true_type(type); | |
2703 | ||
2704 | if (type->is_base_type()) { | |
2705 | t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); | |
2706 | switch (tbase) { | |
2707 | case t_base_type::TYPE_STRING: | |
2708 | ts_type = "string"; | |
2709 | break; | |
2710 | case t_base_type::TYPE_BOOL: | |
2711 | ts_type = "boolean"; | |
2712 | break; | |
2713 | case t_base_type::TYPE_I8: | |
2714 | ts_type = "any"; | |
2715 | break; | |
2716 | case t_base_type::TYPE_I16: | |
2717 | case t_base_type::TYPE_I32: | |
2718 | case t_base_type::TYPE_DOUBLE: | |
2719 | ts_type = "number"; | |
2720 | break; | |
2721 | case t_base_type::TYPE_I64: | |
2722 | ts_type = "Int64"; | |
2723 | break; | |
2724 | case t_base_type::TYPE_VOID: | |
2725 | ts_type = "void"; | |
2726 | } | |
2727 | } else if (type->is_enum() || type->is_struct() || type->is_xception()) { | |
2728 | std::string type_name; | |
2729 | ||
2730 | if (type->get_program()) { | |
2731 | type_name = js_namespace(type->get_program()); | |
2732 | ||
2733 | // If the type is not defined within the current program, we need to prefix it with the same name as | |
2734 | // the generated "import" statement for the types containing program | |
2735 | if(type->get_program() != program_) { | |
2736 | auto prefix = include_2_import_name.find(type->get_program()); | |
2737 | ||
2738 | if(prefix != include_2_import_name.end()) { | |
2739 | type_name.append(prefix->second); | |
2740 | type_name.append("."); | |
2741 | } | |
2742 | } | |
2743 | } | |
2744 | ||
2745 | type_name.append(type->get_name()); | |
2746 | ts_type = type_name; | |
2747 | } else if (type->is_list() || type->is_set()) { | |
2748 | t_type* etype; | |
2749 | ||
2750 | if (type->is_list()) { | |
2751 | etype = ((t_list*)type)->get_elem_type(); | |
2752 | } else { | |
2753 | etype = ((t_set*)type)->get_elem_type(); | |
2754 | } | |
2755 | ||
2756 | ts_type = ts_get_type(etype) + "[]"; | |
2757 | } else if (type->is_map()) { | |
2758 | string ktype = ts_get_type(((t_map*)type)->get_key_type()); | |
2759 | string vtype = ts_get_type(((t_map*)type)->get_val_type()); | |
2760 | ||
2761 | ||
2762 | if (ktype == "number" || ktype == "string" ) { | |
2763 | ts_type = "{ [k: " + ktype + "]: " + vtype + "; }"; | |
2764 | } else if ((((t_map*)type)->get_key_type())->is_enum()) { | |
2765 | // Not yet supported (enum map): https://github.com/Microsoft/TypeScript/pull/2652 | |
2766 | //ts_type = "{ [k: " + ktype + "]: " + vtype + "; }"; | |
2767 | ts_type = "{ [k: number /*" + ktype + "*/]: " + vtype + "; }"; | |
2768 | } else { | |
2769 | ts_type = "any"; | |
2770 | } | |
2771 | } | |
2772 | ||
2773 | return ts_type; | |
2774 | } | |
2775 | ||
2776 | /** | |
2777 | * Renders a TypeScript function signature of the form 'name(args: types): type;' | |
2778 | * | |
2779 | * @param t_function Function definition | |
2780 | * @param bool in-/exclude the callback argument | |
2781 | * @return String of rendered function definition | |
2782 | */ | |
2783 | std::string t_js_generator::ts_function_signature(t_function* tfunction, bool include_callback) { | |
2784 | string str; | |
2785 | const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); | |
2786 | vector<t_field*>::const_iterator f_iter; | |
2787 | ||
2788 | str = tfunction->get_name() + "("; | |
2789 | ||
2790 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
2791 | str += (*f_iter)->get_name() + ts_get_req(*f_iter) + ": " + ts_get_type((*f_iter)->get_type()); | |
2792 | ||
2793 | if (f_iter + 1 != fields.end() || (include_callback && fields.size() > 0)) { | |
2794 | str += ", "; | |
2795 | } | |
2796 | } | |
2797 | ||
2798 | if (include_callback) { | |
2799 | if (gen_node_) { | |
2800 | t_struct* exceptions = tfunction->get_xceptions(); | |
2801 | string exception_types; | |
2802 | if (exceptions) { | |
2803 | const vector<t_field*>& members = exceptions->get_members(); | |
2804 | for (vector<t_field*>::const_iterator it = members.begin(); it != members.end(); ++it) { | |
2805 | t_type* t = get_true_type((*it)->get_type()); | |
2806 | if (it == members.begin()) { | |
2807 | exception_types = js_type_namespace(t->get_program()) + t->get_name(); | |
2808 | } else { | |
2809 | exception_types += " | " + js_type_namespace(t->get_program()) + t->get_name(); | |
2810 | } | |
2811 | } | |
2812 | } | |
2813 | if (exception_types == "") { | |
2814 | str += "callback?: (error: void, response: " + ts_get_type(tfunction->get_returntype()) + ")=>void): "; | |
2815 | } else { | |
2816 | str += "callback?: (error: " + exception_types + ", response: " + ts_get_type(tfunction->get_returntype()) + ")=>void): "; | |
2817 | } | |
2818 | } else { | |
2819 | str += "callback?: (data: " + ts_get_type(tfunction->get_returntype()) + ")=>void): "; | |
2820 | } | |
2821 | ||
2822 | if (gen_jquery_) { | |
2823 | str += "JQueryPromise<" + ts_get_type(tfunction->get_returntype()) +">;"; | |
2824 | } else { | |
2825 | str += "void;"; | |
2826 | } | |
2827 | } else { | |
2828 | if (gen_es6_) { | |
2829 | str += "): Promise<" + ts_get_type(tfunction->get_returntype()) + ">;"; | |
2830 | } | |
2831 | else { | |
2832 | str += "): " + ts_get_type(tfunction->get_returntype()) + ";"; | |
2833 | } | |
2834 | } | |
2835 | ||
2836 | return str; | |
2837 | } | |
2838 | ||
2839 | /** | |
2840 | * Takes a name and produces a valid NodeJS identifier from it | |
2841 | * | |
2842 | * @param name The name which shall become a valid NodeJS identifier | |
2843 | * @return The modified name with the updated identifier | |
2844 | */ | |
2845 | std::string t_js_generator::make_valid_nodeJs_identifier(std::string const& name) { | |
2846 | std::string str = name; | |
2847 | if (str.empty()) { | |
2848 | return str; | |
2849 | } | |
2850 | ||
2851 | // tests rely on this | |
2852 | assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); | |
2853 | ||
2854 | // if the first letter is a number, we add an additional underscore in front of it | |
2855 | char c = str.at(0); | |
2856 | if (('0' <= c) && (c <= '9')) { | |
2857 | str = "_" + str; | |
2858 | } | |
2859 | ||
2860 | // following chars: letter, number or underscore | |
2861 | for (size_t i = 0; i < str.size(); ++i) { | |
2862 | c = str.at(i); | |
2863 | if ((('A' > c) || (c > 'Z')) && (('a' > c) || (c > 'z')) && (('0' > c) || (c > '9')) | |
2864 | && ('_' != c) && ('$' != c)) { | |
2865 | str.replace(i, 1, "_"); | |
2866 | } | |
2867 | } | |
2868 | ||
2869 | return str; | |
2870 | } | |
2871 | ||
2872 | void t_js_generator::parse_imports(t_program* program, const std::string& imports_string) { | |
2873 | if (program->get_recursive()) { | |
2874 | throw std::invalid_argument("[-gen js:imports=] option is not usable in recursive code generation mode"); | |
2875 | } | |
2876 | std::stringstream sstream(imports_string); | |
2877 | std::string import; | |
2878 | while (std::getline(sstream, import, ':')) { | |
2879 | imports.emplace_back(import); | |
2880 | } | |
2881 | if (imports.empty()) { | |
2882 | throw std::invalid_argument("invalid usage: [-gen js:imports=] requires at least one path " | |
2883 | "(multiple paths are separated by ':')"); | |
2884 | } | |
2885 | for (auto& import : imports) { | |
2886 | // Strip trailing '/' | |
2887 | if (!import.empty() && import[import.size() - 1] == '/') { | |
2888 | import = import.substr(0, import.size() - 1); | |
2889 | } | |
2890 | if (import.empty()) { | |
2891 | throw std::invalid_argument("empty paths are not allowed in imports"); | |
2892 | } | |
2893 | std::ifstream episode_file; | |
2894 | string line; | |
2895 | const auto episode_file_path = import + "/" + episode_file_name; | |
2896 | episode_file.open(episode_file_path); | |
2897 | if (!episode_file) { | |
2898 | throw std::runtime_error("failed to open the file '" + episode_file_path + "'"); | |
2899 | } | |
2900 | while (std::getline(episode_file, line)) { | |
2901 | const auto separator_position = line.find(':'); | |
2902 | if (separator_position == string::npos) { | |
2903 | // could not find the separator in the line | |
2904 | throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the line '" + line | |
2905 | + "' does not have a key:value separator ':'"); | |
2906 | } | |
2907 | const auto module_name = line.substr(0, separator_position); | |
2908 | const auto import_path = line.substr(separator_position + 1); | |
2909 | if (module_name.empty()) { | |
2910 | throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the module name is empty"); | |
2911 | } | |
2912 | if (import_path.empty()) { | |
2913 | throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the import path is empty"); | |
2914 | } | |
2915 | const auto module_import_path = import.substr(import.find_last_of('/') + 1) + "/" + import_path; | |
2916 | const auto result = module_name_2_import_path.emplace(module_name, module_import_path); | |
2917 | if (!result.second) { | |
2918 | throw std::runtime_error("multiple providers of import path found for " + module_name | |
2919 | + "\n\t" + module_import_path + "\n\t" + result.first->second); | |
2920 | } | |
2921 | } | |
2922 | } | |
2923 | } | |
2924 | void t_js_generator::parse_thrift_package_output_directory(const std::string& thrift_package_output_directory) { | |
2925 | thrift_package_output_directory_ = thrift_package_output_directory; | |
2926 | // Strip trailing '/' | |
2927 | if (!thrift_package_output_directory_.empty() && thrift_package_output_directory_[thrift_package_output_directory_.size() - 1] == '/') { | |
2928 | thrift_package_output_directory_ = thrift_package_output_directory_.substr(0, thrift_package_output_directory_.size() - 1); | |
2929 | } | |
2930 | // Check that the thrift_package_output_directory is not empty after stripping | |
2931 | if (thrift_package_output_directory_.empty()) { | |
2932 | throw std::invalid_argument("the thrift_package_output_directory argument must not be empty"); | |
2933 | } else { | |
2934 | gen_episode_file_ = true; | |
2935 | } | |
2936 | } | |
2937 | ||
2938 | THRIFT_REGISTER_GENERATOR(js, | |
2939 | "Javascript", | |
2940 | " jquery: Generate jQuery compatible code.\n" | |
2941 | " node: Generate node.js compatible code.\n" | |
2942 | " ts: Generate TypeScript definition files.\n" | |
2943 | " with_ns: Create global namespace objects when using node.js\n" | |
2944 | " es6: Create ES6 code with Promises\n" | |
2945 | " thrift_package_output_directory=<path>:\n" | |
2946 | " Generate episode file and use the <path> as prefix\n" | |
2947 | " imports=<paths_to_modules>:\n" | |
2948 | " ':' separated list of paths of modules that has episode files in their root\n") |