void t_go_generator::generate_go_struct_definition()

in compiler/cpp/src/thrift/generate/t_go_generator.cc [1265:1499]


void t_go_generator::generate_go_struct_definition(ostream& out,
                                                   t_struct* tstruct,
                                                   bool is_exception,
                                                   bool is_result,
                                                   bool is_args) {
  const vector<t_field*>& members = tstruct->get_members();
  const vector<t_field*>& sorted_members = tstruct->get_sorted_members();
  vector<t_field*>::const_iterator m_iter;

  std::string tstruct_name(publicize(tstruct->get_name(), is_args || is_result));
  generate_go_docstring(out, tstruct);
  generate_deprecation_comment(out, tstruct->annotations_);
  out << indent() << "type " << tstruct_name << " struct {" << '\n';
  /*
     Here we generate the structure specification for the fastbinary codec.
     These specifications have the following structure:
     thrift_spec -> tuple of item_spec
     item_spec -> nil | (tag, type_enum, name, spec_args, default)
     tag -> integer
     type_enum -> TType.I32 | TType.STRING | TType.STRUCT | ...
     name -> string_literal
     default -> nil  # Handled by __init__
     spec_args -> nil  # For simple types
                | (type_enum, spec_args)  # Value type for list/set
                | (type_enum, spec_args, type_enum, spec_args)
                  # Key and value for map
                | (class_name, spec_args_ptr) # For struct/exception
     class_name -> identifier  # Basically a pointer to the class
     spec_args_ptr -> expression  # just class_name.spec_args

     TODO(dreiss): Consider making this work for structs with negative tags.
  */
  // TODO(dreiss): Look into generating an empty tuple instead of nil
  // for structures with no members.
  // TODO(dreiss): Test encoding of structs where some inner structs
  // don't have thrift_spec.
  indent_up();

  int num_setable = 0;
  if (sorted_members.empty() || (sorted_members[0]->get_key() >= 0)) {
    int sorted_keys_pos = 0;

    for (m_iter = sorted_members.begin(); m_iter != sorted_members.end(); ++m_iter) {
      // Set field to optional if field is union, this is so we can get a
      // pointer to the field.
      if (tstruct->is_union())
        (*m_iter)->set_req(t_field::T_OPTIONAL);
      if (sorted_keys_pos != (*m_iter)->get_key()) {
        int first_unused = (std::max)(1, sorted_keys_pos++);
        while (sorted_keys_pos != (*m_iter)->get_key()) {
          ++sorted_keys_pos;
        }
        int last_unused = sorted_keys_pos - 1;
        if (first_unused < last_unused) {
          indent(out) << "// unused fields # " << first_unused << " to " << last_unused << '\n';
        } else if (first_unused == last_unused) {
          indent(out) << "// unused field # " << first_unused << '\n';
        }
      }

      t_type* fieldType = (*m_iter)->get_type();
      string goType = type_to_go_type_with_opt(fieldType, is_pointer_field(*m_iter));

      map<string,string>tags;
      tags["db"]=escape_string((*m_iter)->get_name());

      // Only add the `omitempty` tag if this field is optional and has no default value.
      // Otherwise a proper value like `false` for a bool field will be ommitted from
      // the JSON output since Go Marshal won't output `zero values`.
      bool has_default = (*m_iter)->get_value();
      bool is_optional = (*m_iter)->get_req() == t_field::T_OPTIONAL;
      if (is_optional && !has_default) {
        tags["json"]=escape_string((*m_iter)->get_name())+",omitempty";
      } else {
        tags["json"]=escape_string((*m_iter)->get_name());
      }

      // Check for user defined tags and them if there are any. User defined tags
      // can override the above db and json tags.
      std::map<string, std::vector<string>>::iterator it = (*m_iter)->annotations_.find("go.tag");
      if (it != (*m_iter)->annotations_.end()) {
        parse_go_tags(&tags, it->second.back());
      }

      string gotag;
      for (auto it = tags.begin(); it != tags.end(); ++it) {
        gotag += it->first + ":\"" + it->second + "\" ";
      }
      // Trailing whitespace
      gotag.resize(gotag.size()-1);

      generate_deprecation_comment(out, (*m_iter)->annotations_);
      indent(out) << publicize((*m_iter)->get_name()) << " " << goType << " `thrift:\""
                  << escape_string((*m_iter)->get_name()) << "," << sorted_keys_pos;
      if ((*m_iter)->get_req() == t_field::T_REQUIRED) {
        out << ",required";
      }

      out << "\" " << gotag << "`" << '\n';
      sorted_keys_pos++;
    }
  } else {
    for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
      generate_deprecation_comment(out, (*m_iter)->annotations_);
      // This fills in default values, as opposed to nulls
      out << indent() << publicize((*m_iter)->get_name()) << " "
          << type_to_go_type((*m_iter)->get_type()) << '\n';
    }
  }

  indent_down();
  out << indent() << "}" << '\n' << '\n';
  generate_deprecation_comment(out, tstruct->annotations_);
  out << indent() << "func New" << tstruct_name << "() *" << tstruct_name << " {" << '\n';
  indent_up();
  out << indent() << "return &";
  generate_go_struct_initializer(out, tstruct, is_result || is_args);
  indent_down();
  out << indent() << "}" << '\n' << '\n';
  // Default values for optional fields
  for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
    string publicized_name;
    t_const_value* def_value;
    get_publicized_name_and_def_value(*m_iter, &publicized_name, &def_value);
    t_type* fieldType = (*m_iter)->get_type();
    string goType = type_to_go_type_with_opt(fieldType, false);
    string def_var_name = tstruct_name + "_" + publicized_name + "_DEFAULT";
    if ((*m_iter)->get_req() == t_field::T_OPTIONAL || is_pointer_field(*m_iter)) {
      generate_deprecation_comment(out, (*m_iter)->annotations_);
      out << indent() << "var " << def_var_name << " " << goType;
      if (def_value != nullptr) {
        out << " = " << render_const_value(fieldType, def_value, (*m_iter)->get_name());
      }
      out << '\n';
    }
    out << '\n';

    // num_setable is used for deciding if Count* methods will be generated for union fields.
    // This applies to all nullable fields including slices (used for set, list and binary) and maps, not just pointers.
    t_type* type = fieldType->get_true_type();
    if (is_pointer_field(*m_iter)|| type->is_map() || type->is_set() || type->is_list() || type->is_binary()) {
      num_setable += 1;
    }

    if (is_pointer_field(*m_iter)) {
      string goOptType = type_to_go_type_with_opt(fieldType, true);
      string maybepointer = goOptType != goType ? "*" : "";
      generate_deprecation_comment(out, (*m_iter)->annotations_);
      out << indent() << "func (p *" << tstruct_name << ") Get" << publicized_name << "() "
          << goType << " {" << '\n';
      indent_up();
      out << indent() << "if !p.IsSet" << publicized_name << "() {" << '\n';
      indent_up();
      out << indent() << "return " << def_var_name << '\n';
      indent_down();
      out << indent() << "}" << '\n';
      out << indent() << "return " << maybepointer << "p." << publicized_name << '\n';
      indent_down();
      out << indent() << "}" << '\n' << '\n';
    } else {
      out << '\n';
      generate_deprecation_comment(out, (*m_iter)->annotations_);
      out << indent() << "func (p *" << tstruct_name << ") Get" << publicized_name << "() "
          << goType << " {" << '\n';
      indent_up();
      out << indent() << "return p." << publicized_name << '\n';
      indent_down();
      out << indent() << "}" << '\n' << '\n';
    }
  }

  if (tstruct->is_union() && num_setable > 0) {
    generate_countsetfields_helper(out, tstruct, tstruct_name, is_result);
  }

  generate_isset_helpers(out, tstruct, tstruct_name, is_result);
  generate_go_struct_reader(out, tstruct, tstruct_name, is_result);
  generate_go_struct_writer(out, tstruct, tstruct_name, is_result, num_setable > 0);
  if (!is_result && !is_args) {
    generate_go_struct_equals(out, tstruct, tstruct_name);
  }

  out << indent() << "func (p *" << tstruct_name << ") String() string {" << '\n';
  indent_up();
  out << indent() << "if p == nil {" << '\n';
  indent_up();
  out << indent() << "return \"<nil>\"" << '\n';
  indent_down();
  out << indent() << "}" << '\n';
  out << indent() << "return fmt.Sprintf(\"" << escape_string(tstruct_name) << "(%+v)\", *p)"
      << '\n';
  indent_down();
  out << indent() << "}" << '\n' << '\n';

  if (is_exception) {
    out << indent() << "func (p *" << tstruct_name << ") Error() string {" << '\n';
    indent_up();
    out << indent() << "return p.String()" << '\n';
    indent_down();
    out << indent() << "}" << '\n' << '\n';

    out << indent() << "func (" << tstruct_name << ") TExceptionType() thrift.TExceptionType {" << '\n';
    indent_up();
    out << indent() << "return thrift.TExceptionTypeCompiled" << '\n';
    indent_down();
    out << indent() << "}" << '\n' << '\n';

    out << indent() << "var _ thrift.TException = (*" << tstruct_name << ")(nil)"
        << '\n' << '\n';
  }

  if (!read_write_private_) {
    // Generate the implementation of slog.LogValuer,
    // see: https://issues.apache.org/jira/browse/THRIFT-5745
    out << indent() << "func (p *" << tstruct_name << ") LogValue() slog.Value {" << '\n';
    indent_up();
    out << indent() << "if p == nil {" << '\n';
    indent_up();
    out << indent() << "return slog.AnyValue(nil)" << '\n';
    indent_down();
    out << indent() << "}" << '\n';
    out << indent() << "v := thrift.SlogTStructWrapper{" << '\n';
    indent_up();
    out << indent() << "Type: \"*" << package_name_ << "." << tstruct_name << "\"," << '\n';
    out << indent() << "Value: p," << '\n';
    indent_down();
    out << indent() << "}" << '\n';
    out << indent() << "return slog.AnyValue(v)" << '\n';
    indent_down();
    out << indent() << "}" << '\n' << '\n';

    out << indent() << "var _ slog.LogValuer = (*" << tstruct_name << ")(nil)"
        << '\n' << '\n';
  }
}