aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/util
diff options
context:
space:
mode:
authorGravatar Bo Yang <teboring@google.com>2016-09-19 13:45:07 -0700
committerGravatar Bo Yang <teboring@google.com>2016-10-10 11:23:36 -0700
commitcc8ca5b6a5478b40546d4206392eb1471454460d (patch)
treec0b45abfa16d7d373a6ea8f7fe50f1de00ab938e /src/google/protobuf/util
parent337a028bb65ccca4dda768695950b5aba53ae2c9 (diff)
Integrate internal changes
Diffstat (limited to 'src/google/protobuf/util')
-rw-r--r--src/google/protobuf/util/field_comparator.h2
-rw-r--r--src/google/protobuf/util/internal/datapiece.cc32
-rw-r--r--src/google/protobuf/util/internal/datapiece.h33
-rw-r--r--src/google/protobuf/util/internal/default_value_objectwriter.cc41
-rw-r--r--src/google/protobuf/util/internal/error_listener.h4
-rw-r--r--src/google/protobuf/util/internal/json_objectwriter.cc21
-rw-r--r--src/google/protobuf/util/internal/json_objectwriter.h20
-rw-r--r--src/google/protobuf/util/internal/json_objectwriter_test.cc4
-rw-r--r--src/google/protobuf/util/internal/json_stream_parser.cc44
-rw-r--r--src/google/protobuf/util/internal/json_stream_parser.h8
-rw-r--r--src/google/protobuf/util/internal/json_stream_parser_test.cc18
-rw-r--r--src/google/protobuf/util/internal/object_writer.h7
-rw-r--r--src/google/protobuf/util/internal/proto_writer.cc9
-rw-r--r--src/google/protobuf/util/internal/proto_writer.h8
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource.cc11
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource.h43
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource_test.cc66
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter.cc131
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter.h62
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter_test.cc459
-rw-r--r--src/google/protobuf/util/internal/testdata/anys.proto69
-rw-r--r--src/google/protobuf/util/internal/testdata/books.proto5
-rw-r--r--src/google/protobuf/util/internal/testdata/maps.proto76
-rw-r--r--src/google/protobuf/util/internal/testdata/oneofs.proto31
-rw-r--r--src/google/protobuf/util/internal/testdata/proto3.proto42
-rw-r--r--src/google/protobuf/util/internal/testdata/struct.proto86
-rw-r--r--src/google/protobuf/util/internal/testdata/timestamp_duration.proto47
-rw-r--r--src/google/protobuf/util/internal/utility.cc41
-rw-r--r--src/google/protobuf/util/internal/utility.h11
-rw-r--r--src/google/protobuf/util/message_differencer.h126
30 files changed, 1264 insertions, 293 deletions
diff --git a/src/google/protobuf/util/field_comparator.h b/src/google/protobuf/util/field_comparator.h
index 715560ed..3c70a314 100644
--- a/src/google/protobuf/util/field_comparator.h
+++ b/src/google/protobuf/util/field_comparator.h
@@ -169,7 +169,7 @@ class LIBPROTOBUF_EXPORT DefaultFieldComparator : public FieldComparator {
};
// Defines the map to store the tolerances for floating point comparison.
- typedef map<const FieldDescriptor*, Tolerance> ToleranceMap;
+ typedef std::map<const FieldDescriptor*, Tolerance> ToleranceMap;
// The following methods get executed when CompareFields is called for the
// basic types (instead of submessages). They return true on success. One
diff --git a/src/google/protobuf/util/internal/datapiece.cc b/src/google/protobuf/util/internal/datapiece.cc
index ef8da427..eeb55c6b 100644
--- a/src/google/protobuf/util/internal/datapiece.cc
+++ b/src/google/protobuf/util/internal/datapiece.cc
@@ -259,7 +259,8 @@ StatusOr<string> DataPiece::ToBytes() const {
}
}
-StatusOr<int> DataPiece::ToEnum(const google::protobuf::Enum* enum_type) const {
+StatusOr<int> DataPiece::ToEnum(const google::protobuf::Enum* enum_type,
+ bool use_lower_camel_for_enums) const {
if (type_ == TYPE_NULL) return google::protobuf::NULL_VALUE;
if (type_ == TYPE_STRING) {
@@ -268,20 +269,34 @@ StatusOr<int> DataPiece::ToEnum(const google::protobuf::Enum* enum_type) const {
const google::protobuf::EnumValue* value =
FindEnumValueByNameOrNull(enum_type, enum_name);
if (value != NULL) return value->number();
+
+ // Check if int version of enum is sent as string.
+ StatusOr<int32> int_value = ToInt32();
+ if (int_value.ok()) {
+ if (const google::protobuf::EnumValue* enum_value =
+ FindEnumValueByNumberOrNull(enum_type, int_value.ValueOrDie())) {
+ return enum_value->number();
+ }
+ }
+
// Next try a normalized name.
for (string::iterator it = enum_name.begin(); it != enum_name.end(); ++it) {
*it = *it == '-' ? '_' : ascii_toupper(*it);
}
value = FindEnumValueByNameOrNull(enum_type, enum_name);
if (value != NULL) return value->number();
- } else {
- StatusOr<int32> value = ToInt32();
- if (value.ok()) {
- if (const google::protobuf::EnumValue* enum_value =
- FindEnumValueByNumberOrNull(enum_type, value.ValueOrDie())) {
- return enum_value->number();
- }
+
+ // If use_lower_camel_for_enums is true try with enum name without
+ // underscore. This will also accept camel case names as the enum_name has
+ // been normalized before.
+ if (use_lower_camel_for_enums) {
+ value = FindEnumValueByNameWithoutUnderscoreOrNull(enum_type, enum_name);
+ if (value != NULL) return value->number();
}
+ } else {
+ // We don't need to check whether the value is actually declared in the
+ // enum because we preserve unknown enum values as well.
+ return ToInt32();
}
return InvalidArgument(
ValueAsStringOrDefault("Cannot find enum with given value."));
@@ -354,6 +369,7 @@ bool DataPiece::DecodeBase64(StringPiece src, string* dest) const {
void DataPiece::InternalCopy(const DataPiece& other) {
type_ = other.type_;
+ use_strict_base64_decoding_ = other.use_strict_base64_decoding_;
switch (type_) {
case TYPE_INT32:
case TYPE_INT64:
diff --git a/src/google/protobuf/util/internal/datapiece.h b/src/google/protobuf/util/internal/datapiece.h
index e82cdbac..83516d09 100644
--- a/src/google/protobuf/util/internal/datapiece.h
+++ b/src/google/protobuf/util/internal/datapiece.h
@@ -76,13 +76,22 @@ class LIBPROTOBUF_EXPORT DataPiece {
};
// Constructors and Destructor
- explicit DataPiece(const int32 value) : type_(TYPE_INT32), i32_(value) {}
- explicit DataPiece(const int64 value) : type_(TYPE_INT64), i64_(value) {}
- explicit DataPiece(const uint32 value) : type_(TYPE_UINT32), u32_(value) {}
- explicit DataPiece(const uint64 value) : type_(TYPE_UINT64), u64_(value) {}
- explicit DataPiece(const double value) : type_(TYPE_DOUBLE), double_(value) {}
- explicit DataPiece(const float value) : type_(TYPE_FLOAT), float_(value) {}
- explicit DataPiece(const bool value) : type_(TYPE_BOOL), bool_(value) {}
+ explicit DataPiece(const int32 value)
+ : type_(TYPE_INT32), i32_(value), use_strict_base64_decoding_(false) {}
+ explicit DataPiece(const int64 value)
+ : type_(TYPE_INT64), i64_(value), use_strict_base64_decoding_(false) {}
+ explicit DataPiece(const uint32 value)
+ : type_(TYPE_UINT32), u32_(value), use_strict_base64_decoding_(false) {}
+ explicit DataPiece(const uint64 value)
+ : type_(TYPE_UINT64), u64_(value), use_strict_base64_decoding_(false) {}
+ explicit DataPiece(const double value)
+ : type_(TYPE_DOUBLE),
+ double_(value),
+ use_strict_base64_decoding_(false) {}
+ explicit DataPiece(const float value)
+ : type_(TYPE_FLOAT), float_(value), use_strict_base64_decoding_(false) {}
+ explicit DataPiece(const bool value)
+ : type_(TYPE_BOOL), bool_(value), use_strict_base64_decoding_(false) {}
DataPiece(StringPiece value, bool use_strict_base64_decoding)
: type_(TYPE_STRING),
str_(StringPiecePod::CreateFromStringPiece(value)),
@@ -108,6 +117,8 @@ class LIBPROTOBUF_EXPORT DataPiece {
// Accessors
Type type() const { return type_; }
+ bool use_strict_base64_decoding() { return use_strict_base64_decoding_; }
+
StringPiece str() const {
GOOGLE_LOG_IF(DFATAL, type_ != TYPE_STRING) << "Not a string type.";
return str_;
@@ -148,16 +159,20 @@ class LIBPROTOBUF_EXPORT DataPiece {
// string, first attempts conversion by name, trying names as follows:
// 1) the directly provided string value.
// 2) the value upper-cased and replacing '-' by '_'
+ // 3) if use_lower_camel_for_enums is true it also attempts by comparing
+ // enum name without underscore with the value upper cased above.
// If the value is not a string, attempts to convert to a 32-bit integer.
// If none of these succeeds, returns a conversion error status.
- util::StatusOr<int> ToEnum(const google::protobuf::Enum* enum_type) const;
+ util::StatusOr<int> ToEnum(const google::protobuf::Enum* enum_type,
+ bool use_lower_camel_for_enums) const;
private:
// Disallow implicit constructor.
DataPiece();
// Helper to create NULL or ENUM types.
- DataPiece(Type type, int32 val) : type_(type), i32_(val) {}
+ DataPiece(Type type, int32 val)
+ : type_(type), i32_(val), use_strict_base64_decoding_(false) {}
// For numeric conversion between
// int32, int64, uint32, uint64, double, float and bool
diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.cc b/src/google/protobuf/util/internal/default_value_objectwriter.cc
index 3479949b..ac1ed9ab 100644
--- a/src/google/protobuf/util/internal/default_value_objectwriter.cc
+++ b/src/google/protobuf/util/internal/default_value_objectwriter.cc
@@ -557,26 +557,29 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::EndList() {
void DefaultValueObjectWriter::RenderDataPiece(StringPiece name,
const DataPiece& data) {
MaybePopulateChildrenOfAny(current_);
- util::StatusOr<string> data_string = data.ToString();
if (current_->type() != NULL && current_->type()->name() == kAnyType &&
- name == "@type" && data_string.ok()) {
- const string& string_value = data_string.ValueOrDie();
- // If the type of current_ is "Any" and its "@type" field is being set here,
- // sets the type of current_ to be the type specified by the "@type".
- util::StatusOr<const google::protobuf::Type*> found_type =
- typeinfo_->ResolveTypeUrl(string_value);
- if (!found_type.ok()) {
- GOOGLE_LOG(WARNING) << "Failed to resolve type '" << string_value << "'.";
- } else {
- current_->set_type(found_type.ValueOrDie());
- }
- current_->set_is_any(true);
- // If the "@type" field is placed after other fields, we should populate
- // other children of primitive type now. Otherwise, we should wait until the
- // first value field is rendered before we populate the children, because
- // the "value" field of a Any message could be omitted.
- if (current_->number_of_children() > 1 && current_->type() != NULL) {
- current_->PopulateChildren(typeinfo_);
+ name == "@type") {
+ util::StatusOr<string> data_string = data.ToString();
+ if (data_string.ok()) {
+ const string& string_value = data_string.ValueOrDie();
+ // If the type of current_ is "Any" and its "@type" field is being set
+ // here, sets the type of current_ to be the type specified by the
+ // "@type".
+ util::StatusOr<const google::protobuf::Type*> found_type =
+ typeinfo_->ResolveTypeUrl(string_value);
+ if (!found_type.ok()) {
+ GOOGLE_LOG(WARNING) << "Failed to resolve type '" << string_value << "'.";
+ } else {
+ current_->set_type(found_type.ValueOrDie());
+ }
+ current_->set_is_any(true);
+ // If the "@type" field is placed after other fields, we should populate
+ // other children of primitive type now. Otherwise, we should wait until
+ // the first value field is rendered before we populate the children,
+ // because the "value" field of a Any message could be omitted.
+ if (current_->number_of_children() > 1 && current_->type() != NULL) {
+ current_->PopulateChildren(typeinfo_);
+ }
}
}
Node* child = current_->FindChild(name);
diff --git a/src/google/protobuf/util/internal/error_listener.h b/src/google/protobuf/util/internal/error_listener.h
index 3f063936..1dc814a3 100644
--- a/src/google/protobuf/util/internal/error_listener.h
+++ b/src/google/protobuf/util/internal/error_listener.h
@@ -57,7 +57,7 @@ class LIBPROTOBUF_EXPORT ErrorListener {
// Reports an invalid name at the given location.
virtual void InvalidName(const LocationTrackerInterface& loc,
- StringPiece unknown_name, StringPiece message) = 0;
+ StringPiece invalid_name, StringPiece message) = 0;
// Reports an invalid value for a field.
virtual void InvalidValue(const LocationTrackerInterface& loc,
@@ -82,7 +82,7 @@ class LIBPROTOBUF_EXPORT NoopErrorListener : public ErrorListener {
virtual ~NoopErrorListener() {}
virtual void InvalidName(const LocationTrackerInterface& loc,
- StringPiece unknown_name, StringPiece message) {}
+ StringPiece invalid_name, StringPiece message) {}
virtual void InvalidValue(const LocationTrackerInterface& loc,
StringPiece type_name, StringPiece value) {}
diff --git a/src/google/protobuf/util/internal/json_objectwriter.cc b/src/google/protobuf/util/internal/json_objectwriter.cc
index b84210c1..6e4edd88 100644
--- a/src/google/protobuf/util/internal/json_objectwriter.cc
+++ b/src/google/protobuf/util/internal/json_objectwriter.cc
@@ -147,7 +147,7 @@ JsonObjectWriter* JsonObjectWriter::RenderBytes(StringPiece name,
string base64;
if (use_websafe_base64_for_bytes_)
- WebSafeBase64Escape(value.ToString(), &base64);
+ WebSafeBase64EscapeWithPadding(value.ToString(), &base64);
else
Base64Escape(value, &base64);
@@ -164,19 +164,32 @@ JsonObjectWriter* JsonObjectWriter::RenderNull(StringPiece name) {
return RenderSimple(name, "null");
}
+JsonObjectWriter* JsonObjectWriter::RenderNullAsEmpty(StringPiece name) {
+ return RenderSimple(name, "");
+}
+
void JsonObjectWriter::WritePrefix(StringPiece name) {
bool not_first = !element()->is_first();
if (not_first) WriteChar(',');
if (not_first || !element()->is_root()) NewLine();
- if (!name.empty()) {
+ bool empty_key_ok = GetAndResetEmptyKeyOk();
+ if (!name.empty() || empty_key_ok) {
WriteChar('"');
- ArrayByteSource source(name);
- JsonEscaping::Escape(&source, &sink_);
+ if (!name.empty()) {
+ ArrayByteSource source(name);
+ JsonEscaping::Escape(&source, &sink_);
+ }
stream_->WriteString("\":");
if (!indent_string_.empty()) WriteChar(' ');
}
}
+bool JsonObjectWriter::GetAndResetEmptyKeyOk() {
+ bool retval = empty_name_ok_for_next_key_;
+ empty_name_ok_for_next_key_ = false;
+ return retval;
+}
+
} // namespace converter
} // namespace util
} // namespace protobuf
diff --git a/src/google/protobuf/util/internal/json_objectwriter.h b/src/google/protobuf/util/internal/json_objectwriter.h
index cb7e2fb3..31edc292 100644
--- a/src/google/protobuf/util/internal/json_objectwriter.h
+++ b/src/google/protobuf/util/internal/json_objectwriter.h
@@ -93,7 +93,8 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
stream_(out),
sink_(out),
indent_string_(indent_string.ToString()),
- use_websafe_base64_for_bytes_(false) {}
+ use_websafe_base64_for_bytes_(false),
+ empty_name_ok_for_next_key_(false) {}
virtual ~JsonObjectWriter();
// ObjectWriter methods.
@@ -111,11 +112,19 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
virtual JsonObjectWriter* RenderString(StringPiece name, StringPiece value);
virtual JsonObjectWriter* RenderBytes(StringPiece name, StringPiece value);
virtual JsonObjectWriter* RenderNull(StringPiece name);
+ virtual JsonObjectWriter* RenderNullAsEmpty(StringPiece name);
void set_use_websafe_base64_for_bytes(bool value) {
use_websafe_base64_for_bytes_ = value;
}
+ // Whether empty strings should be rendered for the next JSON key. This
+ // setting is only valid until the next key is rendered, after which it gets
+ // reset to false.
+ virtual void empty_name_ok_for_next_key() {
+ empty_name_ok_for_next_key_ = true;
+ }
+
protected:
class LIBPROTOBUF_EXPORT Element : public BaseElement {
public:
@@ -195,6 +204,10 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
// Writes an individual character to the output.
void WriteChar(const char c) { stream_->WriteRaw(&c, sizeof(c)); }
+ // Returns the current value of empty_name_ok_for_next_key_ and resets it to
+ // false.
+ bool GetAndResetEmptyKeyOk();
+
google::protobuf::scoped_ptr<Element> element_;
google::protobuf::io::CodedOutputStream* stream_;
ByteSinkWrapper sink_;
@@ -204,6 +217,11 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
// to regular base64 encoding.
bool use_websafe_base64_for_bytes_;
+ // Whether empty strings should be rendered for the next JSON key. This
+ // setting is only valid until the next key is rendered, after which it gets
+ // reset to false.
+ bool empty_name_ok_for_next_key_;
+
GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonObjectWriter);
};
diff --git a/src/google/protobuf/util/internal/json_objectwriter_test.cc b/src/google/protobuf/util/internal/json_objectwriter_test.cc
index b87b06ac..bbd9d78a 100644
--- a/src/google/protobuf/util/internal/json_objectwriter_test.cc
+++ b/src/google/protobuf/util/internal/json_objectwriter_test.cc
@@ -299,11 +299,11 @@ TEST_F(JsonObjectWriterTest, TestWebsafeByteEncoding) {
ow_ = new JsonObjectWriter("", out_stream_);
ow_->set_use_websafe_base64_for_bytes(true);
ow_->StartObject("")
- ->RenderBytes("bytes", "\x03\xef\xc0")
+ ->RenderBytes("bytes", "\x03\xef\xc0\x10")
->EndObject();
// Test that we get websafe base64 encoding when explicitly asked.
- EXPECT_EQ("{\"bytes\":\"A-_A\"}",
+ EXPECT_EQ("{\"bytes\":\"A-_AEA==\"}",
output_.substr(0, out_stream_->ByteCount()));
}
diff --git a/src/google/protobuf/util/internal/json_stream_parser.cc b/src/google/protobuf/util/internal/json_stream_parser.cc
index be51ce56..a8d48eff 100644
--- a/src/google/protobuf/util/internal/json_stream_parser.cc
+++ b/src/google/protobuf/util/internal/json_stream_parser.cc
@@ -107,7 +107,8 @@ JsonStreamParser::JsonStreamParser(ObjectWriter* ow)
parsed_storage_(),
string_open_(0),
chunk_storage_(),
- coerce_to_utf8_(false) {
+ coerce_to_utf8_(false),
+ allow_empty_null_(false) {
// Initialize the stack with a single value to be parsed.
stack_.push(VALUE);
}
@@ -277,6 +278,10 @@ util::Status JsonStreamParser::ParseValue(TokenType type) {
case UNKNOWN:
return ReportUnknown("Expected a value.");
default: {
+ if (allow_empty_null_ && IsEmptyNullAllowed(type)) {
+ return ParseEmptyNull();
+ }
+
// Special case for having been cut off while parsing, wait for more data.
// This handles things like 'fals' being at the end of the string, we
// don't know if the next char would be e, completing it, or something
@@ -293,8 +298,8 @@ util::Status JsonStreamParser::ParseString() {
util::Status result = ParseStringHelper();
if (result.ok()) {
ow_->RenderString(key_, parsed_);
- key_.clear();
- parsed_.clear();
+ key_ = StringPiece();
+ parsed_ = StringPiece();
parsed_storage_.clear();
}
return result;
@@ -469,17 +474,17 @@ util::Status JsonStreamParser::ParseNumber() {
switch (number.type) {
case NumberResult::DOUBLE:
ow_->RenderDouble(key_, number.double_val);
- key_.clear();
+ key_ = StringPiece();
break;
case NumberResult::INT:
ow_->RenderInt64(key_, number.int_val);
- key_.clear();
+ key_ = StringPiece();
break;
case NumberResult::UINT:
ow_->RenderUint64(key_, number.uint_val);
- key_.clear();
+ key_ = StringPiece();
break;
default:
@@ -563,7 +568,7 @@ util::Status JsonStreamParser::HandleBeginObject() {
GOOGLE_DCHECK_EQ('{', *p_.data());
Advance();
ow_->StartObject(key_);
- key_.clear();
+ key_ = StringPiece();
stack_.push(ENTRY);
return util::Status::OK;
}
@@ -613,7 +618,7 @@ util::Status JsonStreamParser::ParseEntry(TokenType type) {
} else {
key_ = parsed_;
}
- parsed_.clear();
+ parsed_ = StringPiece();
}
} else if (type == BEGIN_KEY) {
// Key is a bare key (back compat), create a StringPiece pointing to it.
@@ -646,7 +651,7 @@ util::Status JsonStreamParser::HandleBeginArray() {
GOOGLE_DCHECK_EQ('[', *p_.data());
Advance();
ow_->StartList(key_);
- key_.clear();
+ key_ = StringPiece();
stack_.push(ARRAY_VALUE);
return util::Status::OK;
}
@@ -663,7 +668,8 @@ util::Status JsonStreamParser::ParseArrayValue(TokenType type) {
}
// The ParseValue call may push something onto the stack so we need to make
- // sure an ARRAY_MID is after it, so we push it on now.
+ // sure an ARRAY_MID is after it, so we push it on now. Also, the parsing of
+ // empty-null array value is relying on this ARRAY_MID token.
stack_.push(ARRAY_MID);
util::Status result = ParseValue(type);
if (result == util::Status::CANCELLED) {
@@ -697,25 +703,37 @@ util::Status JsonStreamParser::ParseArrayMid(TokenType type) {
util::Status JsonStreamParser::ParseTrue() {
ow_->RenderBool(key_, true);
- key_.clear();
+ key_ = StringPiece();
p_.remove_prefix(true_len);
return util::Status::OK;
}
util::Status JsonStreamParser::ParseFalse() {
ow_->RenderBool(key_, false);
- key_.clear();
+ key_ = StringPiece();
p_.remove_prefix(false_len);
return util::Status::OK;
}
util::Status JsonStreamParser::ParseNull() {
ow_->RenderNull(key_);
- key_.clear();
+ key_ = StringPiece();
p_.remove_prefix(null_len);
return util::Status::OK;
}
+util::Status JsonStreamParser::ParseEmptyNull() {
+ ow_->RenderNull(key_);
+ key_ = StringPiece();
+ return util::Status::OK;
+}
+
+bool JsonStreamParser::IsEmptyNullAllowed(TokenType type) {
+ if (stack_.empty()) return false;
+ return (stack_.top() == ARRAY_MID && type == VALUE_SEPARATOR) ||
+ stack_.top() == OBJ_MID;
+}
+
util::Status JsonStreamParser::ReportFailure(StringPiece message) {
static const int kContextLength = 20;
const char* p_start = p_.data();
diff --git a/src/google/protobuf/util/internal/json_stream_parser.h b/src/google/protobuf/util/internal/json_stream_parser.h
index 0278c28f..78b35cc2 100644
--- a/src/google/protobuf/util/internal/json_stream_parser.h
+++ b/src/google/protobuf/util/internal/json_stream_parser.h
@@ -179,6 +179,10 @@ class LIBPROTOBUF_EXPORT JsonStreamParser {
util::Status ParseTrue();
util::Status ParseFalse();
util::Status ParseNull();
+ util::Status ParseEmptyNull();
+
+ // Whether an empty-null is allowed in the current state.
+ bool IsEmptyNullAllowed(TokenType type);
// Report a failure as a util::Status.
util::Status ReportFailure(StringPiece message);
@@ -247,6 +251,10 @@ class LIBPROTOBUF_EXPORT JsonStreamParser {
// Whether to allow non UTF-8 encoded input and replace invalid code points.
bool coerce_to_utf8_;
+ // Whether allows empty string represented null array value or object entry
+ // value.
+ bool allow_empty_null_;
+
GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonStreamParser);
};
diff --git a/src/google/protobuf/util/internal/json_stream_parser_test.cc b/src/google/protobuf/util/internal/json_stream_parser_test.cc
index 059ea6d8..b30d529f 100644
--- a/src/google/protobuf/util/internal/json_stream_parser_test.cc
+++ b/src/google/protobuf/util/internal/json_stream_parser_test.cc
@@ -81,12 +81,15 @@ using util::Status;
// For each test we split the input string on every possible character to ensure
// the parser is able to handle arbitrarily split input for all cases. We also
// do a final test of the entire test case one character at a time.
+//
+// It is verified that expected calls to the mocked objects are in sequence.
class JsonStreamParserTest : public ::testing::Test {
protected:
JsonStreamParserTest() : mock_(), ow_(&mock_) {}
virtual ~JsonStreamParserTest() {}
- util::Status RunTest(StringPiece json, int split, bool coerce_utf8 = false) {
+ util::Status RunTest(StringPiece json, int split, bool coerce_utf8 = false,
+ bool allow_empty_null = false) {
JsonStreamParser parser(&mock_);
// Special case for split == length, test parsing one character at a time.
@@ -116,8 +119,10 @@ class JsonStreamParserTest : public ::testing::Test {
return result;
}
- void DoTest(StringPiece json, int split, bool coerce_utf8 = false) {
- util::Status result = RunTest(json, split, coerce_utf8);
+ void DoTest(StringPiece json, int split, bool coerce_utf8 = false,
+ bool allow_empty_null = false) {
+ util::Status result =
+ RunTest(json, split, coerce_utf8, allow_empty_null);
if (!result.ok()) {
GOOGLE_LOG(WARNING) << result;
}
@@ -125,14 +130,16 @@ class JsonStreamParserTest : public ::testing::Test {
}
void DoErrorTest(StringPiece json, int split, StringPiece error_prefix,
- bool coerce_utf8 = false) {
- util::Status result = RunTest(json, split, coerce_utf8);
+ bool coerce_utf8 = false, bool allow_empty_null = false) {
+ util::Status result =
+ RunTest(json, split, coerce_utf8, allow_empty_null);
EXPECT_EQ(util::error::INVALID_ARGUMENT, result.error_code());
StringPiece error_message(result.error_message());
EXPECT_EQ(error_prefix, error_message.substr(0, error_prefix.size()));
}
+ ::testing::InSequence in_sequence_;
MockObjectWriter mock_;
ExpectingObjectWriter ow_;
};
@@ -335,6 +342,7 @@ TEST_F(JsonStreamParserTest, ArrayValues) {
}
}
+
// - object containing array, object, value (true, false, null, num, string)
TEST_F(JsonStreamParserTest, ObjectValues) {
StringPiece str =
diff --git a/src/google/protobuf/util/internal/object_writer.h b/src/google/protobuf/util/internal/object_writer.h
index 5781aa1e..b6fbd19b 100644
--- a/src/google/protobuf/util/internal/object_writer.h
+++ b/src/google/protobuf/util/internal/object_writer.h
@@ -119,6 +119,13 @@ class LIBPROTOBUF_EXPORT ObjectWriter {
return use_strict_base64_decoding_;
}
+ // Whether empty strings should be rendered for the next name for Start/Render
+ // calls. This setting is only valid until the next key is rendered, after
+ // which it gets reset.
+ // It is up to the derived classes to interpret this and render accordingly.
+ // Default implementation ignores this setting.
+ virtual void empty_name_ok_for_next_key() {}
+
protected:
ObjectWriter() : use_strict_base64_decoding_(true) {}
diff --git a/src/google/protobuf/util/internal/proto_writer.cc b/src/google/protobuf/util/internal/proto_writer.cc
index 0c38aeb9..4dcf4c3b 100644
--- a/src/google/protobuf/util/internal/proto_writer.cc
+++ b/src/google/protobuf/util/internal/proto_writer.cc
@@ -65,6 +65,7 @@ ProtoWriter::ProtoWriter(TypeResolver* type_resolver,
own_typeinfo_(true),
done_(false),
ignore_unknown_fields_(false),
+ use_lower_camel_for_enums_(false),
element_(NULL),
size_insert_(),
output_(output),
@@ -83,6 +84,7 @@ ProtoWriter::ProtoWriter(const TypeInfo* typeinfo,
own_typeinfo_(false),
done_(false),
ignore_unknown_fields_(false),
+ use_lower_camel_for_enums_(false),
element_(NULL),
size_insert_(),
output_(output),
@@ -264,8 +266,9 @@ inline Status WriteString(int field_number, const DataPiece& data,
// 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<int> e = data.ToEnum(enum_type);
+ CodedOutputStream* stream,
+ bool use_lower_camel_for_enums) {
+ StatusOr<int> e = data.ToEnum(enum_type, use_lower_camel_for_enums);
if (e.ok()) {
WireFormatLite::WriteEnum(field_number, e.ValueOrDie(), stream);
}
@@ -662,7 +665,7 @@ ProtoWriter* ProtoWriter::RenderPrimitiveField(
case google::protobuf::Field_Kind_TYPE_ENUM: {
status = WriteEnum(field.number(), data,
typeinfo_->GetEnumByTypeUrl(field.type_url()),
- stream_.get());
+ stream_.get(), use_lower_camel_for_enums_);
break;
}
default: // TYPE_GROUP or TYPE_MESSAGE
diff --git a/src/google/protobuf/util/internal/proto_writer.h b/src/google/protobuf/util/internal/proto_writer.h
index 7f1108ab..21dff88d 100644
--- a/src/google/protobuf/util/internal/proto_writer.h
+++ b/src/google/protobuf/util/internal/proto_writer.h
@@ -148,6 +148,10 @@ class LIBPROTOBUF_EXPORT ProtoWriter : public StructuredObjectWriter {
ignore_unknown_fields_ = ignore_unknown_fields;
}
+ void set_use_lower_camel_for_enums(bool use_lower_camel_for_enums) {
+ use_lower_camel_for_enums_ = use_lower_camel_for_enums;
+ }
+
protected:
class LIBPROTOBUF_EXPORT ProtoElement : public BaseElement, public LocationTrackerInterface {
public:
@@ -308,6 +312,10 @@ class LIBPROTOBUF_EXPORT ProtoWriter : public StructuredObjectWriter {
// If true, don't report unknown field names to the listener.
bool ignore_unknown_fields_;
+ // If true, check if enum name in camel case or without underscore matches the
+ // field name.
+ bool use_lower_camel_for_enums_;
+
// Variable for internal state processing:
// element_ : the current element.
// size_insert_: sizes of nested messages.
diff --git a/src/google/protobuf/util/internal/protostream_objectsource.cc b/src/google/protobuf/util/internal/protostream_objectsource.cc
index 0048d75b..150f3cf1 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource.cc
+++ b/src/google/protobuf/util/internal/protostream_objectsource.cc
@@ -288,6 +288,8 @@ StatusOr<uint32> ProtoStreamObjectSource::RenderMap(
return Status(util::error::INTERNAL, "Invalid map entry.");
}
ASSIGN_OR_RETURN(map_key, MapKeyDefaultValueAsString(*key_field));
+ // Key is empty, force it to render as empty (for string values).
+ ow->empty_name_ok_for_next_key();
}
RETURN_IF_ERROR(RenderField(field, map_key, ow));
} else {
@@ -857,7 +859,8 @@ Status ProtoStreamObjectSource::RenderNonMessageField(
// up.
const google::protobuf::Enum* en =
typeinfo_->GetEnumByTypeUrl(field->type_url());
- // Lookup the name of the enum, and render that. Skips unknown enums.
+ // Lookup the name of the enum, and render that. Unknown enum values
+ // are printed as integers.
if (en != NULL) {
const google::protobuf::EnumValue* enum_value =
FindEnumValueByNumber(*en, buffer32);
@@ -866,9 +869,11 @@ Status ProtoStreamObjectSource::RenderNonMessageField(
ow->RenderString(field_name, ToCamelCase(enum_value->name()));
else
ow->RenderString(field_name, enum_value->name());
+ } else {
+ ow->RenderInt32(field_name, buffer32);
}
} else {
- GOOGLE_LOG(INFO) << "Unknown enum skipped: " << field->type_url();
+ ow->RenderInt32(field_name, buffer32);
}
break;
}
@@ -1099,6 +1104,8 @@ const google::protobuf::EnumValue* FindEnumValueByNumber(
// TODO(skarvaje): Look into optimizing this by not doing computation on
// double.
const string FormatNanos(uint32 nanos) {
+ if (nanos == 0) return "";
+
const char* format =
(nanos % 1000 != 0) ? "%.9f" : (nanos % 1000000 != 0) ? "%.6f" : "%.3f";
string formatted =
diff --git a/src/google/protobuf/util/internal/protostream_objectsource.h b/src/google/protobuf/util/internal/protostream_objectsource.h
index 243f85b2..adecfbd3 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource.h
+++ b/src/google/protobuf/util/internal/protostream_objectsource.h
@@ -129,6 +129,28 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
bool include_start_and_end,
ObjectWriter* ow) const;
+ // Renders a repeating field (packed or unpacked). Returns the next tag after
+ // reading all sequential repeating elements. The caller should use this tag
+ // before reading more tags from the stream.
+ virtual util::StatusOr<uint32> RenderList(
+ const google::protobuf::Field* field, StringPiece name, uint32 list_tag,
+ ObjectWriter* ow) const;
+
+ // Looks up a field and verify its consistency with wire type in tag.
+ const google::protobuf::Field* FindAndVerifyField(
+ const google::protobuf::Type& type, uint32 tag) const;
+
+ // Renders a field value to the ObjectWriter.
+ util::Status RenderField(const google::protobuf::Field* field,
+ StringPiece field_name, ObjectWriter* ow) const;
+
+ // Reads field value according to Field spec in 'field' and returns the read
+ // value as string. This only works for primitive datatypes (no message
+ // types).
+ const string ReadFieldValueAsString(
+ const google::protobuf::Field& field) const;
+
+
private:
ProtoStreamObjectSource(google::protobuf::io::CodedInputStream* stream,
const TypeInfo* typeinfo,
@@ -138,19 +160,9 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
const google::protobuf::Type&,
StringPiece, ObjectWriter*);
- // Looks up a field and verify its consistency with wire type in tag.
- const google::protobuf::Field* FindAndVerifyField(
- const google::protobuf::Type& type, uint32 tag) const;
-
// TODO(skarvaje): Mark these methods as non-const as they modify internal
// state (stream_).
//
- // Renders a repeating field (packed or unpacked).
- // Returns the next tag after reading all sequential repeating elements. The
- // caller should use this tag before reading more tags from the stream.
- util::StatusOr<uint32> RenderList(const google::protobuf::Field* field,
- StringPiece name, uint32 list_tag,
- ObjectWriter* ow) const;
// Renders a NWP map.
// Returns the next tag after reading all map entries. The caller should use
// this tag before reading more tags from the stream.
@@ -234,10 +246,6 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
static void DeleteRendererMap();
static TypeRenderer* FindTypeRenderer(const string& type_url);
- // Renders a field value to the ObjectWriter.
- util::Status RenderField(const google::protobuf::Field* field,
- StringPiece field_name, ObjectWriter* ow) const;
-
// Same as above but renders all non-message field types. Callers don't call
// this function directly. They just use RenderField.
util::Status RenderNonMessageField(const google::protobuf::Field* field,
@@ -245,12 +253,6 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
ObjectWriter* ow) const;
- // Reads field value according to Field spec in 'field' and returns the read
- // value as string. This only works for primitive datatypes (no message
- // types).
- const string ReadFieldValueAsString(
- const google::protobuf::Field& field) const;
-
// Utility function to detect proto maps. The 'field' MUST be repeated.
bool IsMap(const google::protobuf::Field& field) const;
@@ -271,6 +273,7 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
// Type information for all the types used in the descriptor. Used to find
// google::protobuf::Type of nested messages/enums.
const TypeInfo* typeinfo_;
+
// Whether this class owns the typeinfo_ object. If true the typeinfo_ object
// should be deleted in the destructor.
bool own_typeinfo_;
diff --git a/src/google/protobuf/util/internal/protostream_objectsource_test.cc b/src/google/protobuf/util/internal/protostream_objectsource_test.cc
index 3f6fdf97..cac28a06 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource_test.cc
+++ b/src/google/protobuf/util/internal/protostream_objectsource_test.cc
@@ -42,15 +42,16 @@
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/util/internal/expecting_objectwriter.h>
+#include <google/protobuf/util/internal/testdata/anys.pb.h>
#include <google/protobuf/util/internal/testdata/books.pb.h>
#include <google/protobuf/util/internal/testdata/field_mask.pb.h>
-#include <google/protobuf/util/internal/type_info_test_helper.h>
-#include <google/protobuf/util/internal/constants.h>
-#include <google/protobuf/stubs/strutil.h>
-#include <google/protobuf/util/internal/testdata/anys.pb.h>
#include <google/protobuf/util/internal/testdata/maps.pb.h>
+#include <google/protobuf/util/internal/testdata/proto3.pb.h>
#include <google/protobuf/util/internal/testdata/struct.pb.h>
#include <google/protobuf/util/internal/testdata/timestamp_duration.pb.h>
+#include <google/protobuf/util/internal/type_info_test_helper.h>
+#include <google/protobuf/util/internal/constants.h>
+#include <google/protobuf/stubs/strutil.h>
#include <gtest/gtest.h>
@@ -66,24 +67,24 @@ using google::protobuf::Message;
using google::protobuf::io::ArrayInputStream;
using google::protobuf::io::CodedInputStream;
using util::Status;
+using google::protobuf::testing::AnyM;
+using google::protobuf::testing::AnyOut;
using google::protobuf::testing::Author;
using google::protobuf::testing::BadAuthor;
using google::protobuf::testing::BadNestedBook;
using google::protobuf::testing::Book;
-using google::protobuf::testing::Cyclic;
using google::protobuf::testing::Book_Label;
+using google::protobuf::testing::Cyclic;
+using google::protobuf::testing::FieldMaskTest;
+using google::protobuf::testing::MapOut;
+using google::protobuf::testing::MapOutWireFormat;
using google::protobuf::testing::NestedBook;
+using google::protobuf::testing::NestedFieldMask;
using google::protobuf::testing::PackedPrimitive;
using google::protobuf::testing::Primitive;
-using google::protobuf::testing::more_author;
-using google::protobuf::testing::maps::MapOut;
-using google::protobuf::testing::maps::MapOutWireFormat;
-using google::protobuf::testing::timestampduration::TimestampDuration;
-using google::protobuf::testing::anys::AnyOut;
-using google::protobuf::testing::anys::AnyM;
-using google::protobuf::testing::FieldMaskTest;
-using google::protobuf::testing::NestedFieldMask;
-using google::protobuf::testing::structs::StructType;
+using google::protobuf::testing::Proto3Message;
+using google::protobuf::testing::StructType;
+using google::protobuf::testing::TimestampDuration;
using ::testing::_;
@@ -101,7 +102,7 @@ class ProtostreamObjectSourceTest
mock_(),
ow_(&mock_),
use_lower_camel_for_enums_(false) {
- helper_.ResetTypeInfo(Book::descriptor());
+ helper_.ResetTypeInfo(Book::descriptor(), Proto3Message::descriptor());
}
virtual ~ProtostreamObjectSourceTest() {}
@@ -493,6 +494,15 @@ TEST_P(ProtostreamObjectSourceTest, EnumCaseIsUnchangedByDefault) {
DoTest(book, Book::descriptor());
}
+TEST_P(ProtostreamObjectSourceTest, UnknownEnum) {
+ Proto3Message message;
+ message.set_enum_value(static_cast<Proto3Message::NestedEnum>(1234));
+ ow_.StartObject("")
+ ->RenderInt32("enumValue", 1234)
+ ->EndObject();
+ DoTest(message, Proto3Message::descriptor());
+}
+
TEST_P(ProtostreamObjectSourceTest, CyclicMessageDepthTest) {
Cyclic cyclic;
cyclic.set_m_int(123);
@@ -679,7 +689,7 @@ INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest,
// This is the example expected output.
// {
// "any": {
-// "@type": "type.googleapis.com/google.protobuf.testing.anys.AnyM"
+// "@type": "type.googleapis.com/google.protobuf.testing.AnyM"
// "foo": "foovalue"
// }
// }
@@ -694,7 +704,7 @@ TEST_P(ProtostreamObjectSourceAnysTest, BasicAny) {
ow_.StartObject("")
->StartObject("any")
->RenderString("@type",
- "type.googleapis.com/google.protobuf.testing.anys.AnyM")
+ "type.googleapis.com/google.protobuf.testing.AnyM")
->RenderString("foo", "foovalue")
->EndObject()
->EndObject();
@@ -708,8 +718,7 @@ TEST_P(ProtostreamObjectSourceAnysTest, RecursiveAny) {
any->set_type_url("type.googleapis.com/google.protobuf.Any");
::google::protobuf::Any nested_any;
- nested_any.set_type_url(
- "type.googleapis.com/google.protobuf.testing.anys.AnyM");
+ nested_any.set_type_url("type.googleapis.com/google.protobuf.testing.AnyM");
AnyM m;
m.set_foo("foovalue");
@@ -722,7 +731,7 @@ TEST_P(ProtostreamObjectSourceAnysTest, RecursiveAny) {
->RenderString("@type", "type.googleapis.com/google.protobuf.Any")
->StartObject("value")
->RenderString("@type",
- "type.googleapis.com/google.protobuf.testing.anys.AnyM")
+ "type.googleapis.com/google.protobuf.testing.AnyM")
->RenderString("foo", "foovalue")
->EndObject()
->EndObject()
@@ -741,7 +750,7 @@ TEST_P(ProtostreamObjectSourceAnysTest, DoubleRecursiveAny) {
::google::protobuf::Any second_nested_any;
second_nested_any.set_type_url(
- "type.googleapis.com/google.protobuf.testing.anys.AnyM");
+ "type.googleapis.com/google.protobuf.testing.AnyM");
AnyM m;
m.set_foo("foovalue");
@@ -756,7 +765,7 @@ TEST_P(ProtostreamObjectSourceAnysTest, DoubleRecursiveAny) {
->RenderString("@type", "type.googleapis.com/google.protobuf.Any")
->StartObject("value")
->RenderString("@type",
- "type.googleapis.com/google.protobuf.testing.anys.AnyM")
+ "type.googleapis.com/google.protobuf.testing.AnyM")
->RenderString("foo", "foovalue")
->EndObject()
->EndObject()
@@ -1001,6 +1010,19 @@ TEST_P(ProtostreamObjectSourceTimestampTest, InvalidDurationAboveMaxTest) {
EXPECT_EQ(util::error::INTERNAL, status.error_code());
}
+TEST_P(ProtostreamObjectSourceTimestampTest, TimestampDurationDefaultValue) {
+ TimestampDuration out;
+ out.mutable_ts()->set_seconds(0);
+ out.mutable_dur()->set_seconds(0);
+
+ ow_.StartObject("")
+ ->RenderString("ts", "1970-01-01T00:00:00Z")
+ ->RenderString("dur", "0s")
+ ->EndObject();
+
+ DoTest(out, TimestampDuration::descriptor());
+}
+
} // namespace converter
} // namespace util
} // namespace protobuf
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.cc b/src/google/protobuf/util/internal/protostream_objectwriter.cc
index 73e05cfe..6c15e862 100644
--- a/src/google/protobuf/util/internal/protostream_objectwriter.cc
+++ b/src/google/protobuf/util/internal/protostream_objectwriter.cc
@@ -65,6 +65,7 @@ ProtoStreamObjectWriter::ProtoStreamObjectWriter(
current_(NULL),
options_(options) {
set_ignore_unknown_fields(options_.ignore_unknown_fields);
+ set_use_lower_camel_for_enums(options_.use_lower_camel_for_enums);
}
ProtoStreamObjectWriter::ProtoStreamObjectWriter(
@@ -192,17 +193,11 @@ 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).
+ // before reaching here, which happens when we have data before the "@type"
+ // field.
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;
- }
+ // 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_ == NULL) {
+ 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;
- }
+ // 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_ == NULL) {
+ // Save data before the "@type" field for later replay.
+ uninterpreted_events_.push_back(Event(Event::END_LIST));
+ } else {
ow_->EndList();
}
}
@@ -278,12 +275,8 @@ void ProtoStreamObjectWriter::AnyWriter::RenderDataPiece(
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;
- }
+ // 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",
@@ -293,7 +286,7 @@ void ProtoStreamObjectWriter::AnyWriter::RenderDataPiece(
if (well_known_type_render_ == NULL) {
// 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;
}
@@ -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 (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,6 +383,41 @@ 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) {
+ value_.str().AppendToString(&value_storage_);
+ 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)
@@ -867,6 +911,7 @@ Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow,
Status ProtoStreamObjectWriter::RenderTimestamp(ProtoStreamObjectWriter* ow,
const DataPiece& data) {
+ if (data.type() == DataPiece::TYPE_NULL) return Status::OK;
if (data.type() != DataPiece::TYPE_STRING) {
return Status(INVALID_ARGUMENT,
StrCat("Invalid data type for timestamp, value is ",
@@ -897,6 +942,7 @@ static inline util::Status RenderOneFieldPath(ProtoStreamObjectWriter* ow,
Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow,
const DataPiece& data) {
+ if (data.type() == DataPiece::TYPE_NULL) return Status::OK;
if (data.type() != DataPiece::TYPE_STRING) {
return Status(INVALID_ARGUMENT,
StrCat("Invalid data type for field mask, value is ",
@@ -913,6 +959,7 @@ Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow,
Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow,
const DataPiece& data) {
+ if (data.type() == DataPiece::TYPE_NULL) return Status::OK;
if (data.type() != DataPiece::TYPE_STRING) {
return Status(INVALID_ARGUMENT,
StrCat("Invalid data type for duration, value is ",
@@ -962,6 +1009,7 @@ Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow,
Status ProtoStreamObjectWriter::RenderWrapperType(ProtoStreamObjectWriter* ow,
const DataPiece& data) {
+ if (data.type() == DataPiece::TYPE_NULL) return Status::OK;
ow->ProtoWriter::RenderDataPiece("value", data);
return Status::OK;
}
@@ -1049,13 +1097,18 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece(
// 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()));
+ // 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;
}
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.h b/src/google/protobuf/util/internal/protostream_objectwriter.h
index 2e4d14d1..732971e1 100644
--- a/src/google/protobuf/util/internal/protostream_objectwriter.h
+++ b/src/google/protobuf/util/internal/protostream_objectwriter.h
@@ -87,8 +87,14 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectWriter : public ProtoWriter {
// just ignore it and continue to process the rest.
bool ignore_unknown_fields;
+ // If true, check if enum name in camel case or without underscore matches
+ // the field name.
+ bool use_lower_camel_for_enums;
+
Options()
- : struct_integers_as_strings(false), ignore_unknown_fields(false) {}
+ : struct_integers_as_strings(false),
+ ignore_unknown_fields(false),
+ use_lower_camel_for_enums(false) {}
// Default instance of Options with all options set to defaults.
static const Options& Defaults() {
@@ -145,6 +151,57 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectWriter : public ProtoWriter {
void RenderDataPiece(StringPiece name, const DataPiece& value);
private:
+ // Before the "@type" field is encountered, we store all incoming data
+ // into this Event struct and replay them after we get the "@type" field.
+ class LIBPROTOBUF_EXPORT Event {
+ public:
+ enum Type {
+ START_OBJECT = 0,
+ END_OBJECT = 1,
+ START_LIST = 2,
+ END_LIST = 3,
+ RENDER_DATA_PIECE = 4,
+ };
+
+ // Constructor for END_OBJECT and END_LIST events.
+ explicit Event(Type type) : type_(type), value_(DataPiece::NullData()) {}
+
+ // Constructor for START_OBJECT and START_LIST events.
+ explicit Event(Type type, StringPiece name)
+ : type_(type),
+ name_(name.ToString()),
+ value_(DataPiece::NullData()) {}
+
+ // Constructor for RENDER_DATA_PIECE events.
+ explicit Event(StringPiece name, const DataPiece& value)
+ : type_(RENDER_DATA_PIECE), name_(name.ToString()), value_(value) {
+ DeepCopy();
+ }
+
+ Event(const Event& other)
+ : type_(other.type_), name_(other.name_), value_(other.value_) {
+ DeepCopy();
+ }
+
+ Event& operator=(const Event& other) {
+ type_ = other.type_;
+ name_ = other.name_;
+ value_ = other.value_;
+ DeepCopy();
+ return *this;
+ }
+
+ void Replay(AnyWriter* writer) const;
+
+ private:
+ void DeepCopy();
+
+ Type type_;
+ string name_;
+ DataPiece value_;
+ string value_storage_;
+ };
+
// Handles starting up the any once we have a type.
void StartAny(const DataPiece& value);
@@ -180,6 +237,9 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectWriter : public ProtoWriter {
// }
bool is_well_known_type_;
TypeRenderer* well_known_type_render_;
+
+ // Store data before the "@type" field.
+ std::vector<Event> uninterpreted_events_;
};
// Represents an item in a stack of items used to keep state between
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter_test.cc b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc
index 30e58e62..e7b38520 100644
--- a/src/google/protobuf/util/internal/protostream_objectwriter_test.cc
+++ b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc
@@ -41,18 +41,20 @@
#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/message.h>
#include <google/protobuf/util/internal/mock_error_listener.h>
+#include <google/protobuf/util/internal/testdata/anys.pb.h>
#include <google/protobuf/util/internal/testdata/books.pb.h>
#include <google/protobuf/util/internal/testdata/field_mask.pb.h>
+#include <google/protobuf/util/internal/testdata/maps.pb.h>
+#include <google/protobuf/util/internal/testdata/oneofs.pb.h>
+#include <google/protobuf/util/internal/testdata/proto3.pb.h>
+#include <google/protobuf/util/internal/testdata/struct.pb.h>
+#include <google/protobuf/util/internal/testdata/timestamp_duration.pb.h>
+#include <google/protobuf/util/internal/testdata/wrappers.pb.h>
#include <google/protobuf/util/internal/type_info_test_helper.h>
#include <google/protobuf/util/internal/constants.h>
#include <google/protobuf/util/message_differencer.h>
#include <google/protobuf/stubs/bytestream.h>
#include <google/protobuf/stubs/strutil.h>
-#include <google/protobuf/util/internal/testdata/anys.pb.h>
-#include <google/protobuf/util/internal/testdata/maps.pb.h>
-#include <google/protobuf/util/internal/testdata/oneofs.pb.h>
-#include <google/protobuf/util/internal/testdata/struct.pb.h>
-#include <google/protobuf/util/internal/testdata/timestamp_duration.pb.h>
#include <gtest/gtest.h>
@@ -61,27 +63,28 @@ namespace protobuf {
namespace util {
namespace converter {
+using google::protobuf::testing::AnyM;
+using google::protobuf::testing::AnyOut;
using google::protobuf::testing::Author;
using google::protobuf::testing::Book;
-using google::protobuf::testing::Book_Data;
+using google::protobuf::testing::FieldMaskTest;
+using google::protobuf::testing::Int32Wrapper;
+using google::protobuf::testing::MapIn;
using google::protobuf::testing::Primitive;
+using google::protobuf::testing::Proto3Message;
using google::protobuf::testing::Publisher;
+using google::protobuf::testing::StructType;
+using google::protobuf::testing::TimestampDuration;
+using google::protobuf::testing::ValueWrapper;
+using google::protobuf::testing::oneofs::OneOfsRequest;
using google::protobuf::Descriptor;
using google::protobuf::DescriptorPool;
using google::protobuf::DynamicMessageFactory;
using google::protobuf::FileDescriptorProto;
using google::protobuf::Message;
-using google::protobuf::io::ArrayInputStream;
using strings::GrowingArrayByteSink;
using ::testing::_;
using ::testing::Args;
-using google::protobuf::testing::anys::AnyM;
-using google::protobuf::testing::anys::AnyOut;
-using google::protobuf::testing::oneofs::OneOfsRequest;
-using google::protobuf::testing::FieldMaskTest;
-using google::protobuf::testing::maps::MapIn;
-using google::protobuf::testing::structs::StructType;
-using google::protobuf::testing::timestampduration::TimestampDuration;
namespace {
@@ -268,6 +271,84 @@ TEST_P(ProtoStreamObjectWriterTest, CustomJsonName) {
CheckOutput(book);
}
+TEST_P(ProtoStreamObjectWriterTest, IntEnumValuesAreAccepted) {
+ Book book;
+ book.set_title("Some Book");
+ book.set_type(google::protobuf::testing::Book_Type_KIDS);
+ Author* robert = book.mutable_author();
+ robert->set_name("robert");
+
+ ow_->StartObject("")
+ ->RenderString("title", "Some Book")
+ ->RenderString("type", "2")
+ ->StartObject("author")
+ ->RenderString("name", "robert")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(book);
+}
+
+TEST_P(ProtoStreamObjectWriterTest, EnumValuesWithoutUnderscoreAreAccepted) {
+ Book book;
+ book.set_title("Some Book");
+ book.set_type(google::protobuf::testing::Book_Type_ACTION_AND_ADVENTURE);
+ Author* robert = book.mutable_author();
+ robert->set_name("robert");
+
+ options_.use_lower_camel_for_enums = true;
+ ResetProtoWriter();
+
+ ow_->StartObject("")
+ ->RenderString("title", "Some Book")
+ ->RenderString("type", "ACTIONANDADVENTURE")
+ ->StartObject("author")
+ ->RenderString("name", "robert")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(book);
+}
+
+TEST_P(ProtoStreamObjectWriterTest, EnumValuesInCamelCaseAreAccepted) {
+ Book book;
+ book.set_title("Some Book");
+ book.set_type(google::protobuf::testing::Book_Type_ACTION_AND_ADVENTURE);
+ Author* robert = book.mutable_author();
+ robert->set_name("robert");
+
+ options_.use_lower_camel_for_enums = true;
+ ResetProtoWriter();
+
+ ow_->StartObject("")
+ ->RenderString("title", "Some Book")
+ ->RenderString("type", "actionAndAdventure")
+ ->StartObject("author")
+ ->RenderString("name", "robert")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(book);
+}
+
+TEST_P(ProtoStreamObjectWriterTest,
+ EnumValuesInCamelCaseWithNameNotUppercaseAreAccepted) {
+ Book book;
+ book.set_title("Some Book");
+ book.set_type(google::protobuf::testing::Book_Type_arts_and_photography);
+ Author* robert = book.mutable_author();
+ robert->set_name("robert");
+
+ options_.use_lower_camel_for_enums = true;
+ ResetProtoWriter();
+
+ ow_->StartObject("")
+ ->RenderString("title", "Some Book")
+ ->RenderString("type", "artsAndPhotography")
+ ->StartObject("author")
+ ->RenderString("name", "robert")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(book);
+}
+
TEST_P(ProtoStreamObjectWriterTest, PrimitiveFromStringConversion) {
Primitive full;
full.set_fix32(101);
@@ -826,6 +907,19 @@ TEST_P(ProtoStreamObjectWriterTest, IgnoreUnknownListAtPublisher) {
CheckOutput(expected);
}
+TEST_P(ProtoStreamObjectWriterTest, AcceptUnknownEnumValue) {
+ ResetTypeInfo(Proto3Message::descriptor());
+
+ Proto3Message expected;
+ expected.set_enum_value(static_cast<Proto3Message::NestedEnum>(12345));
+
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")
+ ->RenderInt32("enumValue", 12345)
+ ->EndObject();
+ CheckOutput(expected);
+}
+
TEST_P(ProtoStreamObjectWriterTest, MissingRequiredField) {
Book expected;
expected.set_title("My Title");
@@ -1338,9 +1432,9 @@ TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
InvalidValue(
_, StringPiece("type.googleapis.com/google.protobuf.Timestamp"),
StringPiece(
- "Field 'ts', Invalid data type for timestamp, value is null")))
+ "Field 'ts', Invalid data type for timestamp, value is 1")))
.With(Args<0>(HasObjectLocation("ts")));
- ow_->StartObject("")->RenderNull("ts")->EndObject();
+ ow_->StartObject("")->RenderInt32("ts", 1)->EndObject();
CheckOutput(timestamp);
}
@@ -1352,8 +1446,22 @@ TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
InvalidValue(
_, StringPiece("type.googleapis.com/google.protobuf.Duration"),
StringPiece(
- "Field 'dur', Invalid data type for duration, value is null")))
+ "Field 'dur', Invalid data type for duration, value is 1")))
.With(Args<0>(HasObjectLocation("dur")));
+ ow_->StartObject("")->RenderInt32("dur", 1)->EndObject();
+ CheckOutput(duration);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest, TimestampAcceptsNull) {
+ TimestampDuration timestamp;
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")->RenderNull("ts")->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest, DurationAcceptsNull) {
+ TimestampDuration duration;
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
ow_->StartObject("")->RenderNull("dur")->EndObject();
CheckOutput(duration);
}
@@ -1415,6 +1523,28 @@ TEST_P(ProtoStreamObjectWriterStructTest, StructInvalidInputFailure) {
CheckOutput(struct_type);
}
+TEST_P(ProtoStreamObjectWriterStructTest, StructAcceptsNull) {
+ StructType struct_type;
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+
+ ow_->StartObject("")->RenderNull("object")->EndObject();
+ CheckOutput(struct_type);
+}
+
+TEST_P(ProtoStreamObjectWriterStructTest, StructValuePreservesNull) {
+ StructType struct_type;
+ (*struct_type.mutable_object()->mutable_fields())["key"].set_null_value(
+ google::protobuf::NULL_VALUE);
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+
+ ow_->StartObject("")
+ ->StartObject("object")
+ ->RenderNull("key")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(struct_type);
+}
+
TEST_P(ProtoStreamObjectWriterStructTest, SimpleRepeatedStructMapKeyTest) {
EXPECT_CALL(
listener_,
@@ -1482,6 +1612,15 @@ TEST_P(ProtoStreamObjectWriterStructTest, OptionStructIntAsStringsTest) {
CheckOutput(struct_type);
}
+TEST_P(ProtoStreamObjectWriterStructTest, ValuePreservesNull) {
+ ValueWrapper value;
+ value.mutable_value()->set_null_value(google::protobuf::NULL_VALUE);
+
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")->RenderNull("value")->EndObject();
+ CheckOutput(value);
+}
+
class ProtoStreamObjectWriterMapTest : public BaseProtoStreamObjectWriterTest {
protected:
ProtoStreamObjectWriterMapTest()
@@ -1527,11 +1666,14 @@ class ProtoStreamObjectWriterAnyTest : public BaseProtoStreamObjectWriterTest {
ProtoStreamObjectWriterAnyTest() {
vector<const Descriptor*> descriptors;
descriptors.push_back(AnyOut::descriptor());
+ descriptors.push_back(Book::descriptor());
+ descriptors.push_back(google::protobuf::Any::descriptor());
descriptors.push_back(google::protobuf::DoubleValue::descriptor());
+ descriptors.push_back(google::protobuf::FieldMask::descriptor());
+ descriptors.push_back(google::protobuf::Int32Value::descriptor());
+ descriptors.push_back(google::protobuf::Struct::descriptor());
descriptors.push_back(google::protobuf::Timestamp::descriptor());
- descriptors.push_back(google::protobuf::Any::descriptor());
descriptors.push_back(google::protobuf::Value::descriptor());
- descriptors.push_back(google::protobuf::Struct::descriptor());
ResetTypeInfo(descriptors);
}
};
@@ -1564,8 +1706,7 @@ TEST_P(ProtoStreamObjectWriterAnyTest, RecursiveAny) {
any->set_type_url("type.googleapis.com/google.protobuf.Any");
::google::protobuf::Any nested_any;
- nested_any.set_type_url(
- "type.googleapis.com/google.protobuf.testing.anys.AnyM");
+ nested_any.set_type_url("type.googleapis.com/google.protobuf.testing.AnyM");
AnyM m;
m.set_foo("foovalue");
@@ -1578,11 +1719,12 @@ TEST_P(ProtoStreamObjectWriterAnyTest, RecursiveAny) {
->RenderString("@type", "type.googleapis.com/google.protobuf.Any")
->StartObject("value")
->RenderString("@type",
- "type.googleapis.com/google.protobuf.testing.anys.AnyM")
+ "type.googleapis.com/google.protobuf.testing.AnyM")
->RenderString("foo", "foovalue")
->EndObject()
->EndObject()
->EndObject();
+ CheckOutput(out, 107);
}
TEST_P(ProtoStreamObjectWriterAnyTest, DoubleRecursiveAny) {
@@ -1595,7 +1737,7 @@ TEST_P(ProtoStreamObjectWriterAnyTest, DoubleRecursiveAny) {
::google::protobuf::Any second_nested_any;
second_nested_any.set_type_url(
- "type.googleapis.com/google.protobuf.testing.anys.AnyM");
+ "type.googleapis.com/google.protobuf.testing.AnyM");
AnyM m;
m.set_foo("foovalue");
@@ -1611,18 +1753,110 @@ TEST_P(ProtoStreamObjectWriterAnyTest, DoubleRecursiveAny) {
->RenderString("@type", "type.googleapis.com/google.protobuf.Any")
->StartObject("value")
->RenderString("@type",
- "type.googleapis.com/google.protobuf.testing.anys.AnyM")
+ "type.googleapis.com/google.protobuf.testing.AnyM")
->RenderString("foo", "foovalue")
->EndObject()
->EndObject()
->EndObject()
->EndObject();
+ CheckOutput(out, 151);
+}
+
+TEST_P(ProtoStreamObjectWriterAnyTest, TypeUrlAtEnd) {
+ Book book;
+ book.set_title("C++");
+ book.set_length(1234);
+ book.set_content("Hello World!");
+
+ ::google::protobuf::Any any;
+ any.PackFrom(book);
+
+ ::google::protobuf::Any outer_any;
+ outer_any.PackFrom(any);
+
+ AnyOut out;
+ out.mutable_any()->PackFrom(outer_any);
+
+ // Put the @type field at the end of each Any message. Parsers should
+ // be able to accept that.
+ ow_->StartObject("")
+ ->StartObject("any")
+ ->StartObject("value")
+ ->StartObject("value")
+ ->RenderString("title", "C++")
+ ->RenderInt32("length", 1234)
+ ->RenderBytes("content", "Hello World!")
+ ->RenderString("@type",
+ "type.googleapis.com/google.protobuf.testing.Book")
+ ->EndObject()
+ ->RenderString("@type", "type.googleapis.com/google.protobuf.Any")
+ ->EndObject()
+ ->RenderString("@type", "type.googleapis.com/google.protobuf.Any")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(out);
+}
+
+// Same as TypeUrlAtEnd, but use temporary string values to make sure we don't
+// mistakenly store StringPiece objects pointing to invalid memory.
+TEST_P(ProtoStreamObjectWriterAnyTest, TypeUrlAtEndWithTemporaryStrings) {
+ Book book;
+ book.set_title("C++");
+ book.set_length(1234);
+ book.set_content("Hello World!");
+
+ ::google::protobuf::Any any;
+ any.PackFrom(book);
+
+ ::google::protobuf::Any outer_any;
+ outer_any.PackFrom(any);
+
+ AnyOut out;
+ out.mutable_any()->PackFrom(outer_any);
+
+ string name, value;
+ // Put the @type field at the end of each Any message. Parsers should
+ // be able to accept that.
+ ow_->StartObject("")->StartObject("any");
+ {
+ ow_->StartObject("value");
+ {
+ ow_->StartObject("value");
+ {
+ name = "title";
+ value = "C++";
+ ow_->RenderString(name, value);
+ name = "length";
+ ow_->RenderInt32(name, 1234);
+ name = "content";
+ value = "Hello World!";
+ ow_->RenderBytes(name, value);
+ name = "@type";
+ value = "type.googleapis.com/google.protobuf.testing.Book";
+ ow_->RenderString(name, value);
+ }
+ ow_->EndObject();
+
+ name = "@type";
+ value = "type.googleapis.com/google.protobuf.Any";
+ ow_->RenderString(name, value);
+ }
+ ow_->EndObject();
+
+ name = "@type";
+ value = "type.googleapis.com/google.protobuf.Any";
+ ow_->RenderString(name, value);
+ }
+ ow_->EndObject()->EndObject();
+ CheckOutput(out);
}
TEST_P(ProtoStreamObjectWriterAnyTest, EmptyAnyFromEmptyObject) {
AnyOut out;
out.mutable_any();
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+
ow_->StartObject("")->StartObject("any")->EndObject()->EndObject();
CheckOutput(out, 2);
@@ -1631,11 +1865,10 @@ TEST_P(ProtoStreamObjectWriterAnyTest, EmptyAnyFromEmptyObject) {
TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails1) {
AnyOut any;
- EXPECT_CALL(
- listener_,
- InvalidValue(_, StringPiece("Any"),
- StringPiece("Missing or invalid @type for any field in "
- "google.protobuf.testing.anys.AnyOut")));
+ EXPECT_CALL(listener_,
+ InvalidValue(_, StringPiece("Any"),
+ StringPiece("Missing @type for any field in "
+ "google.protobuf.testing.AnyOut")));
ow_->StartObject("")
->StartObject("any")
@@ -1649,11 +1882,10 @@ TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails1) {
TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails2) {
AnyOut any;
- EXPECT_CALL(
- listener_,
- InvalidValue(_, StringPiece("Any"),
- StringPiece("Missing or invalid @type for any field in "
- "google.protobuf.testing.anys.AnyOut")));
+ EXPECT_CALL(listener_,
+ InvalidValue(_, StringPiece("Any"),
+ StringPiece("Missing @type for any field in "
+ "google.protobuf.testing.AnyOut")));
ow_->StartObject("")
->StartObject("any")
@@ -1667,11 +1899,10 @@ TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails2) {
TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails3) {
AnyOut any;
- EXPECT_CALL(
- listener_,
- InvalidValue(_, StringPiece("Any"),
- StringPiece("Missing or invalid @type for any field in "
- "google.protobuf.testing.anys.AnyOut")));
+ EXPECT_CALL(listener_,
+ InvalidValue(_, StringPiece("Any"),
+ StringPiece("Missing @type for any field in "
+ "google.protobuf.testing.AnyOut")));
ow_->StartObject("")
->StartObject("any")
@@ -1716,9 +1947,21 @@ TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithUnknownTypeFails) {
CheckOutput(any);
}
-TEST_P(ProtoStreamObjectWriterAnyTest, AnyNullInputFails) {
+TEST_P(ProtoStreamObjectWriterAnyTest, AnyIncorrectInputTypeFails) {
+ AnyOut any;
+
+ EXPECT_CALL(
+ listener_,
+ InvalidValue(_, StringPiece("type.googleapis.com/google.protobuf.Any"),
+ StringPiece("1")));
+ ow_->StartObject("")->RenderInt32("any", 1)->EndObject();
+ CheckOutput(any);
+}
+
+TEST_P(ProtoStreamObjectWriterAnyTest, AnyAcceptsNull) {
AnyOut any;
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
ow_->StartObject("")->RenderNull("any")->EndObject();
CheckOutput(any);
}
@@ -1956,6 +2199,111 @@ TEST_P(ProtoStreamObjectWriterAnyTest, AnyWellKnownTypesExpectObjectForAny) {
CheckOutput(any);
}
+// {
+// "any": {
+// "@type": "type.googleapis.com/google.protobuf.Any",
+// "value": null
+// }
+// }
+TEST_P(ProtoStreamObjectWriterAnyTest, AnyInAnyAcceptsNull) {
+ AnyOut out;
+ google::protobuf::Any empty;
+ out.mutable_any()->PackFrom(empty);
+
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")
+ ->StartObject("any")
+ ->RenderString("@type", "type.googleapis.com/google.protobuf.Any")
+ ->RenderNull("value")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(out);
+}
+
+// {
+// "any": {
+// "@type": "type.googleapis.com/google.protobuf.Timestamp",
+// "value": null
+// }
+// }
+TEST_P(ProtoStreamObjectWriterAnyTest, TimestampInAnyAcceptsNull) {
+ AnyOut out;
+ google::protobuf::Timestamp empty;
+ out.mutable_any()->PackFrom(empty);
+
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")
+ ->StartObject("any")
+ ->RenderString("@type", "type.googleapis.com/google.protobuf.Timestamp")
+ ->RenderNull("value")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(out);
+}
+
+// {
+// "any": {
+// "@type": "type.googleapis.com/google.protobuf.Duration",
+// "value": null
+// }
+// }
+TEST_P(ProtoStreamObjectWriterAnyTest, DurationInAnyAcceptsNull) {
+ AnyOut out;
+ google::protobuf::Duration empty;
+ out.mutable_any()->PackFrom(empty);
+
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")
+ ->StartObject("any")
+ ->RenderString("@type", "type.googleapis.com/google.protobuf.Duration")
+ ->RenderNull("value")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(out);
+}
+
+// {
+// "any": {
+// "@type": "type.googleapis.com/google.protobuf.FieldMask",
+// "value": null
+// }
+// }
+TEST_P(ProtoStreamObjectWriterAnyTest, FieldMaskInAnyAcceptsNull) {
+ AnyOut out;
+ google::protobuf::FieldMask empty;
+ out.mutable_any()->PackFrom(empty);
+
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")
+ ->StartObject("any")
+ ->RenderString("@type", "type.googleapis.com/google.protobuf.FieldMask")
+ ->RenderNull("value")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(out);
+}
+
+// {
+// "any": {
+// "@type": "type.googleapis.com/google.protobuf.Int32Value",
+// "value": null
+// }
+// }
+TEST_P(ProtoStreamObjectWriterAnyTest, WrapperInAnyAcceptsNull) {
+ AnyOut out;
+ google::protobuf::Int32Value empty;
+ out.mutable_any()->PackFrom(empty);
+
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")
+ ->StartObject("any")
+ ->RenderString("@type", "type.googleapis.com/google.protobuf.Int32Value")
+ ->RenderNull("value")
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(out);
+}
+
class ProtoStreamObjectWriterFieldMaskTest
: public BaseProtoStreamObjectWriterTest {
protected:
@@ -2201,6 +2549,36 @@ TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyCanContainAnyChars) {
CheckOutput(expected);
}
+TEST_P(ProtoStreamObjectWriterFieldMaskTest, FieldMaskAcceptsNull) {
+ FieldMaskTest expected;
+ EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0);
+ ow_->StartObject("")->RenderNull("single_mask")->EndObject();
+ CheckOutput(expected);
+}
+
+class ProtoStreamObjectWriterWrappersTest
+ : public BaseProtoStreamObjectWriterTest {
+ protected:
+ ProtoStreamObjectWriterWrappersTest() {
+ vector<const Descriptor*> descriptors;
+ descriptors.push_back(Int32Wrapper::descriptor());
+ descriptors.push_back(google::protobuf::Int32Value::descriptor());
+ ResetTypeInfo(descriptors);
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest,
+ ProtoStreamObjectWriterWrappersTest,
+ ::testing::Values(
+ testing::USE_TYPE_RESOLVER));
+
+TEST_P(ProtoStreamObjectWriterWrappersTest, WrapperAcceptsNull) {
+ Int32Wrapper wrapper;
+ EXPECT_CALL(listener_, InvalidName(_, _, _)).Times(0);
+ ow_->StartObject("")->RenderNull("int32")->EndObject();
+ CheckOutput(wrapper);
+}
+
class ProtoStreamObjectWriterOneOfsTest
: public BaseProtoStreamObjectWriterTest {
protected:
@@ -2368,7 +2746,6 @@ TEST_P(ProtoStreamObjectWriterOneOfsTest,
StringPiece("oneof field 'data' is already set. "
"Cannot set 'intData'")));
- using google::protobuf::testing::oneofs::OneOfsRequest;
// JSON:
// { "anyData":
// { "@type":
diff --git a/src/google/protobuf/util/internal/testdata/anys.proto b/src/google/protobuf/util/internal/testdata/anys.proto
index 18c59cbb..a9ebca3d 100644
--- a/src/google/protobuf/util/internal/testdata/anys.proto
+++ b/src/google/protobuf/util/internal/testdata/anys.proto
@@ -28,16 +28,75 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// Proto to test Proto3 Any serialization.
syntax = "proto3";
-package google.protobuf.testing.anys;
-option java_package = "com.google.protobuf.testing.anys";
+package google.protobuf.testing;
import "google/protobuf/any.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/wrappers.proto";
+
+// Top-level test cases proto used by MarshallingTest. See description
+// at the top of the class MarshallingTest for details on how to write
+// test cases.
+message AnyTestCases {
+ AnyWrapper empty_any = 1;
+ AnyWrapper type_only_any = 2;
+ AnyWrapper wrapper_any = 3;
+ AnyWrapper any_with_timestamp_value = 4;
+ AnyWrapper any_with_duration_value = 5;
+ AnyWrapper any_with_struct_value = 6;
+ AnyWrapper recursive_any = 7;
+ AnyWrapper any_with_message_value = 8;
+ AnyWrapper any_with_nested_message = 9;
+ AnyWrapper any_with_message_with_wrapper_type = 10;
+ AnyWrapper any_with_message_with_timestamp = 11;
+ AnyWrapper any_with_message_containing_map = 12;
+ AnyWrapper any_with_message_containing_struct = 13;
+ AnyWrapper any_with_message_containing_repeated_message = 14;
+ AnyWrapper recursive_any_with_type_field_at_end = 15;
+
+ google.protobuf.Any top_level_any = 50;
+ google.protobuf.Any top_level_any_with_type_field_at_end = 51;
+}
+
+message AnyWrapper {
+ google.protobuf.Any any = 1;
+}
+
+// Hack to make sure the types we put into the any are included in the types.
+// Real solution is to add these types to the service config.
+message Imports {
+ google.protobuf.DoubleValue dbl = 1;
+ google.protobuf.Struct struct = 2;
+ google.protobuf.Timestamp timestamp = 3;
+ google.protobuf.Duration duration = 4;
+ google.protobuf.Int32Value i32 = 5;
+ Data data = 100;
+}
+
+message Data {
+ int32 attr = 1;
+ string str = 2;
+ repeated string msgs = 3;
+ Data nested_data = 4;
+ google.protobuf.Int32Value int_wrapper = 5;
+ google.protobuf.Timestamp time = 6;
+ map<string, string> map_data = 7;
+ google.protobuf.Struct struct_data = 8;
+ repeated Data repeated_data = 9;
+}
+
+service AnyTestService {
+ rpc Call(AnyTestCases) returns (AnyTestCases);
+ rpc Call1(Imports) returns (Imports);
+}
message AnyIn {
string something = 1;
+ google.protobuf.Any any = 2;
}
message AnyOut {
@@ -47,7 +106,3 @@ message AnyOut {
message AnyM {
string foo = 1;
}
-
-service TestService {
- rpc Call(AnyIn) returns (AnyOut);
-}
diff --git a/src/google/protobuf/util/internal/testdata/books.proto b/src/google/protobuf/util/internal/testdata/books.proto
index 1cbbba47..9fe4f7aa 100644
--- a/src/google/protobuf/util/internal/testdata/books.proto
+++ b/src/google/protobuf/util/internal/testdata/books.proto
@@ -31,6 +31,10 @@
// Author: sven@google.com (Sven Mawson)
//
// Sample protos for testing.
+
+// Some of the older enums don't use CAPITALS_WITH_UNDERSCORES for testing.
+// LINT: LEGACY_NAMES
+
syntax = "proto2";
package google.protobuf.testing;
@@ -60,6 +64,7 @@ message Book {
FICTION = 1;
KIDS = 2;
ACTION_AND_ADVENTURE = 3;
+ arts_and_photography = 4;
}
optional Type type = 11;
diff --git a/src/google/protobuf/util/internal/testdata/maps.proto b/src/google/protobuf/util/internal/testdata/maps.proto
index 6475ecdd..0f381b32 100644
--- a/src/google/protobuf/util/internal/testdata/maps.proto
+++ b/src/google/protobuf/util/internal/testdata/maps.proto
@@ -28,11 +28,76 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// Proto to test proto3 maps.
syntax = "proto3";
-package google.protobuf.testing.maps;
-option java_package = "com.google.protobuf.testing.maps";
+package google.protobuf.testing;
+
+// Top-level test cases proto used by MarshallingTest. See description
+// at the top of the class MarshallingTest for details on how to write
+// test cases.
+message MapsTestCases {
+ EmptyMap empty_map = 1;
+ StringtoInt string_to_int = 2;
+ IntToString int_to_string = 3;
+ Mixed1 mixed1 = 4;
+ Mixed2 mixed2 = 5;
+ MapOfObjects map_of_objects = 6;
+
+ // Empty key tests
+ StringtoInt empty_key_string_to_int1 = 7;
+ StringtoInt empty_key_string_to_int2 = 8;
+ StringtoInt empty_key_string_to_int3 = 9;
+ BoolToString empty_key_bool_to_string = 10;
+ IntToString empty_key_int_to_string = 11;
+ Mixed1 empty_key_mixed = 12;
+ MapOfObjects empty_key_map_objects = 13;
+}
+
+message EmptyMap {
+ map<int32, int32> map = 1;
+}
+
+message StringtoInt {
+ map<string, int32> map = 1;
+}
+
+message IntToString {
+ map<int32, string> map = 1;
+}
+
+message BoolToString {
+ map<bool, string> map = 1;
+}
+
+message Mixed1 {
+ string msg = 1;
+ map<string, float> map = 2;
+}
+
+message Mixed2 {
+ enum E {
+ E0 = 0;
+ E1 = 1;
+ E2 = 2;
+ E3 = 3;
+ }
+ map<int32, bool> map = 1;
+ E ee = 2;
+}
+
+message MapOfObjects {
+ message M {
+ string inner_text = 1;
+ }
+ map<string, M> map = 1;
+}
+
+message DummyRequest {
+}
+
+service MapsTestService {
+ rpc Call(DummyRequest) returns (MapsTestCases);
+}
message MapIn {
string other = 1;
@@ -79,8 +144,3 @@ message MapOutWireFormat {
message MapM {
string foo = 1;
}
-
-service TestService {
- rpc Call1(MapIn) returns (MapOut);
- rpc Call2(MapIn) returns (MapOut);
-}
diff --git a/src/google/protobuf/util/internal/testdata/oneofs.proto b/src/google/protobuf/util/internal/testdata/oneofs.proto
index 5bc9fa08..c37da083 100644
--- a/src/google/protobuf/util/internal/testdata/oneofs.proto
+++ b/src/google/protobuf/util/internal/testdata/oneofs.proto
@@ -28,7 +28,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// Proto to test oneofs.
+// Proto to test proto3 oneofs.
syntax = "proto3";
import "google/protobuf/any.proto";
@@ -36,7 +36,6 @@ import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";
package google.protobuf.testing.oneofs;
-option java_package = "com.google.protobuf.testing.oneofs";
message OneOfsRequest {
string value = 1;
@@ -45,24 +44,34 @@ message OneOfsRequest {
int32 int_data = 3;
// Simple message
Data message_data = 4;
+ MoreData more_data = 5;
// Well known types
- google.protobuf.Struct struct_data = 5;
- google.protobuf.Value value_data = 6;
- google.protobuf.ListValue list_value_data = 7;
- google.protobuf.Timestamp ts_data = 8;
+ google.protobuf.Struct struct_data = 6;
+ google.protobuf.Value value_data = 7;
+ google.protobuf.ListValue list_value_data = 8;
+ google.protobuf.Timestamp ts_data = 9;
}
google.protobuf.Any any_data = 19;
}
+message RequestWithSimpleOneof {
+ string value = 1;
+ oneof data {
+ string str_data = 2;
+ int32 int_data = 3;
+ Data message_data = 4;
+ MoreData more_data = 5;
+ }
+}
+
message Data {
int32 data_value = 1;
}
-message Response {
- string value = 1;
+message MoreData {
+ string str_value = 1;
}
-service TestService {
- // Test call.
- rpc Call(OneOfsRequest) returns (Response);
+message Response {
+ string value = 1;
}
diff --git a/src/google/protobuf/util/internal/testdata/proto3.proto b/src/google/protobuf/util/internal/testdata/proto3.proto
new file mode 100644
index 00000000..c013cee3
--- /dev/null
+++ b/src/google/protobuf/util/internal/testdata/proto3.proto
@@ -0,0 +1,42 @@
+// 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.
+
+syntax = "proto3";
+
+package google.protobuf.testing;
+
+message Proto3Message {
+ enum NestedEnum {
+ FOO = 0;
+ BAR = 1;
+ BAZ = 2;
+ }
+ NestedEnum enum_value = 1;
+}
diff --git a/src/google/protobuf/util/internal/testdata/struct.proto b/src/google/protobuf/util/internal/testdata/struct.proto
index c15aba0d..7b1cc1b9 100644
--- a/src/google/protobuf/util/internal/testdata/struct.proto
+++ b/src/google/protobuf/util/internal/testdata/struct.proto
@@ -28,18 +28,90 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// Proto to test proto3 struct.
syntax = "proto3";
-package google.protobuf.testing.structs;
-option java_package = "com.google.protobuf.testing.structs";
+package google.protobuf.testing;
import "google/protobuf/struct.proto";
-message StructType {
- google.protobuf.Struct object = 1;
+message StructTestCases {
+ StructWrapper empty_value = 1;
+ StructWrapper empty_value2 = 2;
+ StructWrapper null_value = 3;
+ StructWrapper simple_struct = 4;
+ StructWrapper longer_struct = 5;
+ StructWrapper struct_with_nested_struct = 6;
+ StructWrapper struct_with_nested_list = 7;
+ StructWrapper struct_with_list_of_nulls = 8;
+ StructWrapper struct_with_list_of_lists = 9;
+ StructWrapper struct_with_list_of_structs = 10;
+ StructWrapper struct_with_empty_list = 11;
+ StructWrapper struct_with_list_with_empty_struct = 12;
+ google.protobuf.Struct top_level_struct = 13;
+ google.protobuf.Struct top_level_struct_with_empty_list = 14;
+ google.protobuf.Struct top_level_struct_with_list_with_empty_struct = 15;
+ ValueWrapper value_wrapper_simple = 16;
+ ValueWrapper value_wrapper_with_struct = 17;
+ ValueWrapper value_wrapper_with_list = 18;
+ ValueWrapper value_wrapper_with_empty_list = 19;
+ ValueWrapper value_wrapper_with_list_with_empty_struct = 20;
+ ListValueWrapper list_value_wrapper = 21;
+ ListValueWrapper list_value_wrapper_with_empty_list = 22;
+ ListValueWrapper list_value_wrapper_with_list_with_empty_struct = 23;
+ google.protobuf.Value top_level_value_simple = 24;
+ google.protobuf.Value top_level_value_with_struct = 25;
+ google.protobuf.Value top_level_value_with_list = 26;
+ google.protobuf.Value top_level_value_with_empty_list = 27;
+ google.protobuf.Value top_level_value_with_list_with_empty_struct = 28;
+ google.protobuf.ListValue top_level_listvalue = 29;
+ google.protobuf.ListValue top_level_empty_listvalue = 30;
+ google.protobuf.ListValue top_level_listvalue_with_empty_struct = 31;
+ RepeatedValueWrapper repeated_value = 32;
+ RepeatedValueWrapper repeated_value_nested_list = 33;
+ RepeatedValueWrapper repeated_value_nested_list2 = 34;
+ RepeatedValueWrapper repeated_value_nested_list3 = 35;
+ RepeatedListValueWrapper repeated_listvalue = 36;
+ MapOfStruct map_of_struct = 37;
+ MapOfStruct map_of_struct_value = 38;
+ MapOfStruct map_of_listvalue = 39;
+}
+
+message StructWrapper {
+ google.protobuf.Struct struct = 1;
+}
+
+message ValueWrapper {
+ google.protobuf.Value value = 1;
+}
+
+message RepeatedValueWrapper {
+ repeated google.protobuf.Value values = 1;
+}
+
+message ListValueWrapper {
+ google.protobuf.ListValue shopping_list = 1;
}
-service TestService {
- rpc Call(StructType) returns (StructType);
+message RepeatedListValueWrapper {
+ repeated google.protobuf.ListValue dimensions = 1;
+}
+
+message MapOfStruct {
+ map<string, google.protobuf.Struct> struct_map = 1;
+ map<string, google.protobuf.Value> value_map = 2;
+ map<string, google.protobuf.ListValue> listvalue_map = 3;
+}
+
+// Hack to test return types with Struct as top-level message. Struct typers
+// cannot be directly used in API requests. Hence using Dummy as request type.
+message Dummy {
+ string text = 1;
+}
+
+service StructTestService {
+ rpc Call(Dummy) returns (StructTestCases);
+}
+
+message StructType {
+ google.protobuf.Struct object = 1;
}
diff --git a/src/google/protobuf/util/internal/testdata/timestamp_duration.proto b/src/google/protobuf/util/internal/testdata/timestamp_duration.proto
index 56351f16..b74484ce 100644
--- a/src/google/protobuf/util/internal/testdata/timestamp_duration.proto
+++ b/src/google/protobuf/util/internal/testdata/timestamp_duration.proto
@@ -28,20 +28,53 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// Proto to test proto3 Timestamp and Duration.
syntax = "proto3";
-package google.protobuf.testing.timestampduration;
-option java_package = "com.google.protobuf.testing.timestampduration";
+package google.protobuf.testing;
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
+message TimestampDurationTestCases {
+ // Timestamp tests
+ TimeStampType epoch = 1;
+ TimeStampType epoch2 = 2;
+ TimeStampType mintime = 3;
+ TimeStampType maxtime = 4;
+ TimeStampType timeval1 = 5;
+ TimeStampType timeval2 = 6;
+ TimeStampType timeval3 = 7;
+ TimeStampType timeval4 = 8;
+ TimeStampType timeval5 = 9;
+ TimeStampType timeval6 = 10;
+ TimeStampType timeval7 = 11;
+ google.protobuf.Timestamp timeval8 = 12;
+
+ // Duration tests
+ DurationType zero_duration = 101;
+ DurationType min_duration = 102;
+ DurationType max_duration = 103;
+ DurationType duration1 = 104;
+ DurationType duration2 = 105;
+ DurationType duration3 = 106;
+ DurationType duration4 = 107;
+ google.protobuf.Duration duration5 = 108;
+}
+
+message TimeStampType {
+ google.protobuf.Timestamp timestamp = 1;
+}
+
+message DurationType {
+ google.protobuf.Duration duration = 1;
+}
+
+service TimestampDurationTestService {
+ rpc Call(TimestampDurationTestCases) returns (TimestampDurationTestCases);
+}
+
message TimestampDuration {
google.protobuf.Timestamp ts = 1;
google.protobuf.Duration dur = 2;
-}
-
-service TestService {
- rpc Call(TimestampDuration) returns (TimestampDuration);
+ repeated google.protobuf.Timestamp rep_ts = 3;
}
diff --git a/src/google/protobuf/util/internal/utility.cc b/src/google/protobuf/util/internal/utility.cc
index 5f613e77..9aab3481 100644
--- a/src/google/protobuf/util/internal/utility.cc
+++ b/src/google/protobuf/util/internal/utility.cc
@@ -41,6 +41,8 @@
#include <google/protobuf/stubs/map_util.h>
#include <google/protobuf/stubs/mathlimits.h>
+#include <algorithm>
+
namespace google {
namespace protobuf {
namespace util {
@@ -178,6 +180,19 @@ const google::protobuf::Field* FindJsonFieldInTypeOrNull(
return NULL;
}
+const google::protobuf::Field* FindFieldInTypeByNumberOrNull(
+ const google::protobuf::Type* type, int32 number) {
+ if (type != NULL) {
+ for (int i = 0; i < type->fields_size(); ++i) {
+ const google::protobuf::Field& field = type->fields(i);
+ if (field.number() == number) {
+ return &field;
+ }
+ }
+ }
+ return NULL;
+}
+
const google::protobuf::EnumValue* FindEnumValueByNameOrNull(
const google::protobuf::Enum* enum_type, StringPiece enum_name) {
if (enum_type != NULL) {
@@ -204,6 +219,32 @@ const google::protobuf::EnumValue* FindEnumValueByNumberOrNull(
return NULL;
}
+const google::protobuf::EnumValue* FindEnumValueByNameWithoutUnderscoreOrNull(
+ const google::protobuf::Enum* enum_type, StringPiece enum_name) {
+ if (enum_type != NULL) {
+ for (int i = 0; i < enum_type->enumvalue_size(); ++i) {
+ const google::protobuf::EnumValue& enum_value = enum_type->enumvalue(i);
+ string enum_name_without_underscore = enum_value.name();
+
+ // Remove underscore from the name.
+ enum_name_without_underscore.erase(
+ std::remove(enum_name_without_underscore.begin(),
+ enum_name_without_underscore.end(), '_'),
+ enum_name_without_underscore.end());
+ // Make the name uppercase.
+ for (string::iterator it = enum_name_without_underscore.begin();
+ it != enum_name_without_underscore.end(); ++it) {
+ *it = ascii_toupper(*it);
+ }
+
+ if (enum_name_without_underscore == enum_name) {
+ return &enum_value;
+ }
+ }
+ }
+ return NULL;
+}
+
string ToCamelCase(const StringPiece input) {
bool capitalize_next = false;
bool was_cap = true;
diff --git a/src/google/protobuf/util/internal/utility.h b/src/google/protobuf/util/internal/utility.h
index 26fed444..667e660c 100644
--- a/src/google/protobuf/util/internal/utility.h
+++ b/src/google/protobuf/util/internal/utility.h
@@ -136,6 +136,10 @@ const google::protobuf::Field* FindFieldInTypeOrNull(
const google::protobuf::Field* FindJsonFieldInTypeOrNull(
const google::protobuf::Type* type, StringPiece json_name);
+// Similar to FindFieldInTypeOrNull, but this looks up fields by number.
+const google::protobuf::Field* FindFieldInTypeByNumberOrNull(
+ const google::protobuf::Type* type, int32 number);
+
// Finds and returns the EnumValue identified by enum_name in the passed tech
// Enum object. Returns NULL if none found.
const google::protobuf::EnumValue* FindEnumValueByNameOrNull(
@@ -146,6 +150,13 @@ const google::protobuf::EnumValue* FindEnumValueByNameOrNull(
const google::protobuf::EnumValue* FindEnumValueByNumberOrNull(
const google::protobuf::Enum* enum_type, int32 value);
+// Finds and returns the EnumValue identified by enum_name without underscore in
+// the passed tech Enum object. Returns NULL if none found.
+// For Ex. if enum_name is ACTIONANDADVENTURE it can get accepted if
+// EnumValue's name is action_and_adventure or ACTION_AND_ADVENTURE.
+const google::protobuf::EnumValue* FindEnumValueByNameWithoutUnderscoreOrNull(
+ const google::protobuf::Enum* enum_type, StringPiece enum_name);
+
// Converts input to camel-case and returns it.
LIBPROTOBUF_EXPORT string ToCamelCase(const StringPiece input);
diff --git a/src/google/protobuf/util/message_differencer.h b/src/google/protobuf/util/message_differencer.h
index 654d1a67..fde37cf9 100644
--- a/src/google/protobuf/util/message_differencer.h
+++ b/src/google/protobuf/util/message_differencer.h
@@ -221,19 +221,19 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// Reports that a field has been added into Message2.
virtual void ReportAdded(
const Message& message1, const Message& message2,
- const vector<SpecificField>& field_path) = 0;
+ const std::vector<SpecificField>& field_path) = 0;
// Reports that a field has been deleted from Message1.
virtual void ReportDeleted(
const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path) = 0;
+ const std::vector<SpecificField>& field_path) = 0;
// Reports that the value of a field has been modified.
virtual void ReportModified(
const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path) = 0;
+ const std::vector<SpecificField>& field_path) = 0;
// Reports that a repeated field has been moved to another location. This
// only applies when using TreatAsSet or TreatAsMap() -- see below. Also
@@ -243,7 +243,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
virtual void ReportMoved(
const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path) { }
+ const std::vector<SpecificField>& field_path) { }
// Reports that two fields match. Useful for doing side-by-side diffs.
// This function is mutually exclusive with ReportModified and ReportMoved.
@@ -252,7 +252,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
virtual void ReportMatched(
const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path) { }
+ const std::vector<SpecificField>& field_path) { }
// Reports that two fields would have been compared, but the
// comparison has been skipped because the field was marked as
@@ -276,14 +276,14 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
virtual void ReportIgnored(
const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path) { }
+ const std::vector<SpecificField>& field_path) { }
// Report that an unknown field is ignored. (see comment above).
// Note this is a different function since the last SpecificField in field
// path has a null field. This could break existing Reporter.
virtual void ReportUnknownFieldIgnored(
const Message& message1, const Message& message2,
- const vector<SpecificField>& field_path) {}
+ const std::vector<SpecificField>& field_path) {}
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Reporter);
@@ -296,9 +296,10 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
MapKeyComparator();
virtual ~MapKeyComparator();
- virtual bool IsMatch(const Message& message1,
- const Message& message2,
- const vector<SpecificField>& parent_fields) const {
+ virtual bool IsMatch(
+ const Message& message1,
+ const Message& message2,
+ const std::vector<SpecificField>& parent_fields) const {
GOOGLE_CHECK(false) << "IsMatch() is not implemented.";
return false;
}
@@ -323,7 +324,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
const Message& message1,
const Message& message2,
const FieldDescriptor* field,
- const vector<SpecificField>& parent_fields) = 0;
+ const std::vector<SpecificField>& parent_fields) = 0;
// Returns true if the unknown field should be ignored.
// Note: This will be called for unknown fields as well in which case
@@ -331,7 +332,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
virtual bool IsUnknownFieldIgnored(
const Message& message1, const Message& message2,
const SpecificField& field,
- const vector<SpecificField>& parent_fields) {
+ const std::vector<SpecificField>& parent_fields) {
return false;
}
};
@@ -440,7 +441,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// size of each element.
void TreatAsMapWithMultipleFieldsAsKey(
const FieldDescriptor* field,
- const vector<const FieldDescriptor*>& key_fields);
+ const std::vector<const FieldDescriptor*>& key_fields);
// Same as TreatAsMapWithMultipleFieldsAsKey, except that each of the field
// do not necessarily need to be a direct subfield. Each element in
// key_field_paths indicate a path from the message being compared, listing
@@ -456,7 +457,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// !key_field_path[i]->is_repeated()
void TreatAsMapWithMultipleFieldPathsAsKey(
const FieldDescriptor* field,
- const vector<vector<const FieldDescriptor*> >& key_field_paths);
+ const std::vector<std::vector<const FieldDescriptor*> >& key_field_paths);
// Uses a custom MapKeyComparator to determine if two elements have the same
// key when comparing a repeated field as a map.
@@ -549,9 +550,10 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// Same as above, except comparing only the list of fields specified by the
// two vectors of FieldDescriptors.
- bool CompareWithFields(const Message& message1, const Message& message2,
- const vector<const FieldDescriptor*>& message1_fields,
- const vector<const FieldDescriptor*>& message2_fields);
+ bool CompareWithFields(
+ const Message& message1, const Message& message2,
+ const std::vector<const FieldDescriptor*>& message1_fields,
+ const std::vector<const FieldDescriptor*>& message2_fields);
// Automatically creates a reporter that will output the differences
// found (if any) to the specified output string pointer. Note that this
@@ -591,35 +593,35 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// The following are implementations of the methods described above.
virtual void ReportAdded(const Message& message1, const Message& message2,
- const vector<SpecificField>& field_path);
+ const std::vector<SpecificField>& field_path);
virtual void ReportDeleted(const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path);
+ const std::vector<SpecificField>& field_path);
virtual void ReportModified(const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path);
+ const std::vector<SpecificField>& field_path);
virtual void ReportMoved(const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path);
+ const std::vector<SpecificField>& field_path);
virtual void ReportMatched(const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path);
+ const std::vector<SpecificField>& field_path);
virtual void ReportIgnored(const Message& message1,
const Message& message2,
- const vector<SpecificField>& field_path);
+ const std::vector<SpecificField>& field_path);
virtual void ReportUnknownFieldIgnored(
const Message& message1, const Message& message2,
- const vector<SpecificField>& field_path);
+ const std::vector<SpecificField>& field_path);
protected:
// Prints the specified path of fields to the buffer.
- virtual void PrintPath(const vector<SpecificField>& field_path,
+ virtual void PrintPath(const std::vector<SpecificField>& field_path,
bool left_side);
// Prints the value of fields to the buffer. left_side is true if the
@@ -628,7 +630,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// unknown_field_index1 or unknown_field_index2 when an unknown field
// is encountered in field_path.
virtual void PrintValue(const Message& message,
- const vector<SpecificField>& field_path,
+ const std::vector<SpecificField>& field_path,
bool left_side);
// Prints the specified path of unknown fields to the buffer.
@@ -659,11 +661,11 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// All fields present in both lists will always be included in the combined
// list. Fields only present in one of the lists will only appear in the
// combined list if the corresponding fields_scope option is set to FULL.
- void CombineFields(const vector<const FieldDescriptor*>& fields1,
+ void CombineFields(const std::vector<const FieldDescriptor*>& fields1,
Scope fields1_scope,
- const vector<const FieldDescriptor*>& fields2,
+ const std::vector<const FieldDescriptor*>& fields2,
Scope fields2_scope,
- vector<const FieldDescriptor*>* combined_fields);
+ std::vector<const FieldDescriptor*>* combined_fields);
// Internal version of the Compare method which performs the actual
// comparison. The parent_fields vector is a vector containing field
@@ -671,34 +673,34 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// (i.e. if the current message is an embedded message, the parent_fields
// vector will contain the field that has this embedded message).
bool Compare(const Message& message1, const Message& message2,
- vector<SpecificField>* parent_fields);
+ std::vector<SpecificField>* parent_fields);
// Compares all the unknown fields in two messages.
bool CompareUnknownFields(const Message& message1, const Message& message2,
const google::protobuf::UnknownFieldSet&,
const google::protobuf::UnknownFieldSet&,
- vector<SpecificField>* parent_fields);
+ std::vector<SpecificField>* parent_fields);
// Compares the specified messages for the requested field lists. The field
// lists are modified depending on comparison settings, and then passed to
// CompareWithFieldsInternal.
bool CompareRequestedFieldsUsingSettings(
const Message& message1, const Message& message2,
- const vector<const FieldDescriptor*>& message1_fields,
- const vector<const FieldDescriptor*>& message2_fields,
- vector<SpecificField>* parent_fields);
+ const std::vector<const FieldDescriptor*>& message1_fields,
+ const std::vector<const FieldDescriptor*>& message2_fields,
+ std::vector<SpecificField>* parent_fields);
// Compares the specified messages with the specified field lists.
bool CompareWithFieldsInternal(
const Message& message1, const Message& message2,
- const vector<const FieldDescriptor*>& message1_fields,
- const vector<const FieldDescriptor*>& message2_fields,
- vector<SpecificField>* parent_fields);
+ const std::vector<const FieldDescriptor*>& message1_fields,
+ const std::vector<const FieldDescriptor*>& message2_fields,
+ std::vector<SpecificField>* parent_fields);
// Compares the repeated fields, and report the error.
bool CompareRepeatedField(const Message& message1, const Message& message2,
const FieldDescriptor* field,
- vector<SpecificField>* parent_fields);
+ std::vector<SpecificField>* parent_fields);
// Shorthand for CompareFieldValueUsingParentFields with NULL parent_fields.
bool CompareFieldValue(const Message& message1,
@@ -716,12 +718,13 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// list of parent messages if it needs to recursively compare the given field.
// To avoid confusing users you should not set it to NULL unless you modified
// Reporter to handle the change of parent_fields correctly.
- bool CompareFieldValueUsingParentFields(const Message& message1,
- const Message& message2,
- const FieldDescriptor* field,
- int index1,
- int index2,
- vector<SpecificField>* parent_fields);
+ bool CompareFieldValueUsingParentFields(
+ const Message& message1,
+ const Message& message2,
+ const FieldDescriptor* field,
+ int index1,
+ int index2,
+ std::vector<SpecificField>* parent_fields);
// Compares the specified field on the two messages, returning comparison
// result, as returned by appropriate FieldComparator.
@@ -736,7 +739,7 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
bool IsMatch(const FieldDescriptor* repeated_field,
const MapKeyComparator* key_comparator,
const Message* message1, const Message* message2,
- const vector<SpecificField>& parent_fields,
+ const std::vector<SpecificField>& parent_fields,
int index1, int index2);
// Returns true when this repeated field has been configured to be treated
@@ -754,13 +757,13 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
const Message& message1,
const Message& message2,
const FieldDescriptor* field,
- const vector<SpecificField>& parent_fields);
+ const std::vector<SpecificField>& parent_fields);
// Returns true if this unknown field is to be ignored when this
// MessageDifferencer compares messages.
bool IsUnknownFieldIgnored(const Message& message1, const Message& message2,
const SpecificField& field,
- const vector<SpecificField>& parent_fields);
+ const std::vector<SpecificField>& parent_fields);
// Returns MapKeyComparator* when this field has been configured to
// be treated as a map. If not, returns NULL.
@@ -773,28 +776,29 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// This method returns false if the match failed. However, it doesn't mean
// that the comparison succeeds when this method returns true (you need to
// double-check in this case).
- bool MatchRepeatedFieldIndices(const Message& message1,
- const Message& message2,
- const FieldDescriptor* repeated_field,
- const vector<SpecificField>& parent_fields,
- vector<int>* match_list1,
- vector<int>* match_list2);
+ bool MatchRepeatedFieldIndices(
+ const Message& message1,
+ const Message& message2,
+ const FieldDescriptor* repeated_field,
+ const std::vector<SpecificField>& parent_fields,
+ std::vector<int>* match_list1,
+ std::vector<int>* match_list2);
// If "any" is of type google.protobuf.Any, extract its payload using
// DynamicMessageFactory and store in "data".
bool UnpackAny(const Message& any, google::protobuf::scoped_ptr<Message>* data);
// Checks if index is equal to new_index in all the specific fields.
- static bool CheckPathChanged(const vector<SpecificField>& parent_fields);
+ static bool CheckPathChanged(const std::vector<SpecificField>& parent_fields);
// Defines a map between field descriptors and their MapKeyComparators.
// Used for repeated fields when they are configured as TreatAsMap.
- typedef map<const FieldDescriptor*,
+ typedef std::map<const FieldDescriptor*,
const MapKeyComparator*> FieldKeyComparatorMap;
// Defines a set to store field descriptors. Used for repeated fields when
// they are configured as TreatAsSet.
- typedef set<const FieldDescriptor*> FieldSet;
+ typedef std::set<const FieldDescriptor*> FieldSet;
Reporter* reporter_;
DefaultFieldComparator default_field_comparator_;
@@ -811,9 +815,9 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// When TreatAsMap or TreatAsMapWithMultipleFieldsAsKey is called, we don't
// store the supplied FieldDescriptors directly. Instead, a new
// MapKeyComparator is created for comparison purpose.
- vector<MapKeyComparator*> owned_key_comparators_;
+ std::vector<MapKeyComparator*> owned_key_comparators_;
FieldKeyComparatorMap map_field_key_comparator_;
- vector<IgnoreCriteria*> ignore_criteria_;
+ std::vector<IgnoreCriteria*> ignore_criteria_;
FieldSet ignored_fields_;
@@ -831,15 +835,15 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
class LIBPROTOBUF_EXPORT FieldContext {
public:
explicit FieldContext(
- vector<MessageDifferencer::SpecificField>* parent_fields)
+ std::vector<MessageDifferencer::SpecificField>* parent_fields)
: parent_fields_(parent_fields) {}
- vector<MessageDifferencer::SpecificField>* parent_fields() const {
+ std::vector<MessageDifferencer::SpecificField>* parent_fields() const {
return parent_fields_;
}
private:
- vector<MessageDifferencer::SpecificField>* parent_fields_;
+ std::vector<MessageDifferencer::SpecificField>* parent_fields_;
};
}