aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/util/internal/protostream_objectwriter.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/google/protobuf/util/internal/protostream_objectwriter.cc')
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter.cc237
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) {