// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include #include #include #include #include #include #include #include #include #include namespace google { namespace protobuf { namespace util { namespace converter { using google::protobuf::internal::WireFormatLite; using google::protobuf::io::CodedOutputStream; using util::error::INVALID_ARGUMENT; using util::Status; using util::StatusOr; ProtoStreamObjectWriter::ProtoStreamObjectWriter( TypeResolver* type_resolver, const google::protobuf::Type& type, strings::ByteSink* output, ErrorListener* listener) : master_type_(type), typeinfo_(TypeInfo::NewTypeInfo(type_resolver)), own_typeinfo_(true), done_(false), element_(NULL), size_insert_(), output_(output), buffer_(), adapter_(&buffer_), stream_(new CodedOutputStream(&adapter_)), listener_(listener), invalid_depth_(0), tracker_(new ObjectLocationTracker()) {} ProtoStreamObjectWriter::ProtoStreamObjectWriter( TypeInfo* typeinfo, const google::protobuf::Type& type, strings::ByteSink* output, ErrorListener* listener) : master_type_(type), typeinfo_(typeinfo), own_typeinfo_(false), done_(false), element_(NULL), size_insert_(), output_(output), buffer_(), adapter_(&buffer_), stream_(new CodedOutputStream(&adapter_)), listener_(listener), invalid_depth_(0), tracker_(new ObjectLocationTracker()) {} ProtoStreamObjectWriter::~ProtoStreamObjectWriter() { // Cleanup explicitly in order to avoid destructor stack overflow when input // is deeply nested. while (element_ != NULL) { element_.reset(element_->pop()); } if (own_typeinfo_) { delete typeinfo_; } } namespace { // Writes an INT32 field, including tag to the stream. inline Status WriteInt32(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr i32 = data.ToInt32(); if (i32.ok()) { WireFormatLite::WriteInt32(field_number, i32.ValueOrDie(), stream); } return i32.status(); } // writes an SFIXED32 field, including tag, to the stream. inline Status WriteSFixed32(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr i32 = data.ToInt32(); if (i32.ok()) { WireFormatLite::WriteSFixed32(field_number, i32.ValueOrDie(), stream); } return i32.status(); } // Writes an SINT32 field, including tag, to the stream. inline Status WriteSInt32(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr i32 = data.ToInt32(); if (i32.ok()) { WireFormatLite::WriteSInt32(field_number, i32.ValueOrDie(), stream); } return i32.status(); } // Writes a FIXED32 field, including tag, to the stream. inline Status WriteFixed32(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr u32 = data.ToUint32(); if (u32.ok()) { WireFormatLite::WriteFixed32(field_number, u32.ValueOrDie(), stream); } return u32.status(); } // Writes a UINT32 field, including tag, to the stream. inline Status WriteUInt32(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr u32 = data.ToUint32(); if (u32.ok()) { WireFormatLite::WriteUInt32(field_number, u32.ValueOrDie(), stream); } return u32.status(); } // Writes an INT64 field, including tag, to the stream. inline Status WriteInt64(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr i64 = data.ToInt64(); if (i64.ok()) { WireFormatLite::WriteInt64(field_number, i64.ValueOrDie(), stream); } return i64.status(); } // Writes an SFIXED64 field, including tag, to the stream. inline Status WriteSFixed64(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr i64 = data.ToInt64(); if (i64.ok()) { WireFormatLite::WriteSFixed64(field_number, i64.ValueOrDie(), stream); } return i64.status(); } // Writes an SINT64 field, including tag, to the stream. inline Status WriteSInt64(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr i64 = data.ToInt64(); if (i64.ok()) { WireFormatLite::WriteSInt64(field_number, i64.ValueOrDie(), stream); } return i64.status(); } // Writes a FIXED64 field, including tag, to the stream. inline Status WriteFixed64(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr u64 = data.ToUint64(); if (u64.ok()) { WireFormatLite::WriteFixed64(field_number, u64.ValueOrDie(), stream); } return u64.status(); } // Writes a UINT64 field, including tag, to the stream. inline Status WriteUInt64(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr u64 = data.ToUint64(); if (u64.ok()) { WireFormatLite::WriteUInt64(field_number, u64.ValueOrDie(), stream); } return u64.status(); } // Writes a DOUBLE field, including tag, to the stream. inline Status WriteDouble(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr d = data.ToDouble(); if (d.ok()) { WireFormatLite::WriteDouble(field_number, d.ValueOrDie(), stream); } return d.status(); } // Writes a FLOAT field, including tag, to the stream. inline Status WriteFloat(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr f = data.ToFloat(); if (f.ok()) { WireFormatLite::WriteFloat(field_number, f.ValueOrDie(), stream); } return f.status(); } // Writes a BOOL field, including tag, to the stream. inline Status WriteBool(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr b = data.ToBool(); if (b.ok()) { WireFormatLite::WriteBool(field_number, b.ValueOrDie(), stream); } return b.status(); } // Writes a BYTES field, including tag, to the stream. inline Status WriteBytes(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr c = data.ToBytes(); if (c.ok()) { WireFormatLite::WriteBytes(field_number, c.ValueOrDie(), stream); } return c.status(); } // Writes a STRING field, including tag, to the stream. inline Status WriteString(int field_number, const DataPiece& data, CodedOutputStream* stream) { StatusOr s = data.ToString(); if (s.ok()) { WireFormatLite::WriteString(field_number, s.ValueOrDie(), stream); } return s.status(); } // Writes an ENUM field, including tag, to the stream. inline Status WriteEnum(int field_number, const DataPiece& data, const google::protobuf::Enum* enum_type, CodedOutputStream* stream) { StatusOr e = data.ToEnum(enum_type); if (e.ok()) { WireFormatLite::WriteEnum(field_number, e.ValueOrDie(), stream); } return e.status(); } // Given a google::protobuf::Type, returns the set of all required fields. std::set GetRequiredFields( const google::protobuf::Type& type) { std::set required; for (int i = 0; i < type.fields_size(); i++) { const google::protobuf::Field& field = type.fields(i); if (field.cardinality() == google::protobuf::Field_Cardinality_CARDINALITY_REQUIRED) { required.insert(&field); } } return required; } // Utility method to split a string representation of Timestamp or Duration and // return the parts. void SplitSecondsAndNanos(StringPiece input, StringPiece* seconds, StringPiece* nanos) { size_t idx = input.rfind('.'); if (idx != string::npos) { *seconds = input.substr(0, idx); *nanos = input.substr(idx + 1); } else { *seconds = input; *nanos = StringPiece(); } } } // namespace ProtoStreamObjectWriter::AnyWriter::AnyWriter(ProtoStreamObjectWriter* parent) : parent_(parent), ow_(), invalid_(false), data_(), output_(&data_), depth_(0), has_injected_value_message_(false) {} ProtoStreamObjectWriter::AnyWriter::~AnyWriter() {} void ProtoStreamObjectWriter::AnyWriter::StartObject(StringPiece name) { ++depth_; // If an object writer is absent, that means we have not called StartAny() // before reaching here. This is an invalid state. StartAny() gets called // whenever we see an "@type" being rendered (see AnyWriter::RenderDataPiece). if (ow_ == NULL) { // Make sure we are not already in an invalid state. This avoids making // multiple unnecessary InvalidValue calls. if (!invalid_) { parent_->InvalidValue("Any", StrCat("Missing or invalid @type for any field in ", parent_->master_type_.name())); invalid_ = true; } } else if (!has_injected_value_message_ || depth_ != 1 || name != "value") { // We don't propagate to ow_ StartObject("value") calls for nested Anys or // Struct at depth 1 as they are nested one level deep with an injected // "value" field. ow_->StartObject(name); } } bool ProtoStreamObjectWriter::AnyWriter::EndObject() { --depth_; // As long as depth_ >= 0, we know we haven't reached the end of Any. // Propagate these EndObject() calls to the contained ow_. If we are in a // nested Any or Struct type, ignore the second to last EndObject call (depth_ // == -1) if (ow_ != NULL && (!has_injected_value_message_ || depth_ >= 0)) { ow_->EndObject(); } // A negative depth_ implies that we have reached the end of Any // object. Now we write out its contents. if (depth_ < 0) { WriteAny(); return false; } return true; } void ProtoStreamObjectWriter::AnyWriter::StartList(StringPiece name) { ++depth_; // We expect ow_ to be present as this call only makes sense inside an Any. if (ow_ == NULL) { if (!invalid_) { parent_->InvalidValue("Any", StrCat("Missing or invalid @type for any field in ", parent_->master_type_.name())); invalid_ = true; } } else { ow_->StartList(name); } } void ProtoStreamObjectWriter::AnyWriter::EndList() { --depth_; if (depth_ < 0) { GOOGLE_LOG(DFATAL) << "Mismatched EndList found, should not be possible"; depth_ = 0; } // We don't write an error on the close, only on the open if (ow_ != NULL) { ow_->EndList(); } } void ProtoStreamObjectWriter::AnyWriter::RenderDataPiece( StringPiece name, const DataPiece& value) { // Start an Any only at depth_ 0. Other RenderDataPiece calls with "@type" // should go to the contained ow_ as they indicate nested Anys. if (depth_ == 0 && ow_ == NULL && name == "@type") { StartAny(value); } else if (ow_ == NULL) { if (!invalid_) { parent_->InvalidValue("Any", StrCat("Missing or invalid @type for any field in ", parent_->master_type_.name())); invalid_ = true; } } else { // Check to see if the data needs to be rendered with well-known-type // renderer. const TypeRenderer* type_renderer = FindTypeRenderer(GetFullTypeWithUrl(ow_->master_type_.name())); if (type_renderer) { // TODO(rikka): Don't just ignore the util::Status object! (*type_renderer)(ow_.get(), value); } else { ow_->RenderDataPiece(name, value); } } } void ProtoStreamObjectWriter::AnyWriter::StartAny(const DataPiece& value) { // Figure out the type url. This is a copy-paste from WriteString but we also // need the value, so we can't just call through to that. if (value.type() == DataPiece::TYPE_STRING) { type_url_ = value.str().ToString(); } else { StatusOr s = value.ToString(); if (!s.ok()) { parent_->InvalidValue("String", s.status().error_message()); invalid_ = true; return; } type_url_ = s.ValueOrDie(); } // Resolve the type url, and report an error if we failed to resolve it. StatusOr resolved_type = parent_->typeinfo_->ResolveTypeUrl(type_url_); if (!resolved_type.ok()) { parent_->InvalidValue("Any", resolved_type.status().error_message()); invalid_ = true; return; } // At this point, type is never null. const google::protobuf::Type* type = resolved_type.ValueOrDie(); // If this is the case of an Any in an Any or Struct in an Any, we need to // expect a StartObject call with "value" while we're at depth_ 0, which we // should ignore (not propagate to our nested object writer). We also need to // ignore the second-to-last EndObject call, and not propagate that either. if (type->name() == kAnyType || type->name() == kStructType) { has_injected_value_message_ = true; } // Create our object writer and initialize it with the first StartObject // call. ow_.reset(new ProtoStreamObjectWriter(parent_->typeinfo_, *type, &output_, parent_->listener_)); ow_->StartObject(""); } void ProtoStreamObjectWriter::AnyWriter::WriteAny() { if (ow_ == NULL) { // If we had no object writer, we never got any content, so just return // immediately, which is equivalent to writing an empty Any. return; } // Render the type_url and value fields directly to the stream. // type_url has tag 1 and value has tag 2. WireFormatLite::WriteString(1, type_url_, parent_->stream_.get()); if (!data_.empty()) { WireFormatLite::WriteBytes(2, data_, parent_->stream_.get()); } } ProtoStreamObjectWriter::ProtoElement::ProtoElement( TypeInfo* typeinfo, const google::protobuf::Type& type, ProtoStreamObjectWriter* enclosing) : BaseElement(NULL), ow_(enclosing), any_(), field_(NULL), typeinfo_(typeinfo), type_(type), required_fields_(GetRequiredFields(type)), is_repeated_type_(false), size_index_(-1), array_index_(-1), element_type_(GetElementType(type_)) { if (element_type_ == ANY) { any_.reset(new AnyWriter(ow_)); } } ProtoStreamObjectWriter::ProtoElement::ProtoElement( ProtoStreamObjectWriter::ProtoElement* parent, const google::protobuf::Field* field, const google::protobuf::Type& type, ElementType element_type) : BaseElement(parent), ow_(this->parent()->ow_), any_(), field_(field), typeinfo_(this->parent()->typeinfo_), type_(type), is_repeated_type_(element_type == ProtoElement::LIST || element_type == ProtoElement::STRUCT_LIST || element_type == ProtoElement::MAP || element_type == ProtoElement::STRUCT_MAP), size_index_(!is_repeated_type_ && field->kind() == google::protobuf::Field_Kind_TYPE_MESSAGE ? ow_->size_insert_.size() : -1), array_index_(is_repeated_type_ ? 0 : -1), element_type_(element_type) { if (!is_repeated_type_) { if (field->cardinality() == google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { // Update array_index_ if it is an explicit list. if (this->parent()->array_index_ >= 0) this->parent()->array_index_++; } else { this->parent()->RegisterField(field); } if (field->kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) { required_fields_ = GetRequiredFields(type_); int start_pos = ow_->stream_->ByteCount(); // length of serialized message is the final buffer position minus // starting buffer position, plus length adjustments for size fields // of any nested messages. We start with -start_pos here, so we only // need to add the final buffer position to it at the end. SizeInfo info = {start_pos, -start_pos}; ow_->size_insert_.push_back(info); } } if (element_type == ANY) { any_.reset(new AnyWriter(ow_)); } } ProtoStreamObjectWriter::ProtoElement* ProtoStreamObjectWriter::ProtoElement::pop() { // Calls the registered error listener for any required field(s) not yet // seen. for (set::iterator it = required_fields_.begin(); it != required_fields_.end(); ++it) { ow_->MissingField((*it)->name()); } // Computes the total number of proto bytes used by a message, also adjusts // the size of all parent messages by the length of this size field. // If size_index_ < 0, this is not a message, so no size field is added. if (size_index_ >= 0) { // Add the final buffer position to compute the total length of this // serialized message. The stored value (before this addition) already // contains the total length of the size fields of all nested messages // minus the initial buffer position. ow_->size_insert_[size_index_].size += ow_->stream_->ByteCount(); // Calculate the length required to serialize the size field of the // message, and propagate this additional size information upward to // all enclosing messages. int size = ow_->size_insert_[size_index_].size; int length = CodedOutputStream::VarintSize32(size); for (ProtoElement* e = parent(); e != NULL; e = e->parent()) { // Only nested messages have size field, lists do not have size field. if (e->size_index_ >= 0) { ow_->size_insert_[e->size_index_].size += length; } } } return BaseElement::pop(); } void ProtoStreamObjectWriter::ProtoElement::RegisterField( const google::protobuf::Field* field) { if (!required_fields_.empty() && field->cardinality() == google::protobuf::Field_Cardinality_CARDINALITY_REQUIRED) { required_fields_.erase(field); } } string ProtoStreamObjectWriter::ProtoElement::ToString() const { if (parent() == NULL) return ""; string loc = parent()->ToString(); if (field_->cardinality() != google::protobuf::Field_Cardinality_CARDINALITY_REPEATED || parent()->field_ != field_) { string name = field_->name(); int i = 0; while (i < name.size() && (ascii_isalnum(name[i]) || name[i] == '_')) ++i; if (i > 0 && i == name.size()) { // safe field name if (loc.empty()) { loc = name; } else { StrAppend(&loc, ".", name); } } else { StrAppend(&loc, "[\"", CEscape(name), "\"]"); } } if (field_->cardinality() == google::protobuf::Field_Cardinality_CARDINALITY_REPEATED && array_index_ > 0) { StrAppend(&loc, "[", array_index_ - 1, "]"); } return loc.empty() ? "." : loc; } inline void ProtoStreamObjectWriter::InvalidName(StringPiece unknown_name, StringPiece message) { listener_->InvalidName(location(), ToSnakeCase(unknown_name), message); } inline void ProtoStreamObjectWriter::InvalidValue(StringPiece type_name, StringPiece value) { listener_->InvalidValue(location(), type_name, value); } inline void ProtoStreamObjectWriter::MissingField(StringPiece missing_name) { listener_->MissingField(location(), missing_name); } ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartObject( StringPiece name) { // Starting the root message. Create the root ProtoElement and return. if (element_ == NULL) { if (!name.empty()) { InvalidName(name, "Root element should not be named."); } element_.reset(new ProtoElement(typeinfo_, master_type_, this)); // If master type is a special type that needs extra values to be written to // stream, we write those values. if (master_type_.name() == kStructType) { StartStruct(NULL); } else if (master_type_.name() == kStructValueType) { // We got a StartObject call with google.protobuf.Value field. This means // we are starting an object within google.protobuf.Value type. The only // object within that type is a struct type. So start a struct. const google::protobuf::Field* field = StartStructValueInStruct(NULL); StartStruct(field); } return this; } const google::protobuf::Field* field = NULL; if (element_ != NULL && element_->IsAny()) { element_->any()->StartObject(name); return this; } else if (element_ != NULL && (element_->IsMap() || element_->IsStructMap())) { field = StartMapEntry(name); if (element_->IsStructMapEntry()) { // If the top element is a map entry, this means we are starting another // struct within a struct. field = StartStructValueInStruct(field); } } else if (element_ != NULL && element_->IsStructList()) { // If the top element is a list, then we are starting a list field within a // struct. field = Lookup(name); field = StartStructValueInStruct(field); } else { field = BeginNamed(name, false); } if (field == NULL) { return this; } const google::protobuf::Type* type = LookupType(field); if (type == NULL) { ++invalid_depth_; InvalidName(name, StrCat("Missing descriptor for field: ", field->type_url())); return this; } if (field->type_url() == GetFullTypeWithUrl(kStructType)) { // Start a struct object. StartStruct(field); } else if (field->type_url() == GetFullTypeWithUrl(kStructValueType)) { // We got a StartObject call with google.protobuf.Value field. This means we // are starting an object within google.protobuf.Value type. The only object // within that type is a struct type. So start a struct. field = StartStructValueInStruct(field); StartStruct(field); } else if (field->type_url() == GetFullTypeWithUrl(kAnyType)) { // Begin an Any. We can't do the real work till we get the @type field. WriteTag(*field); element_.reset( new ProtoElement(element_.release(), field, *type, ProtoElement::ANY)); } else if (IsMap(*field)) { // Begin a map. // A map is triggered by a StartObject() call if the current field has a map // type. Map values are written to proto in a manner detailed in comments // above StartMapEntry() function. element_.reset( new ProtoElement(element_.release(), field, *type, ProtoElement::MAP)); } else { WriteTag(*field); element_.reset(new ProtoElement(element_.release(), field, *type, ProtoElement::MESSAGE)); } return this; } // Proto3 maps are represented on the wire as a message with // "key" and a "value". // // For example, the syntax: // map map_field = N; // // is represented as: // message MapFieldEntry { // option map_entry = true; // marks the map construct in the descriptor // // key_type key = 1; // value_type value = 2; // } // repeated MapFieldEntry map_field = N; // // See go/proto3-maps for more information. const google::protobuf::Field* ProtoStreamObjectWriter::StartMapEntry( StringPiece name) { // top of stack is already a map field const google::protobuf::Field* field = element_->field(); const google::protobuf::Type& type = element_->type(); // If we come from a regular map, use MAP_ENTRY or if we come from a struct, // use STRUCT_MAP_ENTRY. These values are used later in StartObject/StartList // or RenderDataPiece for making appropriate decisions. ProtoElement::ElementType element_type = element_->IsStructMap() ? ProtoElement::STRUCT_MAP_ENTRY : ProtoElement::MAP_ENTRY; WriteTag(*field); element_.reset( new ProtoElement(element_.release(), field, type, element_type)); RenderDataPiece("key", DataPiece(name)); return BeginNamed("value", false); } // Starts a google.protobuf.Struct. // 'field' represents a field in a message of type google.protobuf.Struct. A // struct contains a map with name 'fields'. This function starts this map as // well. // When 'field' is NULL, it means that the top level message is of struct // type. void ProtoStreamObjectWriter::StartStruct( const google::protobuf::Field* field) { const google::protobuf::Type* type = NULL; if (field) { type = LookupType(field); WriteTag(*field); element_.reset(new ProtoElement(element_.release(), field, *type, ProtoElement::STRUCT)); } const google::protobuf::Field* struct_field = BeginNamed("fields", false); if (!struct_field) { // It is a programmatic error if this happens. Log an error. GOOGLE_LOG(ERROR) << "Invalid internal state. Cannot find 'fields' within " << (field ? field->type_url() : "google.protobuf.Struct"); return; } type = LookupType(struct_field); element_.reset(new ProtoElement(element_.release(), struct_field, *type, ProtoElement::STRUCT_MAP)); } // Starts a "struct_value" within struct.proto's google.protobuf.Value type. // 'field' should be of the type google.protobuf.Value. // Returns the field identifying "struct_value" within the given field. // // If field is NULL, then we are starting struct_value at the top-level, in // this case skip writing any tag information for the passed field. const google::protobuf::Field* ProtoStreamObjectWriter::StartStructValueInStruct( const google::protobuf::Field* field) { if (field) { const google::protobuf::Type* type = LookupType(field); WriteTag(*field); element_.reset(new ProtoElement(element_.release(), field, *type, ProtoElement::STRUCT_VALUE)); } return BeginNamed("struct_value", false); } // Starts a "list_value" within struct.proto's google.protobuf.Value type. // 'field' should be of the type google.protobuf.Value. // Returns the field identifying "list_value" within the given field. // // If field is NULL, then we are starting list_value at the top-level, in // this case skip writing any tag information for the passed field. const google::protobuf::Field* ProtoStreamObjectWriter::StartListValueInStruct( const google::protobuf::Field* field) { if (field) { const google::protobuf::Type* type = LookupType(field); WriteTag(*field); element_.reset(new ProtoElement(element_.release(), field, *type, ProtoElement::STRUCT_VALUE)); } const google::protobuf::Field* list_value = BeginNamed("list_value", false); if (!list_value) { // It is a programmatic error if this happens. Log an error. GOOGLE_LOG(ERROR) << "Invalid internal state. Cannot find 'list_value' within " << (field ? field->type_url() : "google.protobuf.Value"); return field; } return StartRepeatedValuesInListValue(list_value); } // Starts the repeated "values" field in struct.proto's // google.protobuf.ListValue type. 'field' should be of type // google.protobuf.ListValue. // // If field is NULL, then we are starting ListValue at the top-level, in // this case skip writing any tag information for the passed field. const google::protobuf::Field* ProtoStreamObjectWriter::StartRepeatedValuesInListValue( const google::protobuf::Field* field) { if (field) { const google::protobuf::Type* type = LookupType(field); WriteTag(*field); element_.reset(new ProtoElement(element_.release(), field, *type, ProtoElement::STRUCT_LIST_VALUE)); } return BeginNamed("values", true); } void ProtoStreamObjectWriter::SkipElements() { if (element_ == NULL) return; ProtoElement::ElementType element_type = element_->element_type(); while (element_type == ProtoElement::STRUCT || element_type == ProtoElement::STRUCT_LIST_VALUE || element_type == ProtoElement::STRUCT_VALUE || element_type == ProtoElement::STRUCT_MAP_ENTRY || element_type == ProtoElement::MAP_ENTRY) { element_.reset(element_->pop()); element_type = element_ != NULL ? element_->element_type() : ProtoElement::MESSAGE; } } ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndObject() { if (invalid_depth_ > 0) { --invalid_depth_; return this; } if (element_ != NULL && element_->IsAny()) { if (element_->any()->EndObject()) { return this; } } if (element_ != NULL) { element_.reset(element_->pop()); } // Skip sentinel elements added to keep track of new proto3 types - map, // struct. SkipElements(); // If ending the root element, // then serialize the full message with calculated sizes. if (element_ == NULL) { WriteRootMessage(); } return this; } ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList(StringPiece name) { const google::protobuf::Field* field = NULL; // Since we cannot have a top-level repeated item in protobuf, the only way // element_ can be null when here is when we start a top-level list // google.protobuf.ListValue. if (element_ == NULL) { if (!name.empty()) { InvalidName(name, "Root element should not be named."); } element_.reset(new ProtoElement(typeinfo_, master_type_, this)); // If master type is a special type that needs extra values to be written to // stream, we write those values. if (master_type_.name() == kStructValueType) { // We got a StartList with google.protobuf.Value master type. This means // we have to start the "list_value" within google.protobuf.Value. field = StartListValueInStruct(NULL); } else if (master_type_.name() == kStructListValueType) { // We got a StartList with google.protobuf.ListValue master type. This // means we have to start the "values" within google.protobuf.ListValue. field = StartRepeatedValuesInListValue(NULL); } // field is NULL when master_type_ is anything other than // google.protobuf.Value or google.protobuf.ListValue. if (field) { const google::protobuf::Type* type = LookupType(field); element_.reset(new ProtoElement(element_.release(), field, *type, ProtoElement::STRUCT_LIST)); } return this; } if (element_->IsAny()) { element_->any()->StartList(name); return this; } // The type of element we push to stack. ProtoElement::ElementType element_type = ProtoElement::LIST; // Check if we need to start a map. This can heppen when there is either a map // or a struct type within a list. if (element_->IsMap() || element_->IsStructMap()) { field = StartMapEntry(name); if (field == NULL) return this; if (element_->IsStructMapEntry()) { // If the top element is a map entry, this means we are starting a list // within a struct or a map. // An example sequence of calls would be // StartObject -> StartList field = StartListValueInStruct(field); if (field == NULL) return this; } element_type = ProtoElement::STRUCT_LIST; } else if (element_->IsStructList()) { // If the top element is a STRUCT_LIST, this means we are starting a list // within the current list (inside a struct). // An example call sequence would be // StartObject -> StartList -> StartList // with StartObject starting a struct. // Lookup the last list type in element stack as we are adding an element of // the same type. field = Lookup(name); if (field == NULL) return this; field = StartListValueInStruct(field); if (field == NULL) return this; element_type = ProtoElement::STRUCT_LIST; } else { // Lookup field corresponding to 'name'. If it is a google.protobuf.Value // or google.protobuf.ListValue type, then StartList is a valid call, start // this list. // We cannot use Lookup() here as it will produce InvalidName() error if the // field is not found. We do not want to error here as it would cause us to // report errors twice, once here and again later in BeginNamed() call. // Also we ignore if the field is not found here as it is caught later. field = typeinfo_->FindField(&element_->type(), name); // It is an error to try to bind to map, which behind the scenes is a list. if (field && IsMap(*field)) { // Push field to stack for error location tracking & reporting. element_.reset(new ProtoElement(element_.release(), field, *LookupType(field), ProtoElement::MESSAGE)); InvalidValue("Map", "Cannot bind a list to map."); ++invalid_depth_; element_->pop(); return this; } if (field && field->type_url() == GetFullTypeWithUrl(kStructValueType)) { // There are 2 cases possible: // a. g.p.Value is repeated // b. g.p.Value is not repeated // // For case (a), the StartList should bind to the repeated g.p.Value. // For case (b), the StartList should bind to g.p.ListValue within the // g.p.Value. // // This means, for case (a), we treat it just like any other repeated // message, except we would apply an appropriate element_type so future // Start or Render calls are routed appropriately. if (field->cardinality() != google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { field = StartListValueInStruct(field); } element_type = ProtoElement::STRUCT_LIST; } else if (field && field->type_url() == GetFullTypeWithUrl(kStructListValueType)) { // We got a StartList with google.protobuf.ListValue master type. This // means we have to start the "values" within google.protobuf.ListValue. field = StartRepeatedValuesInListValue(field); } else { // If no special types are to be bound, fall back to normal processing of // StartList. field = BeginNamed(name, true); } if (field == NULL) return this; } const google::protobuf::Type* type = LookupType(field); if (type == NULL) { ++invalid_depth_; InvalidName(name, StrCat("Missing descriptor for field: ", field->type_url())); return this; } element_.reset( new ProtoElement(element_.release(), field, *type, element_type)); return this; } ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndList() { if (invalid_depth_ > 0) { --invalid_depth_; } else if (element_ != NULL) { if (element_->IsAny()) { element_->any()->EndList(); } else { element_.reset(element_->pop()); // Skip sentinel elements added to keep track of new proto3 types - map, // struct. SkipElements(); } } // When element_ is NULL, we have reached the root message type. Write out // the bytes. if (element_ == NULL) { WriteRootMessage(); } return this; } Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow, const DataPiece& data) { string struct_field_name; switch (data.type()) { // Our JSON parser parses numbers as either int64, uint64, or double. case DataPiece::TYPE_INT64: case DataPiece::TYPE_UINT64: case DataPiece::TYPE_DOUBLE: { struct_field_name = "number_value"; break; } case DataPiece::TYPE_STRING: { struct_field_name = "string_value"; break; } case DataPiece::TYPE_BOOL: { struct_field_name = "bool_value"; break; } case DataPiece::TYPE_NULL: { struct_field_name = "null_value"; break; } default: { return Status(INVALID_ARGUMENT, "Invalid struct data type. Only number, string, boolean or " "null values are supported."); } } ow->RenderDataPiece(struct_field_name, data); return Status::OK; } Status ProtoStreamObjectWriter::RenderTimestamp(ProtoStreamObjectWriter* ow, const DataPiece& data) { if (data.type() != DataPiece::TYPE_STRING) { return Status(INVALID_ARGUMENT, StrCat("Invalid data type for timestamp, value is ", data.ValueAsStringOrDefault(""))); } StringPiece value(data.str()); int64 seconds; int32 nanos; if (!::google::protobuf::internal::ParseTime(value.ToString(), &seconds, &nanos)) { return Status(INVALID_ARGUMENT, StrCat("Invalid time format: ", value)); } ow->RenderDataPiece("seconds", DataPiece(seconds)); ow->RenderDataPiece("nanos", DataPiece(nanos)); return Status::OK; } static inline util::Status RenderOneFieldPath(ProtoStreamObjectWriter* ow, StringPiece path) { ow->RenderDataPiece("paths", DataPiece(ConvertFieldMaskPath(path, &ToSnakeCase))); return Status::OK; } Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow, const DataPiece& data) { if (data.type() != DataPiece::TYPE_STRING) { return Status(INVALID_ARGUMENT, StrCat("Invalid data type for field mask, value is ", data.ValueAsStringOrDefault(""))); } // TODO(tsun): figure out how to do proto descriptor based snake case // conversions as much as possible. Because ToSnakeCase sometimes returns the // wrong value. google::protobuf::scoped_ptr > callback( NewPermanentCallback(&RenderOneFieldPath, ow)); return DecodeCompactFieldMaskPaths(data.str(), callback.get()); } Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow, const DataPiece& data) { if (data.type() != DataPiece::TYPE_STRING) { return Status(INVALID_ARGUMENT, StrCat("Invalid data type for duration, value is ", data.ValueAsStringOrDefault(""))); } StringPiece value(data.str()); if (!value.ends_with("s")) { return Status(INVALID_ARGUMENT, "Illegal duration format; duration must end with 's'"); } value = value.substr(0, value.size() - 1); int sign = 1; if (value.starts_with("-")) { sign = -1; value = value.substr(1); } StringPiece s_secs, s_nanos; SplitSecondsAndNanos(value, &s_secs, &s_nanos); uint64 unsigned_seconds; if (!safe_strtou64(s_secs, &unsigned_seconds)) { return Status(INVALID_ARGUMENT, "Invalid duration format, failed to parse seconds"); } double d_nanos = 0; if (!safe_strtod("0." + s_nanos.ToString(), &d_nanos)) { return Status(INVALID_ARGUMENT, "Invalid duration format, failed to parse nanos seconds"); } int32 nanos = sign * static_cast(d_nanos * kNanosPerSecond); int64 seconds = sign * unsigned_seconds; if (seconds > kMaxSeconds || seconds < kMinSeconds || nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { return Status(INVALID_ARGUMENT, "Duration value exceeds limits"); } ow->RenderDataPiece("seconds", DataPiece(seconds)); ow->RenderDataPiece("nanos", DataPiece(nanos)); return Status::OK; } Status ProtoStreamObjectWriter::RenderWrapperType(ProtoStreamObjectWriter* ow, const DataPiece& data) { ow->RenderDataPiece("value", data); return Status::OK; } ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( StringPiece name, const DataPiece& data) { Status status; if (invalid_depth_ > 0) return this; if (element_ != NULL && element_->IsAny()) { element_->any()->RenderDataPiece(name, data); return this; } const google::protobuf::Field* field = NULL; string type_url; bool is_map_entry = false; if (element_ == NULL) { type_url = GetFullTypeWithUrl(master_type_.name()); } else { if (element_->IsMap() || element_->IsStructMap()) { is_map_entry = true; field = StartMapEntry(name); } else { field = Lookup(name); } if (field == NULL) { return this; } type_url = field->type_url(); } // Check if there are any well known type renderers available for type_url. const TypeRenderer* type_renderer = FindTypeRenderer(type_url); if (type_renderer != NULL) { // Push the current element to stack so lookups in type_renderer will // find the fields. We do an EndObject soon after, which pops this. This is // safe because all well-known types are messages. if (element_ == NULL) { element_.reset(new ProtoElement(typeinfo_, master_type_, this)); } else { if (field) { WriteTag(*field); const google::protobuf::Type* type = LookupType(field); element_.reset(new ProtoElement(element_.release(), field, *type, ProtoElement::MESSAGE)); } } status = (*type_renderer)(this, data); if (!status.ok()) { InvalidValue(type_url, StrCat("Field '", name, "', ", status.error_message())); } EndObject(); return this; } else if (element_ == NULL) { // no message type found at root element_.reset(new ProtoElement(typeinfo_, master_type_, this)); InvalidName(name, "Root element must be a message."); return this; } if (field == NULL) { return this; } const google::protobuf::Type* type = LookupType(field); if (type == NULL) { InvalidName(name, StrCat("Missing descriptor for field: ", field->type_url())); return this; } // Whether we should pop at the end. Set to true if the data field is a // message type, which can happen in case of struct values. bool should_pop = false; RenderSimpleDataPiece(*field, *type, data); if (should_pop && element_ != NULL) { element_.reset(element_->pop()); } if (is_map_entry) { // Ending map is the same as ending an object. EndObject(); } return this; } void ProtoStreamObjectWriter::RenderSimpleDataPiece( const google::protobuf::Field& field, const google::protobuf::Type& type, const DataPiece& data) { // If we are rendering explicit null values and the backend proto field is not // of the google.protobuf.NullType type, we do nothing. if (data.type() == DataPiece::TYPE_NULL && field.type_url() != kStructNullValueTypeUrl) { return; } // Pushing a ProtoElement and then pop it off at the end for 2 purposes: // error location reporting and required field accounting. element_.reset(new ProtoElement(element_.release(), &field, type, ProtoElement::MESSAGE)); // Make sure that field represents a simple data type. if (field.kind() == google::protobuf::Field_Kind_TYPE_UNKNOWN || field.kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) { InvalidValue(field.type_url().empty() ? google::protobuf::Field_Kind_Name(field.kind()) : field.type_url(), data.ValueAsStringOrDefault("")); return; } Status status; switch (field.kind()) { case google::protobuf::Field_Kind_TYPE_INT32: { status = WriteInt32(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_SFIXED32: { status = WriteSFixed32(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_SINT32: { status = WriteSInt32(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_FIXED32: { status = WriteFixed32(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_UINT32: { status = WriteUInt32(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_INT64: { status = WriteInt64(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_SFIXED64: { status = WriteSFixed64(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_SINT64: { status = WriteSInt64(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_FIXED64: { status = WriteFixed64(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_UINT64: { status = WriteUInt64(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_DOUBLE: { status = WriteDouble(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_FLOAT: { status = WriteFloat(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_BOOL: { status = WriteBool(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_BYTES: { status = WriteBytes(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_STRING: { status = WriteString(field.number(), data, stream_.get()); break; } case google::protobuf::Field_Kind_TYPE_ENUM: { status = WriteEnum(field.number(), data, typeinfo_->GetEnum(field.type_url()), stream_.get()); break; } default: // TYPE_GROUP or TYPE_MESSAGE status = Status(INVALID_ARGUMENT, data.ToString().ValueOrDie()); } if (!status.ok()) { InvalidValue(google::protobuf::Field_Kind_Name(field.kind()), status.error_message()); } element_.reset(element_->pop()); } // Map of functions that are responsible for rendering well known type // represented by the key. hash_map* ProtoStreamObjectWriter::CreateRendererMap() { google::protobuf::scoped_ptr > result(new hash_map()); (*result)["type.googleapis.com/google.protobuf.Timestamp"] = &ProtoStreamObjectWriter::RenderTimestamp; (*result)["type.googleapis.com/google.protobuf.Duration"] = &ProtoStreamObjectWriter::RenderDuration; (*result)["type.googleapis.com/google.protobuf.FieldMask"] = &ProtoStreamObjectWriter::RenderFieldMask; (*result)["type.googleapis.com/google.protobuf.Double"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Float"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Int64"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.UInt64"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Int32"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.UInt32"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Bool"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.String"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Bytes"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.DoubleValue"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.FloatValue"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Int64Value"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.UInt64Value"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Int32Value"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.UInt32Value"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.BoolValue"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.StringValue"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.BytesValue"] = &ProtoStreamObjectWriter::RenderWrapperType; (*result)["type.googleapis.com/google.protobuf.Value"] = &ProtoStreamObjectWriter::RenderStructValue; return result.release(); } ProtoStreamObjectWriter::TypeRenderer* ProtoStreamObjectWriter::FindTypeRenderer(const string& type_url) { static hash_map* renderers = CreateRendererMap(); return FindOrNull(*renderers, type_url); } ProtoStreamObjectWriter::ProtoElement::ElementType ProtoStreamObjectWriter::GetElementType(const google::protobuf::Type& type) { if (type.name() == kAnyType) { return ProtoElement::ANY; } else if (type.name() == kStructType) { return ProtoElement::STRUCT; } else if (type.name() == kStructValueType) { return ProtoElement::STRUCT_VALUE; } else if (type.name() == kStructListValueType) { return ProtoElement::STRUCT_LIST_VALUE; } else { return ProtoElement::MESSAGE; } } const google::protobuf::Field* ProtoStreamObjectWriter::BeginNamed( StringPiece name, bool is_list) { if (invalid_depth_ > 0) { ++invalid_depth_; return NULL; } const google::protobuf::Field* field = Lookup(name); if (field == NULL) { ++invalid_depth_; // InvalidName() already called in Lookup(). return NULL; } if (is_list && field->cardinality() != google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { ++invalid_depth_; InvalidName(name, "Proto field is not repeating, cannot start list."); return NULL; } return field; } const google::protobuf::Field* ProtoStreamObjectWriter::Lookup( StringPiece unnormalized_name) { ProtoElement* e = element(); if (e == NULL) { InvalidName(unnormalized_name, "Root element must be a message."); return NULL; } if (unnormalized_name.empty()) { // Objects in repeated field inherit the same field descriptor. if (e->field() == NULL) { InvalidName(unnormalized_name, "Proto fields must have a name."); } else if (e->field()->cardinality() != google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { InvalidName(unnormalized_name, "Proto fields must have a name."); return NULL; } return e->field(); } const google::protobuf::Field* field = typeinfo_->FindField(&e->type(), unnormalized_name); if (field == NULL) InvalidName(unnormalized_name, "Cannot find field."); return field; } const google::protobuf::Type* ProtoStreamObjectWriter::LookupType( const google::protobuf::Field* field) { return (field->kind() == google::protobuf::Field_Kind_TYPE_MESSAGE ? typeinfo_->GetType(field->type_url()) : &element_->type()); } // Looks up the oneof struct field based on the data type. StatusOr ProtoStreamObjectWriter::LookupStructField(DataPiece::Type type) { const google::protobuf::Field* field = NULL; switch (type) { // Our JSON parser parses numbers as either int64, uint64, or double. case DataPiece::TYPE_INT64: case DataPiece::TYPE_UINT64: case DataPiece::TYPE_DOUBLE: { field = Lookup("number_value"); break; } case DataPiece::TYPE_STRING: { field = Lookup("string_value"); break; } case DataPiece::TYPE_BOOL: { field = Lookup("bool_value"); break; } case DataPiece::TYPE_NULL: { field = Lookup("null_value"); break; } default: { return Status(INVALID_ARGUMENT, "Invalid struct data type"); } } if (field == NULL) { return Status(INVALID_ARGUMENT, "Could not lookup struct field"); } return field; } void ProtoStreamObjectWriter::WriteRootMessage() { GOOGLE_DCHECK(!done_); int curr_pos = 0; // Calls the destructor of CodedOutputStream to remove any uninitialized // memory from the Cord before we read it. stream_.reset(NULL); const void* data; int length; google::protobuf::io::ArrayInputStream input_stream(buffer_.data(), buffer_.size()); while (input_stream.Next(&data, &length)) { if (length == 0) continue; int num_bytes = length; // Write up to where we need to insert the size field. // The number of bytes we may write is the smaller of: // - the current fragment size // - the distance to the next position where a size field needs to be // inserted. if (!size_insert_.empty() && size_insert_.front().pos - curr_pos < num_bytes) { num_bytes = size_insert_.front().pos - curr_pos; } output_->Append(static_cast(data), num_bytes); if (num_bytes < length) { input_stream.BackUp(length - num_bytes); } curr_pos += num_bytes; // Insert the size field. // size_insert_.front(): the next pair to be written. // size_insert_.front().pos: position of the size field. // size_insert_.front().size: the size (integer) to be inserted. if (!size_insert_.empty() && curr_pos == size_insert_.front().pos) { // Varint32 occupies at most 10 bytes. uint8 insert_buffer[10]; uint8* insert_buffer_pos = CodedOutputStream::WriteVarint32ToArray( size_insert_.front().size, insert_buffer); output_->Append(reinterpret_cast(insert_buffer), insert_buffer_pos - insert_buffer); size_insert_.pop_front(); } } output_->Flush(); stream_.reset(new CodedOutputStream(&adapter_)); done_ = true; } bool ProtoStreamObjectWriter::IsMap(const google::protobuf::Field& field) { if (field.type_url().empty() || field.kind() != google::protobuf::Field_Kind_TYPE_MESSAGE || field.cardinality() != google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { return false; } const google::protobuf::Type* field_type = typeinfo_->GetType(field.type_url()); // TODO(xiaofeng): Unify option names. return GetBoolOptionOrDefault(field_type->options(), "google.protobuf.MessageOptions.map_entry", false) || GetBoolOptionOrDefault(field_type->options(), "map_entry", false); } void ProtoStreamObjectWriter::WriteTag(const google::protobuf::Field& field) { WireFormatLite::WireType wire_type = WireFormatLite::WireTypeForFieldType( static_cast(field.kind())); stream_->WriteTag(WireFormatLite::MakeTag(field.number(), wire_type)); } } // namespace converter } // namespace util } // namespace protobuf } // namespace google