void t_js_generator::generate_service_client()

in compiler/cpp/src/thrift/generate/t_js_generator.cc [1758:2270]


void t_js_generator::generate_service_client(t_service* tservice) {

  bool is_subclass_service = tservice->get_extends() != nullptr;

  string client_var = js_namespace(tservice->get_program()) + service_name_ + "Client";
  if (gen_node_) {
    string prefix = has_js_namespace(tservice->get_program()) ? "" : js_const_type_;
    f_service_ << prefix << client_var;
    if (gen_ts_) {
      f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class "
                    << "Client ";
      if (tservice->get_extends() != nullptr) {
        f_service_ts_ << "extends " << tservice->get_extends()->get_name() << ".Client ";
      }
      f_service_ts_ << "{" << '\n';
    }
  } else {
    f_service_ << client_var;
    if (gen_ts_) {
      f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class "
                    << service_name_ << "Client ";
      if (is_subclass_service) {
        f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Client ";
      }
      f_service_ts_ << "{" << '\n';
    }
  }

  // ES6 Constructor
  if (gen_es6_) {

    if (is_subclass_service) {
      f_service_ << " = class " << service_name_ << "Client extends " << js_namespace(tservice->get_extends()->get_program())
                       << tservice->get_extends()->get_name() << "Client {" << '\n';
    } else {
      f_service_ << " = class " << service_name_ << "Client {" << '\n';
    }
    indent_up();
    if (gen_node_) {
      indent(f_service_) << "constructor(output, pClass) {" << '\n';
    } else {
      indent(f_service_) << "constructor(input, output) {" << '\n';
    }
  } else {
    if (gen_node_) {
      f_service_ << " = function(output, pClass) {" << '\n';
    } else {
      f_service_ << " = function(input, output) {" << '\n';
    }
  }

  indent_up();

  if (gen_node_) {
    if (gen_es6_ && is_subclass_service) {
      indent(f_service_) << "super(output, pClass);" << '\n';
    }
    indent(f_service_) << "this.output = output;" << '\n';
    indent(f_service_) << "this.pClass = pClass;" << '\n';
    indent(f_service_) << "this._seqid = 0;" << '\n';
    indent(f_service_) << "this._reqs = {};" << '\n';
    if (gen_ts_) {
      if(!is_subclass_service) {
        f_service_ts_ << ts_indent() << "private output: thrift.TTransport;" << '\n'
                      << ts_indent() << "private pClass: thrift.TProtocol;" << '\n'
                      << ts_indent() << "private _seqid: number;" << '\n'
                      << '\n';
      }

      f_service_ts_ << ts_indent() << "constructor(output: thrift.TTransport, pClass: { new(trans: thrift.TTransport): thrift.TProtocol });"
                    << '\n';
    }
  } else {
    indent(f_service_) << "this.input = input;" << '\n';
    indent(f_service_) << "this.output = (!output) ? input : output;" << '\n';
    indent(f_service_) << "this.seqid = 0;" << '\n';
    if (gen_ts_) {
      f_service_ts_ << ts_indent() << "input: Thrift.TJSONProtocol;" << '\n' << ts_indent()
                    << "output: Thrift.TJSONProtocol;" << '\n' << ts_indent() << "seqid: number;"
                    << '\n' << '\n' << ts_indent()
                    << "constructor(input: Thrift.TJSONProtocol, output?: Thrift.TJSONProtocol);"
                    << '\n';
    }
  }

  indent_down();

  if (gen_es6_) {
    indent(f_service_) << "}" << '\n';
  } else {
    indent(f_service_) << "};" << '\n';
    if (is_subclass_service) {
      indent(f_service_) << "Thrift.inherits(" << js_namespace(tservice->get_program())
                        << service_name_ << "Client, "
                        << js_namespace(tservice->get_extends()->get_program())
                        << tservice->get_extends()->get_name() << "Client);" << '\n';
    } else {
      // init prototype
      indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
                        << "Client.prototype = {};" << '\n';
    }
  }

  // utils for multiplexed services
  if (gen_node_) {
    if (gen_es6_) {
      indent(f_service_) << "seqid () { return this._seqid; }" << '\n';
      indent(f_service_) << "new_seqid () { return this._seqid += 1; }" << '\n';
    } else {
      indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
                        << "Client.prototype.seqid = function() { return this._seqid; };" << '\n'
                        << js_namespace(tservice->get_program()) << service_name_
                        << "Client.prototype.new_seqid = function() { return this._seqid += 1; };"
                        << '\n';
    }
  }

  // Generate client method implementations
  vector<t_function*> functions = tservice->get_functions();
  vector<t_function*>::const_iterator f_iter;
  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
    t_struct* arg_struct = (*f_iter)->get_arglist();
    const vector<t_field*>& fields = arg_struct->get_members();
    vector<t_field*>::const_iterator fld_iter;
    string funname = (*f_iter)->get_name();
    string arglist = argument_list(arg_struct);

    // Open function
    f_service_ << '\n';
    if (gen_es6_) {
      indent(f_service_) << funname << " (" << arglist << ") {" << '\n';
    } else {
      indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype."
                << function_signature(*f_iter, "", !gen_es6_) << " {" << '\n';
    }

    indent_up();

    if (gen_ts_) {
      // function definition without callback
      f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, false) << '\n';
      if (!gen_es6_) {
        // overload with callback
        f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << '\n';
      } else {
        // overload with callback
        f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << '\n';
      }
    }

    if (gen_es6_ && gen_node_) {
      indent(f_service_) << "this._seqid = this.new_seqid();" << '\n';
      indent(f_service_) << js_const_type_ << "self = this;" << '\n' << indent()
                 << "return new Promise((resolve, reject) => {" << '\n';
      indent_up();
      indent(f_service_) << "self._reqs[self.seqid()] = (error, result) => {" << '\n';
      indent_up();
      indent(f_service_) << "return error ? reject(error) : resolve(result);" << '\n';
      indent_down();
      indent(f_service_) << "};" << '\n';
      indent(f_service_) << "self.send_" << funname << "(" << arglist << ");" << '\n';
      indent_down();
      indent(f_service_) << "});" << '\n';
    } else if (gen_node_) { // Node.js output      ./gen-nodejs
      f_service_ << indent() << "this._seqid = this.new_seqid();" << '\n' << indent()
                 << "if (callback === undefined) {" << '\n';
      indent_up();
      f_service_ << indent() << js_const_type_ << "_defer = Q.defer();" << '\n' << indent()
                 << "this._reqs[this.seqid()] = function(error, result) {" << '\n';
      indent_up();
      indent(f_service_) << "if (error) {" << '\n';
      indent_up();
      indent(f_service_) << "_defer.reject(error);" << '\n';
      indent_down();
      indent(f_service_) << "} else {" << '\n';
      indent_up();
      indent(f_service_) << "_defer.resolve(result);" << '\n';
      indent_down();
      indent(f_service_) << "}" << '\n';
      indent_down();
      indent(f_service_) << "};" << '\n';
      f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << '\n'
                 << indent() << "return _defer.promise;" << '\n';
      indent_down();
      indent(f_service_) << "} else {" << '\n';
      indent_up();
      f_service_ << indent() << "this._reqs[this.seqid()] = callback;" << '\n' << indent()
                 << "this.send_" << funname << "(" << arglist << ");" << '\n';
      indent_down();
      indent(f_service_) << "}" << '\n';
    } else if (gen_es6_) {
      f_service_ << indent() << js_const_type_ << "self = this;" << '\n' << indent()
                 << "return new Promise((resolve, reject) => {" << '\n';
      indent_up();
      f_service_ << indent() << "self.send_" << funname << "(" << arglist
                 << (arglist.empty() ? "" : ", ") << "(error, result) => {" << '\n';
      indent_up();
      indent(f_service_) << "return error ? reject(error) : resolve(result);" << '\n';
      indent_down();
      f_service_ << indent() << "});" << '\n';
      indent_down();
      f_service_ << indent() << "});" << '\n';

    } else if (gen_jquery_) { // jQuery output       ./gen-js
      f_service_ << indent() << "if (callback === undefined) {" << '\n';
      indent_up();
      f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << '\n';
      if (!(*f_iter)->is_oneway()) {
        f_service_ << indent();
        if (!(*f_iter)->get_returntype()->is_void()) {
          f_service_ << "return ";
        }
        f_service_ << "this.recv_" << funname << "();" << '\n';
      }
      indent_down();
      f_service_ << indent() << "} else {" << '\n';
      indent_up();
      f_service_ << indent() << js_const_type_ << "postData = this.send_" << funname << "(" << arglist
                 << (arglist.empty() ? "" : ", ") << "true);" << '\n';
      f_service_ << indent() << "return this.output.getTransport()" << '\n';
      indent_up();
      f_service_ << indent() << ".jqRequest(this, postData, arguments, this.recv_" << funname
                 << ");" << '\n';
      indent_down();
      indent_down();
      f_service_ << indent() << "}" << '\n';
    } else { // Standard JavaScript ./gen-js
      f_service_ << indent() << "this.send_" << funname << "(" << arglist
                 << (arglist.empty() ? "" : ", ") << "callback); " << '\n';
      if (!(*f_iter)->is_oneway()) {
        f_service_ << indent() << "if (!callback) {" << '\n';
        f_service_ << indent();
        if (!(*f_iter)->get_returntype()->is_void()) {
          f_service_ << "  return ";
        }
        f_service_ << "this.recv_" << funname << "();" << '\n';
        f_service_ << indent() << "}" << '\n';
      }
    }

    indent_down();

    if (gen_es6_) {
      indent(f_service_) << "}" << '\n' << '\n';
    } else {
      indent(f_service_) << "};" << '\n' << '\n';
    }

    // Send function
    if (gen_es6_) {
      if (gen_node_) {
        indent(f_service_) << "send_" << funname << " (" << arglist << ") {" << '\n';
      } else {
        // ES6 js still uses callbacks here. Should refactor this to promise style later..
        indent(f_service_) << "send_" << funname << " (" << argument_list(arg_struct, true) << ") {" << '\n';
      }
    } else {
      indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype.send_"
                << function_signature(*f_iter, "", !gen_node_) << " {" << '\n';
    }

    indent_up();

    std::string outputVar;
    if (gen_node_) {
      f_service_ << indent() << js_const_type_ << "output = new this.pClass(this.output);" << '\n';
      outputVar = "output";
    } else {
      outputVar = "this.output";
    }

    std::string argsname = js_namespace(program_) + service_name_ + "_" + (*f_iter)->get_name()
                           + "_args";

    std::string messageType = (*f_iter)->is_oneway() ? "Thrift.MessageType.ONEWAY"
                                                     : "Thrift.MessageType.CALL";
    // Build args
    if (fields.size() > 0){
      // It is possible that a method argument is named "params", we need to ensure the locally
      // generated identifier "params" is uniquely named
      std::string params_identifier = this->next_identifier_name(fields, "params");
      f_service_ << indent() << js_const_type_ << params_identifier << " = {" << '\n';
      indent_up();
      for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
        indent(f_service_) << (*fld_iter)->get_name() << ": " << (*fld_iter)->get_name();
        if (fld_iter != fields.end()-1) {
          f_service_ << "," << '\n';
        } else {
          f_service_ << '\n';
        }
      }
      indent_down();
      indent(f_service_) << "};" << '\n';

      // NOTE: "args" is a reserved keyword, so no need to generate a unique identifier
      indent(f_service_) << js_const_type_ << "args = new " << argsname << "(" << params_identifier << ");" << '\n';
    } else {
      indent(f_service_) << js_const_type_ << "args = new " << argsname << "();" << '\n';
    }


    // Serialize the request header within try/catch
    indent(f_service_) << "try {" << '\n';
    indent_up();

    if (gen_node_) {
      f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name()
                 << "', " << messageType << ", this.seqid());" << '\n';
    } else {
      f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name()
                 << "', " << messageType << ", this.seqid);" << '\n';
    }


    // Write to the stream
    f_service_ << indent() << "args[Symbol.for(\"write\")](" << outputVar << ");" << '\n' << indent() << outputVar
               << ".writeMessageEnd();" << '\n';

    if (gen_node_) {
      if((*f_iter)->is_oneway()) {
        f_service_ << indent() << "this.output.flush();" << '\n';
        f_service_ << indent() << js_const_type_ << "callback = this._reqs[this.seqid()] || function() {};" << '\n';
        f_service_ << indent() << "delete this._reqs[this.seqid()];" << '\n';
        f_service_ << indent() << "callback(null);" << '\n';
      } else {
        f_service_ << indent() << "return this.output.flush();" << '\n';
      }
    } else {
      if (gen_jquery_) {
        f_service_ << indent() << "return this.output.getTransport().flush(callback);" << '\n';
      } else if (gen_es6_) {
        f_service_ << indent() << js_const_type_ << "self = this;" << '\n';
        if((*f_iter)->is_oneway()) {
          f_service_ << indent() << "this.output.getTransport().flush(true, null);" << '\n';
          f_service_ << indent() << "callback();" << '\n';
        } else {
          f_service_ << indent() << "this.output.getTransport().flush(true, () => {" << '\n';
          indent_up();
          f_service_ << indent() << js_let_type_ << "error = null, result = null;" << '\n';
          f_service_ << indent() << "try {" << '\n';
          f_service_ << indent() << "  result = self.recv_" << funname << "();" << '\n';
          f_service_ << indent() << "} catch (e) {" << '\n';
          f_service_ << indent() << "  error = e;" << '\n';
          f_service_ << indent() << "}" << '\n';
          f_service_ << indent() << "callback(error, result);" << '\n';
          indent_down();
          f_service_ << indent() << "});" << '\n';
        }
      } else {
        f_service_ << indent() << "if (callback) {" << '\n';
        indent_up();
        if((*f_iter)->is_oneway()) {
          f_service_ << indent() << "this.output.getTransport().flush(true, null);" << '\n';
          f_service_ << indent() << "callback();" << '\n';
        } else {
          f_service_ << indent() << js_const_type_ << "self = this;" << '\n';
          f_service_ << indent() << "this.output.getTransport().flush(true, function() {" << '\n';
          indent_up();
          f_service_ << indent() << js_let_type_ << "result = null;" << '\n';
          f_service_ << indent() << "try {" << '\n';
          f_service_ << indent() << "  result = self.recv_" << funname << "();" << '\n';
          f_service_ << indent() << "} catch (e) {" << '\n';
          f_service_ << indent() << "  result = e;" << '\n';
          f_service_ << indent() << "}" << '\n';
          f_service_ << indent() << "callback(result);" << '\n';
          indent_down();
          f_service_ << indent() << "});" << '\n';
        }
        indent_down();
        f_service_ << indent() << "} else {" << '\n';
        f_service_ << indent() << "  return this.output.getTransport().flush();" << '\n';
        f_service_ << indent() << "}" << '\n';
      }
    }

    indent_down();
    f_service_ << indent() << "}" << '\n';

    // Reset the transport and delete registered callback if there was a serialization error
    f_service_ << indent() << "catch (e) {" << '\n';
    indent_up();
    if (gen_node_) {
      f_service_ << indent() << "delete this._reqs[this.seqid()];" << '\n';
      f_service_ << indent() << "if (typeof " << outputVar << ".reset === 'function') {" << '\n';
      f_service_ << indent() << "  " << outputVar << ".reset();" << '\n';
      f_service_ << indent() << "}" << '\n';
    } else {
      f_service_ << indent() << "if (typeof " << outputVar << ".getTransport().reset === 'function') {" << '\n';
      f_service_ << indent() << "  " << outputVar << ".getTransport().reset();" << '\n';
      f_service_ << indent() << "}" << '\n';
    }
    f_service_ << indent() << "throw e;" << '\n';
    indent_down();
    f_service_ << indent() << "}" << '\n';

    indent_down();

    // Close send function
    if (gen_es6_) {
      indent(f_service_) << "}" << '\n';
    } else {
      indent(f_service_) << "};" << '\n';
    }

    // Receive function
    if (!(*f_iter)->is_oneway()) {
      std::string resultname = js_namespace(tservice->get_program()) + service_name_ + "_"
                               + (*f_iter)->get_name() + "_result";

      f_service_ << '\n';
      // Open receive function
      if (gen_node_) {
        if (gen_es6_) {
          indent(f_service_) << "recv_" << (*f_iter)->get_name() << " (input, mtype, rseqid) {" << '\n';
        } else {
          indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
                      << "Client.prototype.recv_" << (*f_iter)->get_name()
                      << " = function(input,mtype,rseqid) {" << '\n';
        }
      } else {
        if (gen_es6_) {
          indent(f_service_) << "recv_" << (*f_iter)->get_name() << " () {" << '\n';
        } else {
          t_struct noargs(program_);

          t_function recv_function((*f_iter)->get_returntype(),
                                  string("recv_") + (*f_iter)->get_name(),
                                  &noargs);
          indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
                    << "Client.prototype." << function_signature(&recv_function) << " {" << '\n';
        }
      }

      indent_up();

      std::string inputVar;
      if (gen_node_) {
        inputVar = "input";
      } else {
        inputVar = "this.input";
      }

      if (gen_node_) {
        f_service_ << indent() << js_const_type_ << "callback = this._reqs[rseqid] || function() {};" << '\n'
                   << indent() << "delete this._reqs[rseqid];" << '\n';
      } else {
        f_service_ << indent() << js_const_type_ << "ret = this.input.readMessageBegin();" << '\n'
                   << indent() << js_const_type_ << "mtype = ret.mtype;" << '\n';
      }

      f_service_ << indent() << "if (mtype == Thrift.MessageType.EXCEPTION) {" << '\n';

      indent_up();
      f_service_ << indent() << js_const_type_ << "x = new Thrift.TApplicationException();" << '\n'
                 << indent() << "x[Symbol.for(\"read\")](" << inputVar << ");" << '\n'
                 << indent() << inputVar << ".readMessageEnd();" << '\n'
                 << indent() << render_recv_throw("x") << '\n';
      scope_down(f_service_);

      f_service_ << indent() << js_const_type_ << "result = new " << resultname << "();" << '\n' << indent()
                 << "result[Symbol.for(\"read\")](" << inputVar << ");" << '\n';

      f_service_ << indent() << inputVar << ".readMessageEnd();" << '\n' << '\n';

      t_struct* xs = (*f_iter)->get_xceptions();
      const std::vector<t_field*>& xceptions = xs->get_members();
      vector<t_field*>::const_iterator x_iter;
      for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
        f_service_ << indent() << "if (null !== result." << (*x_iter)->get_name() << ") {" << '\n'
                   << indent() << "  " << render_recv_throw("result." + (*x_iter)->get_name())
                   << '\n' << indent() << "}" << '\n';
      }

      // Careful, only return result if not a void function
      if (!(*f_iter)->get_returntype()->is_void()) {
        f_service_ << indent() << "if (null !== result.success) {" << '\n' << indent() << "  "
                   << render_recv_return("result.success") << '\n' << indent() << "}" << '\n';
        f_service_ << indent()
                   << render_recv_throw("'" + (*f_iter)->get_name() + " failed: unknown result'")
                   << '\n';
      } else {
        if (gen_node_) {
          indent(f_service_) << "callback(null);" << '\n';
        } else {
          indent(f_service_) << "return;" << '\n';
        }
      }

      // Close receive function
      indent_down();
      if (gen_es6_) {
        indent(f_service_) << "}" << '\n';
      } else {
        indent(f_service_) << "};" << '\n';
      }
    }
  }

  // Finish class definitions
  if (gen_ts_) {
    f_service_ts_ << ts_indent() << "}" << '\n';
  }
  if (gen_es6_) {
    indent_down();
    f_service_ << "};" << '\n';
  }

  if(gen_esm_) {
    f_service_ << "export { " << client_var << " as Client };" << '\n';
  } else if(gen_node_) {
    f_service_ << "exports.Client = " << client_var << ";" << '\n';
  }
}