diff options
Diffstat (limited to 'src/google/protobuf/util/internal/protostream_objectwriter.cc')
-rw-r--r-- | src/google/protobuf/util/internal/protostream_objectwriter.cc | 237 |
1 files changed, 148 insertions, 89 deletions
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.cc b/src/google/protobuf/util/internal/protostream_objectwriter.cc index 73e05cfe..2edfd075 100644 --- a/src/google/protobuf/util/internal/protostream_objectwriter.cc +++ b/src/google/protobuf/util/internal/protostream_objectwriter.cc @@ -62,9 +62,10 @@ ProtoStreamObjectWriter::ProtoStreamObjectWriter( const ProtoStreamObjectWriter::Options& options) : ProtoWriter(type_resolver, type, output, listener), master_type_(type), - current_(NULL), + current_(nullptr), options_(options) { set_ignore_unknown_fields(options_.ignore_unknown_fields); + set_use_lower_camel_for_enums(options_.use_lower_camel_for_enums); } ProtoStreamObjectWriter::ProtoStreamObjectWriter( @@ -72,18 +73,18 @@ ProtoStreamObjectWriter::ProtoStreamObjectWriter( strings::ByteSink* output, ErrorListener* listener) : ProtoWriter(typeinfo, type, output, listener), master_type_(type), - current_(NULL), + current_(nullptr), options_(ProtoStreamObjectWriter::Options::Defaults()) {} ProtoStreamObjectWriter::~ProtoStreamObjectWriter() { - if (current_ == NULL) return; + if (current_ == nullptr) return; // Cleanup explicitly in order to avoid destructor stack overflow when input // is deeply nested. // Cast to BaseElement to avoid doing additional checks (like missing fields) // during pop(). - google::protobuf::scoped_ptr<BaseElement> element( + std::unique_ptr<BaseElement> element( static_cast<BaseElement*>(current_.get())->pop<BaseElement>()); - while (element != NULL) { + while (element != nullptr) { element.reset(element->pop<BaseElement>()); } } @@ -172,7 +173,7 @@ Status GetNanosFromStringPiece(StringPiece s_nanos, *nanos = i_nanos * conversion; } - return Status::OK; + return Status(); } } // namespace @@ -185,24 +186,18 @@ ProtoStreamObjectWriter::AnyWriter::AnyWriter(ProtoStreamObjectWriter* parent) output_(&data_), depth_(0), is_well_known_type_(false), - well_known_type_render_(NULL) {} + well_known_type_render_(nullptr) {} 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; - } + // before reaching here, which happens when we have data before the "@type" + // field. + if (ow_ == nullptr) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::START_OBJECT, name)); } else if (is_well_known_type_ && depth_ == 1) { // For well-known types, the only other field besides "@type" should be a // "value" field. @@ -222,10 +217,15 @@ void ProtoStreamObjectWriter::AnyWriter::StartObject(StringPiece 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_. For regular - // message types, we propagate the end of Any as well. - if (ow_ != NULL && (depth_ >= 0 || !is_well_known_type_)) { + if (ow_ == nullptr) { + if (depth_ >= 0) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::END_OBJECT)); + } + } else if (depth_ >= 0 || !is_well_known_type_) { + // As long as depth_ >= 0, we know we haven't reached the end of Any. + // Propagate these EndObject() calls to the contained ow_. For regular + // message types, we propagate the end of Any as well. ow_->EndObject(); } // A negative depth_ implies that we have reached the end of Any @@ -239,14 +239,9 @@ bool ProtoStreamObjectWriter::AnyWriter::EndObject() { 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; - } + if (ow_ == nullptr) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::START_LIST, name)); } else if (is_well_known_type_ && depth_ == 1) { if (name != "value" && !invalid_) { parent_->InvalidValue("Any", @@ -265,8 +260,10 @@ void ProtoStreamObjectWriter::AnyWriter::EndList() { 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) { + if (ow_ == nullptr) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::END_LIST)); + } else { ow_->EndList(); } } @@ -275,25 +272,21 @@ 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") { + if (depth_ == 0 && ow_ == nullptr && 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 if (ow_ == nullptr) { + // Save data before the "@type" field. + uninterpreted_events_.push_back(Event(name, value)); } else if (depth_ == 0 && is_well_known_type_) { if (name != "value" && !invalid_) { parent_->InvalidValue("Any", "Expect a \"value\" field for well-known types."); invalid_ = true; } - if (well_known_type_render_ == NULL) { + if (well_known_type_render_ == nullptr) { // Only Any and Struct don't have a special type render but both of // them expect a JSON object (i.e., a StartObject() call). - if (!invalid_) { + if (value.type() != DataPiece::TYPE_NULL && !invalid_) { parent_->InvalidValue("Any", "Expect a JSON object."); invalid_ = true; } @@ -334,7 +327,7 @@ void ProtoStreamObjectWriter::AnyWriter::StartAny(const DataPiece& value) { const google::protobuf::Type* type = resolved_type.ValueOrDie(); well_known_type_render_ = FindTypeRenderer(type_url_); - if (well_known_type_render_ != NULL || + if (well_known_type_render_ != nullptr || // Explicitly list Any and Struct here because they don't have a // custom renderer. type->name() == kAnyType || type->name() == kStructType) { @@ -358,13 +351,29 @@ void ProtoStreamObjectWriter::AnyWriter::StartAny(const DataPiece& value) { if (!is_well_known_type_) { ow_->StartObject(""); } + + // Now we know the proto type and can interpret all data fields we gathered + // before the "@type" field. + for (int i = 0; i < uninterpreted_events_.size(); ++i) { + uninterpreted_events_[i].Replay(this); + } } 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; + if (ow_ == nullptr) { + if (uninterpreted_events_.empty()) { + // We never got any content, so just return immediately, which is + // equivalent to writing an empty Any. + return; + } else { + // There are uninterpreted data, but we never got a "@type" field. + if (!invalid_) { + parent_->InvalidValue("Any", StrCat("Missing @type for any field in ", + parent_->master_type_.name())); + invalid_ = true; + } + return; + } } // Render the type_url and value fields directly to the stream. // type_url has tag 1 and value has tag 2. @@ -374,10 +383,45 @@ void ProtoStreamObjectWriter::AnyWriter::WriteAny() { } } +void ProtoStreamObjectWriter::AnyWriter::Event::Replay( + AnyWriter* writer) const { + switch (type_) { + case START_OBJECT: + writer->StartObject(name_); + break; + case END_OBJECT: + writer->EndObject(); + break; + case START_LIST: + writer->StartList(name_); + break; + case END_LIST: + writer->EndList(); + break; + case RENDER_DATA_PIECE: + writer->RenderDataPiece(name_, value_); + break; + } +} + +void ProtoStreamObjectWriter::AnyWriter::Event::DeepCopy() { + // DataPiece only contains a string reference. To make sure the referenced + // string value stays valid, we make a copy of the string value and update + // DataPiece to reference our own copy. + if (value_.type() == DataPiece::TYPE_STRING) { + StrAppend(&value_storage_, value_.str()); + value_ = DataPiece(value_storage_, value_.use_strict_base64_decoding()); + } else if (value_.type() == DataPiece::TYPE_BYTES) { + value_storage_ = value_.ToBytes().ValueOrDie(); + value_ = + DataPiece(value_storage_, true, value_.use_strict_base64_decoding()); + } +} + ProtoStreamObjectWriter::Item::Item(ProtoStreamObjectWriter* enclosing, ItemType item_type, bool is_placeholder, bool is_list) - : BaseElement(NULL), + : BaseElement(nullptr), ow_(enclosing), any_(), item_type_(item_type), @@ -423,7 +467,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartObject( // Starting the root message. Create the root Item and return. // ANY message type does not need special handling, just set the ItemType // to ANY. - if (current_ == NULL) { + if (current_ == nullptr) { ProtoWriter::StartObject(name); current_.reset(new Item( this, master_type_.name() == kAnyType ? Item::ANY : Item::MESSAGE, @@ -497,14 +541,14 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartObject( // If top of stack is g.p.Struct type, start the struct the map field within // it. - if (element() != NULL && IsStruct(*element()->parent_field())) { + if (element() != nullptr && IsStruct(*element()->parent_field())) { // Render "fields": [ Push("fields", Item::MAP, true, true); return this; } // If top of stack is g.p.Value type, start the Struct within it. - if (element() != NULL && IsStructValue(*element()->parent_field())) { + if (element() != nullptr && IsStructValue(*element()->parent_field())) { // Render // "struct_value": { // "fields": [ @@ -515,7 +559,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartObject( } const google::protobuf::Field* field = BeginNamed(name, false); - if (field == NULL) return this; + if (field == nullptr) return this; if (IsStruct(*field)) { // Start a struct object. @@ -563,7 +607,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndObject() { return this; } - if (current_ == NULL) return this; + if (current_ == nullptr) return this; if (current_->IsAny()) { if (current_->any()->EndObject()) return this; @@ -583,7 +627,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList(StringPiece name) { // Since we cannot have a top-level repeated item in protobuf, the only way // this is valid is if we start a special type google.protobuf.ListValue or // google.protobuf.Value. - if (current_ == NULL) { + if (current_ == nullptr) { if (!name.empty()) { InvalidName(name, "Root element should not be named."); IncrementInvalidDepth(); @@ -662,7 +706,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList(StringPiece name) { if (invalid_depth() > 0) return this; // case i and ii above. Start "list_value" field within g.p.Value - if (element() != NULL && element()->parent_field() != NULL) { + if (element() != nullptr && element()->parent_field() != nullptr) { // Render // "list_value": { // "values": [ // Start this list @@ -690,7 +734,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList(StringPiece name) { // When name is empty and stack is not empty, we are rendering an item within // a list. if (name.empty()) { - if (element() != NULL && element()->parent_field() != NULL) { + if (element() != nullptr && element()->parent_field() != nullptr) { if (IsStructValue(*element()->parent_field())) { // Since it is g.p.Value, we bind directly to the list_value. // Render @@ -721,7 +765,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList(StringPiece name) { // name is not empty const google::protobuf::Field* field = Lookup(name); - if (field == NULL) { + if (field == nullptr) { IncrementInvalidDepth(); return this; } @@ -793,7 +837,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndList() { return this; } - if (current_ == NULL) return this; + if (current_ == nullptr) return this; if (current_->IsAny()) { current_->any()->EndList(); @@ -818,7 +862,7 @@ Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow, ow->ProtoWriter::RenderDataPiece( "string_value", DataPiece(SimpleItoa(int_value.ValueOrDie()), true)); - return Status::OK; + return Status(); } } struct_field_name = "number_value"; @@ -833,13 +877,22 @@ Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow, ow->ProtoWriter::RenderDataPiece( "string_value", DataPiece(SimpleItoa(int_value.ValueOrDie()), true)); - return Status::OK; + return Status(); } } struct_field_name = "number_value"; break; } case DataPiece::TYPE_DOUBLE: { + if (ow->options_.struct_integers_as_strings) { + StatusOr<double> double_value = data.ToDouble(); + if (double_value.ok()) { + ow->ProtoWriter::RenderDataPiece( + "string_value", + DataPiece(SimpleDtoa(double_value.ValueOrDie()), true)); + return Status(); + } + } struct_field_name = "number_value"; break; } @@ -862,11 +915,12 @@ Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow, } } ow->ProtoWriter::RenderDataPiece(struct_field_name, data); - return Status::OK; + return Status(); } Status ProtoStreamObjectWriter::RenderTimestamp(ProtoStreamObjectWriter* ow, const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); if (data.type() != DataPiece::TYPE_STRING) { return Status(INVALID_ARGUMENT, StrCat("Invalid data type for timestamp, value is ", @@ -885,18 +939,19 @@ Status ProtoStreamObjectWriter::RenderTimestamp(ProtoStreamObjectWriter* ow, ow->ProtoWriter::RenderDataPiece("seconds", DataPiece(seconds)); ow->ProtoWriter::RenderDataPiece("nanos", DataPiece(nanos)); - return Status::OK; + return Status(); } static inline util::Status RenderOneFieldPath(ProtoStreamObjectWriter* ow, StringPiece path) { ow->ProtoWriter::RenderDataPiece( "paths", DataPiece(ConvertFieldMaskPath(path, &ToSnakeCase), true)); - return Status::OK; + return Status(); } Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow, const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); if (data.type() != DataPiece::TYPE_STRING) { return Status(INVALID_ARGUMENT, StrCat("Invalid data type for field mask, value is ", @@ -906,13 +961,14 @@ Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow, // 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<ResultCallback1<util::Status, StringPiece> > callback( - NewPermanentCallback(&RenderOneFieldPath, ow)); + std::unique_ptr<ResultCallback1<util::Status, StringPiece> > callback( + ::google::protobuf::NewPermanentCallback(&RenderOneFieldPath, ow)); return DecodeCompactFieldMaskPaths(data.str(), callback.get()); } Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow, const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); if (data.type() != DataPiece::TYPE_STRING) { return Status(INVALID_ARGUMENT, StrCat("Invalid data type for duration, value is ", @@ -921,13 +977,13 @@ Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow, StringPiece value(data.str()); - if (!value.ends_with("s")) { + if (!StringEndsWith(value, "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("-")) { + if (StringStartsWith(value, "-")) { sign = -1; value = value.substr(1); } @@ -957,13 +1013,14 @@ Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow, ow->ProtoWriter::RenderDataPiece("seconds", DataPiece(seconds)); ow->ProtoWriter::RenderDataPiece("nanos", DataPiece(nanos)); - return Status::OK; + return Status(); } Status ProtoStreamObjectWriter::RenderWrapperType(ProtoStreamObjectWriter* ow, const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); ow->ProtoWriter::RenderDataPiece("value", data); - return Status::OK; + return Status(); } ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( @@ -971,10 +1028,10 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( Status status; if (invalid_depth() > 0) return this; - if (current_ == NULL) { + if (current_ == nullptr) { const TypeRenderer* type_renderer = FindTypeRenderer(GetFullTypeWithUrl(master_type_.name())); - if (type_renderer == NULL) { + if (type_renderer == nullptr) { InvalidName(name, "Root element must be a message."); return this; } @@ -997,7 +1054,7 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( return this; } - const google::protobuf::Field* field = NULL; + const google::protobuf::Field* field = nullptr; if (current_->IsMap()) { if (!ValidMapKey(name)) return this; @@ -1007,14 +1064,14 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( ProtoWriter::RenderDataPiece("key", DataPiece(name, use_strict_base64_decoding())); field = Lookup("value"); - if (field == NULL) { + if (field == nullptr) { Pop(); GOOGLE_LOG(DFATAL) << "Map does not have a value field."; return this; } const TypeRenderer* type_renderer = FindTypeRenderer(field->type_url()); - if (type_renderer != NULL) { + if (type_renderer != nullptr) { // Map's value type is a special type. Render it like a message: // "value": { // ... Render special type ... @@ -1044,18 +1101,23 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( } field = Lookup(name); - if (field == NULL) return this; + if (field == nullptr) return this; // Check if the field is of special type. Render it accordingly if so. const TypeRenderer* type_renderer = FindTypeRenderer(field->type_url()); - if (type_renderer != NULL) { - Push(name, Item::MESSAGE, false, false); - status = (*type_renderer)(this, data); - if (!status.ok()) { - InvalidValue(field->type_url(), - StrCat("Field '", name, "', ", status.error_message())); + if (type_renderer != nullptr) { + // Pass through null value only for google.protobuf.Value. For other + // types we ignore null value just like for regular field types. + if (data.type() != DataPiece::TYPE_NULL || + field->type_url() == kStructValueTypeUrl) { + Push(name, Item::MESSAGE, false, false); + status = (*type_renderer)(this, data); + if (!status.ok()) { + InvalidValue(field->type_url(), + StrCat("Field '", name, "', ", status.error_message())); + } + Pop(); } - Pop(); return this; } @@ -1137,7 +1199,7 @@ ProtoStreamObjectWriter::FindTypeRenderer(const string& type_url) { } bool ProtoStreamObjectWriter::ValidMapKey(StringPiece unnormalized_name) { - if (current_ == NULL) return true; + if (current_ == nullptr) return true; if (!current_->InsertMapKeyIfNotPresent(unnormalized_name)) { listener()->InvalidName( @@ -1162,10 +1224,10 @@ void ProtoStreamObjectWriter::Push(StringPiece name, Item::ItemType item_type, void ProtoStreamObjectWriter::Pop() { // Pop all placeholder items sending StartObject or StartList events to // ProtoWriter according to is_list value. - while (current_ != NULL && current_->is_placeholder()) { + while (current_ != nullptr && current_->is_placeholder()) { PopOneElement(); } - if (current_ != NULL) { + if (current_ != nullptr) { PopOneElement(); } } @@ -1185,10 +1247,7 @@ bool ProtoStreamObjectWriter::IsMap(const google::protobuf::Field& field) { const google::protobuf::Type* field_type = typeinfo()->GetTypeByTypeUrl(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); + return google::protobuf::util::converter::IsMap(field, *field_type); } bool ProtoStreamObjectWriter::IsAny(const google::protobuf::Field& field) { |