diff options
Diffstat (limited to 'NorthstarDedicatedTest/include/protobuf/util')
77 files changed, 31813 insertions, 0 deletions
diff --git a/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util.cc b/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util.cc new file mode 100644 index 00000000..1efa52a7 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util.cc @@ -0,0 +1,127 @@ +// 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. + +// Adapted from the patch of kenton@google.com (Kenton Varda) +// See https://github.com/protocolbuffers/protobuf/pull/710 for details. + +#include <util/delimited_message_util.h> +#include <io/coded_stream.h> + +namespace google { +namespace protobuf { +namespace util { + +bool SerializeDelimitedToFileDescriptor(const MessageLite& message, + int file_descriptor) { + io::FileOutputStream output(file_descriptor); + return SerializeDelimitedToZeroCopyStream(message, &output); +} + +bool SerializeDelimitedToOstream(const MessageLite& message, + std::ostream* output) { + { + io::OstreamOutputStream zero_copy_output(output); + if (!SerializeDelimitedToZeroCopyStream(message, &zero_copy_output)) + return false; + } + return output->good(); +} + +bool ParseDelimitedFromZeroCopyStream(MessageLite* message, + io::ZeroCopyInputStream* input, + bool* clean_eof) { + io::CodedInputStream coded_input(input); + return ParseDelimitedFromCodedStream(message, &coded_input, clean_eof); +} + +bool ParseDelimitedFromCodedStream(MessageLite* message, + io::CodedInputStream* input, + bool* clean_eof) { + if (clean_eof != NULL) *clean_eof = false; + int start = input->CurrentPosition(); + + // Read the size. + uint32 size; + if (!input->ReadVarint32(&size)) { + if (clean_eof != NULL) *clean_eof = input->CurrentPosition() == start; + return false; + } + + // Get the position after any size bytes have been read (and only the message + // itself remains). + int position_after_size = input->CurrentPosition(); + + // Tell the stream not to read beyond that size. + io::CodedInputStream::Limit limit = input->PushLimit(size); + + // Parse the message. + if (!message->MergeFromCodedStream(input)) return false; + if (!input->ConsumedEntireMessage()) return false; + if (input->CurrentPosition() - position_after_size != static_cast<int>(size)) + return false; + + // Release the limit. + input->PopLimit(limit); + + return true; +} + +bool SerializeDelimitedToZeroCopyStream(const MessageLite& message, + io::ZeroCopyOutputStream* output) { + io::CodedOutputStream coded_output(output); + return SerializeDelimitedToCodedStream(message, &coded_output); +} + +bool SerializeDelimitedToCodedStream(const MessageLite& message, + io::CodedOutputStream* output) { + // Write the size. + size_t size = message.ByteSizeLong(); + if (size > INT_MAX) return false; + + output->WriteVarint32(size); + + // Write the content. + uint8* buffer = output->GetDirectBufferForNBytesAndAdvance(size); + if (buffer != NULL) { + // Optimization: The message fits in one buffer, so use the faster + // direct-to-array serialization path. + message.SerializeWithCachedSizesToArray(buffer); + } else { + // Slightly-slower path when the message is multiple buffers. + message.SerializeWithCachedSizes(output); + if (output->HadError()) return false; + } + + return true; +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util.h b/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util.h new file mode 100644 index 00000000..2279151d --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util.h @@ -0,0 +1,108 @@ +// 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. + +// Adapted from the patch of kenton@google.com (Kenton Varda) +// See https://github.com/protocolbuffers/protobuf/pull/710 for details. + +#ifndef GOOGLE_PROTOBUF_UTIL_DELIMITED_MESSAGE_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_DELIMITED_MESSAGE_UTIL_H__ + + +#include <ostream> + +#include <message_lite.h> +#include <io/coded_stream.h> +#include <io/zero_copy_stream_impl.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { + +// Write a single size-delimited message from the given stream. Delimited +// format allows a single file or stream to contain multiple messages, +// whereas normally writing multiple non-delimited messages to the same +// stream would cause them to be merged. A delimited message is a varint +// encoding the message size followed by a message of exactly that size. +// +// Note that if you want to *read* a delimited message from a file descriptor +// or istream, you will need to construct an io::FileInputStream or +// io::OstreamInputStream (implementations of io::ZeroCopyStream) and use the +// utility function ParseDelimitedFromZeroCopyStream(). You must then +// continue to use the same ZeroCopyInputStream to read all further data from +// the stream until EOF. This is because these ZeroCopyInputStream +// implementations are buffered: they read a big chunk of data at a time, +// then parse it. As a result, they may read past the end of the delimited +// message. There is no way for them to push the extra data back into the +// underlying source, so instead you must keep using the same stream object. +bool PROTOBUF_EXPORT SerializeDelimitedToFileDescriptor( + const MessageLite& message, int file_descriptor); + +bool PROTOBUF_EXPORT SerializeDelimitedToOstream(const MessageLite& message, + std::ostream* output); + +// Read a single size-delimited message from the given stream. Delimited +// format allows a single file or stream to contain multiple messages, +// whereas normally parsing consumes the entire input. A delimited message +// is a varint encoding the message size followed by a message of exactly +// that size. +// +// If |clean_eof| is not NULL, then it will be set to indicate whether the +// stream ended cleanly. That is, if the stream ends without this method +// having read any data at all from it, then *clean_eof will be set true, +// otherwise it will be set false. Note that these methods return false +// on EOF, but they also return false on other errors, so |clean_eof| is +// needed to distinguish a clean end from errors. +bool PROTOBUF_EXPORT ParseDelimitedFromZeroCopyStream( + MessageLite* message, io::ZeroCopyInputStream* input, bool* clean_eof); + +bool PROTOBUF_EXPORT ParseDelimitedFromCodedStream(MessageLite* message, + io::CodedInputStream* input, + bool* clean_eof); + +// Write a single size-delimited message from the given stream. Delimited +// format allows a single file or stream to contain multiple messages, +// whereas normally writing multiple non-delimited messages to the same +// stream would cause them to be merged. A delimited message is a varint +// encoding the message size followed by a message of exactly that size. +bool PROTOBUF_EXPORT SerializeDelimitedToZeroCopyStream( + const MessageLite& message, io::ZeroCopyOutputStream* output); + +bool PROTOBUF_EXPORT SerializeDelimitedToCodedStream( + const MessageLite& message, io::CodedOutputStream* output); + +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_DELIMITED_MESSAGE_UTIL_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util_test.cc b/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util_test.cc new file mode 100644 index 00000000..52cb1091 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/delimited_message_util_test.cc @@ -0,0 +1,116 @@ +// 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. + +// Adapted from the patch of kenton@google.com (Kenton Varda) +// See https://github.com/protocolbuffers/protobuf/pull/710 for details. + +#include <util/delimited_message_util.h> + +#include <sstream> + +#include <test_util.h> +#include <unittest.pb.h> +#include <testing/googletest.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { + +TEST(DelimitedMessageUtilTest, DelimitedMessages) { + std::stringstream stream; + + { + protobuf_unittest::TestAllTypes message1; + TestUtil::SetAllFields(&message1); + EXPECT_TRUE(SerializeDelimitedToOstream(message1, &stream)); + + protobuf_unittest::TestPackedTypes message2; + TestUtil::SetPackedFields(&message2); + EXPECT_TRUE(SerializeDelimitedToOstream(message2, &stream)); + } + + { + bool clean_eof; + io::IstreamInputStream zstream(&stream); + + protobuf_unittest::TestAllTypes message1; + clean_eof = true; + EXPECT_TRUE(ParseDelimitedFromZeroCopyStream(&message1, + &zstream, &clean_eof)); + EXPECT_FALSE(clean_eof); + TestUtil::ExpectAllFieldsSet(message1); + + protobuf_unittest::TestPackedTypes message2; + clean_eof = true; + EXPECT_TRUE(ParseDelimitedFromZeroCopyStream(&message2, + &zstream, &clean_eof)); + EXPECT_FALSE(clean_eof); + TestUtil::ExpectPackedFieldsSet(message2); + + clean_eof = false; + EXPECT_FALSE(ParseDelimitedFromZeroCopyStream(&message2, + &zstream, &clean_eof)); + EXPECT_TRUE(clean_eof); + } +} + +TEST(DelimitedMessageUtilTest, FailsAtEndOfStream) { + std::stringstream full_stream; + std::stringstream partial_stream; + + { + protobuf_unittest::ForeignMessage message; + message.set_c(42); + message.set_d(24); + EXPECT_TRUE(SerializeDelimitedToOstream(message, &full_stream)); + + std::string full_output = full_stream.str(); + ASSERT_GT(full_output.size(), size_t{2}); + ASSERT_EQ(full_output[0], 4); + + partial_stream << full_output[0] << full_output[1] << full_output[2]; + } + + { + bool clean_eof; + io::IstreamInputStream zstream(&partial_stream); + + protobuf_unittest::ForeignMessage message; + clean_eof = true; + EXPECT_FALSE(ParseDelimitedFromZeroCopyStream(&message, + &zstream, &clean_eof)); + EXPECT_FALSE(clean_eof); + } +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/field_comparator.cc b/NorthstarDedicatedTest/include/protobuf/util/field_comparator.cc new file mode 100644 index 00000000..596edd81 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/field_comparator.cc @@ -0,0 +1,210 @@ +// 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. + +// Author: ksroka@google.com (Krzysztof Sroka) + +#include <util/field_comparator.h> + +#include <limits> +#include <string> + +#include <descriptor.h> +#include <message.h> +#include <util/message_differencer.h> +#include <stubs/map_util.h> +#include <stubs/mathutil.h> + +namespace google { +namespace protobuf { +namespace util { + +FieldComparator::FieldComparator() {} +FieldComparator::~FieldComparator() {} + +SimpleFieldComparator::SimpleFieldComparator() + : float_comparison_(EXACT), + treat_nan_as_equal_(false), + has_default_tolerance_(false) {} + +SimpleFieldComparator::~SimpleFieldComparator() {} + +FieldComparator::ComparisonResult SimpleFieldComparator::SimpleCompare( + const Message& message_1, const Message& message_2, + const FieldDescriptor* field, int index_1, int index_2, + const util::FieldContext* /*field_context*/) { + const Reflection* reflection_1 = message_1.GetReflection(); + const Reflection* reflection_2 = message_2.GetReflection(); + + switch (field->cpp_type()) { +#define COMPARE_FIELD(METHOD) \ + if (field->is_repeated()) { \ + return ResultFromBoolean(Compare##METHOD( \ + *field, reflection_1->GetRepeated##METHOD(message_1, field, index_1), \ + reflection_2->GetRepeated##METHOD(message_2, field, index_2))); \ + } else { \ + return ResultFromBoolean( \ + Compare##METHOD(*field, reflection_1->Get##METHOD(message_1, field), \ + reflection_2->Get##METHOD(message_2, field))); \ + } \ + break; // Make sure no fall-through is introduced. + + case FieldDescriptor::CPPTYPE_BOOL: + COMPARE_FIELD(Bool); + case FieldDescriptor::CPPTYPE_DOUBLE: + COMPARE_FIELD(Double); + case FieldDescriptor::CPPTYPE_ENUM: + COMPARE_FIELD(Enum); + case FieldDescriptor::CPPTYPE_FLOAT: + COMPARE_FIELD(Float); + case FieldDescriptor::CPPTYPE_INT32: + COMPARE_FIELD(Int32); + case FieldDescriptor::CPPTYPE_INT64: + COMPARE_FIELD(Int64); + case FieldDescriptor::CPPTYPE_STRING: + if (field->is_repeated()) { + // Allocate scratch strings to store the result if a conversion is + // needed. + std::string scratch1; + std::string scratch2; + return ResultFromBoolean( + CompareString(*field, + reflection_1->GetRepeatedStringReference( + message_1, field, index_1, &scratch1), + reflection_2->GetRepeatedStringReference( + message_2, field, index_2, &scratch2))); + } else { + // Allocate scratch strings to store the result if a conversion is + // needed. + std::string scratch1; + std::string scratch2; + return ResultFromBoolean(CompareString( + *field, + reflection_1->GetStringReference(message_1, field, &scratch1), + reflection_2->GetStringReference(message_2, field, &scratch2))); + } + break; + case FieldDescriptor::CPPTYPE_UINT32: + COMPARE_FIELD(UInt32); + case FieldDescriptor::CPPTYPE_UINT64: + COMPARE_FIELD(UInt64); + +#undef COMPARE_FIELD + + case FieldDescriptor::CPPTYPE_MESSAGE: + return RECURSE; + + default: + GOOGLE_LOG(FATAL) << "No comparison code for field " << field->full_name() + << " of CppType = " << field->cpp_type(); + return DIFFERENT; + } +} + +bool SimpleFieldComparator::CompareWithDifferencer( + MessageDifferencer* differencer, const Message& message1, + const Message& message2, const util::FieldContext* field_context) { + return differencer->Compare(message1, message2, + field_context->parent_fields()); +} + +void SimpleFieldComparator::SetDefaultFractionAndMargin(double fraction, + double margin) { + default_tolerance_ = Tolerance(fraction, margin); + has_default_tolerance_ = true; +} + +void SimpleFieldComparator::SetFractionAndMargin(const FieldDescriptor* field, + double fraction, + double margin) { + GOOGLE_CHECK(FieldDescriptor::CPPTYPE_FLOAT == field->cpp_type() || + FieldDescriptor::CPPTYPE_DOUBLE == field->cpp_type()) + << "Field has to be float or double type. Field name is: " + << field->full_name(); + map_tolerance_[field] = Tolerance(fraction, margin); +} + +bool SimpleFieldComparator::CompareDouble(const FieldDescriptor& field, + double value_1, double value_2) { + return CompareDoubleOrFloat(field, value_1, value_2); +} + +bool SimpleFieldComparator::CompareEnum(const FieldDescriptor& /*field*/, + const EnumValueDescriptor* value_1, + const EnumValueDescriptor* value_2) { + return value_1->number() == value_2->number(); +} + +bool SimpleFieldComparator::CompareFloat(const FieldDescriptor& field, + float value_1, float value_2) { + return CompareDoubleOrFloat(field, value_1, value_2); +} + +template <typename T> +bool SimpleFieldComparator::CompareDoubleOrFloat(const FieldDescriptor& field, + T value_1, T value_2) { + if (value_1 == value_2) { + // Covers +inf and -inf (which are not within margin or fraction of + // themselves), and is a shortcut for finite values. + return true; + } else if (float_comparison_ == EXACT) { + if (treat_nan_as_equal_ && std::isnan(value_1) && std::isnan(value_2)) { + return true; + } + return false; + } else { + if (treat_nan_as_equal_ && std::isnan(value_1) && std::isnan(value_2)) { + return true; + } + // float_comparison_ == APPROXIMATE covers two use cases. + Tolerance* tolerance = FindOrNull(map_tolerance_, &field); + if (tolerance == NULL && has_default_tolerance_) { + tolerance = &default_tolerance_; + } + if (tolerance == NULL) { + return MathUtil::AlmostEquals(value_1, value_2); + } else { + // Use user-provided fraction and margin. Since they are stored as + // doubles, we explicitly cast them to types of values provided. This + // is very likely to fail if provided values are not numeric. + return MathUtil::WithinFractionOrMargin( + value_1, value_2, static_cast<T>(tolerance->fraction), + static_cast<T>(tolerance->margin)); + } + } +} + +FieldComparator::ComparisonResult SimpleFieldComparator::ResultFromBoolean( + bool boolean_result) const { + return boolean_result ? FieldComparator::SAME : FieldComparator::DIFFERENT; +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/field_comparator.h b/NorthstarDedicatedTest/include/protobuf/util/field_comparator.h new file mode 100644 index 00000000..f52e2485 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/field_comparator.h @@ -0,0 +1,285 @@ +// 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. + +// Defines classes for field comparison. + +#ifndef GOOGLE_PROTOBUF_UTIL_FIELD_COMPARATOR_H__ +#define GOOGLE_PROTOBUF_UTIL_FIELD_COMPARATOR_H__ + +#include <cstdint> +#include <map> +#include <string> +#include <vector> + +#include <stubs/common.h> +#include <port_def.inc> + +namespace google { +namespace protobuf { + +class Message; +class EnumValueDescriptor; +class FieldDescriptor; + +namespace util { + +class FieldContext; +class MessageDifferencer; + +// Base class specifying the interface for comparing protocol buffer fields. +// Regular users should consider using or subclassing DefaultFieldComparator +// rather than this interface. +// Currently, this does not support comparing unknown fields. +class PROTOBUF_EXPORT FieldComparator { + public: + FieldComparator(); + virtual ~FieldComparator(); + + enum ComparisonResult { + SAME, // Compared fields are equal. In case of comparing submessages, + // user should not recursively compare their contents. + DIFFERENT, // Compared fields are different. In case of comparing + // submessages, user should not recursively compare their + // contents. + RECURSE, // Compared submessages need to be compared recursively. + // FieldComparator does not specify the semantics of recursive + // comparison. This value should not be returned for simple + // values. + }; + + // Compares the values of a field in two protocol buffer messages. + // Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE + // for submessages. Returning RECURSE for fields not being submessages is + // illegal. + // In case the given FieldDescriptor points to a repeated field, the indices + // need to be valid. Otherwise they should be ignored. + // + // FieldContext contains information about the specific instances of the + // fields being compared, versus FieldDescriptor which only contains general + // type information about the fields. + virtual ComparisonResult Compare(const Message& message_1, + const Message& message_2, + const FieldDescriptor* field, int index_1, + int index_2, + const util::FieldContext* field_context) = 0; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FieldComparator); +}; + +// Basic implementation of FieldComparator. Supports three modes of floating +// point value comparison: exact, approximate using MathUtil::AlmostEqual +// method, and arbitrarily precise using MathUtil::WithinFractionOrMargin. +class PROTOBUF_EXPORT SimpleFieldComparator : public FieldComparator { + public: + enum FloatComparison { + EXACT, // Floats and doubles are compared exactly. + APPROXIMATE, // Floats and doubles are compared using the + // MathUtil::AlmostEqual method or + // MathUtil::WithinFractionOrMargin method. + // TODO(ksroka): Introduce third value to differentiate uses of AlmostEqual + // and WithinFractionOrMargin. + }; + + // Creates new comparator with float comparison set to EXACT. + SimpleFieldComparator(); + + ~SimpleFieldComparator() override; + + void set_float_comparison(FloatComparison float_comparison) { + float_comparison_ = float_comparison; + } + + FloatComparison float_comparison() const { return float_comparison_; } + + // Set whether the FieldComparator shall treat floats or doubles that are both + // NaN as equal (treat_nan_as_equal = true) or as different + // (treat_nan_as_equal = false). Default is treating NaNs always as different. + void set_treat_nan_as_equal(bool treat_nan_as_equal) { + treat_nan_as_equal_ = treat_nan_as_equal; + } + + bool treat_nan_as_equal() const { return treat_nan_as_equal_; } + + // Sets the fraction and margin for the float comparison of a given field. + // Uses MathUtil::WithinFractionOrMargin to compare the values. + // + // REQUIRES: field->cpp_type == FieldDescriptor::CPPTYPE_DOUBLE or + // field->cpp_type == FieldDescriptor::CPPTYPE_FLOAT + // REQUIRES: float_comparison_ == APPROXIMATE + void SetFractionAndMargin(const FieldDescriptor* field, double fraction, + double margin); + + // Sets the fraction and margin for the float comparison of all float and + // double fields, unless a field has been given a specific setting via + // SetFractionAndMargin() above. + // Uses MathUtil::WithinFractionOrMargin to compare the values. + // + // REQUIRES: float_comparison_ == APPROXIMATE + void SetDefaultFractionAndMargin(double fraction, double margin); + + protected: + // Returns the comparison result for the given field in two messages. + // + // This function is called directly by DefaultFieldComparator::Compare. + // Subclasses can call this function to compare fields they do not need to + // handle specially. + ComparisonResult SimpleCompare(const Message& message_1, + const Message& message_2, + const FieldDescriptor* field, int index_1, + int index_2, + const util::FieldContext* field_context); + + // Compare using the provided message_differencer. For example, a subclass can + // use this method to compare some field in a certain way using the same + // message_differencer instance and the field context. + bool CompareWithDifferencer(MessageDifferencer* differencer, + const Message& message1, const Message& message2, + const util::FieldContext* field_context); + + // Returns FieldComparator::SAME if boolean_result is true and + // FieldComparator::DIFFERENT otherwise. + ComparisonResult ResultFromBoolean(bool boolean_result) const; + + private: + // Defines the tolerance for floating point comparison (fraction and margin). + struct Tolerance { + double fraction; + double margin; + Tolerance() : fraction(0.0), margin(0.0) {} + Tolerance(double f, double m) : fraction(f), margin(m) {} + }; + + // Defines the map to store the tolerances for floating point comparison. + typedef std::map<const FieldDescriptor*, Tolerance> ToleranceMap; + + friend class MessageDifferencer; + // The following methods get executed when CompareFields is called for the + // basic types (instead of submessages). They return true on success. One + // can use ResultFromBoolean() to convert that boolean to a ComparisonResult + // value. + bool CompareBool(const FieldDescriptor& /* unused */, bool value_1, + bool value_2) { + return value_1 == value_2; + } + + // Uses CompareDoubleOrFloat, a helper function used by both CompareDouble and + // CompareFloat. + bool CompareDouble(const FieldDescriptor& field, double value_1, + double value_2); + + bool CompareEnum(const FieldDescriptor& field, + const EnumValueDescriptor* value_1, + const EnumValueDescriptor* value_2); + + // Uses CompareDoubleOrFloat, a helper function used by both CompareDouble and + // CompareFloat. + bool CompareFloat(const FieldDescriptor& field, float value_1, float value_2); + + bool CompareInt32(const FieldDescriptor& /* unused */, int32_t value_1, + int32_t value_2) { + return value_1 == value_2; + } + + bool CompareInt64(const FieldDescriptor& /* unused */, int64_t value_1, + int64_t value_2) { + return value_1 == value_2; + } + + bool CompareString(const FieldDescriptor& /* unused */, + const std::string& value_1, const std::string& value_2) { + return value_1 == value_2; + } + + bool CompareUInt32(const FieldDescriptor& /* unused */, uint32_t value_1, + uint32_t value_2) { + return value_1 == value_2; + } + + bool CompareUInt64(const FieldDescriptor& /* unused */, uint64_t value_1, + uint64_t value_2) { + return value_1 == value_2; + } + + // This function is used by CompareDouble and CompareFloat to avoid code + // duplication. There are no checks done against types of the values passed, + // but it's likely to fail if passed non-numeric arguments. + template <typename T> + bool CompareDoubleOrFloat(const FieldDescriptor& field, T value_1, T value_2); + + FloatComparison float_comparison_; + + // If true, floats and doubles that are both NaN are considered to be + // equal. Otherwise, two floats or doubles that are NaN are considered to be + // different. + bool treat_nan_as_equal_; + + // True iff default_tolerance_ has been explicitly set. + // + // If false, then the default tolerance for floats and doubles is that which + // is used by MathUtil::AlmostEquals(). + bool has_default_tolerance_; + + // Default float/double tolerance. Only meaningful if + // has_default_tolerance_ == true. + Tolerance default_tolerance_; + + // Field-specific float/double tolerances, which override any default for + // those particular fields. + ToleranceMap map_tolerance_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(SimpleFieldComparator); +}; + +// Default field comparison: use the basic implementation of FieldComparator. +#ifdef PROTOBUF_FUTURE_BREAKING_CHANGES +class PROTOBUF_EXPORT DefaultFieldComparator final + : public SimpleFieldComparator +#else // PROTOBUF_FUTURE_BREAKING_CHANGES +class PROTOBUF_EXPORT DefaultFieldComparator : public SimpleFieldComparator +#endif // PROTOBUF_FUTURE_BREAKING_CHANGES +{ + public: + ComparisonResult Compare(const Message& message_1, const Message& message_2, + const FieldDescriptor* field, int index_1, + int index_2, + const util::FieldContext* field_context) override { + return SimpleCompare(message_1, message_2, field, index_1, index_2, + field_context); + } +}; + +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_FIELD_COMPARATOR_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/field_comparator_test.cc b/NorthstarDedicatedTest/include/protobuf/util/field_comparator_test.cc new file mode 100644 index 00000000..611b4940 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/field_comparator_test.cc @@ -0,0 +1,495 @@ +// 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. + +// Author: ksroka@google.com (Krzysztof Sroka) + +#include <util/field_comparator.h> + +#include <limits> + +#include <unittest.pb.h> +#include <descriptor.h> +#include <gtest/gtest.h> +#include <stubs/mathutil.h> + +namespace google { +namespace protobuf { +namespace util { +namespace { + +using protobuf_unittest::TestAllTypes; + +class DefaultFieldComparatorTest : public ::testing::Test { + protected: + void SetUp() { descriptor_ = TestAllTypes::descriptor(); } + + const Descriptor* descriptor_; + DefaultFieldComparator comparator_; + TestAllTypes message_1_; + TestAllTypes message_2_; +}; + +TEST_F(DefaultFieldComparatorTest, RecursesIntoGroup) { + const FieldDescriptor* field = descriptor_->FindFieldByName("optionalgroup"); + EXPECT_EQ(FieldComparator::RECURSE, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, RecursesIntoNestedMessage) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_nested_message"); + EXPECT_EQ(FieldComparator::RECURSE, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, RecursesIntoForeignMessage) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_foreign_message"); + EXPECT_EQ(FieldComparator::RECURSE, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, Int32Comparison) { + const FieldDescriptor* field = descriptor_->FindFieldByName("optional_int32"); + message_1_.set_optional_int32(1); + message_2_.set_optional_int32(1); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_int32(-1); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, Int64Comparison) { + const FieldDescriptor* field = descriptor_->FindFieldByName("optional_int64"); + message_1_.set_optional_int64(1L); + message_2_.set_optional_int64(1L); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_int64(-1L); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, UInt32Comparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_uint32"); + message_1_.set_optional_uint32(1); + message_2_.set_optional_uint32(1); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_uint32(2); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, UInt64Comparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_uint64"); + message_1_.set_optional_uint64(1L); + message_2_.set_optional_uint64(1L); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_uint64(2L); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, BooleanComparison) { + const FieldDescriptor* field = descriptor_->FindFieldByName("optional_bool"); + message_1_.set_optional_bool(true); + message_2_.set_optional_bool(true); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_bool(false); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, EnumComparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_nested_enum"); + message_1_.set_optional_nested_enum(TestAllTypes::BAR); + message_2_.set_optional_nested_enum(TestAllTypes::BAR); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_nested_enum(TestAllTypes::BAZ); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, StringComparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_string"); + message_1_.set_optional_string("foo"); + message_2_.set_optional_string("foo"); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_string("bar"); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, FloatingPointComparisonExact) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(0.1f); + message_2_.set_optional_float(0.1f); + message_1_.set_optional_double(0.1); + message_2_.set_optional_double(0.1); + + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + message_2_.set_optional_float(0.2f); + message_2_.set_optional_double(0.2); + + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, FloatingPointComparisonApproximate) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(2.300005f); + message_2_.set_optional_float(2.300006f); + message_1_.set_optional_double(2.3000000000000003); + message_2_.set_optional_double(2.3000000000000007); + + // Approximate comparison depends on MathUtil, so we assert on MathUtil + // results first to check if that's where the failure was introduced. + ASSERT_NE(message_1_.optional_float(), message_2_.optional_float()); + ASSERT_NE(message_1_.optional_double(), message_2_.optional_double()); + ASSERT_TRUE(MathUtil::AlmostEquals(message_1_.optional_float(), + message_2_.optional_float())); + ASSERT_TRUE(MathUtil::AlmostEquals(message_1_.optional_double(), + message_2_.optional_double())); + + // DefaultFieldComparator's default float comparison mode is EXACT. + ASSERT_EQ(DefaultFieldComparator::EXACT, comparator_.float_comparison()); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, FloatingPointComparisonTreatNaNsAsEqual) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(std::numeric_limits<float>::quiet_NaN()); + message_2_.set_optional_float(std::numeric_limits<float>::quiet_NaN()); + message_1_.set_optional_double(std::numeric_limits<double>::quiet_NaN()); + message_2_.set_optional_double(std::numeric_limits<double>::quiet_NaN()); + + // DefaultFieldComparator's default float comparison mode is EXACT with + // treating NaNs as different. + ASSERT_EQ(DefaultFieldComparator::EXACT, comparator_.float_comparison()); + ASSERT_EQ(false, comparator_.treat_nan_as_equal()); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + comparator_.set_treat_nan_as_equal(true); + ASSERT_EQ(true, comparator_.treat_nan_as_equal()); + comparator_.set_float_comparison(DefaultFieldComparator::EXACT); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, + FloatingPointComparisonWithinFractionOrMargin) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(100.0f); + message_2_.set_optional_float(109.9f); + message_1_.set_optional_double(100.0); + message_2_.set_optional_double(109.9); + + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Should fail since the fraction is too low. + comparator_.SetFractionAndMargin(field_float, 0.01, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.01, 0.0); + + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Should fail since the margin is too low. + comparator_.SetFractionAndMargin(field_float, 0.0, 9.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 9.0); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Should succeed since the fraction is high enough. + comparator_.SetFractionAndMargin(field_float, 0.2, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.2, 0.0); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Should succeed since the margin is high enough. + comparator_.SetFractionAndMargin(field_float, 0.0, 10.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 10.0); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Setting values for one of the fields should not affect the other. + comparator_.SetFractionAndMargin(field_double, 0.0, 0.0); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // +inf should be equal even though they are not technically within margin or + // fraction. + message_1_.set_optional_float(std::numeric_limits<float>::infinity()); + message_2_.set_optional_float(std::numeric_limits<float>::infinity()); + message_1_.set_optional_double(std::numeric_limits<double>::infinity()); + message_2_.set_optional_double(std::numeric_limits<double>::infinity()); + comparator_.SetFractionAndMargin(field_float, 0.0, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 0.0); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // -inf should be equal even though they are not technically within margin or + // fraction. + message_1_.set_optional_float(-std::numeric_limits<float>::infinity()); + message_2_.set_optional_float(-std::numeric_limits<float>::infinity()); + message_1_.set_optional_double(-std::numeric_limits<double>::infinity()); + message_2_.set_optional_double(-std::numeric_limits<double>::infinity()); + comparator_.SetFractionAndMargin(field_float, 0.0, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 0.0); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Finite values and inf should not be equal, even for a positive fraction. + message_1_.set_optional_float(std::numeric_limits<float>::infinity()); + message_2_.set_optional_float(0.0f); + message_1_.set_optional_double(std::numeric_limits<double>::infinity()); + message_2_.set_optional_double(0.0); + comparator_.SetFractionAndMargin(field_float, 0.1, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.1, 0.0); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, + nullptr)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, + nullptr)); +} + +TEST_F(DefaultFieldComparatorTest, + FloatingPointComparisonWithinDefaultFractionOrMargin) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(100.0f); + message_2_.set_optional_float(109.9f); + message_1_.set_optional_double(100.0); + message_2_.set_optional_double(109.9); + + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Set default fraction and margin. + comparator_.SetDefaultFractionAndMargin(0.01, 0.0); + + // Float comparisons should fail since the fraction is too low. + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Set field-specific fraction and margin for one field (field_float) but not + // the other (field_double) + comparator_.SetFractionAndMargin(field_float, 0.2, 0.0); + + // The field with the override should succeed, since its field-specific + // fraction is high enough. + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + // The field with no override should fail, since the default fraction is too + // low + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // Set the default fraction and margin high enough so that fields that use + // the default should succeed + comparator_.SetDefaultFractionAndMargin(0.2, 0.0); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); + + // The field with an override should still be OK + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + + // Set fraction and margin for the field with an override to be too low + comparator_.SetFractionAndMargin(field_float, 0.01, 0.0); + + // Now our default is high enough but field_float's override is too low. + EXPECT_EQ( + FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field_float, -1, -1, NULL)); + EXPECT_EQ( + FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field_double, -1, -1, NULL)); +} + +// Simple test checking whether we compare values at correct indices. +TEST_F(DefaultFieldComparatorTest, RepeatedFieldComparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("repeated_string"); + + message_1_.add_repeated_string("foo"); + message_1_.add_repeated_string("bar"); + message_2_.add_repeated_string("bar"); + message_2_.add_repeated_string("baz"); + + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, 0, 0, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, 1, 1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, 1, 0, NULL)); +} + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/field_mask_util.cc b/NorthstarDedicatedTest/include/protobuf/util/field_mask_util.cc new file mode 100644 index 00000000..6151adcc --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/field_mask_util.cc @@ -0,0 +1,720 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/field_mask_util.h> + +#include <cstdint> + +#include <message.h> +#include <stubs/strutil.h> +#include <stubs/map_util.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { + +using google::protobuf::FieldMask; + +std::string FieldMaskUtil::ToString(const FieldMask& mask) { + return Join(mask.paths(), ","); +} + +void FieldMaskUtil::FromString(StringPiece str, FieldMask* out) { + out->Clear(); + std::vector<std::string> paths = Split(str, ","); + for (const std::string& path : paths) { + if (path.empty()) continue; + out->add_paths(path); + } +} + +bool FieldMaskUtil::SnakeCaseToCamelCase(StringPiece input, + std::string* output) { + output->clear(); + bool after_underscore = false; + for (char input_char : input) { + if (input_char >= 'A' && input_char <= 'Z') { + // The field name must not contain uppercase letters. + return false; + } + if (after_underscore) { + if (input_char >= 'a' && input_char <= 'z') { + output->push_back(input_char + 'A' - 'a'); + after_underscore = false; + } else { + // The character after a "_" must be a lowercase letter. + return false; + } + } else if (input_char == '_') { + after_underscore = true; + } else { + output->push_back(input_char); + } + } + if (after_underscore) { + // Trailing "_". + return false; + } + return true; +} + +bool FieldMaskUtil::CamelCaseToSnakeCase(StringPiece input, + std::string* output) { + output->clear(); + for (const char c : input) { + if (c == '_') { + // The field name must not contain "_"s. + return false; + } + if (c >= 'A' && c <= 'Z') { + output->push_back('_'); + output->push_back(c + 'a' - 'A'); + } else { + output->push_back(c); + } + } + return true; +} + +bool FieldMaskUtil::ToJsonString(const FieldMask& mask, std::string* out) { + out->clear(); + for (int i = 0; i < mask.paths_size(); ++i) { + const std::string& path = mask.paths(i); + std::string camelcase_path; + if (!SnakeCaseToCamelCase(path, &camelcase_path)) { + return false; + } + if (i > 0) { + out->push_back(','); + } + out->append(camelcase_path); + } + return true; +} + +bool FieldMaskUtil::FromJsonString(StringPiece str, FieldMask* out) { + out->Clear(); + std::vector<std::string> paths = Split(str, ","); + for (const std::string& path : paths) { + if (path.empty()) continue; + std::string snakecase_path; + if (!CamelCaseToSnakeCase(path, &snakecase_path)) { + return false; + } + out->add_paths(snakecase_path); + } + return true; +} + +bool FieldMaskUtil::GetFieldDescriptors( + const Descriptor* descriptor, StringPiece path, + std::vector<const FieldDescriptor*>* field_descriptors) { + if (field_descriptors != nullptr) { + field_descriptors->clear(); + } + std::vector<std::string> parts = Split(path, "."); + for (const std::string& field_name : parts) { + if (descriptor == nullptr) { + return false; + } + const FieldDescriptor* field = descriptor->FindFieldByName(field_name); + if (field == nullptr) { + return false; + } + if (field_descriptors != nullptr) { + field_descriptors->push_back(field); + } + if (!field->is_repeated() && + field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + descriptor = field->message_type(); + } else { + descriptor = nullptr; + } + } + return true; +} + +void FieldMaskUtil::GetFieldMaskForAllFields(const Descriptor* descriptor, + FieldMask* out) { + for (int i = 0; i < descriptor->field_count(); ++i) { + out->add_paths(descriptor->field(i)->name()); + } +} + +namespace { +// A FieldMaskTree represents a FieldMask in a tree structure. For example, +// given a FieldMask "foo.bar,foo.baz,bar.baz", the FieldMaskTree will be: +// +// [root] -+- foo -+- bar +// | | +// | +- baz +// | +// +- bar --- baz +// +// In the tree, each leaf node represents a field path. +class FieldMaskTree { + public: + FieldMaskTree(); + ~FieldMaskTree(); + + void MergeFromFieldMask(const FieldMask& mask); + void MergeToFieldMask(FieldMask* mask); + + // Add a field path into the tree. In a FieldMask, each field path matches + // the specified field and also all its sub-fields. If the field path to + // add is a sub-path of an existing field path in the tree (i.e., a leaf + // node), it means the tree already matches the given path so nothing will + // be added to the tree. If the path matches an existing non-leaf node in the + // tree, that non-leaf node will be turned into a leaf node with all its + // children removed because the path matches all the node's children. + void AddPath(const std::string& path); + + // Remove a path from the tree. + // If the path is a sub-path of an existing field path in the tree, it means + // we need remove the existing field path and add all sub-paths except + // specified path. If the path matches an existing node in the tree, this node + // will be moved. + void RemovePath(const std::string& path, const Descriptor* descriptor); + + // Calculate the intersection part of a field path with this tree and add + // the intersection field path into out. + void IntersectPath(const std::string& path, FieldMaskTree* out); + + // Merge all fields specified by this tree from one message to another. + void MergeMessage(const Message& source, + const FieldMaskUtil::MergeOptions& options, + Message* destination) { + // Do nothing if the tree is empty. + if (root_.children.empty()) { + return; + } + MergeMessage(&root_, source, options, destination); + } + + // Add required field path of the message to this tree based on current tree + // structure. If a message is present in the tree, add the path of its + // required field to the tree. This is to make sure that after trimming a + // message with required fields are set, check IsInitialized() will not fail. + void AddRequiredFieldPath(const Descriptor* descriptor) { + // Do nothing if the tree is empty. + if (root_.children.empty()) { + return; + } + AddRequiredFieldPath(&root_, descriptor); + } + + // Trims all fields not specified by this tree from the given message. + // Returns true if the message is modified. + bool TrimMessage(Message* message) { + // Do nothing if the tree is empty. + if (root_.children.empty()) { + return false; + } + return TrimMessage(&root_, message); + } + + private: + struct Node { + Node() {} + + ~Node() { ClearChildren(); } + + void ClearChildren() { + for (std::map<std::string, Node*>::iterator it = children.begin(); + it != children.end(); ++it) { + delete it->second; + } + children.clear(); + } + + std::map<std::string, Node*> children; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Node); + }; + + // Merge a sub-tree to mask. This method adds the field paths represented + // by all leaf nodes descended from "node" to mask. + void MergeToFieldMask(const std::string& prefix, const Node* node, + FieldMask* out); + + // Merge all leaf nodes of a sub-tree to another tree. + void MergeLeafNodesToTree(const std::string& prefix, const Node* node, + FieldMaskTree* out); + + // Merge all fields specified by a sub-tree from one message to another. + void MergeMessage(const Node* node, const Message& source, + const FieldMaskUtil::MergeOptions& options, + Message* destination); + + // Add required field path of the message to this tree based on current tree + // structure. If a message is present in the tree, add the path of its + // required field to the tree. This is to make sure that after trimming a + // message with required fields are set, check IsInitialized() will not fail. + void AddRequiredFieldPath(Node* node, const Descriptor* descriptor); + + // Trims all fields not specified by this sub-tree from the given message. + // Returns true if the message is actually modified + bool TrimMessage(const Node* node, Message* message); + + Node root_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FieldMaskTree); +}; + +FieldMaskTree::FieldMaskTree() {} + +FieldMaskTree::~FieldMaskTree() {} + +void FieldMaskTree::MergeFromFieldMask(const FieldMask& mask) { + for (int i = 0; i < mask.paths_size(); ++i) { + AddPath(mask.paths(i)); + } +} + +void FieldMaskTree::MergeToFieldMask(FieldMask* mask) { + MergeToFieldMask("", &root_, mask); +} + +void FieldMaskTree::MergeToFieldMask(const std::string& prefix, + const Node* node, FieldMask* out) { + if (node->children.empty()) { + if (prefix.empty()) { + // This is the root node. + return; + } + out->add_paths(prefix); + return; + } + for (std::map<std::string, Node*>::const_iterator it = node->children.begin(); + it != node->children.end(); ++it) { + std::string current_path = + prefix.empty() ? it->first : prefix + "." + it->first; + MergeToFieldMask(current_path, it->second, out); + } +} + +void FieldMaskTree::AddPath(const std::string& path) { + std::vector<std::string> parts = Split(path, "."); + if (parts.empty()) { + return; + } + bool new_branch = false; + Node* node = &root_; + for (const std::string& node_name : parts) { + if (!new_branch && node != &root_ && node->children.empty()) { + // Path matches an existing leaf node. This means the path is already + // covered by this tree (for example, adding "foo.bar.baz" to a tree + // which already contains "foo.bar"). + return; + } + Node*& child = node->children[node_name]; + if (child == NULL) { + new_branch = true; + child = new Node(); + } + node = child; + } + if (!node->children.empty()) { + node->ClearChildren(); + } +} + +void FieldMaskTree::RemovePath(const std::string& path, + const Descriptor* descriptor) { + if (root_.children.empty()) { + // Nothing to be removed from an empty tree. We shortcut it here so an empty + // tree won't be interpreted as a field mask containing all fields by the + // code below. + return; + } + std::vector<std::string> parts = Split(path, "."); + if (parts.empty()) { + return; + } + std::vector<Node*> nodes(parts.size()); + Node* node = &root_; + const Descriptor* current_descriptor = descriptor; + Node* new_branch_node = nullptr; + for (int i = 0; i < parts.size(); ++i) { + nodes[i] = node; + const FieldDescriptor* field_descriptor = + current_descriptor->FindFieldByName(parts[i]); + if (field_descriptor == nullptr || + (field_descriptor->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE && + i != parts.size() - 1)) { + // Invalid path. + if (new_branch_node != nullptr) { + // If add any new nodes, cleanup. + new_branch_node->ClearChildren(); + } + return; + } + + if (node->children.empty()) { + if (new_branch_node == nullptr) { + new_branch_node = node; + } + for (int j = 0; j < current_descriptor->field_count(); ++j) { + node->children[current_descriptor->field(j)->name()] = new Node(); + } + } + if (ContainsKey(node->children, parts[i])) { + node = node->children[parts[i]]; + if (field_descriptor->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + current_descriptor = field_descriptor->message_type(); + } + } else { + // Path does not exist. + return; + } + } + // Remove path. + for (int i = parts.size() - 1; i >= 0; i--) { + delete nodes[i]->children[parts[i]]; + nodes[i]->children.erase(parts[i]); + if (!nodes[i]->children.empty()) { + break; + } + } +} + +void FieldMaskTree::IntersectPath(const std::string& path, FieldMaskTree* out) { + std::vector<std::string> parts = Split(path, "."); + if (parts.empty()) { + return; + } + const Node* node = &root_; + for (const std::string& node_name : parts) { + if (node->children.empty()) { + if (node != &root_) { + out->AddPath(path); + } + return; + } + const Node* result = FindPtrOrNull(node->children, node_name); + if (result == NULL) { + // No intersection found. + return; + } + node = result; + } + // Now we found a matching node with the given path. Add all leaf nodes + // to out. + MergeLeafNodesToTree(path, node, out); +} + +void FieldMaskTree::MergeLeafNodesToTree(const std::string& prefix, + const Node* node, FieldMaskTree* out) { + if (node->children.empty()) { + out->AddPath(prefix); + } + for (std::map<std::string, Node*>::const_iterator it = node->children.begin(); + it != node->children.end(); ++it) { + std::string current_path = + prefix.empty() ? it->first : prefix + "." + it->first; + MergeLeafNodesToTree(current_path, it->second, out); + } +} + +void FieldMaskTree::MergeMessage(const Node* node, const Message& source, + const FieldMaskUtil::MergeOptions& options, + Message* destination) { + GOOGLE_DCHECK(!node->children.empty()); + const Reflection* source_reflection = source.GetReflection(); + const Reflection* destination_reflection = destination->GetReflection(); + const Descriptor* descriptor = source.GetDescriptor(); + for (std::map<std::string, Node*>::const_iterator it = node->children.begin(); + it != node->children.end(); ++it) { + const std::string& field_name = it->first; + const Node* child = it->second; + const FieldDescriptor* field = descriptor->FindFieldByName(field_name); + if (field == NULL) { + GOOGLE_LOG(ERROR) << "Cannot find field \"" << field_name << "\" in message " + << descriptor->full_name(); + continue; + } + if (!child->children.empty()) { + // Sub-paths are only allowed for singular message fields. + if (field->is_repeated() || + field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { + GOOGLE_LOG(ERROR) << "Field \"" << field_name << "\" in message " + << descriptor->full_name() + << " is not a singular message field and cannot " + << "have sub-fields."; + continue; + } + MergeMessage(child, source_reflection->GetMessage(source, field), options, + destination_reflection->MutableMessage(destination, field)); + continue; + } + if (!field->is_repeated()) { + switch (field->cpp_type()) { +#define COPY_VALUE(TYPE, Name) \ + case FieldDescriptor::CPPTYPE_##TYPE: { \ + if (source_reflection->HasField(source, field)) { \ + destination_reflection->Set##Name( \ + destination, field, source_reflection->Get##Name(source, field)); \ + } else { \ + destination_reflection->ClearField(destination, field); \ + } \ + break; \ + } + COPY_VALUE(BOOL, Bool) + COPY_VALUE(INT32, Int32) + COPY_VALUE(INT64, Int64) + COPY_VALUE(UINT32, UInt32) + COPY_VALUE(UINT64, UInt64) + COPY_VALUE(FLOAT, Float) + COPY_VALUE(DOUBLE, Double) + COPY_VALUE(ENUM, Enum) + COPY_VALUE(STRING, String) +#undef COPY_VALUE + case FieldDescriptor::CPPTYPE_MESSAGE: { + if (options.replace_message_fields()) { + destination_reflection->ClearField(destination, field); + } + if (source_reflection->HasField(source, field)) { + destination_reflection->MutableMessage(destination, field) + ->MergeFrom(source_reflection->GetMessage(source, field)); + } + break; + } + } + } else { + if (options.replace_repeated_fields()) { + destination_reflection->ClearField(destination, field); + } + switch (field->cpp_type()) { +#define COPY_REPEATED_VALUE(TYPE, Name) \ + case FieldDescriptor::CPPTYPE_##TYPE: { \ + int size = source_reflection->FieldSize(source, field); \ + for (int i = 0; i < size; ++i) { \ + destination_reflection->Add##Name( \ + destination, field, \ + source_reflection->GetRepeated##Name(source, field, i)); \ + } \ + break; \ + } + COPY_REPEATED_VALUE(BOOL, Bool) + COPY_REPEATED_VALUE(INT32, Int32) + COPY_REPEATED_VALUE(INT64, Int64) + COPY_REPEATED_VALUE(UINT32, UInt32) + COPY_REPEATED_VALUE(UINT64, UInt64) + COPY_REPEATED_VALUE(FLOAT, Float) + COPY_REPEATED_VALUE(DOUBLE, Double) + COPY_REPEATED_VALUE(ENUM, Enum) + COPY_REPEATED_VALUE(STRING, String) +#undef COPY_REPEATED_VALUE + case FieldDescriptor::CPPTYPE_MESSAGE: { + int size = source_reflection->FieldSize(source, field); + for (int i = 0; i < size; ++i) { + destination_reflection->AddMessage(destination, field) + ->MergeFrom( + source_reflection->GetRepeatedMessage(source, field, i)); + } + break; + } + } + } + } +} + +void FieldMaskTree::AddRequiredFieldPath(Node* node, + const Descriptor* descriptor) { + const int32_t field_count = descriptor->field_count(); + for (int index = 0; index < field_count; ++index) { + const FieldDescriptor* field = descriptor->field(index); + if (field->is_required()) { + const std::string& node_name = field->name(); + Node*& child = node->children[node_name]; + if (child == nullptr) { + // Add required field path to the tree + child = new Node(); + } else if (child->children.empty()) { + // If the required field is in the tree and does not have any children, + // do nothing. + continue; + } + // Add required field in the children to the tree if the field is message. + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + AddRequiredFieldPath(child, field->message_type()); + } + } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + std::map<std::string, Node*>::const_iterator it = + node->children.find(field->name()); + if (it != node->children.end()) { + // Add required fields in the children to the + // tree if the field is a message and present in the tree. + Node* child = it->second; + if (!child->children.empty()) { + AddRequiredFieldPath(child, field->message_type()); + } + } + } + } +} + +bool FieldMaskTree::TrimMessage(const Node* node, Message* message) { + GOOGLE_DCHECK(!node->children.empty()); + const Reflection* reflection = message->GetReflection(); + const Descriptor* descriptor = message->GetDescriptor(); + const int32_t field_count = descriptor->field_count(); + bool modified = false; + for (int index = 0; index < field_count; ++index) { + const FieldDescriptor* field = descriptor->field(index); + std::map<std::string, Node*>::const_iterator it = + node->children.find(field->name()); + if (it == node->children.end()) { + if (field->is_repeated()) { + if (reflection->FieldSize(*message, field) != 0) { + modified = true; + } + } else { + if (reflection->HasField(*message, field)) { + modified = true; + } + } + reflection->ClearField(message, field); + } else { + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + Node* child = it->second; + if (!child->children.empty() && reflection->HasField(*message, field)) { + bool nestedMessageChanged = + TrimMessage(child, reflection->MutableMessage(message, field)); + modified = nestedMessageChanged || modified; + } + } + } + } + return modified; +} + +} // namespace + +void FieldMaskUtil::ToCanonicalForm(const FieldMask& mask, FieldMask* out) { + FieldMaskTree tree; + tree.MergeFromFieldMask(mask); + out->Clear(); + tree.MergeToFieldMask(out); +} + +void FieldMaskUtil::Union(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out) { + FieldMaskTree tree; + tree.MergeFromFieldMask(mask1); + tree.MergeFromFieldMask(mask2); + out->Clear(); + tree.MergeToFieldMask(out); +} + +void FieldMaskUtil::Intersect(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out) { + FieldMaskTree tree, intersection; + tree.MergeFromFieldMask(mask1); + for (int i = 0; i < mask2.paths_size(); ++i) { + tree.IntersectPath(mask2.paths(i), &intersection); + } + out->Clear(); + intersection.MergeToFieldMask(out); +} + +void FieldMaskUtil::Subtract(const Descriptor* descriptor, + const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out) { + if (mask1.paths().empty()) { + out->Clear(); + return; + } + FieldMaskTree tree; + tree.MergeFromFieldMask(mask1); + for (int i = 0; i < mask2.paths_size(); ++i) { + tree.RemovePath(mask2.paths(i), descriptor); + } + out->Clear(); + tree.MergeToFieldMask(out); +} + +bool FieldMaskUtil::IsPathInFieldMask(StringPiece path, + const FieldMask& mask) { + for (int i = 0; i < mask.paths_size(); ++i) { + const std::string& mask_path = mask.paths(i); + if (path == mask_path) { + return true; + } else if (mask_path.length() < path.length()) { + // Also check whether mask.paths(i) is a prefix of path. + if (path.substr(0, mask_path.length() + 1).compare(mask_path + ".") == + 0) { + return true; + } + } + } + return false; +} + +void FieldMaskUtil::MergeMessageTo(const Message& source, const FieldMask& mask, + const MergeOptions& options, + Message* destination) { + GOOGLE_CHECK(source.GetDescriptor() == destination->GetDescriptor()); + // Build a FieldMaskTree and walk through the tree to merge all specified + // fields. + FieldMaskTree tree; + tree.MergeFromFieldMask(mask); + tree.MergeMessage(source, options, destination); +} + +bool FieldMaskUtil::TrimMessage(const FieldMask& mask, Message* message) { + // Build a FieldMaskTree and walk through the tree to merge all specified + // fields. + FieldMaskTree tree; + tree.MergeFromFieldMask(mask); + return tree.TrimMessage(GOOGLE_CHECK_NOTNULL(message)); +} + +bool FieldMaskUtil::TrimMessage(const FieldMask& mask, Message* message, + const TrimOptions& options) { + // Build a FieldMaskTree and walk through the tree to merge all specified + // fields. + FieldMaskTree tree; + tree.MergeFromFieldMask(mask); + // If keep_required_fields is true, implicitly add required fields of + // a message present in the tree to prevent from trimming. + if (options.keep_required_fields()) { + tree.AddRequiredFieldPath(GOOGLE_CHECK_NOTNULL(message->GetDescriptor())); + } + return tree.TrimMessage(GOOGLE_CHECK_NOTNULL(message)); +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/field_mask_util.h b/NorthstarDedicatedTest/include/protobuf/util/field_mask_util.h new file mode 100644 index 00000000..5bc50fb3 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/field_mask_util.h @@ -0,0 +1,262 @@ +// 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. + +// Defines utilities for the FieldMask well known type. + +#ifndef GOOGLE_PROTOBUF_UTIL_FIELD_MASK_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_FIELD_MASK_UTIL_H__ + +#include <cstdint> +#include <string> + +#include <field_mask.pb.h> +#include <descriptor.h> +#include <stubs/strutil.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { + +class PROTOBUF_EXPORT FieldMaskUtil { + typedef google::protobuf::FieldMask FieldMask; + + public: + // Converts FieldMask to/from string, formatted by separating each path + // with a comma (e.g., "foo_bar,baz.quz"). + static std::string ToString(const FieldMask& mask); + static void FromString(StringPiece str, FieldMask* out); + + // Populates the FieldMask with the paths corresponding to the fields with the + // given numbers, after checking that all field numbers are valid. + template <typename T> + static void FromFieldNumbers(const std::vector<int64_t>& field_numbers, + FieldMask* out) { + for (const auto field_number : field_numbers) { + const FieldDescriptor* field_desc = + T::descriptor()->FindFieldByNumber(field_number); + GOOGLE_CHECK(field_desc != nullptr) + << "Invalid field number for " << T::descriptor()->full_name() << ": " + << field_number; + AddPathToFieldMask<T>(field_desc->lowercase_name(), out); + } + } + + // Converts FieldMask to/from string, formatted according to proto3 JSON + // spec for FieldMask (e.g., "fooBar,baz.quz"). If the field name is not + // style conforming (i.e., not snake_case when converted to string, or not + // camelCase when converted from string), the conversion will fail. + static bool ToJsonString(const FieldMask& mask, std::string* out); + static bool FromJsonString(StringPiece str, FieldMask* out); + + // Get the descriptors of the fields which the given path from the message + // descriptor traverses, if field_descriptors is not null. + // Return false if the path is not valid, and the content of field_descriptors + // is unspecified. + static bool GetFieldDescriptors( + const Descriptor* descriptor, StringPiece path, + std::vector<const FieldDescriptor*>* field_descriptors); + + // Checks whether the given path is valid for type T. + template <typename T> + static bool IsValidPath(StringPiece path) { + return GetFieldDescriptors(T::descriptor(), path, nullptr); + } + + // Checks whether the given FieldMask is valid for type T. + template <typename T> + static bool IsValidFieldMask(const FieldMask& mask) { + for (int i = 0; i < mask.paths_size(); ++i) { + if (!GetFieldDescriptors(T::descriptor(), mask.paths(i), nullptr)) + return false; + } + return true; + } + + // Adds a path to FieldMask after checking whether the given path is valid. + // This method check-fails if the path is not a valid path for type T. + template <typename T> + static void AddPathToFieldMask(StringPiece path, FieldMask* mask) { + GOOGLE_CHECK(IsValidPath<T>(path)) << path; + mask->add_paths(std::string(path)); + } + + // Creates a FieldMask with all fields of type T. This FieldMask only + // contains fields of T but not any sub-message fields. + template <typename T> + static FieldMask GetFieldMaskForAllFields() { + FieldMask out; + GetFieldMaskForAllFields(T::descriptor(), &out); + return out; + } + template <typename T> + PROTOBUF_DEPRECATED_MSG("Use *out = GetFieldMaskForAllFields() instead") + static void GetFieldMaskForAllFields(FieldMask* out) { + GetFieldMaskForAllFields(T::descriptor(), out); + } + // This flavor takes the protobuf type descriptor as an argument. + // Useful when the type is not known at compile time. + static void GetFieldMaskForAllFields(const Descriptor* descriptor, + FieldMask* out); + + // Converts a FieldMask to the canonical form. It will: + // 1. Remove paths that are covered by another path. For example, + // "foo.bar" is covered by "foo" and will be removed if "foo" + // is also in the FieldMask. + // 2. Sort all paths in alphabetical order. + static void ToCanonicalForm(const FieldMask& mask, FieldMask* out); + + // Creates an union of two FieldMasks. + static void Union(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out); + + // Creates an intersection of two FieldMasks. + static void Intersect(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out); + + // Subtracts mask2 from mask1 base of type T. + template <typename T> + static void Subtract(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out) { + Subtract(T::descriptor(), mask1, mask2, out); + } + // This flavor takes the protobuf type descriptor as an argument. + // Useful when the type is not known at compile time. + static void Subtract(const Descriptor* descriptor, const FieldMask& mask1, + const FieldMask& mask2, FieldMask* out); + + // Returns true if path is covered by the given FieldMask. Note that path + // "foo.bar" covers all paths like "foo.bar.baz", "foo.bar.quz.x", etc. + // Also note that parent paths are not covered by explicit child path, i.e. + // "foo.bar" does NOT cover "foo", even if "bar" is the only child. + static bool IsPathInFieldMask(StringPiece path, const FieldMask& mask); + + class MergeOptions; + // Merges fields specified in a FieldMask into another message. + static void MergeMessageTo(const Message& source, const FieldMask& mask, + const MergeOptions& options, Message* destination); + + class TrimOptions; + // Removes from 'message' any field that is not represented in the given + // FieldMask. If the FieldMask is empty, does nothing. + // Returns true if the message is modified. + static bool TrimMessage(const FieldMask& mask, Message* message); + + // Removes from 'message' any field that is not represented in the given + // FieldMask with customized TrimOptions. + // If the FieldMask is empty, does nothing. + // Returns true if the message is modified. + static bool TrimMessage(const FieldMask& mask, Message* message, + const TrimOptions& options); + + private: + friend class SnakeCaseCamelCaseTest; + // Converts a field name from snake_case to camelCase: + // 1. Every character after "_" will be converted to uppercase. + // 2. All "_"s are removed. + // The conversion will fail if: + // 1. The field name contains uppercase letters. + // 2. Any character after a "_" is not a lowercase letter. + // If the conversion succeeds, it's guaranteed that the resulted + // camelCase name will yield the original snake_case name when + // converted using CamelCaseToSnakeCase(). + // + // Note that the input can contain characters not allowed in C identifiers. + // For example, "foo_bar,baz_quz" will be converted to "fooBar,bazQuz" + // successfully. + static bool SnakeCaseToCamelCase(StringPiece input, + std::string* output); + // Converts a field name from camelCase to snake_case: + // 1. Every uppercase letter is converted to lowercase with an additional + // preceding "_". + // The conversion will fail if: + // 1. The field name contains "_"s. + // If the conversion succeeds, it's guaranteed that the resulted + // snake_case name will yield the original camelCase name when + // converted using SnakeCaseToCamelCase(). + // + // Note that the input can contain characters not allowed in C identifiers. + // For example, "fooBar,bazQuz" will be converted to "foo_bar,baz_quz" + // successfully. + static bool CamelCaseToSnakeCase(StringPiece input, + std::string* output); +}; + +class PROTOBUF_EXPORT FieldMaskUtil::MergeOptions { + public: + MergeOptions() + : replace_message_fields_(false), replace_repeated_fields_(false) {} + // When merging message fields, the default behavior is to merge the + // content of two message fields together. If you instead want to use + // the field from the source message to replace the corresponding field + // in the destination message, set this flag to true. When this flag is set, + // specified submessage fields that are missing in source will be cleared in + // destination. + void set_replace_message_fields(bool value) { + replace_message_fields_ = value; + } + bool replace_message_fields() const { return replace_message_fields_; } + // The default merging behavior will append entries from the source + // repeated field to the destination repeated field. If you only want + // to keep the entries from the source repeated field, set this flag + // to true. + void set_replace_repeated_fields(bool value) { + replace_repeated_fields_ = value; + } + bool replace_repeated_fields() const { return replace_repeated_fields_; } + + private: + bool replace_message_fields_; + bool replace_repeated_fields_; +}; + +class PROTOBUF_EXPORT FieldMaskUtil::TrimOptions { + public: + TrimOptions() : keep_required_fields_(false) {} + // When trimming message fields, the default behavior is to trim required + // fields of the present message if they are not specified in the field mask. + // If you instead want to keep required fields of the present message even + // when they are not specified in the field mask, set this flag to true. + void set_keep_required_fields(bool value) { keep_required_fields_ = value; } + bool keep_required_fields() const { return keep_required_fields_; } + + private: + bool keep_required_fields_; +}; + +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_FIELD_MASK_UTIL_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/field_mask_util_test.cc b/NorthstarDedicatedTest/include/protobuf/util/field_mask_util_test.cc new file mode 100644 index 00000000..b8e13b97 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/field_mask_util_test.cc @@ -0,0 +1,836 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/field_mask_util.h> + +#include <algorithm> +#include <cstdint> +#include <vector> + +#include <field_mask.pb.h> +#include <test_util.h> +#include <unittest.pb.h> +#include <gtest/gtest.h> +#include <stubs/logging.h> +#include <stubs/common.h> + +namespace google { +namespace protobuf { +namespace util { + +class SnakeCaseCamelCaseTest : public ::testing::Test { + protected: + std::string SnakeCaseToCamelCase(const std::string& input) { + std::string output; + if (FieldMaskUtil::SnakeCaseToCamelCase(input, &output)) { + return output; + } else { + return "#FAIL#"; + } + } + + std::string CamelCaseToSnakeCase(const std::string& input) { + std::string output; + if (FieldMaskUtil::CamelCaseToSnakeCase(input, &output)) { + return output; + } else { + return "#FAIL#"; + } + } +}; + +namespace { + +TEST_F(SnakeCaseCamelCaseTest, SnakeToCamel) { + EXPECT_EQ("fooBar", SnakeCaseToCamelCase("foo_bar")); + EXPECT_EQ("FooBar", SnakeCaseToCamelCase("_foo_bar")); + EXPECT_EQ("foo3Bar", SnakeCaseToCamelCase("foo3_bar")); + // No uppercase letter is allowed. + EXPECT_EQ("#FAIL#", SnakeCaseToCamelCase("Foo")); + // Any character after a "_" must be a lowercase letter. + // 1. "_" cannot be followed by another "_". + // 2. "_" cannot be followed by a digit. + // 3. "_" cannot appear as the last character. + EXPECT_EQ("#FAIL#", SnakeCaseToCamelCase("foo__bar")); + EXPECT_EQ("#FAIL#", SnakeCaseToCamelCase("foo_3bar")); + EXPECT_EQ("#FAIL#", SnakeCaseToCamelCase("foo_bar_")); +} + +TEST_F(SnakeCaseCamelCaseTest, CamelToSnake) { + EXPECT_EQ("foo_bar", CamelCaseToSnakeCase("fooBar")); + EXPECT_EQ("_foo_bar", CamelCaseToSnakeCase("FooBar")); + EXPECT_EQ("foo3_bar", CamelCaseToSnakeCase("foo3Bar")); + // "_"s are not allowed. + EXPECT_EQ("#FAIL#", CamelCaseToSnakeCase("foo_bar")); +} + +TEST_F(SnakeCaseCamelCaseTest, RoundTripTest) { + // Enumerates all possible snake_case names and test that converting it to + // camelCase and then to snake_case again will yield the original name. + std::string name = "___abc123"; + std::sort(name.begin(), name.end()); + do { + std::string camelName = SnakeCaseToCamelCase(name); + if (camelName != "#FAIL#") { + EXPECT_EQ(name, CamelCaseToSnakeCase(camelName)); + } + } while (std::next_permutation(name.begin(), name.end())); + + // Enumerates all possible camelCase names and test that converting it to + // snake_case and then to camelCase again will yield the original name. + name = "abcABC123"; + std::sort(name.begin(), name.end()); + do { + std::string camelName = CamelCaseToSnakeCase(name); + if (camelName != "#FAIL#") { + EXPECT_EQ(name, SnakeCaseToCamelCase(camelName)); + } + } while (std::next_permutation(name.begin(), name.end())); +} + +using google::protobuf::FieldMask; +using protobuf_unittest::NestedTestAllTypes; +using protobuf_unittest::TestAllTypes; +using protobuf_unittest::TestRequired; +using protobuf_unittest::TestRequiredMessage; + +TEST(FieldMaskUtilTest, StringFormat) { + FieldMask mask; + EXPECT_EQ("", FieldMaskUtil::ToString(mask)); + mask.add_paths("foo_bar"); + EXPECT_EQ("foo_bar", FieldMaskUtil::ToString(mask)); + mask.add_paths("baz_quz"); + EXPECT_EQ("foo_bar,baz_quz", FieldMaskUtil::ToString(mask)); + + FieldMaskUtil::FromString("", &mask); + EXPECT_EQ(0, mask.paths_size()); + FieldMaskUtil::FromString("fooBar", &mask); + EXPECT_EQ(1, mask.paths_size()); + EXPECT_EQ("fooBar", mask.paths(0)); + FieldMaskUtil::FromString("fooBar,bazQuz", &mask); + EXPECT_EQ(2, mask.paths_size()); + EXPECT_EQ("fooBar", mask.paths(0)); + EXPECT_EQ("bazQuz", mask.paths(1)); +} + +TEST(FieldMaskUtilTest, JsonStringFormat) { + FieldMask mask; + std::string value; + EXPECT_TRUE(FieldMaskUtil::ToJsonString(mask, &value)); + EXPECT_EQ("", value); + mask.add_paths("foo_bar"); + EXPECT_TRUE(FieldMaskUtil::ToJsonString(mask, &value)); + EXPECT_EQ("fooBar", value); + mask.add_paths("bar_quz"); + EXPECT_TRUE(FieldMaskUtil::ToJsonString(mask, &value)); + EXPECT_EQ("fooBar,barQuz", value); + + FieldMaskUtil::FromJsonString("", &mask); + EXPECT_EQ(0, mask.paths_size()); + FieldMaskUtil::FromJsonString("fooBar", &mask); + EXPECT_EQ(1, mask.paths_size()); + EXPECT_EQ("foo_bar", mask.paths(0)); + FieldMaskUtil::FromJsonString("fooBar,bazQuz", &mask); + EXPECT_EQ(2, mask.paths_size()); + EXPECT_EQ("foo_bar", mask.paths(0)); + EXPECT_EQ("baz_quz", mask.paths(1)); +} + +TEST(FieldMaskUtilTest, FromFieldNumbers) { + FieldMask mask; + std::vector<int64_t> field_numbers = { + TestAllTypes::kOptionalInt64FieldNumber, + TestAllTypes::kOptionalBoolFieldNumber, + TestAllTypes::kRepeatedStringFieldNumber, + }; + FieldMaskUtil::FromFieldNumbers<TestAllTypes>(field_numbers, &mask); + ASSERT_EQ(3, mask.paths_size()); + EXPECT_EQ("optional_int64", mask.paths(0)); + EXPECT_EQ("optional_bool", mask.paths(1)); + EXPECT_EQ("repeated_string", mask.paths(2)); +} + +TEST(FieldMaskUtilTest, GetFieldDescriptors) { + std::vector<const FieldDescriptor*> field_descriptors; + EXPECT_TRUE(FieldMaskUtil::GetFieldDescriptors( + TestAllTypes::descriptor(), "optional_int32", &field_descriptors)); + EXPECT_EQ(1, field_descriptors.size()); + EXPECT_EQ("optional_int32", field_descriptors[0]->name()); + EXPECT_FALSE(FieldMaskUtil::GetFieldDescriptors( + TestAllTypes::descriptor(), "optional_nonexist", nullptr)); + EXPECT_TRUE(FieldMaskUtil::GetFieldDescriptors(TestAllTypes::descriptor(), + "optional_nested_message.bb", + &field_descriptors)); + EXPECT_EQ(2, field_descriptors.size()); + EXPECT_EQ("optional_nested_message", field_descriptors[0]->name()); + EXPECT_EQ("bb", field_descriptors[1]->name()); + EXPECT_FALSE(FieldMaskUtil::GetFieldDescriptors( + TestAllTypes::descriptor(), "optional_nested_message.nonexist", nullptr)); + // FieldMask cannot be used to specify sub-fields of a repeated message. + EXPECT_FALSE(FieldMaskUtil::GetFieldDescriptors( + TestAllTypes::descriptor(), "repeated_nested_message.bb", nullptr)); +} + +TEST(FieldMaskUtilTest, TestIsVaildPath) { + EXPECT_TRUE(FieldMaskUtil::IsValidPath<TestAllTypes>("optional_int32")); + EXPECT_FALSE(FieldMaskUtil::IsValidPath<TestAllTypes>("optional_nonexist")); + EXPECT_TRUE( + FieldMaskUtil::IsValidPath<TestAllTypes>("optional_nested_message.bb")); + EXPECT_FALSE(FieldMaskUtil::IsValidPath<TestAllTypes>( + "optional_nested_message.nonexist")); + // FieldMask cannot be used to specify sub-fields of a repeated message. + EXPECT_FALSE( + FieldMaskUtil::IsValidPath<TestAllTypes>("repeated_nested_message.bb")); +} + +TEST(FieldMaskUtilTest, TestIsValidFieldMask) { + FieldMask mask; + FieldMaskUtil::FromString("optional_int32,optional_nested_message.bb", &mask); + EXPECT_TRUE(FieldMaskUtil::IsValidFieldMask<TestAllTypes>(mask)); + + FieldMaskUtil::FromString( + "optional_int32,optional_nested_message.bb,optional_nonexist", &mask); + EXPECT_FALSE(FieldMaskUtil::IsValidFieldMask<TestAllTypes>(mask)); +} + +TEST(FieldMaskUtilTest, TestGetFieldMaskForAllFields) { + FieldMask mask; + mask = FieldMaskUtil::GetFieldMaskForAllFields<TestAllTypes::NestedMessage>(); + EXPECT_EQ(1, mask.paths_size()); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("bb", mask)); + + mask = FieldMaskUtil::GetFieldMaskForAllFields<TestAllTypes>(); + EXPECT_EQ(75, mask.paths_size()); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_int32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_int64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_uint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_uint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_fixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_fixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sfixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sfixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_float", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_double", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_bool", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_string", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_bytes", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("optional_nested_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("optional_foreign_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("optional_import_message", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_nested_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_foreign_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_import_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_int32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_int64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_uint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_uint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_fixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_fixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sfixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sfixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_float", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_double", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_bool", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_string", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_bytes", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("repeated_nested_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("repeated_foreign_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("repeated_import_message", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_nested_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_foreign_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_import_enum", mask)); +} + +TEST(FieldMaskUtilTest, TestToCanonicalForm) { + FieldMask in, out; + // Paths will be sorted. + FieldMaskUtil::FromString("baz.quz,bar,foo", &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("bar,baz.quz,foo", FieldMaskUtil::ToString(out)); + // Duplicated paths will be removed. + FieldMaskUtil::FromString("foo,bar,foo", &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("bar,foo", FieldMaskUtil::ToString(out)); + // Sub-paths of other paths will be removed. + FieldMaskUtil::FromString("foo.b1,bar.b1,foo.b2,bar", &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("bar,foo.b1,foo.b2", FieldMaskUtil::ToString(out)); + + // Test more deeply nested cases. + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2.quz," + "foo.bar.baz2", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo.bar.baz1,foo.bar.baz2", FieldMaskUtil::ToString(out)); + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2," + "foo.bar.baz2.quz", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo.bar.baz1,foo.bar.baz2", FieldMaskUtil::ToString(out)); + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2," + "foo.bar.baz2.quz," + "foo.bar", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo.bar", FieldMaskUtil::ToString(out)); + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2," + "foo.bar.baz2.quz," + "foo", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo", FieldMaskUtil::ToString(out)); +} + +TEST(FieldMaskUtilTest, TestUnion) { + FieldMask mask1, mask2, out; + // Test cases without overlapping. + FieldMaskUtil::FromString("foo,baz", &mask1); + FieldMaskUtil::FromString("bar,quz", &mask2); + FieldMaskUtil::Union(mask1, mask2, &out); + EXPECT_EQ("bar,baz,foo,quz", FieldMaskUtil::ToString(out)); + // Overlap with duplicated paths. + FieldMaskUtil::FromString("foo,baz.bb", &mask1); + FieldMaskUtil::FromString("baz.bb,quz", &mask2); + FieldMaskUtil::Union(mask1, mask2, &out); + EXPECT_EQ("baz.bb,foo,quz", FieldMaskUtil::ToString(out)); + // Overlap with paths covering some other paths. + FieldMaskUtil::FromString("foo.bar.baz,quz", &mask1); + FieldMaskUtil::FromString("foo.bar,bar", &mask2); + FieldMaskUtil::Union(mask1, mask2, &out); + EXPECT_EQ("bar,foo.bar,quz", FieldMaskUtil::ToString(out)); +} + +TEST(FieldMaskUtilTest, TestIntersect) { + FieldMask mask1, mask2, out; + // Test cases without overlapping. + FieldMaskUtil::FromString("foo,baz", &mask1); + FieldMaskUtil::FromString("bar,quz", &mask2); + FieldMaskUtil::Intersect(mask1, mask2, &out); + EXPECT_EQ("", FieldMaskUtil::ToString(out)); + // Overlap with duplicated paths. + FieldMaskUtil::FromString("foo,baz.bb", &mask1); + FieldMaskUtil::FromString("baz.bb,quz", &mask2); + FieldMaskUtil::Intersect(mask1, mask2, &out); + EXPECT_EQ("baz.bb", FieldMaskUtil::ToString(out)); + // Overlap with paths covering some other paths. + FieldMaskUtil::FromString("foo.bar.baz,quz", &mask1); + FieldMaskUtil::FromString("foo.bar,bar", &mask2); + FieldMaskUtil::Intersect(mask1, mask2, &out); + EXPECT_EQ("foo.bar.baz", FieldMaskUtil::ToString(out)); +} + +TEST(FieldMaskUtilTest, TestSubtract) { + FieldMask mask1, mask2, out; + // Normal case. + FieldMaskUtil::FromString( + "optional_int32,optional_uint64,optional_nested_message,optional_foreign_" + "message,repeated_int32,repeated_foreign_message,repeated_nested_message." + "bb", + &mask1); + + FieldMaskUtil::FromString( + "optional_int32,optional_nested_message.bb,optional_foreign_message.c," + "repeated_int32,repeated_nested_message.bb,repeated_foreign_message.f," + "repeated_foreign_message.d,repeated_nested_message.bb,repeated_uint32", + &mask2); + + FieldMaskUtil::Subtract<TestAllTypes>(mask1, mask2, &out); + EXPECT_EQ( + "optional_foreign_message.d,optional_uint64,repeated_foreign_message.c", + FieldMaskUtil::ToString(out)); + + // mask1 is empty. + FieldMaskUtil::FromString("", &mask1); + FieldMaskUtil::Subtract<TestAllTypes>(mask1, mask2, &out); + EXPECT_EQ("", FieldMaskUtil::ToString(out)); + + // mask1 is "optional_nested_message" and mask2 is + // "optional_nested_message.nonexist_field". + FieldMaskUtil::FromString("optional_nested_message", &mask1); + FieldMaskUtil::FromString("optional_nested_message.nonexist_field", &mask2); + FieldMaskUtil::Subtract<TestAllTypes>(mask1, mask2, &out); + EXPECT_EQ("optional_nested_message", FieldMaskUtil::ToString(out)); + + // mask1 is "optional_nested_message" and mask2 is + // "optional_nested_message". + FieldMaskUtil::FromString("optional_nested_message", &mask1); + FieldMaskUtil::FromString("optional_nested_message", &mask2); + FieldMaskUtil::Subtract<TestAllTypes>(mask1, mask2, &out); + EXPECT_EQ("", FieldMaskUtil::ToString(out)); + + // Regression test for b/72727550 + FieldMaskUtil::FromString("optional_foreign_message.c", &mask1); + FieldMaskUtil::FromString("optional_foreign_message,optional_nested_message", + &mask2); + FieldMaskUtil::Subtract<TestAllTypes>(mask1, mask2, &out); + EXPECT_EQ("", FieldMaskUtil::ToString(out)); +} + +TEST(FieldMaskUtilTest, TestIspathInFieldMask) { + FieldMask mask; + FieldMaskUtil::FromString("foo.bar", &mask); + EXPECT_FALSE(FieldMaskUtil::IsPathInFieldMask("", mask)); + EXPECT_FALSE(FieldMaskUtil::IsPathInFieldMask("foo", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("foo.bar", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("foo.bar.baz", mask)); + EXPECT_FALSE(FieldMaskUtil::IsPathInFieldMask("foo.bar0.baz", mask)); +} + +TEST(FieldMaskUtilTest, MergeMessage) { + TestAllTypes src, dst; + TestUtil::SetAllFields(&src); + FieldMaskUtil::MergeOptions options; + +#define TEST_MERGE_ONE_PRIMITIVE_FIELD(field_name) \ + { \ + TestAllTypes tmp; \ + tmp.set_##field_name(src.field_name()); \ + FieldMask mask; \ + mask.add_paths(#field_name); \ + dst.Clear(); \ + FieldMaskUtil::MergeMessageTo(src, mask, options, &dst); \ + EXPECT_EQ(tmp.DebugString(), dst.DebugString()); \ + src.clear_##field_name(); \ + tmp.clear_##field_name(); \ + FieldMaskUtil::MergeMessageTo(src, mask, options, &dst); \ + EXPECT_EQ(tmp.DebugString(), dst.DebugString()); \ + } + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_int32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_int64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_uint32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_uint64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sint32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sint64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_fixed32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_fixed64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sfixed32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sfixed64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_float) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_double) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_bool) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_string) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_bytes) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_nested_enum) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_foreign_enum) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_import_enum) +#undef TEST_MERGE_ONE_PRIMITIVE_FIELD + +#define TEST_MERGE_ONE_FIELD(field_name) \ + { \ + TestAllTypes tmp; \ + *tmp.mutable_##field_name() = src.field_name(); \ + FieldMask mask; \ + mask.add_paths(#field_name); \ + dst.Clear(); \ + FieldMaskUtil::MergeMessageTo(src, mask, options, &dst); \ + EXPECT_EQ(tmp.DebugString(), dst.DebugString()); \ + } + TEST_MERGE_ONE_FIELD(optional_nested_message) + TEST_MERGE_ONE_FIELD(optional_foreign_message) + TEST_MERGE_ONE_FIELD(optional_import_message) + + TEST_MERGE_ONE_FIELD(repeated_int32) + TEST_MERGE_ONE_FIELD(repeated_int64) + TEST_MERGE_ONE_FIELD(repeated_uint32) + TEST_MERGE_ONE_FIELD(repeated_uint64) + TEST_MERGE_ONE_FIELD(repeated_sint32) + TEST_MERGE_ONE_FIELD(repeated_sint64) + TEST_MERGE_ONE_FIELD(repeated_fixed32) + TEST_MERGE_ONE_FIELD(repeated_fixed64) + TEST_MERGE_ONE_FIELD(repeated_sfixed32) + TEST_MERGE_ONE_FIELD(repeated_sfixed64) + TEST_MERGE_ONE_FIELD(repeated_float) + TEST_MERGE_ONE_FIELD(repeated_double) + TEST_MERGE_ONE_FIELD(repeated_bool) + TEST_MERGE_ONE_FIELD(repeated_string) + TEST_MERGE_ONE_FIELD(repeated_bytes) + TEST_MERGE_ONE_FIELD(repeated_nested_message) + TEST_MERGE_ONE_FIELD(repeated_foreign_message) + TEST_MERGE_ONE_FIELD(repeated_import_message) + TEST_MERGE_ONE_FIELD(repeated_nested_enum) + TEST_MERGE_ONE_FIELD(repeated_foreign_enum) + TEST_MERGE_ONE_FIELD(repeated_import_enum) +#undef TEST_MERGE_ONE_FIELD + + // Test merge nested fields. + NestedTestAllTypes nested_src, nested_dst; + nested_src.mutable_child()->mutable_payload()->set_optional_int32(1234); + nested_src.mutable_child() + ->mutable_child() + ->mutable_payload() + ->set_optional_int32(5678); + FieldMask mask; + FieldMaskUtil::FromString("child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(0, nested_dst.child().child().payload().optional_int32()); + + FieldMaskUtil::FromString("child.child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(5678, nested_dst.child().child().payload().optional_int32()); + + nested_dst.Clear(); + FieldMaskUtil::FromString("child.child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(0, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(5678, nested_dst.child().child().payload().optional_int32()); + + nested_dst.Clear(); + FieldMaskUtil::FromString("child", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(5678, nested_dst.child().child().payload().optional_int32()); + + // Test MergeOptions. + + nested_dst.Clear(); + nested_dst.mutable_child()->mutable_payload()->set_optional_int64(4321); + // Message fields will be merged by default. + FieldMaskUtil::FromString("child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(4321, nested_dst.child().payload().optional_int64()); + // Change the behavior to replace message fields. + options.set_replace_message_fields(true); + FieldMaskUtil::FromString("child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(0, nested_dst.child().payload().optional_int64()); + + // By default, fields missing in source are not cleared in destination. + options.set_replace_message_fields(false); + nested_dst.mutable_payload(); + EXPECT_TRUE(nested_dst.has_payload()); + FieldMaskUtil::FromString("payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_TRUE(nested_dst.has_payload()); + // But they are cleared when replacing message fields. + options.set_replace_message_fields(true); + nested_dst.Clear(); + nested_dst.mutable_payload(); + FieldMaskUtil::FromString("payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_FALSE(nested_dst.has_payload()); + + nested_src.mutable_payload()->add_repeated_int32(1234); + nested_dst.mutable_payload()->add_repeated_int32(5678); + // Repeated fields will be appended by default. + FieldMaskUtil::FromString("payload.repeated_int32", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + ASSERT_EQ(2, nested_dst.payload().repeated_int32_size()); + EXPECT_EQ(5678, nested_dst.payload().repeated_int32(0)); + EXPECT_EQ(1234, nested_dst.payload().repeated_int32(1)); + // Change the behavior to replace repeated fields. + options.set_replace_repeated_fields(true); + FieldMaskUtil::FromString("payload.repeated_int32", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + ASSERT_EQ(1, nested_dst.payload().repeated_int32_size()); + EXPECT_EQ(1234, nested_dst.payload().repeated_int32(0)); +} + +TEST(FieldMaskUtilTest, TrimMessage) { +#define TEST_TRIM_ONE_PRIMITIVE_FIELD(field_name) \ + { \ + TestAllTypes msg; \ + TestUtil::SetAllFields(&msg); \ + TestAllTypes tmp; \ + tmp.set_##field_name(msg.field_name()); \ + FieldMask mask; \ + mask.add_paths(#field_name); \ + FieldMaskUtil::TrimMessage(mask, &msg); \ + EXPECT_EQ(tmp.DebugString(), msg.DebugString()); \ + } + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_int32) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_int64) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_uint32) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_uint64) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_sint32) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_sint64) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_fixed32) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_fixed64) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_sfixed32) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_sfixed64) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_float) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_double) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_bool) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_string) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_bytes) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_nested_enum) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_foreign_enum) + TEST_TRIM_ONE_PRIMITIVE_FIELD(optional_import_enum) +#undef TEST_TRIM_ONE_PRIMITIVE_FIELD + +#define TEST_TRIM_ONE_FIELD(field_name) \ + { \ + TestAllTypes msg; \ + TestUtil::SetAllFields(&msg); \ + TestAllTypes tmp; \ + *tmp.mutable_##field_name() = msg.field_name(); \ + FieldMask mask; \ + mask.add_paths(#field_name); \ + FieldMaskUtil::TrimMessage(mask, &msg); \ + EXPECT_EQ(tmp.DebugString(), msg.DebugString()); \ + } + TEST_TRIM_ONE_FIELD(optional_nested_message) + TEST_TRIM_ONE_FIELD(optional_foreign_message) + TEST_TRIM_ONE_FIELD(optional_import_message) + + TEST_TRIM_ONE_FIELD(repeated_int32) + TEST_TRIM_ONE_FIELD(repeated_int64) + TEST_TRIM_ONE_FIELD(repeated_uint32) + TEST_TRIM_ONE_FIELD(repeated_uint64) + TEST_TRIM_ONE_FIELD(repeated_sint32) + TEST_TRIM_ONE_FIELD(repeated_sint64) + TEST_TRIM_ONE_FIELD(repeated_fixed32) + TEST_TRIM_ONE_FIELD(repeated_fixed64) + TEST_TRIM_ONE_FIELD(repeated_sfixed32) + TEST_TRIM_ONE_FIELD(repeated_sfixed64) + TEST_TRIM_ONE_FIELD(repeated_float) + TEST_TRIM_ONE_FIELD(repeated_double) + TEST_TRIM_ONE_FIELD(repeated_bool) + TEST_TRIM_ONE_FIELD(repeated_string) + TEST_TRIM_ONE_FIELD(repeated_bytes) + TEST_TRIM_ONE_FIELD(repeated_nested_message) + TEST_TRIM_ONE_FIELD(repeated_foreign_message) + TEST_TRIM_ONE_FIELD(repeated_import_message) + TEST_TRIM_ONE_FIELD(repeated_nested_enum) + TEST_TRIM_ONE_FIELD(repeated_foreign_enum) + TEST_TRIM_ONE_FIELD(repeated_import_enum) +#undef TEST_TRIM_ONE_FIELD + + // Test trim nested fields. + NestedTestAllTypes nested_msg; + nested_msg.mutable_child()->mutable_payload()->set_optional_int32(1234); + nested_msg.mutable_child() + ->mutable_child() + ->mutable_payload() + ->set_optional_int32(5678); + NestedTestAllTypes trimmed_msg(nested_msg); + FieldMask mask; + FieldMaskUtil::FromString("child.payload", &mask); + FieldMaskUtil::TrimMessage(mask, &trimmed_msg); + EXPECT_EQ(1234, trimmed_msg.child().payload().optional_int32()); + EXPECT_EQ(0, trimmed_msg.child().child().payload().optional_int32()); + + trimmed_msg = nested_msg; + FieldMaskUtil::FromString("child.child.payload", &mask); + FieldMaskUtil::TrimMessage(mask, &trimmed_msg); + EXPECT_EQ(0, trimmed_msg.child().payload().optional_int32()); + EXPECT_EQ(5678, trimmed_msg.child().child().payload().optional_int32()); + + trimmed_msg = nested_msg; + FieldMaskUtil::FromString("child", &mask); + FieldMaskUtil::TrimMessage(mask, &trimmed_msg); + EXPECT_EQ(1234, trimmed_msg.child().payload().optional_int32()); + EXPECT_EQ(5678, trimmed_msg.child().child().payload().optional_int32()); + + trimmed_msg = nested_msg; + FieldMaskUtil::FromString("child.child", &mask); + FieldMaskUtil::TrimMessage(mask, &trimmed_msg); + EXPECT_EQ(0, trimmed_msg.child().payload().optional_int32()); + EXPECT_EQ(5678, trimmed_msg.child().child().payload().optional_int32()); + + // Verify than an empty FieldMask trims nothing + TestAllTypes all_types_msg; + TestUtil::SetAllFields(&all_types_msg); + TestAllTypes trimmed_all_types(all_types_msg); + FieldMask empty_mask; + FieldMaskUtil::TrimMessage(empty_mask, &trimmed_all_types); + EXPECT_EQ(trimmed_all_types.DebugString(), all_types_msg.DebugString()); + + // Test trim required fields with keep_required_fields is set true. + FieldMaskUtil::TrimOptions options; + TestRequired required_msg_1; + required_msg_1.set_a(1234); + required_msg_1.set_b(3456); + required_msg_1.set_c(5678); + TestRequired trimmed_required_msg_1(required_msg_1); + FieldMaskUtil::FromString("dummy2", &mask); + options.set_keep_required_fields(true); + FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_1, options); + EXPECT_EQ(trimmed_required_msg_1.DebugString(), required_msg_1.DebugString()); + + // Test trim required fields with keep_required_fields is set false. + required_msg_1.clear_a(); + required_msg_1.clear_b(); + required_msg_1.clear_c(); + options.set_keep_required_fields(false); + FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_1, options); + EXPECT_EQ(trimmed_required_msg_1.DebugString(), required_msg_1.DebugString()); + + // Test trim required message with keep_required_fields is set true. + TestRequiredMessage required_msg_2; + required_msg_2.mutable_optional_message()->set_a(1234); + required_msg_2.mutable_optional_message()->set_b(3456); + required_msg_2.mutable_optional_message()->set_c(5678); + required_msg_2.mutable_required_message()->set_a(1234); + required_msg_2.mutable_required_message()->set_b(3456); + required_msg_2.mutable_required_message()->set_c(5678); + required_msg_2.mutable_required_message()->set_dummy2(7890); + TestRequired* repeated_msg = required_msg_2.add_repeated_message(); + repeated_msg->set_a(1234); + repeated_msg->set_b(3456); + repeated_msg->set_c(5678); + TestRequiredMessage trimmed_required_msg_2(required_msg_2); + FieldMaskUtil::FromString("optional_message.dummy2", &mask); + options.set_keep_required_fields(true); + required_msg_2.clear_repeated_message(); + required_msg_2.mutable_required_message()->clear_dummy2(); + FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_2, options); + EXPECT_EQ(trimmed_required_msg_2.DebugString(), required_msg_2.DebugString()); + + FieldMaskUtil::FromString("required_message", &mask); + required_msg_2.mutable_required_message()->set_dummy2(7890); + trimmed_required_msg_2.mutable_required_message()->set_dummy2(7890); + required_msg_2.clear_optional_message(); + FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_2, options); + EXPECT_EQ(trimmed_required_msg_2.DebugString(), required_msg_2.DebugString()); + + // Test trim required message with keep_required_fields is set false. + FieldMaskUtil::FromString("required_message.dummy2", &mask); + required_msg_2.mutable_required_message()->clear_a(); + required_msg_2.mutable_required_message()->clear_b(); + required_msg_2.mutable_required_message()->clear_c(); + options.set_keep_required_fields(false); + FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_2, options); + EXPECT_EQ(trimmed_required_msg_2.DebugString(), required_msg_2.DebugString()); + + // Verify that trimming an empty message has no effect. In particular, fields + // mentioned in the field mask should not be created or changed. + TestAllTypes empty_msg; + FieldMaskUtil::FromString( + "optional_int32,optional_bytes,optional_nested_message.bb", &mask); + FieldMaskUtil::TrimMessage(mask, &empty_msg); + EXPECT_FALSE(empty_msg.has_optional_int32()); + EXPECT_FALSE(empty_msg.has_optional_bytes()); + EXPECT_FALSE(empty_msg.has_optional_nested_message()); + + // Verify trimming of oneof fields. This should work as expected even if + // multiple elements of the same oneof are included in the FieldMask. + TestAllTypes oneof_msg; + oneof_msg.set_oneof_uint32(11); + FieldMaskUtil::FromString("oneof_uint32,oneof_nested_message.bb", &mask); + FieldMaskUtil::TrimMessage(mask, &oneof_msg); + EXPECT_EQ(11, oneof_msg.oneof_uint32()); +} + +TEST(FieldMaskUtilTest, TrimMessageReturnValue) { + FieldMask mask; + TestAllTypes trimed_msg; + TestAllTypes default_msg; + + // Field mask on optional field. + FieldMaskUtil::FromString("optional_int32", &mask); + + // Verify that if a message is updated by FieldMaskUtil::TrimMessage(), the + // function returns true. + // Test on primary field. + trimed_msg.set_optional_string("abc"); + EXPECT_TRUE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.DebugString(), default_msg.DebugString()); + trimed_msg.Clear(); + + // Test on repeated primary field. + trimed_msg.add_repeated_string("abc"); + trimed_msg.add_repeated_string("def"); + EXPECT_TRUE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.DebugString(), default_msg.DebugString()); + trimed_msg.Clear(); + + // Test on nested message. + trimed_msg.mutable_optional_nested_message()->set_bb(123); + EXPECT_TRUE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.DebugString(), default_msg.DebugString()); + trimed_msg.Clear(); + + // Test on repeated nested message. + trimed_msg.add_repeated_nested_message()->set_bb(123); + trimed_msg.add_repeated_nested_message()->set_bb(456); + EXPECT_TRUE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.DebugString(), default_msg.DebugString()); + trimed_msg.Clear(); + + // Test on oneof field. + trimed_msg.set_oneof_uint32(123); + EXPECT_TRUE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.DebugString(), default_msg.DebugString()); + trimed_msg.Clear(); + + // If there is no field set other then those whitelisted, + // FieldMaskUtil::TrimMessage() should return false. + trimed_msg.set_optional_int32(123); + EXPECT_FALSE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.optional_int32(), 123); + trimed_msg.Clear(); + + // Field mask on repeated field. + FieldMaskUtil::FromString("repeated_string", &mask); + trimed_msg.add_repeated_string("abc"); + trimed_msg.add_repeated_string("def"); + EXPECT_FALSE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.repeated_string(0), "abc"); + EXPECT_EQ(trimed_msg.repeated_string(1), "def"); + trimed_msg.Clear(); + + // Field mask on nested message. + FieldMaskUtil::FromString("optional_nested_message.bb", &mask); + trimed_msg.mutable_optional_nested_message()->set_bb(123); + EXPECT_FALSE(FieldMaskUtil::TrimMessage(mask, &trimed_msg)); + EXPECT_EQ(trimed_msg.optional_nested_message().bb(), 123); + trimed_msg.Clear(); + + // TODO(b/32443320): field mask on repeated nested message is not yet + // supported. +} + + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/constants.h b/NorthstarDedicatedTest/include/protobuf/util/internal/constants.h new file mode 100644 index 00000000..c43aaa42 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/constants.h @@ -0,0 +1,101 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_CONSTANTS_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_CONSTANTS_H__ + +#include <cstdint> + +#include <stubs/common.h> + +// This file contains constants used by //net/proto2/util/converter. + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +// Prefix for type URLs. +const char kTypeServiceBaseUrl[] = "type.googleapis.com"; + +// Format string for RFC3339 timestamp formatting. +const char kRfc3339TimeFormat[] = "%E4Y-%m-%dT%H:%M:%S"; + +// Same as above, but the year value is not zero-padded i.e. this accepts +// timestamps like "1-01-0001T23:59:59Z" instead of "0001-01-0001T23:59:59Z". +const char kRfc3339TimeFormatNoPadding[] = "%Y-%m-%dT%H:%M:%S"; + +// Minimum seconds allowed in a google.protobuf.Timestamp value. +const int64_t kTimestampMinSeconds = -62135596800LL; + +// Maximum seconds allowed in a google.protobuf.Timestamp value. +const int64_t kTimestampMaxSeconds = 253402300799LL; + +// Minimum seconds allowed in a google.protobuf.Duration value. +const int64_t kDurationMinSeconds = -315576000000LL; + +// Maximum seconds allowed in a google.protobuf.Duration value. +const int64_t kDurationMaxSeconds = 315576000000LL; + +// Nano seconds in a second. +const int32_t kNanosPerSecond = 1000000000; + +// Type url representing NULL values in google.protobuf.Struct type. +const char kStructNullValueTypeUrl[] = + "type.googleapis.com/google.protobuf.NullValue"; + +// Type string for google.protobuf.Struct +const char kStructType[] = "google.protobuf.Struct"; + +// Type string for struct.proto's google.protobuf.Value value type. +const char kStructValueType[] = "google.protobuf.Value"; + +// Type string for struct.proto's google.protobuf.ListValue value type. +const char kStructListValueType[] = "google.protobuf.ListValue"; + +// Type string for google.protobuf.Timestamp +const char kTimestampType[] = "google.protobuf.Timestamp"; + +// Type string for google.protobuf.Duration +const char kDurationType[] = "google.protobuf.Duration"; + +// Type URL for struct value type google.protobuf.Value +const char kStructValueTypeUrl[] = "type.googleapis.com/google.protobuf.Value"; + +// Type string for google.protobuf.Any +const char kAnyType[] = "google.protobuf.Any"; + +// The protobuf option name of jspb.message_id; +const char kOptionJspbMessageId[] = "jspb.message_id"; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_CONSTANTS_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/datapiece.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/datapiece.cc new file mode 100644 index 00000000..b32b37a8 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/datapiece.cc @@ -0,0 +1,423 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/datapiece.h> + +#include <cmath> +#include <cstdint> +#include <limits> + +#include <struct.pb.h> +#include <type.pb.h> +#include <descriptor.h> +#include <util/internal/utility.h> +#include <stubs/status.h> +#include <stubs/strutil.h> +#include <stubs/mathutil.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using util::Status; + +namespace { + +template <typename To, typename From> +util::StatusOr<To> ValidateNumberConversion(To after, From before) { + if (after == before && + MathUtil::Sign<From>(before) == MathUtil::Sign<To>(after)) { + return after; + } else { + return util::InvalidArgumentError( + std::is_integral<From>::value ? ValueAsString(before) + : std::is_same<From, double>::value ? DoubleAsString(before) + : FloatAsString(before)); + } +} + +// For general conversion between +// int32, int64, uint32, uint64, double and float +// except conversion between double and float. +template <typename To, typename From> +util::StatusOr<To> NumberConvertAndCheck(From before) { + if (std::is_same<From, To>::value) return before; + + To after = static_cast<To>(before); + return ValidateNumberConversion(after, before); +} + +// For conversion to integer types (int32, int64, uint32, uint64) from floating +// point types (double, float) only. +template <typename To, typename From> +util::StatusOr<To> FloatingPointToIntConvertAndCheck(From before) { + if (std::is_same<From, To>::value) return before; + + To after = static_cast<To>(before); + return ValidateNumberConversion(after, before); +} + +// For conversion between double and float only. +util::StatusOr<double> FloatToDouble(float before) { + // Casting float to double should just work as double has more precision + // than float. + return static_cast<double>(before); +} + +util::StatusOr<float> DoubleToFloat(double before) { + if (std::isnan(before)) { + return std::numeric_limits<float>::quiet_NaN(); + } else if (!std::isfinite(before)) { + // Converting a double +inf/-inf to float should just work. + return static_cast<float>(before); + } else if (before > std::numeric_limits<float>::max() || + before < -std::numeric_limits<float>::max()) { + // Double value outside of the range of float. + return util::InvalidArgumentError(DoubleAsString(before)); + } else { + return static_cast<float>(before); + } +} + +} // namespace + +util::StatusOr<int32_t> DataPiece::ToInt32() const { + if (type_ == TYPE_STRING) + return StringToNumber<int32_t>(safe_strto32); + + if (type_ == TYPE_DOUBLE) + return FloatingPointToIntConvertAndCheck<int32_t, double>(double_); + + if (type_ == TYPE_FLOAT) + return FloatingPointToIntConvertAndCheck<int32_t, float>(float_); + + return GenericConvert<int32_t>(); +} + +util::StatusOr<uint32_t> DataPiece::ToUint32() const { + if (type_ == TYPE_STRING) + return StringToNumber<uint32_t>(safe_strtou32); + + if (type_ == TYPE_DOUBLE) + return FloatingPointToIntConvertAndCheck<uint32_t, double>(double_); + + if (type_ == TYPE_FLOAT) + return FloatingPointToIntConvertAndCheck<uint32_t, float>(float_); + + return GenericConvert<uint32_t>(); +} + +util::StatusOr<int64_t> DataPiece::ToInt64() const { + if (type_ == TYPE_STRING) + return StringToNumber<int64_t>(safe_strto64); + + if (type_ == TYPE_DOUBLE) + return FloatingPointToIntConvertAndCheck<int64_t, double>(double_); + + if (type_ == TYPE_FLOAT) + return FloatingPointToIntConvertAndCheck<int64_t, float>(float_); + + return GenericConvert<int64_t>(); +} + +util::StatusOr<uint64_t> DataPiece::ToUint64() const { + if (type_ == TYPE_STRING) + return StringToNumber<uint64_t>(safe_strtou64); + + if (type_ == TYPE_DOUBLE) + return FloatingPointToIntConvertAndCheck<uint64_t, double>(double_); + + if (type_ == TYPE_FLOAT) + return FloatingPointToIntConvertAndCheck<uint64_t, float>(float_); + + return GenericConvert<uint64_t>(); +} + +util::StatusOr<double> DataPiece::ToDouble() const { + if (type_ == TYPE_FLOAT) { + return FloatToDouble(float_); + } + if (type_ == TYPE_STRING) { + if (str_ == "Infinity") return std::numeric_limits<double>::infinity(); + if (str_ == "-Infinity") return -std::numeric_limits<double>::infinity(); + if (str_ == "NaN") return std::numeric_limits<double>::quiet_NaN(); + util::StatusOr<double> value = StringToNumber<double>(safe_strtod); + if (value.ok() && !std::isfinite(value.value())) { + // safe_strtod converts out-of-range values to +inf/-inf, but we want + // to report them as errors. + return util::InvalidArgumentError(StrCat("\"", str_, "\"")); + } else { + return value; + } + } + return GenericConvert<double>(); +} + +util::StatusOr<float> DataPiece::ToFloat() const { + if (type_ == TYPE_DOUBLE) { + return DoubleToFloat(double_); + } + if (type_ == TYPE_STRING) { + if (str_ == "Infinity") return std::numeric_limits<float>::infinity(); + if (str_ == "-Infinity") return -std::numeric_limits<float>::infinity(); + if (str_ == "NaN") return std::numeric_limits<float>::quiet_NaN(); + // SafeStrToFloat() is used instead of safe_strtof() because the later + // does not fail on inputs like SimpleDtoa(DBL_MAX). + return StringToNumber<float>(SafeStrToFloat); + } + return GenericConvert<float>(); +} + +util::StatusOr<bool> DataPiece::ToBool() const { + switch (type_) { + case TYPE_BOOL: + return bool_; + case TYPE_STRING: + return StringToNumber<bool>(safe_strtob); + default: + return util::InvalidArgumentError( + ValueAsStringOrDefault("Wrong type. Cannot convert to Bool.")); + } +} + +util::StatusOr<std::string> DataPiece::ToString() const { + switch (type_) { + case TYPE_STRING: + return std::string(str_); + case TYPE_BYTES: { + std::string base64; + Base64Escape(str_, &base64); + return base64; + } + default: + return util::InvalidArgumentError( + ValueAsStringOrDefault("Cannot convert to string.")); + } +} + +std::string DataPiece::ValueAsStringOrDefault( + StringPiece default_string) const { + switch (type_) { + case TYPE_INT32: + return StrCat(i32_); + case TYPE_INT64: + return StrCat(i64_); + case TYPE_UINT32: + return StrCat(u32_); + case TYPE_UINT64: + return StrCat(u64_); + case TYPE_DOUBLE: + return DoubleAsString(double_); + case TYPE_FLOAT: + return FloatAsString(float_); + case TYPE_BOOL: + return SimpleBtoa(bool_); + case TYPE_STRING: + return StrCat("\"", str_.ToString(), "\""); + case TYPE_BYTES: { + std::string base64; + WebSafeBase64Escape(str_, &base64); + return StrCat("\"", base64, "\""); + } + case TYPE_NULL: + return "null"; + default: + return std::string(default_string); + } +} + +util::StatusOr<std::string> DataPiece::ToBytes() const { + if (type_ == TYPE_BYTES) return str_.ToString(); + if (type_ == TYPE_STRING) { + std::string decoded; + if (!DecodeBase64(str_, &decoded)) { + return util::InvalidArgumentError( + ValueAsStringOrDefault("Invalid data in input.")); + } + return decoded; + } else { + return util::InvalidArgumentError(ValueAsStringOrDefault( + "Wrong type. Only String or Bytes can be converted to Bytes.")); + } +} + +util::StatusOr<int> DataPiece::ToEnum(const google::protobuf::Enum* enum_type, + bool use_lower_camel_for_enums, + bool case_insensitive_enum_parsing, + bool ignore_unknown_enum_values, + bool* is_unknown_enum_value) const { + if (type_ == TYPE_NULL) return google::protobuf::NULL_VALUE; + + if (type_ == TYPE_STRING) { + // First try the given value as a name. + std::string enum_name = std::string(str_); + const google::protobuf::EnumValue* value = + FindEnumValueByNameOrNull(enum_type, enum_name); + if (value != nullptr) return value->number(); + + // Check if int version of enum is sent as string. + util::StatusOr<int32_t> int_value = ToInt32(); + if (int_value.ok()) { + if (const google::protobuf::EnumValue* enum_value = + FindEnumValueByNumberOrNull(enum_type, int_value.value())) { + return enum_value->number(); + } + } + + // Next try a normalized name. + bool should_normalize_enum = + case_insensitive_enum_parsing || use_lower_camel_for_enums; + if (should_normalize_enum) { + for (std::string::iterator it = enum_name.begin(); it != enum_name.end(); + ++it) { + *it = *it == '-' ? '_' : ascii_toupper(*it); + } + value = FindEnumValueByNameOrNull(enum_type, enum_name); + if (value != nullptr) return 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 != nullptr) return value->number(); + } + + // If ignore_unknown_enum_values is true an unknown enum value is ignored. + if (ignore_unknown_enum_values) { + *is_unknown_enum_value = true; + if (enum_type->enumvalue_size() > 0) { + return enum_type->enumvalue(0).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 util::InvalidArgumentError( + ValueAsStringOrDefault("Cannot find enum with given value.")); +} + +template <typename To> +util::StatusOr<To> DataPiece::GenericConvert() const { + switch (type_) { + case TYPE_INT32: + return NumberConvertAndCheck<To, int32_t>(i32_); + case TYPE_INT64: + return NumberConvertAndCheck<To, int64_t>(i64_); + case TYPE_UINT32: + return NumberConvertAndCheck<To, uint32_t>(u32_); + case TYPE_UINT64: + return NumberConvertAndCheck<To, uint64_t>(u64_); + case TYPE_DOUBLE: + return NumberConvertAndCheck<To, double>(double_); + case TYPE_FLOAT: + return NumberConvertAndCheck<To, float>(float_); + default: // TYPE_ENUM, TYPE_STRING, TYPE_CORD, TYPE_BOOL + return util::InvalidArgumentError(ValueAsStringOrDefault( + "Wrong type. Bool, Enum, String and Cord not supported in " + "GenericConvert.")); + } +} + +template <typename To> +util::StatusOr<To> DataPiece::StringToNumber(bool (*func)(StringPiece, + To*)) const { + if (str_.size() > 0 && (str_[0] == ' ' || str_[str_.size() - 1] == ' ')) { + return util::InvalidArgumentError(StrCat("\"", str_, "\"")); + } + To result; + if (func(str_, &result)) return result; + return util::InvalidArgumentError( + StrCat("\"", std::string(str_), "\"")); +} + +bool DataPiece::DecodeBase64(StringPiece src, std::string* dest) const { + // Try web-safe decode first, if it fails, try the non-web-safe decode. + if (WebSafeBase64Unescape(src, dest)) { + if (use_strict_base64_decoding_) { + // In strict mode, check if the escaped version gives us the same value as + // unescaped. + std::string encoded; + // WebSafeBase64Escape does no padding by default. + WebSafeBase64Escape(*dest, &encoded); + // Remove trailing padding '=' characters before comparison. + StringPiece src_no_padding = StringPiece(src).substr( + 0, HasSuffixString(src, "=") ? src.find_last_not_of('=') + 1 + : src.length()); + return encoded == src_no_padding; + } + return true; + } + + if (Base64Unescape(src, dest)) { + if (use_strict_base64_decoding_) { + std::string encoded; + Base64Escape(reinterpret_cast<const unsigned char*>(dest->data()), + dest->length(), &encoded, false); + StringPiece src_no_padding = StringPiece(src).substr( + 0, HasSuffixString(src, "=") ? src.find_last_not_of('=') + 1 + : src.length()); + return encoded == src_no_padding; + } + return true; + } + + return false; +} + +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: + case TYPE_UINT32: + case TYPE_UINT64: + case TYPE_DOUBLE: + case TYPE_FLOAT: + case TYPE_BOOL: + case TYPE_ENUM: + case TYPE_NULL: + case TYPE_BYTES: + case TYPE_STRING: { + str_ = other.str_; + break; + } + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/datapiece.h b/NorthstarDedicatedTest/include/protobuf/util/internal/datapiece.h new file mode 100644 index 00000000..aa13ae02 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/datapiece.h @@ -0,0 +1,218 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_DATAPIECE_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_DATAPIECE_H__ + +#include <cstdint> +#include <string> + +#include <stubs/common.h> +#include <stubs/logging.h> +#include <type.pb.h> +#include <stubs/statusor.h> +#include <stubs/strutil.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +class ProtoWriter; + +// Container for a single piece of data together with its data type. +// +// For primitive types (int32, int64, uint32, uint64, double, float, bool), +// the data is stored by value. +// +// For string, a StringPiece is stored. For Cord, a pointer to Cord is stored. +// Just like StringPiece, the DataPiece class does not own the storage for +// the actual string or Cord, so it is the user's responsibility to guarantee +// that the underlying storage is still valid when the DataPiece is accessed. +class PROTOBUF_EXPORT DataPiece { + public: + // Identifies data type of the value. + // These are the types supported by DataPiece. + enum Type { + TYPE_INT32 = 1, + TYPE_INT64 = 2, + TYPE_UINT32 = 3, + TYPE_UINT64 = 4, + TYPE_DOUBLE = 5, + TYPE_FLOAT = 6, + TYPE_BOOL = 7, + TYPE_ENUM = 8, + TYPE_STRING = 9, + TYPE_BYTES = 10, + TYPE_NULL = 11, // explicit NULL type + }; + + // Constructors and Destructor + explicit DataPiece(const int32_t value) + : type_(TYPE_INT32), i32_(value), use_strict_base64_decoding_(false) {} + explicit DataPiece(const int64_t value) + : type_(TYPE_INT64), i64_(value), use_strict_base64_decoding_(false) {} + explicit DataPiece(const uint32_t value) + : type_(TYPE_UINT32), u32_(value), use_strict_base64_decoding_(false) {} + explicit DataPiece(const uint64_t 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_(value), + use_strict_base64_decoding_(use_strict_base64_decoding) {} + // Constructor for bytes. The second parameter is not used. + DataPiece(StringPiece value, bool /*dummy*/, bool use_strict_base64_decoding) + : type_(TYPE_BYTES), + str_(value), + use_strict_base64_decoding_(use_strict_base64_decoding) {} + + DataPiece(const DataPiece& r) : type_(r.type_) { InternalCopy(r); } + + DataPiece& operator=(const DataPiece& x) { + InternalCopy(x); + return *this; + } + + static DataPiece NullData() { return DataPiece(TYPE_NULL, 0); } + + virtual ~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_; + } + + + // Parses, casts or converts the value stored in the DataPiece into an int32. + util::StatusOr<int32_t> ToInt32() const; + + // Parses, casts or converts the value stored in the DataPiece into a uint32. + util::StatusOr<uint32_t> ToUint32() const; + + // Parses, casts or converts the value stored in the DataPiece into an int64. + util::StatusOr<int64_t> ToInt64() const; + + // Parses, casts or converts the value stored in the DataPiece into a uint64. + util::StatusOr<uint64_t> ToUint64() const; + + // Parses, casts or converts the value stored in the DataPiece into a double. + util::StatusOr<double> ToDouble() const; + + // Parses, casts or converts the value stored in the DataPiece into a float. + util::StatusOr<float> ToFloat() const; + + // Parses, casts or converts the value stored in the DataPiece into a bool. + util::StatusOr<bool> ToBool() const; + + // Parses, casts or converts the value stored in the DataPiece into a string. + util::StatusOr<std::string> ToString() const; + + // Tries to convert the value contained in this datapiece to string. If the + // conversion fails, it returns the default_string. + std::string ValueAsStringOrDefault(StringPiece default_string) const; + + util::StatusOr<std::string> ToBytes() const; + + private: + friend class ProtoWriter; + + // Disallow implicit constructor. + DataPiece(); + + // Helper to create NULL or ENUM types. + DataPiece(Type type, int32_t val) + : type_(type), i32_(val), use_strict_base64_decoding_(false) {} + + // Same as the ToEnum() method above but with additional flag to ignore + // unknown enum values. + util::StatusOr<int> ToEnum(const google::protobuf::Enum* enum_type, + bool use_lower_camel_for_enums, + bool case_insensitive_enum_parsing, + bool ignore_unknown_enum_values, + bool* is_unknown_enum_value) const; + + // For numeric conversion between + // int32, int64, uint32, uint64, double, float and bool + template <typename To> + util::StatusOr<To> GenericConvert() const; + + // For conversion from string to + // int32, int64, uint32, uint64, double, float and bool + template <typename To> + util::StatusOr<To> StringToNumber(bool (*func)(StringPiece, To*)) const; + + // Decodes a base64 string. Returns true on success. + bool DecodeBase64(StringPiece src, std::string* dest) const; + + // Helper function to initialize this DataPiece with 'other'. + void InternalCopy(const DataPiece& other); + + // Data type for this piece of data. + Type type_; + + // Stored piece of data. + union { + int32_t i32_; + int64_t i64_; + uint32_t u32_; + uint64_t u64_; + double double_; + float float_; + bool bool_; + StringPiece str_; + }; + + // Uses a stricter version of base64 decoding for byte fields. + bool use_strict_base64_decoding_; +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_DATAPIECE_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter.cc new file mode 100644 index 00000000..d3ac429b --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter.cc @@ -0,0 +1,642 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/default_value_objectwriter.h> + +#include <cstdint> +#include <unordered_map> + +#include <util/internal/constants.h> +#include <util/internal/utility.h> +#include <stubs/map_util.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +namespace { +// Helper function to convert string value to given data type by calling the +// passed converter function on the DataPiece created from "value" argument. +// If value is empty or if conversion fails, the default_value is returned. +template <typename T> +T ConvertTo(StringPiece value, + util::StatusOr<T> (DataPiece::*converter_fn)() const, + T default_value) { + if (value.empty()) return default_value; + util::StatusOr<T> result = (DataPiece(value, true).*converter_fn)(); + return result.ok() ? result.value() : default_value; +} +} // namespace + +DefaultValueObjectWriter::DefaultValueObjectWriter( + TypeResolver* type_resolver, const google::protobuf::Type& type, + ObjectWriter* ow) + : typeinfo_(TypeInfo::NewTypeInfo(type_resolver)), + own_typeinfo_(true), + type_(type), + current_(nullptr), + root_(nullptr), + suppress_empty_list_(false), + preserve_proto_field_names_(false), + use_ints_for_enums_(false), + ow_(ow) {} + +DefaultValueObjectWriter::~DefaultValueObjectWriter() { + if (own_typeinfo_) { + delete typeinfo_; + } +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderBool( + StringPiece name, bool value) { + if (current_ == nullptr) { + ow_->RenderBool(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderInt32( + StringPiece name, int32_t value) { + if (current_ == nullptr) { + ow_->RenderInt32(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderUint32( + StringPiece name, uint32_t value) { + if (current_ == nullptr) { + ow_->RenderUint32(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderInt64( + StringPiece name, int64_t value) { + if (current_ == nullptr) { + ow_->RenderInt64(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderUint64( + StringPiece name, uint64_t value) { + if (current_ == nullptr) { + ow_->RenderUint64(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderDouble( + StringPiece name, double value) { + if (current_ == nullptr) { + ow_->RenderDouble(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderFloat( + StringPiece name, float value) { + if (current_ == nullptr) { + ow_->RenderBool(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderString( + StringPiece name, StringPiece value) { + if (current_ == nullptr) { + ow_->RenderString(name, value); + } else { + // Since StringPiece is essentially a pointer, takes a copy of "value" to + // avoid ownership issues. + string_values_.emplace_back(new std::string(value)); + RenderDataPiece(name, DataPiece(*string_values_.back(), true)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderBytes( + StringPiece name, StringPiece value) { + if (current_ == nullptr) { + ow_->RenderBytes(name, value); + } else { + // Since StringPiece is essentially a pointer, takes a copy of "value" to + // avoid ownership issues. + string_values_.emplace_back(new std::string(value)); + RenderDataPiece(name, DataPiece(*string_values_.back(), false, true)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderNull( + StringPiece name) { + if (current_ == nullptr) { + ow_->RenderNull(name); + } else { + RenderDataPiece(name, DataPiece::NullData()); + } + return this; +} + +void DefaultValueObjectWriter::RegisterFieldScrubCallBack( + FieldScrubCallBack field_scrub_callback) { + field_scrub_callback_ = std::move(field_scrub_callback); +} + +DefaultValueObjectWriter::Node* DefaultValueObjectWriter::CreateNewNode( + const std::string& name, const google::protobuf::Type* type, NodeKind kind, + const DataPiece& data, bool is_placeholder, + const std::vector<std::string>& path, bool suppress_empty_list, + bool preserve_proto_field_names, bool use_ints_for_enums, + FieldScrubCallBack field_scrub_callback) { + return new Node(name, type, kind, data, is_placeholder, path, + suppress_empty_list, preserve_proto_field_names, + use_ints_for_enums, std::move(field_scrub_callback)); +} + +DefaultValueObjectWriter::Node::Node( + const std::string& name, const google::protobuf::Type* type, NodeKind kind, + const DataPiece& data, bool is_placeholder, + const std::vector<std::string>& path, bool suppress_empty_list, + bool preserve_proto_field_names, bool use_ints_for_enums, + FieldScrubCallBack field_scrub_callback) + : name_(name), + type_(type), + kind_(kind), + is_any_(false), + data_(data), + is_placeholder_(is_placeholder), + path_(path), + suppress_empty_list_(suppress_empty_list), + preserve_proto_field_names_(preserve_proto_field_names), + use_ints_for_enums_(use_ints_for_enums), + field_scrub_callback_(std::move(field_scrub_callback)) {} + +DefaultValueObjectWriter::Node* DefaultValueObjectWriter::Node::FindChild( + StringPiece name) { + if (name.empty() || kind_ != OBJECT) { + return nullptr; + } + for (Node* child : children_) { + if (child->name() == name) { + return child; + } + } + return nullptr; +} + +void DefaultValueObjectWriter::Node::WriteTo(ObjectWriter* ow) { + if (kind_ == PRIMITIVE) { + ObjectWriter::RenderDataPieceTo(data_, name_, ow); + return; + } + + // Render maps. Empty maps are rendered as "{}". + if (kind_ == MAP) { + ow->StartObject(name_); + WriteChildren(ow); + ow->EndObject(); + return; + } + + // Write out lists. If we didn't have any list in response, write out empty + // list. + if (kind_ == LIST) { + // Suppress empty lists if requested. + if (suppress_empty_list_ && is_placeholder_) return; + + ow->StartList(name_); + WriteChildren(ow); + ow->EndList(); + return; + } + + // If is_placeholder_ = true, we didn't see this node in the response, so + // skip output. + if (is_placeholder_) return; + + ow->StartObject(name_); + WriteChildren(ow); + ow->EndObject(); +} + +void DefaultValueObjectWriter::Node::WriteChildren(ObjectWriter* ow) { + for (Node* child : children_) { + child->WriteTo(ow); + } +} + +const google::protobuf::Type* DefaultValueObjectWriter::Node::GetMapValueType( + const google::protobuf::Type& found_type, const TypeInfo* typeinfo) { + // If this field is a map, we should use the type of its "Value" as + // the type of the child node. + for (int i = 0; i < found_type.fields_size(); ++i) { + const google::protobuf::Field& sub_field = found_type.fields(i); + if (sub_field.number() != 2) { + continue; + } + if (sub_field.kind() != google::protobuf::Field::TYPE_MESSAGE) { + // This map's value type is not a message type. We don't need to + // get the field_type in this case. + break; + } + util::StatusOr<const google::protobuf::Type*> sub_type = + typeinfo->ResolveTypeUrl(sub_field.type_url()); + if (!sub_type.ok()) { + GOOGLE_LOG(WARNING) << "Cannot resolve type '" << sub_field.type_url() << "'."; + } else { + return sub_type.value(); + } + break; + } + return nullptr; +} + +void DefaultValueObjectWriter::Node::PopulateChildren( + const TypeInfo* typeinfo) { + // Ignores well known types that don't require automatically populating their + // primitive children. For type "Any", we only populate its children when the + // "@type" field is set. + // TODO(tsun): remove "kStructValueType" from the list. It's being checked + // now because of a bug in the tool-chain that causes the "oneof_index" + // of kStructValueType to not be set correctly. + if (type_ == nullptr || type_->name() == kAnyType || + type_->name() == kStructType || type_->name() == kTimestampType || + type_->name() == kDurationType || type_->name() == kStructValueType) { + return; + } + std::vector<Node*> new_children; + std::unordered_map<std::string, int> orig_children_map; + + // Creates a map of child nodes to speed up lookup. + for (int i = 0; i < children_.size(); ++i) { + InsertIfNotPresent(&orig_children_map, children_[i]->name_, i); + } + + for (int i = 0; i < type_->fields_size(); ++i) { + const google::protobuf::Field& field = type_->fields(i); + + // This code is checking if the field to be added to the tree should be + // scrubbed or not by calling the field_scrub_callback_ callback function. + std::vector<std::string> path; + if (!path_.empty()) { + path.insert(path.begin(), path_.begin(), path_.end()); + } + path.push_back(field.name()); + if (field_scrub_callback_ && field_scrub_callback_(path, &field)) { + continue; + } + + std::unordered_map<std::string, int>::iterator found = + orig_children_map.find(field.name()); + // If the child field has already been set, we just add it to the new list + // of children. + if (found != orig_children_map.end()) { + new_children.push_back(children_[found->second]); + children_[found->second] = nullptr; + continue; + } + + const google::protobuf::Type* field_type = nullptr; + bool is_map = false; + NodeKind kind = PRIMITIVE; + + if (field.kind() == google::protobuf::Field::TYPE_MESSAGE) { + kind = OBJECT; + util::StatusOr<const google::protobuf::Type*> found_result = + typeinfo->ResolveTypeUrl(field.type_url()); + if (!found_result.ok()) { + // "field" is of an unknown type. + GOOGLE_LOG(WARNING) << "Cannot resolve type '" << field.type_url() << "'."; + } else { + const google::protobuf::Type* found_type = found_result.value(); + is_map = IsMap(field, *found_type); + + if (!is_map) { + field_type = found_type; + } else { + // If this field is a map, we should use the type of its "Value" as + // the type of the child node. + field_type = GetMapValueType(*found_type, typeinfo); + kind = MAP; + } + } + } + + if (!is_map && + field.cardinality() == google::protobuf::Field::CARDINALITY_REPEATED) { + kind = LIST; + } + + // If oneof_index() != 0, the child field is part of a "oneof", which means + // the child field is optional and we shouldn't populate its default + // primitive value. + if (field.oneof_index() != 0 && kind == PRIMITIVE) continue; + + // If the child field is of primitive type, sets its data to the default + // value of its type. + std::unique_ptr<Node> child( + new Node(preserve_proto_field_names_ ? field.name() : field.json_name(), + field_type, kind, + kind == PRIMITIVE ? CreateDefaultDataPieceForField( + field, typeinfo, use_ints_for_enums_) + : DataPiece::NullData(), + true, path, suppress_empty_list_, preserve_proto_field_names_, + use_ints_for_enums_, field_scrub_callback_)); + new_children.push_back(child.release()); + } + // Adds all leftover nodes in children_ to the beginning of new_child. + for (int i = 0; i < children_.size(); ++i) { + if (children_[i] == nullptr) { + continue; + } + new_children.insert(new_children.begin(), children_[i]); + children_[i] = nullptr; + } + children_.swap(new_children); +} + +void DefaultValueObjectWriter::MaybePopulateChildrenOfAny(Node* node) { + // If this is an "Any" node with "@type" already given and no other children + // have been added, populates its children. + if (node != nullptr && node->is_any() && node->type() != nullptr && + node->type()->name() != kAnyType && node->number_of_children() == 1) { + node->PopulateChildren(typeinfo_); + } +} + +DataPiece DefaultValueObjectWriter::FindEnumDefault( + const google::protobuf::Field& field, const TypeInfo* typeinfo, + bool use_ints_for_enums) { + const google::protobuf::Enum* enum_type = + typeinfo->GetEnumByTypeUrl(field.type_url()); + if (!enum_type) { + GOOGLE_LOG(WARNING) << "Could not find enum with type '" << field.type_url() + << "'"; + return DataPiece::NullData(); + } + if (!field.default_value().empty()) { + if (!use_ints_for_enums) { + return DataPiece(field.default_value(), true); + } else { + const std::string& enum_default_value_name = field.default_value(); + for (int enum_index = 0; enum_index < enum_type->enumvalue_size(); + ++enum_index) { + auto& enum_value = enum_type->enumvalue(enum_index); + if (enum_value.name() == enum_default_value_name) + return DataPiece(enum_value.number()); + } + GOOGLE_LOG(WARNING) << "Could not find enum value '" << enum_default_value_name + << "' with type '" << field.type_url() << "'"; + return DataPiece::NullData(); + } + } + // We treat the first value as the default if none is specified. + return enum_type->enumvalue_size() > 0 + ? (use_ints_for_enums + ? DataPiece(enum_type->enumvalue(0).number()) + : DataPiece(enum_type->enumvalue(0).name(), true)) + : DataPiece::NullData(); +} + +DataPiece DefaultValueObjectWriter::CreateDefaultDataPieceForField( + const google::protobuf::Field& field, const TypeInfo* typeinfo, + bool use_ints_for_enums) { + switch (field.kind()) { + case google::protobuf::Field::TYPE_DOUBLE: { + return DataPiece(ConvertTo<double>( + field.default_value(), &DataPiece::ToDouble, static_cast<double>(0))); + } + case google::protobuf::Field::TYPE_FLOAT: { + return DataPiece(ConvertTo<float>( + field.default_value(), &DataPiece::ToFloat, static_cast<float>(0))); + } + case google::protobuf::Field::TYPE_INT64: + case google::protobuf::Field::TYPE_SINT64: + case google::protobuf::Field::TYPE_SFIXED64: { + return DataPiece(ConvertTo<int64_t>( + field.default_value(), &DataPiece::ToInt64, static_cast<int64_t>(0))); + } + case google::protobuf::Field::TYPE_UINT64: + case google::protobuf::Field::TYPE_FIXED64: { + return DataPiece(ConvertTo<uint64_t>(field.default_value(), + &DataPiece::ToUint64, + static_cast<uint64_t>(0))); + } + case google::protobuf::Field::TYPE_INT32: + case google::protobuf::Field::TYPE_SINT32: + case google::protobuf::Field::TYPE_SFIXED32: { + return DataPiece(ConvertTo<int32_t>( + field.default_value(), &DataPiece::ToInt32, static_cast<int32_t>(0))); + } + case google::protobuf::Field::TYPE_BOOL: { + return DataPiece( + ConvertTo<bool>(field.default_value(), &DataPiece::ToBool, false)); + } + case google::protobuf::Field::TYPE_STRING: { + return DataPiece(field.default_value(), true); + } + case google::protobuf::Field::TYPE_BYTES: { + return DataPiece(field.default_value(), false, true); + } + case google::protobuf::Field::TYPE_UINT32: + case google::protobuf::Field::TYPE_FIXED32: { + return DataPiece(ConvertTo<uint32_t>(field.default_value(), + &DataPiece::ToUint32, + static_cast<uint32_t>(0))); + } + case google::protobuf::Field::TYPE_ENUM: { + return FindEnumDefault(field, typeinfo, use_ints_for_enums); + } + default: { + return DataPiece::NullData(); + } + } +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::StartObject( + StringPiece name) { + if (current_ == nullptr) { + std::vector<std::string> path; + root_.reset(CreateNewNode(std::string(name), &type_, OBJECT, + DataPiece::NullData(), false, path, + suppress_empty_list_, preserve_proto_field_names_, + use_ints_for_enums_, field_scrub_callback_)); + root_->PopulateChildren(typeinfo_); + current_ = root_.get(); + return this; + } + MaybePopulateChildrenOfAny(current_); + Node* child = current_->FindChild(name); + if (current_->kind() == LIST || current_->kind() == MAP || child == nullptr) { + // If current_ is a list or a map node, we should create a new child and use + // the type of current_ as the type of the new child. + std::unique_ptr<Node> node( + CreateNewNode(std::string(name), + ((current_->kind() == LIST || current_->kind() == MAP) + ? current_->type() + : nullptr), + OBJECT, DataPiece::NullData(), false, + child == nullptr ? current_->path() : child->path(), + suppress_empty_list_, preserve_proto_field_names_, + use_ints_for_enums_, field_scrub_callback_)); + child = node.get(); + current_->AddChild(node.release()); + } + + child->set_is_placeholder(false); + if (child->kind() == OBJECT && child->number_of_children() == 0) { + child->PopulateChildren(typeinfo_); + } + + stack_.push(current_); + current_ = child; + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::EndObject() { + if (stack_.empty()) { + // The root object ends here. Writes out the tree. + WriteRoot(); + return this; + } + current_ = stack_.top(); + stack_.pop(); + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::StartList( + StringPiece name) { + if (current_ == nullptr) { + std::vector<std::string> path; + root_.reset(CreateNewNode(std::string(name), &type_, LIST, + DataPiece::NullData(), false, path, + suppress_empty_list_, preserve_proto_field_names_, + use_ints_for_enums_, field_scrub_callback_)); + current_ = root_.get(); + return this; + } + MaybePopulateChildrenOfAny(current_); + Node* child = current_->FindChild(name); + if (child == nullptr || child->kind() != LIST) { + std::unique_ptr<Node> node(CreateNewNode( + std::string(name), nullptr, LIST, DataPiece::NullData(), false, + child == nullptr ? current_->path() : child->path(), + suppress_empty_list_, preserve_proto_field_names_, use_ints_for_enums_, + field_scrub_callback_)); + child = node.get(); + current_->AddChild(node.release()); + } + child->set_is_placeholder(false); + + stack_.push(current_); + current_ = child; + return this; +} + +void DefaultValueObjectWriter::WriteRoot() { + root_->WriteTo(ow_); + root_.reset(nullptr); + current_ = nullptr; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::EndList() { + if (stack_.empty()) { + WriteRoot(); + return this; + } + current_ = stack_.top(); + stack_.pop(); + return this; +} + +void DefaultValueObjectWriter::RenderDataPiece(StringPiece name, + const DataPiece& data) { + MaybePopulateChildrenOfAny(current_); + if (current_->type() != nullptr && current_->type()->name() == kAnyType && + name == "@type") { + util::StatusOr<std::string> data_string = data.ToString(); + if (data_string.ok()) { + const std::string& string_value = data_string.value(); + // 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.value()); + } + 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() != nullptr) { + current_->PopulateChildren(typeinfo_); + } + } + } + Node* child = current_->FindChild(name); + if (child == nullptr || child->kind() != PRIMITIVE) { + // No children are found, creates a new child. + std::unique_ptr<Node> node( + CreateNewNode(std::string(name), nullptr, PRIMITIVE, data, false, + child == nullptr ? current_->path() : child->path(), + suppress_empty_list_, preserve_proto_field_names_, + use_ints_for_enums_, field_scrub_callback_)); + current_->AddChild(node.release()); + } else { + child->set_data(data); + child->set_is_placeholder(false); + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter.h b/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter.h new file mode 100644 index 00000000..e97d7bf1 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter.h @@ -0,0 +1,332 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_DEFAULT_VALUE_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_DEFAULT_VALUE_OBJECTWRITER_H__ + +#include <cstdint> +#include <functional> +#include <memory> +#include <stack> +#include <vector> + +#include <stubs/common.h> +#include <util/internal/type_info.h> +#include <util/internal/datapiece.h> +#include <util/internal/object_writer.h> +#include <util/internal/utility.h> +#include <util/type_resolver.h> +#include <stubs/strutil.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// An ObjectWriter that renders non-repeated primitive fields of proto messages +// with their default values. DefaultValueObjectWriter holds objects, lists and +// fields it receives in a tree structure and writes them out to another +// ObjectWriter when EndObject() is called on the root object. It also writes +// out all non-repeated primitive fields that haven't been explicitly rendered +// with their default values (0 for numbers, "" for strings, etc). +class PROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter { + public: + // A Callback function to check whether a field needs to be scrubbed. + // + // Returns true if the field should not be present in the output. Returns + // false otherwise. + // + // The 'path' parameter is a vector of path to the field from root. For + // example: if a nested field "a.b.c" (b is the parent message field of c and + // a is the parent message field of b), then the vector should contain { "a", + // "b", "c" }. + // + // The Field* should point to the google::protobuf::Field of "c". + typedef std::function<bool( + const std::vector<std::string>& /*path of the field*/, + const google::protobuf::Field* /*field*/)> + FieldScrubCallBack; + + DefaultValueObjectWriter(TypeResolver* type_resolver, + const google::protobuf::Type& type, + ObjectWriter* ow); + + virtual ~DefaultValueObjectWriter(); + + // ObjectWriter methods. + DefaultValueObjectWriter* StartObject(StringPiece name) override; + + DefaultValueObjectWriter* EndObject() override; + + DefaultValueObjectWriter* StartList(StringPiece name) override; + + DefaultValueObjectWriter* EndList() override; + + DefaultValueObjectWriter* RenderBool(StringPiece name, + bool value) override; + + DefaultValueObjectWriter* RenderInt32(StringPiece name, + int32_t value) override; + + DefaultValueObjectWriter* RenderUint32(StringPiece name, + uint32_t value) override; + + DefaultValueObjectWriter* RenderInt64(StringPiece name, + int64_t value) override; + + DefaultValueObjectWriter* RenderUint64(StringPiece name, + uint64_t value) override; + + DefaultValueObjectWriter* RenderDouble(StringPiece name, + double value) override; + + DefaultValueObjectWriter* RenderFloat(StringPiece name, + float value) override; + + DefaultValueObjectWriter* RenderString(StringPiece name, + StringPiece value) override; + DefaultValueObjectWriter* RenderBytes(StringPiece name, + StringPiece value) override; + + DefaultValueObjectWriter* RenderNull(StringPiece name) override; + + // Register the callback for scrubbing of fields. + void RegisterFieldScrubCallBack(FieldScrubCallBack field_scrub_callback); + + // If set to true, empty lists are suppressed from output when default values + // are written. + void set_suppress_empty_list(bool value) { suppress_empty_list_ = value; } + + // If set to true, original proto field names are used + void set_preserve_proto_field_names(bool value) { + preserve_proto_field_names_ = value; + } + + // If set to true, enums are rendered as ints from output when default values + // are written. + void set_print_enums_as_ints(bool value) { use_ints_for_enums_ = value; } + + protected: + enum NodeKind { + PRIMITIVE = 0, + OBJECT = 1, + LIST = 2, + MAP = 3, + }; + + // "Node" represents a node in the tree that holds the input of + // DefaultValueObjectWriter. + class PROTOBUF_EXPORT Node { + public: + Node(const std::string& name, const google::protobuf::Type* type, + NodeKind kind, const DataPiece& data, bool is_placeholder, + const std::vector<std::string>& path, bool suppress_empty_list, + bool preserve_proto_field_names, bool use_ints_for_enums, + FieldScrubCallBack field_scrub_callback); + virtual ~Node() { + for (int i = 0; i < children_.size(); ++i) { + delete children_[i]; + } + } + + // Adds a child to this node. Takes ownership of this child. + void AddChild(Node* child) { children_.push_back(child); } + + // Finds the child given its name. + Node* FindChild(StringPiece name); + + // Populates children of this Node based on its type. If there are already + // children created, they will be merged to the result. Caller should pass + // in TypeInfo for looking up types of the children. + virtual void PopulateChildren(const TypeInfo* typeinfo); + + // If this node is a leaf (has data), writes the current node to the + // ObjectWriter; if not, then recursively writes the children to the + // ObjectWriter. + virtual void WriteTo(ObjectWriter* ow); + + // Accessors + const std::string& name() const { return name_; } + + const std::vector<std::string>& path() const { return path_; } + + const google::protobuf::Type* type() const { return type_; } + + void set_type(const google::protobuf::Type* type) { type_ = type; } + + NodeKind kind() const { return kind_; } + + int number_of_children() const { return children_.size(); } + + void set_data(const DataPiece& data) { data_ = data; } + + bool is_any() const { return is_any_; } + + void set_is_any(bool is_any) { is_any_ = is_any; } + + void set_is_placeholder(bool is_placeholder) { + is_placeholder_ = is_placeholder; + } + + protected: + // Returns the Value Type of a map given the Type of the map entry and a + // TypeInfo instance. + const google::protobuf::Type* GetMapValueType( + const google::protobuf::Type& found_type, const TypeInfo* typeinfo); + + // Calls WriteTo() on every child in children_. + void WriteChildren(ObjectWriter* ow); + + // The name of this node. + std::string name_; + // google::protobuf::Type of this node. Owned by TypeInfo. + const google::protobuf::Type* type_; + // The kind of this node. + NodeKind kind_; + // Whether this is a node for "Any". + bool is_any_; + // The data of this node when it is a leaf node. + DataPiece data_; + // Children of this node. + std::vector<Node*> children_; + // Whether this node is a placeholder for an object or list automatically + // generated when creating the parent node. Should be set to false after + // the parent node's StartObject()/StartList() method is called with this + // node's name. + bool is_placeholder_; + + // Path of the field of this node + std::vector<std::string> path_; + + // Whether to suppress empty list output. + bool suppress_empty_list_; + + // Whether to preserve original proto field names + bool preserve_proto_field_names_; + + // Whether to always print enums as ints + bool use_ints_for_enums_; + + // Function for determining whether a field needs to be scrubbed or not. + FieldScrubCallBack field_scrub_callback_; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Node); + }; + + // Creates a new Node and returns it. Caller owns memory of returned object. + virtual Node* CreateNewNode(const std::string& name, + const google::protobuf::Type* type, NodeKind kind, + const DataPiece& data, bool is_placeholder, + const std::vector<std::string>& path, + bool suppress_empty_list, + bool preserve_proto_field_names, + bool use_ints_for_enums, + FieldScrubCallBack field_scrub_callback); + + // Creates a DataPiece containing the default value of the type of the field. + static DataPiece CreateDefaultDataPieceForField( + const google::protobuf::Field& field, const TypeInfo* typeinfo) { + return CreateDefaultDataPieceForField(field, typeinfo, false); + } + + // Same as the above but with a flag to use ints instead of enum names. + static DataPiece CreateDefaultDataPieceForField( + const google::protobuf::Field& field, const TypeInfo* typeinfo, + bool use_ints_for_enums); + + protected: + // Returns a pointer to current Node in tree. + Node* current() { return current_; } + + private: + // Populates children of "node" if it is an "any" Node and its real type has + // been given. + void MaybePopulateChildrenOfAny(Node* node); + + // Writes the root_ node to ow_ and resets the root_ and current_ pointer to + // nullptr. + void WriteRoot(); + + // Adds or replaces the data_ of a primitive child node. + void RenderDataPiece(StringPiece name, const DataPiece& data); + + // Returns the default enum value as a DataPiece, or the first enum value if + // there is no default. For proto3, where we cannot specify an explicit + // default, a zero value will always be returned. + static DataPiece FindEnumDefault(const google::protobuf::Field& field, + const TypeInfo* typeinfo, + bool use_ints_for_enums); + + // Type information for all the types used in the descriptor. Used to find + // google::protobuf::Type of nested messages/enums. + const TypeInfo* typeinfo_; + // Whether the TypeInfo object is owned by this class. + bool own_typeinfo_; + // google::protobuf::Type of the root message type. + const google::protobuf::Type& type_; + // Holds copies of strings passed to RenderString. + std::vector<std::unique_ptr<std::string>> string_values_; + + // The current Node. Owned by its parents. + Node* current_; + // The root Node. + std::unique_ptr<Node> root_; + // The stack to hold the path of Nodes from current_ to root_; + std::stack<Node*> stack_; + + // Whether to suppress output of empty lists. + bool suppress_empty_list_; + + // Whether to preserve original proto field names + bool preserve_proto_field_names_; + + // Whether to always print enums as ints + bool use_ints_for_enums_; + + // Function for determining whether a field needs to be scrubbed or not. + FieldScrubCallBack field_scrub_callback_; + + ObjectWriter* ow_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(DefaultValueObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_DEFAULT_VALUE_OBJECTWRITER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter_test.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter_test.cc new file mode 100644 index 00000000..dad638e4 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/default_value_objectwriter_test.cc @@ -0,0 +1,191 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/default_value_objectwriter.h> + +#include <util/internal/expecting_objectwriter.h> +#include <util/internal/testdata/default_value_test.pb.h> +#include <util/internal/type_info_test_helper.h> +#include <util/internal/constants.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +namespace testing { + +using proto_util_converter::testing::DefaultValueTest; + +// Base class for setting up required state for running default values tests on +// different descriptors. +class BaseDefaultValueObjectWriterTest + : public ::testing::TestWithParam<testing::TypeInfoSource> { + protected: + explicit BaseDefaultValueObjectWriterTest(const Descriptor* descriptor) + : helper_(GetParam()), mock_(), expects_(&mock_) { + helper_.ResetTypeInfo(descriptor); + testing_.reset(helper_.NewDefaultValueWriter( + std::string(kTypeServiceBaseUrl) + "/" + descriptor->full_name(), + &mock_)); + } + + virtual ~BaseDefaultValueObjectWriterTest() {} + + TypeInfoTestHelper helper_; + MockObjectWriter mock_; + ExpectingObjectWriter expects_; + std::unique_ptr<DefaultValueObjectWriter> testing_; +}; + +// Tests to cover some basic DefaultValueObjectWriter use cases. More tests are +// in the marshalling_test.cc and translator_integration_test.cc. +class DefaultValueObjectWriterTest : public BaseDefaultValueObjectWriterTest { + protected: + DefaultValueObjectWriterTest() + : BaseDefaultValueObjectWriterTest(DefaultValueTest::descriptor()) {} + virtual ~DefaultValueObjectWriterTest() {} +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + DefaultValueObjectWriterTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(DefaultValueObjectWriterTest, Empty) { + // Set expectation + expects_.StartObject("") + ->RenderDouble("doubleValue", 0.0) + ->StartList("repeatedDouble") + ->EndList() + ->RenderFloat("floatValue", 0.0) + ->RenderInt64("int64Value", 0) + ->RenderUint64("uint64Value", 0) + ->RenderInt32("int32Value", 0) + ->RenderUint32("uint32Value", 0) + ->RenderBool("boolValue", false) + ->RenderString("stringValue", "") + ->RenderBytes("bytesValue", "") + ->RenderString("enumValue", "ENUM_FIRST") + ->EndObject(); + + // Actual testing + testing_->StartObject("")->EndObject(); +} + +TEST_P(DefaultValueObjectWriterTest, NonDefaultDouble) { + // Set expectation + expects_.StartObject("") + ->RenderDouble("doubleValue", 1.0) + ->StartList("repeatedDouble") + ->EndList() + ->RenderFloat("floatValue", 0.0) + ->RenderInt64("int64Value", 0) + ->RenderUint64("uint64Value", 0) + ->RenderInt32("int32Value", 0) + ->RenderUint32("uint32Value", 0) + ->RenderBool("boolValue", false) + ->RenderString("stringValue", "") + ->RenderString("enumValue", "ENUM_FIRST") + ->EndObject(); + + // Actual testing + testing_->StartObject("")->RenderDouble("doubleValue", 1.0)->EndObject(); +} + +TEST_P(DefaultValueObjectWriterTest, ShouldRetainUnknownField) { + // Set expectation + expects_.StartObject("") + ->RenderDouble("doubleValue", 1.0) + ->StartList("repeatedDouble") + ->EndList() + ->RenderFloat("floatValue", 0.0) + ->RenderInt64("int64Value", 0) + ->RenderUint64("uint64Value", 0) + ->RenderInt32("int32Value", 0) + ->RenderUint32("uint32Value", 0) + ->RenderBool("boolValue", false) + ->RenderString("stringValue", "") + ->RenderString("unknown", "abc") + ->StartObject("unknownObject") + ->RenderString("unknown", "def") + ->EndObject() + ->RenderString("enumValue", "ENUM_FIRST") + ->EndObject(); + + // Actual testing + testing_->StartObject("") + ->RenderDouble("doubleValue", 1.0) + ->RenderString("unknown", "abc") + ->StartObject("unknownObject") + ->RenderString("unknown", "def") + ->EndObject() + ->EndObject(); +} + + +class DefaultValueObjectWriterSuppressListTest + : public BaseDefaultValueObjectWriterTest { + protected: + DefaultValueObjectWriterSuppressListTest() + : BaseDefaultValueObjectWriterTest(DefaultValueTest::descriptor()) { + testing_->set_suppress_empty_list(true); + } + ~DefaultValueObjectWriterSuppressListTest() override {} +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + DefaultValueObjectWriterSuppressListTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(DefaultValueObjectWriterSuppressListTest, Empty) { + // Set expectation. Empty lists should be suppressed. + expects_.StartObject("") + ->RenderDouble("doubleValue", 0.0) + ->RenderFloat("floatValue", 0.0) + ->RenderInt64("int64Value", 0) + ->RenderUint64("uint64Value", 0) + ->RenderInt32("int32Value", 0) + ->RenderUint32("uint32Value", 0) + ->RenderBool("boolValue", false) + ->RenderString("stringValue", "") + ->RenderBytes("bytesValue", "") + ->RenderString("enumValue", "ENUM_FIRST") + ->EndObject(); + + // Actual testing + testing_->StartObject("")->EndObject(); +} +} // namespace testing +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/error_listener.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/error_listener.cc new file mode 100644 index 00000000..f086a7e1 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/error_listener.cc @@ -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. + +#include <util/internal/error_listener.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/error_listener.h b/NorthstarDedicatedTest/include/protobuf/util/internal/error_listener.h new file mode 100644 index 00000000..df51ff27 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/error_listener.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_ERROR_LISTENER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_ERROR_LISTENER_H__ + +#include <algorithm> +#include <memory> +#include <string> +#include <vector> + +#include <stubs/callback.h> +#include <stubs/common.h> +#include <stubs/logging.h> +#include <util/internal/location_tracker.h> +#include <stubs/strutil.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// Interface for error listener. +class PROTOBUF_EXPORT ErrorListener { + public: + virtual ~ErrorListener() {} + + // Reports an invalid name at the given location. + virtual void InvalidName(const LocationTrackerInterface& loc, + StringPiece invalid_name, + StringPiece message) = 0; + + // Reports an invalid value for a field. + virtual void InvalidValue(const LocationTrackerInterface& loc, + StringPiece type_name, + StringPiece value) = 0; + + // Reports a missing required field. + virtual void MissingField(const LocationTrackerInterface& loc, + StringPiece missing_name) = 0; + + protected: + ErrorListener() {} + + private: + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ErrorListener); +}; + +// An error listener that ignores all errors. +class PROTOBUF_EXPORT NoopErrorListener : public ErrorListener { + public: + NoopErrorListener() {} + ~NoopErrorListener() override {} + + void InvalidName(const LocationTrackerInterface& /*loc*/, + StringPiece /* invalid_name */, + StringPiece /* message */) override {} + + void InvalidValue(const LocationTrackerInterface& /*loc*/, + StringPiece /* type_name */, + StringPiece /* value */) override {} + + void MissingField(const LocationTrackerInterface& /* loc */, + StringPiece /* missing_name */) override {} + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(NoopErrorListener); +}; + + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_ERROR_LISTENER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/expecting_objectwriter.h b/NorthstarDedicatedTest/include/protobuf/util/internal/expecting_objectwriter.h new file mode 100644 index 00000000..414ffd8d --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/expecting_objectwriter.h @@ -0,0 +1,250 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_EXPECTING_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_EXPECTING_OBJECTWRITER_H__ + +// An implementation of ObjectWriter that automatically sets the +// gmock expectations for the response to a method. Every method +// returns the object itself for chaining. +// +// Usage: +// // Setup +// MockObjectWriter mock; +// ExpectingObjectWriter ow(&mock); +// +// // Set expectation +// ow.StartObject("") +// ->RenderString("key", "value") +// ->EndObject(); +// +// // Actual testing +// mock.StartObject(StringPiece()) +// ->RenderString("key", "value") +// ->EndObject(); + +#include <cstdint> + +#include <stubs/common.h> +#include <util/internal/object_writer.h> +#include <gmock/gmock.h> +#include <stubs/strutil.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using testing::Eq; +using testing::IsEmpty; +using testing::NanSensitiveDoubleEq; +using testing::NanSensitiveFloatEq; +using testing::Return; +using testing::StrEq; +using testing::TypedEq; + +class MockObjectWriter : public ObjectWriter { + public: + MockObjectWriter() {} + + MOCK_METHOD(ObjectWriter*, StartObject, (StringPiece), (override)); + MOCK_METHOD(ObjectWriter*, EndObject, (), (override)); + MOCK_METHOD(ObjectWriter*, StartList, (StringPiece), (override)); + MOCK_METHOD(ObjectWriter*, EndList, (), (override)); + MOCK_METHOD(ObjectWriter*, RenderBool, (StringPiece, bool), (override)); + MOCK_METHOD(ObjectWriter*, RenderInt32, (StringPiece, int32_t), + (override)); + MOCK_METHOD(ObjectWriter*, RenderUint32, (StringPiece, uint32_t), + (override)); + MOCK_METHOD(ObjectWriter*, RenderInt64, (StringPiece, int64_t), + (override)); + MOCK_METHOD(ObjectWriter*, RenderUint64, (StringPiece, uint64_t), + (override)); + MOCK_METHOD(ObjectWriter*, RenderDouble, (StringPiece, double), + (override)); + MOCK_METHOD(ObjectWriter*, RenderFloat, (StringPiece, float), + (override)); + MOCK_METHOD(ObjectWriter*, RenderString, + (StringPiece, StringPiece), (override)); + MOCK_METHOD(ObjectWriter*, RenderBytes, (StringPiece, StringPiece), + (override)); + MOCK_METHOD(ObjectWriter*, RenderNull, (StringPiece), (override)); +}; + +class ExpectingObjectWriter : public ObjectWriter { + public: + explicit ExpectingObjectWriter(MockObjectWriter* mock) : mock_(mock) {} + + virtual ObjectWriter* StartObject(StringPiece name) { + (name.empty() ? EXPECT_CALL(*mock_, StartObject(IsEmpty())) + : EXPECT_CALL(*mock_, StartObject(Eq(std::string(name))))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* EndObject() { + EXPECT_CALL(*mock_, EndObject()) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* StartList(StringPiece name) { + (name.empty() ? EXPECT_CALL(*mock_, StartList(IsEmpty())) + : EXPECT_CALL(*mock_, StartList(Eq(std::string(name))))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* EndList() { + EXPECT_CALL(*mock_, EndList()) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderBool(StringPiece name, bool value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderBool(IsEmpty(), TypedEq<bool>(value))) + : EXPECT_CALL(*mock_, + RenderBool(Eq(std::string(name)), TypedEq<bool>(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderInt32(StringPiece name, int32_t value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderInt32(IsEmpty(), TypedEq<int32_t>(value))) + : EXPECT_CALL(*mock_, RenderInt32(Eq(std::string(name)), + TypedEq<int32_t>(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderUint32(StringPiece name, uint32_t value) { + (name.empty() ? EXPECT_CALL(*mock_, RenderUint32(IsEmpty(), + TypedEq<uint32_t>(value))) + : EXPECT_CALL(*mock_, RenderUint32(Eq(std::string(name)), + TypedEq<uint32_t>(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderInt64(StringPiece name, int64_t value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderInt64(IsEmpty(), TypedEq<int64_t>(value))) + : EXPECT_CALL(*mock_, RenderInt64(Eq(std::string(name)), + TypedEq<int64_t>(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderUint64(StringPiece name, uint64_t value) { + (name.empty() ? EXPECT_CALL(*mock_, RenderUint64(IsEmpty(), + TypedEq<uint64_t>(value))) + : EXPECT_CALL(*mock_, RenderUint64(Eq(std::string(name)), + TypedEq<uint64_t>(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderDouble(StringPiece name, double value) { + (name.empty() + ? EXPECT_CALL(*mock_, + RenderDouble(IsEmpty(), NanSensitiveDoubleEq(value))) + : EXPECT_CALL(*mock_, RenderDouble(Eq(std::string(name)), + NanSensitiveDoubleEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderFloat(StringPiece name, float value) { + (name.empty() + ? EXPECT_CALL(*mock_, + RenderFloat(IsEmpty(), NanSensitiveFloatEq(value))) + : EXPECT_CALL(*mock_, RenderFloat(Eq(std::string(name)), + NanSensitiveFloatEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderString(StringPiece name, + StringPiece value) { + (name.empty() ? EXPECT_CALL(*mock_, RenderString(IsEmpty(), + TypedEq<StringPiece>( + std::string(value)))) + : EXPECT_CALL(*mock_, RenderString(Eq(std::string(name)), + TypedEq<StringPiece>( + std::string(value))))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + virtual ObjectWriter* RenderBytes(StringPiece name, StringPiece value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderBytes(IsEmpty(), TypedEq<StringPiece>( + value.ToString()))) + : EXPECT_CALL(*mock_, + RenderBytes(Eq(std::string(name)), + TypedEq<StringPiece>(value.ToString())))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderNull(StringPiece name) { + (name.empty() ? EXPECT_CALL(*mock_, RenderNull(IsEmpty())) + : EXPECT_CALL(*mock_, RenderNull(Eq(std::string(name)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation()); + return this; + } + + private: + MockObjectWriter* mock_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ExpectingObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_EXPECTING_OBJECTWRITER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/field_mask_utility.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/field_mask_utility.cc new file mode 100644 index 00000000..5b1bfe6c --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/field_mask_utility.cc @@ -0,0 +1,218 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/field_mask_utility.h> + +#include <util/internal/utility.h> +#include <stubs/status.h> +#include <stubs/strutil.h> +#include <stubs/status_macros.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +namespace { + +// Appends a FieldMask path segment to a prefix. +std::string AppendPathSegmentToPrefix(StringPiece prefix, + StringPiece segment) { + if (prefix.empty()) { + return std::string(segment); + } + if (segment.empty()) { + return std::string(prefix); + } + // If the segment is a map key, appends it to the prefix without the ".". + if (HasPrefixString(segment, "[\"")) { + return StrCat(prefix, segment); + } + return StrCat(prefix, ".", segment); +} + +} // namespace + +std::string ConvertFieldMaskPath(const StringPiece path, + ConverterCallback converter) { + std::string result; + result.reserve(path.size() << 1); + + bool is_quoted = false; + bool is_escaping = false; + int current_segment_start = 0; + + // Loops until 1 passed the end of the input to make handling the last + // segment easier. + for (size_t i = 0; i <= path.size(); ++i) { + // Outputs quoted string as-is. + if (is_quoted) { + if (i == path.size()) { + break; + } + result.push_back(path[i]); + if (is_escaping) { + is_escaping = false; + } else if (path[i] == '\\') { + is_escaping = true; + } else if (path[i] == '\"') { + current_segment_start = i + 1; + is_quoted = false; + } + continue; + } + if (i == path.size() || path[i] == '.' || path[i] == '(' || + path[i] == ')' || path[i] == '\"') { + result += converter( + path.substr(current_segment_start, i - current_segment_start)); + if (i < path.size()) { + result.push_back(path[i]); + } + current_segment_start = i + 1; + } + if (i < path.size() && path[i] == '\"') { + is_quoted = true; + } + } + return result; +} + +util::Status DecodeCompactFieldMaskPaths(StringPiece paths, + PathSinkCallback path_sink) { + std::stack<std::string> prefix; + int length = paths.length(); + int previous_position = 0; + bool in_map_key = false; + bool is_escaping = false; + // Loops until 1 passed the end of the input to make the handle of the last + // segment easier. + for (int i = 0; i <= length; ++i) { + if (i != length) { + // Skips everything in a map key until we hit the end of it, which is + // marked by an un-escaped '"' immediately followed by a ']'. + if (in_map_key) { + if (is_escaping) { + is_escaping = false; + continue; + } + if (paths[i] == '\\') { + is_escaping = true; + continue; + } + if (paths[i] != '\"') { + continue; + } + // Un-escaped '"' must be followed with a ']'. + if (i >= length - 1 || paths[i + 1] != ']') { + return util::InvalidArgumentError(StrCat( + "Invalid FieldMask '", paths, + "'. Map keys should be represented as [\"some_key\"].")); + } + // The end of the map key ("\"]") has been found. + in_map_key = false; + // Skips ']'. + i++; + // Checks whether the key ends at the end of a path segment. + if (i < length - 1 && paths[i + 1] != '.' && paths[i + 1] != ',' && + paths[i + 1] != ')' && paths[i + 1] != '(') { + return util::InvalidArgumentError(StrCat( + "Invalid FieldMask '", paths, + "'. Map keys should be at the end of a path segment.")); + } + is_escaping = false; + continue; + } + + // We are not in a map key, look for the start of one. + if (paths[i] == '[') { + if (i >= length - 1 || paths[i + 1] != '\"') { + return util::InvalidArgumentError(StrCat( + "Invalid FieldMask '", paths, + "'. Map keys should be represented as [\"some_key\"].")); + } + // "[\"" starts a map key. + in_map_key = true; + i++; // Skips the '\"'. + continue; + } + // If the current character is not a special character (',', '(' or ')'), + // continue to the next. + if (paths[i] != ',' && paths[i] != ')' && paths[i] != '(') { + continue; + } + } + // Gets the current segment - sub-string between previous position (after + // '(', ')', ',', or the beginning of the input) and the current position. + StringPiece segment = + paths.substr(previous_position, i - previous_position); + std::string current_prefix = prefix.empty() ? "" : prefix.top(); + + if (i < length && paths[i] == '(') { + // Builds a prefix and save it into the stack. + prefix.push(AppendPathSegmentToPrefix(current_prefix, segment)); + } else if (!segment.empty()) { + // When the current character is ')', ',' or the current position has + // passed the end of the input, builds and outputs a new paths by + // concatenating the last prefix with the current segment. + RETURN_IF_ERROR( + path_sink(AppendPathSegmentToPrefix(current_prefix, segment))); + } + + // Removes the last prefix after seeing a ')'. + if (i < length && paths[i] == ')') { + if (prefix.empty()) { + return util::InvalidArgumentError( + StrCat("Invalid FieldMask '", paths, + "'. Cannot find matching '(' for all ')'.")); + } + prefix.pop(); + } + previous_position = i + 1; + } + if (in_map_key) { + return util::InvalidArgumentError( + StrCat("Invalid FieldMask '", paths, + "'. Cannot find matching ']' for all '['.")); + } + if (!prefix.empty()) { + return util::InvalidArgumentError( + StrCat("Invalid FieldMask '", paths, + "'. Cannot find matching ')' for all '('.")); + } + return util::Status(); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/field_mask_utility.h b/NorthstarDedicatedTest/include/protobuf/util/internal/field_mask_utility.h new file mode 100644 index 00000000..b238f533 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/field_mask_utility.h @@ -0,0 +1,74 @@ +// 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. + +// FieldMask related utility methods. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_FIELD_MASK_UTILITY_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_FIELD_MASK_UTILITY_H__ + +#include <functional> +#include <stack> + +#include <stubs/callback.h> +#include <stubs/common.h> +#include <stubs/status.h> +#include <stubs/strutil.h> +#include <stubs/status.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +typedef std::function<std::string(StringPiece)> ConverterCallback; +typedef std::function<util::Status(StringPiece)> PathSinkCallback; + +// Applies a 'converter' to each segment of a FieldMask path and returns the +// result. Quoted strings in the 'path' are copied to the output as-is without +// converting their content. Escaping is supported within quoted strings. +// For example, "ab\"_c" will be returned as "ab\"_c" without any changes. +std::string ConvertFieldMaskPath(const StringPiece path, + ConverterCallback converter); + +// Decodes a compact list of FieldMasks. For example, "a.b,a.c.d,a.c.e" will be +// decoded into a list of field paths - "a.b", "a.c.d", "a.c.e". And the results +// will be sent to 'path_sink', i.e. 'path_sink' will be called once per +// resulting path. +// Note that we also support Apiary style FieldMask form. The above example in +// the Apiary style will look like "a.b,a.c(d,e)". +util::Status DecodeCompactFieldMaskPaths(StringPiece paths, + PathSinkCallback path_sink); + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_FIELD_MASK_UTILITY_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_escaping.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/json_escaping.cc new file mode 100644 index 00000000..22d981ab --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_escaping.cc @@ -0,0 +1,372 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/json_escaping.h> + +#include <cstdint> + +#include <stubs/logging.h> +#include <stubs/common.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +namespace { + +// Array of hex characters for conversion to hex. +static const char kHex[] = "0123456789abcdef"; + +// Characters 0x00 to 0x9f are very commonly used, so we provide a special +// table lookup. +// +// For unicode code point ch < 0xa0: +// kCommonEscapes[ch] is the escaped string of ch, if escaping is needed; +// or an empty string, if escaping is not needed. +static const char kCommonEscapes[160][7] = { + // C0 (ASCII and derivatives) control characters + "\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00 + "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000b", + "\\f", "\\r", "\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", + "\\u0013", // 0x10 + "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", + "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f", + // Escaping of " and \ are required by www.json.org string definition. + // Escaping of < and > are required for HTML security. + "", "", "\\\"", "", "", "", "", "", // 0x20 + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", // 0x30 + "", "", "", "", "\\u003c", "", "\\u003e", "", "", "", "", "", "", "", "", + "", // 0x40 + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", // 0x50 + "", "", "", "", "\\\\", "", "", "", "", "", "", "", "", "", "", "", // 0x60 + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", // 0x70 + "", "", "", "", "", "", "", "\\u007f", + // C1 (ISO 8859 and Unicode) extended control characters + "\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80 + "\\u0084", "\\u0085", "\\u0086", "\\u0087", "\\u0088", "\\u0089", "\\u008a", + "\\u008b", "\\u008c", "\\u008d", "\\u008e", "\\u008f", "\\u0090", "\\u0091", + "\\u0092", "\\u0093", // 0x90 + "\\u0094", "\\u0095", "\\u0096", "\\u0097", "\\u0098", "\\u0099", "\\u009a", + "\\u009b", "\\u009c", "\\u009d", "\\u009e", "\\u009f"}; + +// Determines if the given char value is a unicode surrogate code unit (either +// high-surrogate or low-surrogate). +inline bool IsSurrogate(uint32_t c) { + // Optimized form of: + // return c >= kMinHighSurrogate && c <= kMaxLowSurrogate; + // (Reduced from 3 ALU instructions to 2 ALU instructions) + return (c & 0xfffff800) == JsonEscaping::kMinHighSurrogate; +} + +// Returns true if the given unicode code point cp is a valid +// unicode code point (i.e. in the range 0 <= cp <= kMaxCodePoint). +inline bool IsValidCodePoint(uint32_t cp) { + return cp <= JsonEscaping::kMaxCodePoint; +} + +// Returns the low surrogate for the given unicode code point. The result is +// meaningless if the given code point is not a supplementary character. +inline uint16_t ToLowSurrogate(uint32_t cp) { + return (cp & + (JsonEscaping::kMaxLowSurrogate - JsonEscaping::kMinLowSurrogate)) + + JsonEscaping::kMinLowSurrogate; +} + +// Returns the high surrogate for the given unicode code point. The result is +// meaningless if the given code point is not a supplementary character. +inline uint16_t ToHighSurrogate(uint32_t cp) { + return (cp >> 10) + (JsonEscaping::kMinHighSurrogate - + (JsonEscaping::kMinSupplementaryCodePoint >> 10)); +} + +// Input str is encoded in UTF-8. A unicode code point could be encoded in +// UTF-8 using anywhere from 1 to 4 characters, and it could span multiple +// reads of the ByteSource. +// +// This function reads the next unicode code point from the input (str) at +// the given position (index), taking into account any left-over partial +// code point from the previous iteration (cp), together with the number +// of characters left to read to complete this code point (num_left). +// +// This function assumes that the input (str) is valid at the given position +// (index). In order words, at least one character could be read successfully. +// +// The code point read (partial or complete) is stored in (cp). Upon return, +// (num_left) stores the number of characters that has yet to be read in +// order to complete the current unicode code point. If the read is complete, +// then (num_left) is 0. Also, (num_read) is the number of characters read. +// +// Returns false if we encounter an invalid UTF-8 string. Returns true +// otherwise, including the case when we reach the end of the input (str) +// before a complete unicode code point is read. +bool ReadCodePoint(StringPiece str, int index, uint32_t* cp, + int* num_left, int* num_read) { + if (*num_left == 0) { + // Last read was complete. Start reading a new unicode code point. + *cp = static_cast<uint8_t>(str[index++]); + *num_read = 1; + // The length of the code point is determined from reading the first byte. + // + // If the first byte is between: + // 0..0x7f: that's the value of the code point. + // 0x80..0xbf: <invalid> + // 0xc0..0xdf: 11-bit code point encoded in 2 bytes. + // bit 10-6, bit 5-0 + // 0xe0..0xef: 16-bit code point encoded in 3 bytes. + // bit 15-12, bit 11-6, bit 5-0 + // 0xf0..0xf7: 21-bit code point encoded in 4 bytes. + // bit 20-18, bit 17-12, bit 11-6, bit 5-0 + // 0xf8..0xff: <invalid> + // + // Meaning of each bit: + // <msb> bit 7: 0 - single byte code point: bits 6-0 are values. + // 1 - multibyte code point + // bit 6: 0 - subsequent bytes of multibyte code point: + // bits 5-0 are values. + // 1 - first byte of multibyte code point + // bit 5: 0 - first byte of 2-byte code point: bits 4-0 are values. + // 1 - first byte of code point with >= 3 bytes. + // bit 4: 0 - first byte of 3-byte code point: bits 3-0 are values. + // 1 - first byte of code point with >= 4 bytes. + // bit 3: 0 - first byte of 4-byte code point: bits 2-0 are values. + // 1 - reserved for future expansion. + if (*cp <= 0x7f) { + return true; + } else if (*cp <= 0xbf) { + return false; + } else if (*cp <= 0xdf) { + *cp &= 0x1f; + *num_left = 1; + } else if (*cp <= 0xef) { + *cp &= 0x0f; + *num_left = 2; + } else if (*cp <= 0xf7) { + *cp &= 0x07; + *num_left = 3; + } else { + return false; + } + } else { + // Last read was partial. Initialize num_read to 0 and continue reading + // the last unicode code point. + *num_read = 0; + } + while (*num_left > 0 && index < str.size()) { + uint32_t ch = static_cast<uint8_t>(str[index++]); + --(*num_left); + ++(*num_read); + *cp = (*cp << 6) | (ch & 0x3f); + if (ch < 0x80 || ch > 0xbf) return false; + } + return *num_left > 0 || (!IsSurrogate(*cp) && IsValidCodePoint(*cp)); +} + +// Stores the 16-bit unicode code point as its hexadecimal digits in buffer +// and returns a StringPiece that points to this buffer. The input buffer needs +// to be at least 6 bytes long. +StringPiece ToHex(uint16_t cp, char* buffer) { + buffer[5] = kHex[cp & 0x0f]; + cp >>= 4; + buffer[4] = kHex[cp & 0x0f]; + cp >>= 4; + buffer[3] = kHex[cp & 0x0f]; + cp >>= 4; + buffer[2] = kHex[cp & 0x0f]; + return StringPiece(buffer, 6); +} + +// Stores the 32-bit unicode code point as its hexadecimal digits in buffer +// and returns a StringPiece that points to this buffer. The input buffer needs +// to be at least 12 bytes long. +StringPiece ToSurrogateHex(uint32_t cp, char* buffer) { + uint16_t low = ToLowSurrogate(cp); + uint16_t high = ToHighSurrogate(cp); + + buffer[11] = kHex[low & 0x0f]; + low >>= 4; + buffer[10] = kHex[low & 0x0f]; + low >>= 4; + buffer[9] = kHex[low & 0x0f]; + low >>= 4; + buffer[8] = kHex[low & 0x0f]; + + buffer[5] = kHex[high & 0x0f]; + high >>= 4; + buffer[4] = kHex[high & 0x0f]; + high >>= 4; + buffer[3] = kHex[high & 0x0f]; + high >>= 4; + buffer[2] = kHex[high & 0x0f]; + + return StringPiece(buffer, 12); +} + +// If the given unicode code point needs escaping, then returns the +// escaped form. The returned StringPiece either points to statically +// pre-allocated char[] or to the given buffer. The input buffer needs +// to be at least 12 bytes long. +// +// If the given unicode code point does not need escaping, an empty +// StringPiece is returned. +StringPiece EscapeCodePoint(uint32_t cp, char* buffer) { + if (cp < 0xa0) return kCommonEscapes[cp]; + switch (cp) { + // These are not required by json spec + // but used to prevent security bugs in javascript. + case 0xfeff: // Zero width no-break space + case 0xfff9: // Interlinear annotation anchor + case 0xfffa: // Interlinear annotation separator + case 0xfffb: // Interlinear annotation terminator + + case 0x00ad: // Soft-hyphen + case 0x06dd: // Arabic end of ayah + case 0x070f: // Syriac abbreviation mark + case 0x17b4: // Khmer vowel inherent Aq + case 0x17b5: // Khmer vowel inherent Aa + return ToHex(cp, buffer); + + default: + if ((cp >= 0x0600 && cp <= 0x0603) || // Arabic signs + (cp >= 0x200b && cp <= 0x200f) || // Zero width etc. + (cp >= 0x2028 && cp <= 0x202e) || // Separators etc. + (cp >= 0x2060 && cp <= 0x2064) || // Invisible etc. + (cp >= 0x206a && cp <= 0x206f)) { // Shaping etc. + return ToHex(cp, buffer); + } + + if (cp == 0x000e0001 || // Language tag + (cp >= 0x0001d173 && cp <= 0x0001d17a) || // Music formatting + (cp >= 0x000e0020 && cp <= 0x000e007f)) { // TAG symbols + return ToSurrogateHex(cp, buffer); + } + } + return StringPiece(); +} + +// Tries to escape the given code point first. If the given code point +// does not need to be escaped, but force_output is true, then render +// the given multi-byte code point in UTF8 in the buffer and returns it. +StringPiece EscapeCodePoint(uint32_t cp, char* buffer, + bool force_output) { + StringPiece sp = EscapeCodePoint(cp, buffer); + if (force_output && sp.empty()) { + buffer[5] = (cp & 0x3f) | 0x80; + cp >>= 6; + if (cp <= 0x1f) { + buffer[4] = cp | 0xc0; + sp = StringPiece(buffer + 4, 2); + return sp; + } + buffer[4] = (cp & 0x3f) | 0x80; + cp >>= 6; + if (cp <= 0x0f) { + buffer[3] = cp | 0xe0; + sp = StringPiece(buffer + 3, 3); + return sp; + } + buffer[3] = (cp & 0x3f) | 0x80; + buffer[2] = ((cp >> 6) & 0x07) | 0xf0; + sp = StringPiece(buffer + 2, 4); + } + return sp; +} + +} // namespace + +void JsonEscaping::Escape(strings::ByteSource* input, + strings::ByteSink* output) { + char buffer[12] = "\\udead\\ubee"; + uint32_t cp = 0; // Current unicode code point. + int num_left = 0; // Num of chars to read to complete the code point. + while (input->Available() > 0) { + StringPiece str = input->Peek(); + StringPiece escaped; + int i = 0; + int num_read; + bool ok; + bool cp_was_split = num_left > 0; + // Loop until we encounter either + // i) a code point that needs to be escaped; or + // ii) a split code point is completely read; or + // iii) a character that is not a valid utf8; or + // iv) end of the StringPiece str is reached. + do { + ok = ReadCodePoint(str, i, &cp, &num_left, &num_read); + if (num_left > 0 || !ok) break; // case iii or iv + escaped = EscapeCodePoint(cp, buffer, cp_was_split); + if (!escaped.empty()) break; // case i or ii + i += num_read; + num_read = 0; + } while (i < str.length()); // case iv + // First copy the un-escaped prefix, if any, to the output ByteSink. + if (i > 0) input->CopyTo(output, i); + if (num_read > 0) input->Skip(num_read); + if (!ok) { + // Case iii: Report error. + // TODO(wpoon): Add error reporting. + num_left = 0; + } else if (num_left == 0 && !escaped.empty()) { + // Case i or ii: Append the escaped code point to the output ByteSink. + output->Append(escaped.data(), escaped.size()); + } + } + if (num_left > 0) { + // Treat as case iii: report error. + // TODO(wpoon): Add error reporting. + } +} + +void JsonEscaping::Escape(StringPiece input, strings::ByteSink* output) { + const size_t len = input.length(); + const char* p = input.data(); + + bool can_skip_escaping = true; + for (int i = 0; i < len; i++) { + char c = p[i]; + if (c < 0x20 || c >= 0x7F || c == '"' || c == '<' || c == '>' || + c == '\\') { + can_skip_escaping = false; + break; + } + } + + if (can_skip_escaping) { + output->Append(input.data(), input.length()); + } else { + strings::ArrayByteSource source(input); + Escape(&source, output); + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_escaping.h b/NorthstarDedicatedTest/include/protobuf/util/internal/json_escaping.h new file mode 100644 index 00000000..61e5b605 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_escaping.h @@ -0,0 +1,98 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_INTERNAL__JSON_ESCAPING_H__ +#define GOOGLE_PROTOBUF_UTIL_INTERNAL__JSON_ESCAPING_H__ + +#include <cstdint> + +#include <stubs/common.h> +#include <stubs/bytestream.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class JsonEscaping { + public: + // The minimum value of a unicode high-surrogate code unit in the utf-16 + // encoding. A high-surrogate is also known as a leading-surrogate. + // See http://www.unicode.org/glossary/#high_surrogate_code_unit + static constexpr uint16_t kMinHighSurrogate = 0xd800; + + // The maximum value of a unicide high-surrogate code unit in the utf-16 + // encoding. A high-surrogate is also known as a leading-surrogate. + // See http://www.unicode.org/glossary/#high_surrogate_code_unit + static constexpr uint16_t kMaxHighSurrogate = 0xdbff; + + // The minimum value of a unicode low-surrogate code unit in the utf-16 + // encoding. A low-surrogate is also known as a trailing-surrogate. + // See http://www.unicode.org/glossary/#low_surrogate_code_unit + static constexpr uint16_t kMinLowSurrogate = 0xdc00; + + // The maximum value of a unicode low-surrogate code unit in the utf-16 + // encoding. A low-surrogate is also known as a trailing surrogate. + // See http://www.unicode.org/glossary/#low_surrogate_code_unit + static constexpr uint16_t kMaxLowSurrogate = 0xdfff; + + // The minimum value of a unicode supplementary code point. + // See http://www.unicode.org/glossary/#supplementary_code_point + static constexpr uint32_t kMinSupplementaryCodePoint = 0x010000; + + // The minimum value of a unicode code point. + // See http://www.unicode.org/glossary/#code_point + static constexpr uint32_t kMinCodePoint = 0x000000; + + // The maximum value of a unicode code point. + // See http://www.unicode.org/glossary/#code_point + static constexpr uint32_t kMaxCodePoint = 0x10ffff; + + JsonEscaping() {} + virtual ~JsonEscaping() {} + + // Escape the given ByteSource to the given ByteSink. + static void Escape(strings::ByteSource* input, strings::ByteSink* output); + + // Escape the given ByteSource to the given ByteSink. + // This is optimized for the case where the string is all printable 7-bit + // ASCII and does not contain a few other characters (such as quotes). + static void Escape(StringPiece input, strings::ByteSink* output); + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(JsonEscaping); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_UTIL_INTERNAL__JSON_ESCAPING_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter.cc new file mode 100644 index 00000000..f28af9b0 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter.cc @@ -0,0 +1,190 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/json_objectwriter.h> + +#include <cmath> +#include <cstdint> +#include <limits> + +#include <stubs/casts.h> +#include <stubs/logging.h> +#include <stubs/common.h> +#include <util/internal/utility.h> +#include <util/internal/json_escaping.h> +#include <stubs/strutil.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +JsonObjectWriter::~JsonObjectWriter() { + if (element_ && !element_->is_root()) { + GOOGLE_LOG(WARNING) << "JsonObjectWriter was not fully closed."; + } +} + +JsonObjectWriter* JsonObjectWriter::StartObject(StringPiece name) { + WritePrefix(name); + WriteChar('{'); + PushObject(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::EndObject() { + Pop(); + WriteChar('}'); + if (element() && element()->is_root()) NewLine(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::StartList(StringPiece name) { + WritePrefix(name); + WriteChar('['); + PushArray(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::EndList() { + Pop(); + WriteChar(']'); + if (element()->is_root()) NewLine(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderBool(StringPiece name, + bool value) { + return RenderSimple(name, value ? "true" : "false"); +} + +JsonObjectWriter* JsonObjectWriter::RenderInt32(StringPiece name, + int32_t value) { + return RenderSimple(name, StrCat(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderUint32(StringPiece name, + uint32_t value) { + return RenderSimple(name, StrCat(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderInt64(StringPiece name, + int64_t value) { + WritePrefix(name); + WriteChar('"'); + WriteRawString(StrCat(value)); + WriteChar('"'); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderUint64(StringPiece name, + uint64_t value) { + WritePrefix(name); + WriteChar('"'); + WriteRawString(StrCat(value)); + WriteChar('"'); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderDouble(StringPiece name, + double value) { + if (std::isfinite(value)) { + return RenderSimple(name, SimpleDtoa(value)); + } + + // Render quoted with NaN/Infinity-aware DoubleAsString. + return RenderString(name, DoubleAsString(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderFloat(StringPiece name, + float value) { + if (std::isfinite(value)) { + return RenderSimple(name, SimpleFtoa(value)); + } + + // Render quoted with NaN/Infinity-aware FloatAsString. + return RenderString(name, FloatAsString(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderString(StringPiece name, + StringPiece value) { + WritePrefix(name); + WriteChar('"'); + JsonEscaping::Escape(value, &sink_); + WriteChar('"'); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderBytes(StringPiece name, + StringPiece value) { + WritePrefix(name); + std::string base64; + + if (use_websafe_base64_for_bytes_) + WebSafeBase64EscapeWithPadding(std::string(value), &base64); + else + Base64Escape(value, &base64); + + WriteChar('"'); + // TODO(wpoon): Consider a ByteSink solution that writes the base64 bytes + // directly to the stream, rather than first putting them + // into a string and then writing them to the stream. + stream_->WriteRaw(base64.data(), base64.size()); + WriteChar('"'); + return this; +} + +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() || element()->is_json_object()) { + WriteChar('"'); + if (!name.empty()) { + JsonEscaping::Escape(name, &sink_); + } + WriteRawString("\":"); + if (!indent_string_.empty()) WriteChar(' '); + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter.h b/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter.h new file mode 100644 index 00000000..791decdc --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter.h @@ -0,0 +1,278 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_OBJECTWRITER_H__ + +#include <cstdint> +#include <memory> +#include <string> + +#include <io/coded_stream.h> +#include <util/internal/structured_objectwriter.h> +#include <stubs/bytestream.h> + +// clang-format off +#include <port_def.inc> +// clang-format on + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +// An ObjectWriter implementation that outputs JSON. This ObjectWriter +// supports writing a compact form or a pretty printed form. +// +// Sample usage: +// string output; +// StringOutputStream* str_stream = new StringOutputStream(&output); +// CodedOutputStream* out_stream = new CodedOutputStream(str_stream); +// JsonObjectWriter* ow = new JsonObjectWriter(" ", out_stream); +// ow->StartObject("") +// ->RenderString("name", "value") +// ->RenderString("emptystring", string()) +// ->StartObject("nested") +// ->RenderInt64("light", 299792458); +// ->RenderDouble("pi", 3.141592653589793); +// ->EndObject() +// ->StartList("empty") +// ->EndList() +// ->EndObject(); +// +// And then the output string would become: +// { +// "name": "value", +// "emptystring": "", +// "nested": { +// "light": "299792458", +// "pi": 3.141592653589793 +// }, +// "empty": [] +// } +// +// JsonObjectWriter does not validate if calls actually result in valid JSON. +// For example, passing an empty name when one would be required won't result +// in an error, just an invalid output. +// +// Note that all int64 and uint64 are rendered as strings instead of numbers. +// This is because JavaScript parses numbers as 64-bit float thus int64 and +// uint64 would lose precision if rendered as numbers. +// +// JsonObjectWriter is thread-unsafe. +class PROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { + public: + JsonObjectWriter(StringPiece indent_string, io::CodedOutputStream* out) + : element_(new Element(/*parent=*/nullptr, /*is_json_object=*/false)), + stream_(out), + sink_(out), + indent_string_(indent_string), + indent_char_('\0'), + indent_count_(0), + use_websafe_base64_for_bytes_(false) { + // See if we have a trivial sequence of indent characters. + if (!indent_string.empty()) { + indent_char_ = indent_string[0]; + indent_count_ = indent_string.length(); + for (int i = 1; i < indent_string.length(); i++) { + if (indent_char_ != indent_string_[i]) { + indent_char_ = '\0'; + indent_count_ = 0; + break; + } + } + } + } + virtual ~JsonObjectWriter(); + + // ObjectWriter methods. + JsonObjectWriter* StartObject(StringPiece name) override; + JsonObjectWriter* EndObject() override; + JsonObjectWriter* StartList(StringPiece name) override; + JsonObjectWriter* EndList() override; + JsonObjectWriter* RenderBool(StringPiece name, bool value) override; + JsonObjectWriter* RenderInt32(StringPiece name, int32_t value) override; + JsonObjectWriter* RenderUint32(StringPiece name, + uint32_t value) override; + JsonObjectWriter* RenderInt64(StringPiece name, int64_t value) override; + JsonObjectWriter* RenderUint64(StringPiece name, + uint64_t value) override; + JsonObjectWriter* RenderDouble(StringPiece name, double value) override; + JsonObjectWriter* RenderFloat(StringPiece name, float value) override; + JsonObjectWriter* RenderString(StringPiece name, + StringPiece value) override; + JsonObjectWriter* RenderBytes(StringPiece name, StringPiece value) override; + JsonObjectWriter* RenderNull(StringPiece name) override; + virtual JsonObjectWriter* RenderNullAsEmpty(StringPiece name); + + void set_use_websafe_base64_for_bytes(bool value) { + use_websafe_base64_for_bytes_ = value; + } + + protected: + class PROTOBUF_EXPORT Element : public BaseElement { + public: + Element(Element* parent, bool is_json_object) + : BaseElement(parent), + is_first_(true), + is_json_object_(is_json_object) {} + + // Called before each field of the Element is to be processed. + // Returns true if this is the first call (processing the first field). + bool is_first() { + if (is_first_) { + is_first_ = false; + return true; + } + return false; + } + + // Whether we are currently rendering inside a JSON object (i.e., between + // StartObject() and EndObject()). + bool is_json_object() const { return is_json_object_; } + + private: + bool is_first_; + bool is_json_object_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(Element); + }; + + Element* element() override { return element_.get(); } + + private: + class PROTOBUF_EXPORT ByteSinkWrapper : public strings::ByteSink { + public: + explicit ByteSinkWrapper(io::CodedOutputStream* stream) : stream_(stream) {} + ~ByteSinkWrapper() override {} + + // ByteSink methods. + void Append(const char* bytes, size_t n) override { + stream_->WriteRaw(bytes, n); + } + + private: + io::CodedOutputStream* stream_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ByteSinkWrapper); + }; + + // Renders a simple value as a string. By default all non-string Render + // methods convert their argument to a string and call this method. This + // method can then be used to render the simple value without escaping it. + JsonObjectWriter* RenderSimple(StringPiece name, + StringPiece value) { + WritePrefix(name); + WriteRawString(value); + return this; + } + + // Pushes a new JSON array element to the stack. + void PushArray() { + element_.reset(new Element(element_.release(), /*is_json_object=*/false)); + } + + // Pushes a new JSON object element to the stack. + void PushObject() { + element_.reset(new Element(element_.release(), /*is_json_object=*/true)); + } + + // Pops an element off of the stack and deletes the popped element. + void Pop() { + bool needs_newline = !element_->is_first(); + element_.reset(element_->pop<Element>()); + if (needs_newline) NewLine(); + } + + // If pretty printing is enabled, this will write a newline to the output, + // followed by optional indentation. Otherwise this method is a noop. + void NewLine() { + if (!indent_string_.empty()) { + size_t len = sizeof('\n') + (indent_string_.size() * element()->level()); + + // Take the slow-path if we don't have sufficient characters remaining in + // our buffer or we have a non-trivial indent string which would prevent + // us from using memset. + uint8_t* out = nullptr; + if (indent_count_ > 0) { + out = stream_->GetDirectBufferForNBytesAndAdvance(len); + } + + if (out != nullptr) { + out[0] = '\n'; + memset(&out[1], indent_char_, len - 1); + } else { + // Slow path, no contiguous output buffer available. + WriteChar('\n'); + for (int i = 0; i < element()->level(); i++) { + stream_->WriteRaw(indent_string_.c_str(), indent_string_.length()); + } + } + } + } + + // Writes a prefix. This will write out any pretty printing and + // commas that are required, followed by the name and a ':' if + // the name is not null. + void WritePrefix(StringPiece name); + + // Writes an individual character to the output. + void WriteChar(const char c) { stream_->WriteRaw(&c, sizeof(c)); } + + // Writes a string to the output. + void WriteRawString(StringPiece s) { + stream_->WriteRaw(s.data(), s.length()); + } + + std::unique_ptr<Element> element_; + io::CodedOutputStream* stream_; + ByteSinkWrapper sink_; + const std::string indent_string_; + + // For the common case of indent being a single character repeated. + char indent_char_; + int indent_count_; + + // Whether to use regular or websafe base64 encoding for byte fields. Defaults + // to regular base64 encoding. + bool use_websafe_base64_for_bytes_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_OBJECTWRITER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter_test.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter_test.cc new file mode 100644 index 00000000..89b43111 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_objectwriter_test.cc @@ -0,0 +1,315 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/json_objectwriter.h> + +#include <cstdint> + +#include <io/zero_copy_stream_impl_lite.h> +#include <util/internal/utility.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using io::CodedOutputStream; +using io::StringOutputStream; + +class JsonObjectWriterTest : public ::testing::Test { + protected: + JsonObjectWriterTest() + : str_stream_(new StringOutputStream(&output_)), + out_stream_(new CodedOutputStream(str_stream_)), + ow_(nullptr) {} + + ~JsonObjectWriterTest() override { delete ow_; } + + std::string CloseStreamAndGetString() { + delete out_stream_; + delete str_stream_; + return output_; + } + + std::string output_; + StringOutputStream* const str_stream_; + CodedOutputStream* const out_stream_; + JsonObjectWriter* ow_; +}; + +TEST_F(JsonObjectWriterTest, EmptyRootObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("")->EndObject(); + EXPECT_EQ("{}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, EmptyObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderString("test", "value") + ->StartObject("empty") + ->EndObject() + ->EndObject(); + EXPECT_EQ("{\"test\":\"value\",\"empty\":{}}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, EmptyRootList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartList("")->EndList(); + EXPECT_EQ("[]", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, EmptyList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderString("test", "value") + ->StartList("empty") + ->EndList() + ->EndObject(); + EXPECT_EQ("{\"test\":\"value\",\"empty\":[]}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, EmptyObjectKey) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("")->RenderString("", "value")->EndObject(); + EXPECT_EQ("{\"\":\"value\"}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, ObjectInObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->StartObject("nested") + ->RenderString("field", "value") + ->EndObject() + ->EndObject(); + EXPECT_EQ("{\"nested\":{\"field\":\"value\"}}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, ListInObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->StartList("nested") + ->RenderString("", "value") + ->EndList() + ->EndObject(); + EXPECT_EQ("{\"nested\":[\"value\"]}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, ObjectInList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartList("") + ->StartObject("") + ->RenderString("field", "value") + ->EndObject() + ->EndList(); + EXPECT_EQ("[{\"field\":\"value\"}]", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, ListInList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartList("") + ->StartList("") + ->RenderString("", "value") + ->EndList() + ->EndList(); + EXPECT_EQ("[[\"value\"]]", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, RenderPrimitives) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderBool("bool", true) + ->RenderDouble("double", std::numeric_limits<double>::max()) + ->RenderFloat("float", std::numeric_limits<float>::max()) + ->RenderInt32("int", std::numeric_limits<int32_t>::min()) + ->RenderInt64("long", std::numeric_limits<int64_t>::min()) + ->RenderBytes("bytes", "abracadabra") + ->RenderString("string", "string") + ->RenderBytes("emptybytes", "") + ->RenderString("emptystring", std::string()) + ->EndObject(); + EXPECT_EQ( + "{\"bool\":true," + "\"double\":" + + ValueAsString<double>(std::numeric_limits<double>::max()) + + "," + "\"float\":" + + ValueAsString<float>(std::numeric_limits<float>::max()) + + "," + "\"int\":-2147483648," + "\"long\":\"-9223372036854775808\"," + "\"bytes\":\"YWJyYWNhZGFicmE=\"," + "\"string\":\"string\"," + "\"emptybytes\":\"\"," + "\"emptystring\":\"\"}", + CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, BytesEncodesAsNonWebSafeBase64) { + std::string s; + s.push_back('\377'); + s.push_back('\357'); + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("")->RenderBytes("bytes", s)->EndObject(); + // Non-web-safe would encode this as "/+8=" + EXPECT_EQ("{\"bytes\":\"/+8=\"}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintList) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->StartList("items") + ->RenderString("", "item1") + ->RenderString("", "item2") + ->RenderString("", "item3") + ->EndList() + ->StartList("empty") + ->EndList() + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"items\": [\n" + " \"item1\",\n" + " \"item2\",\n" + " \"item3\"\n" + " ],\n" + " \"empty\": []\n" + "}\n", + CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintObject) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->StartObject("items") + ->RenderString("key1", "item1") + ->RenderString("key2", "item2") + ->RenderString("key3", "item3") + ->EndObject() + ->StartObject("empty") + ->EndObject() + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"items\": {\n" + " \"key1\": \"item1\",\n" + " \"key2\": \"item2\",\n" + " \"key3\": \"item3\"\n" + " },\n" + " \"empty\": {}\n" + "}\n", + CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintEmptyObjectInEmptyList) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->StartList("list") + ->StartObject("") + ->EndObject() + ->EndList() + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"list\": [\n" + " {}\n" + " ]\n" + "}\n", + CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintDoubleIndent) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->RenderBool("bool", true) + ->RenderInt32("int", 42) + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"bool\": true,\n" + " \"int\": 42\n" + "}\n", + CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, StringsEscapedAndEnclosedInDoubleQuotes) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("")->RenderString("string", "'<>&\\\"\r\n")->EndObject(); + EXPECT_EQ("{\"string\":\"'\\u003c\\u003e&\\\\\\\"\\r\\n\"}", + CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, Stringification) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderDouble("double_nan", std::numeric_limits<double>::quiet_NaN()) + ->RenderFloat("float_nan", std::numeric_limits<float>::quiet_NaN()) + ->RenderDouble("double_pos", std::numeric_limits<double>::infinity()) + ->RenderFloat("float_pos", std::numeric_limits<float>::infinity()) + ->RenderDouble("double_neg", -std::numeric_limits<double>::infinity()) + ->RenderFloat("float_neg", -std::numeric_limits<float>::infinity()) + ->EndObject(); + EXPECT_EQ( + "{\"double_nan\":\"NaN\"," + "\"float_nan\":\"NaN\"," + "\"double_pos\":\"Infinity\"," + "\"float_pos\":\"Infinity\"," + "\"double_neg\":\"-Infinity\"," + "\"float_neg\":\"-Infinity\"}", + CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, TestRegularByteEncoding) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderBytes("bytes", "\x03\xef\xc0") + ->EndObject(); + + // Test that we get regular (non websafe) base64 encoding on byte fields by + // default. + EXPECT_EQ("{\"bytes\":\"A+/A\"}", CloseStreamAndGetString()); +} + +TEST_F(JsonObjectWriterTest, TestWebsafeByteEncoding) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->set_use_websafe_base64_for_bytes(true); + ow_->StartObject("") + ->RenderBytes("bytes", "\x03\xef\xc0\x10") + ->EndObject(); + + // Test that we get websafe base64 encoding when explicitly asked. + EXPECT_EQ("{\"bytes\":\"A-_AEA==\"}", CloseStreamAndGetString()); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser.cc new file mode 100644 index 00000000..c5a72931 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser.cc @@ -0,0 +1,995 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/json_stream_parser.h> + +#include <algorithm> +#include <cctype> +#include <cmath> +#include <memory> +#include <stack> +#include <string> + +#include <stubs/common.h> +#include <stubs/logging.h> +#include <stubs/strutil.h> +#include <stubs/status.h> +#include <util/internal/object_writer.h> +#include <util/internal/json_escaping.h> + + +namespace google { +namespace protobuf { +namespace util { + +namespace converter { + +// Number of digits in an escaped UTF-16 code unit ('\\' 'u' X X X X) +static const int kUnicodeEscapedLength = 6; + +static const int kDefaultMaxRecursionDepth = 100; + +// These cannot be constexpr for portability with VS2015. +static const StringPiece kKeywordTrue = "true"; +static const StringPiece kKeywordFalse = "false"; +static const StringPiece kKeywordNull = "null"; + +inline bool IsLetter(char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || (c == '_') || + (c == '$'); +} + +inline bool IsAlphanumeric(char c) { + return IsLetter(c) || ('0' <= c && c <= '9'); +} + +// Indicates a character may not be part of an unquoted key. +inline bool IsKeySeparator(char c) { + return (ascii_isspace(c) || c == '"' || c == '\'' || c == '{' || + c == '}' || c == '[' || c == ']' || c == ':' || c == ','); +} + +inline void ReplaceInvalidCodePoints(StringPiece str, + const std::string& replacement, + std::string* dst) { + while (!str.empty()) { + int n_valid_bytes = internal::UTF8SpnStructurallyValid(str); + StringPiece valid_part = str.substr(0, n_valid_bytes); + StrAppend(dst, valid_part); + + if (n_valid_bytes == str.size()) { + break; + } + + // Append replacement value. + StrAppend(dst, replacement); + + // Move past valid bytes + one invalid byte. + str.remove_prefix(n_valid_bytes + 1); + } +} + +static bool ConsumeKey(StringPiece* input, StringPiece* key) { + if (input->empty() || !IsLetter((*input)[0])) return false; + int len = 1; + for (; len < input->size(); ++len) { + if (!IsAlphanumeric((*input)[len])) { + break; + } + } + *key = StringPiece(input->data(), len); + *input = StringPiece(input->data() + len, input->size() - len); + return true; +} + +// Same as 'ConsumeKey', but allows a widened set of key characters. +static bool ConsumeKeyPermissive(StringPiece* input, + StringPiece* key) { + if (input->empty() || !IsLetter((*input)[0])) return false; + int len = 1; + for (; len < input->size(); ++len) { + if (IsKeySeparator((*input)[len])) { + break; + } + } + *key = StringPiece(input->data(), len); + *input = StringPiece(input->data() + len, input->size() - len); + return true; +} + +static bool MatchKey(StringPiece input) { + return !input.empty() && IsLetter(input[0]); +} + +JsonStreamParser::JsonStreamParser(ObjectWriter* ow) + : ow_(ow), + stack_(), + leftover_(), + json_(), + p_(), + key_(), + key_storage_(), + finishing_(false), + seen_non_whitespace_(false), + allow_no_root_element_(false), + parsed_(), + parsed_storage_(), + string_open_(0), + chunk_storage_(), + coerce_to_utf8_(false), + utf8_replacement_character_(" "), + allow_empty_null_(false), + allow_permissive_key_naming_(false), + loose_float_number_conversion_(false), + recursion_depth_(0), + max_recursion_depth_(kDefaultMaxRecursionDepth) { + // Initialize the stack with a single value to be parsed. + stack_.push(VALUE); +} + +JsonStreamParser::~JsonStreamParser() {} + + +util::Status JsonStreamParser::Parse(StringPiece json) { + StringPiece chunk = json; + // If we have leftovers from a previous chunk, append the new chunk to it + // and create a new StringPiece pointing at the string's data. This could + // be large but we rely on the chunks to be small, assuming they are + // fragments of a Cord. + if (!leftover_.empty()) { + // Don't point chunk to leftover_ because leftover_ will be updated in + // ParseChunk(chunk). + chunk_storage_.swap(leftover_); + StrAppend(&chunk_storage_, json); + chunk = StringPiece(chunk_storage_); + } + + // Find the structurally valid UTF8 prefix and parse only that. + int n = internal::UTF8SpnStructurallyValid(chunk); + if (n > 0) { + util::Status status = ParseChunk(chunk.substr(0, n)); + + // Any leftover characters are stashed in leftover_ for later parsing when + // there is more data available. + StrAppend(&leftover_, chunk.substr(n)); + return status; + } else { + leftover_.assign(chunk.data(), chunk.size()); + return util::Status(); + } +} + +util::Status JsonStreamParser::FinishParse() { + // If we do not expect anything and there is nothing left to parse we're all + // done. + if (stack_.empty() && leftover_.empty()) { + return util::Status(); + } + + // Lifetime needs to last until RunParser returns, so keep this variable + // outside of the coerce_to_utf8 block. + std::unique_ptr<std::string> scratch; + + bool is_valid_utf8 = internal::IsStructurallyValidUTF8(leftover_); + if (coerce_to_utf8_ && !is_valid_utf8) { + scratch.reset(new std::string); + scratch->reserve(leftover_.size() * utf8_replacement_character_.size()); + ReplaceInvalidCodePoints(leftover_, utf8_replacement_character_, + scratch.get()); + p_ = json_ = *scratch; + } else { + p_ = json_ = leftover_; + if (!is_valid_utf8) { + return ReportFailure("Encountered non UTF-8 code points.", + ParseErrorType::NON_UTF_8); + } + } + + // Parse the remainder in finishing mode, which reports errors for things like + // unterminated strings or unknown tokens that would normally be retried. + finishing_ = true; + util::Status result = RunParser(); + if (result.ok()) { + SkipWhitespace(); + if (!p_.empty()) { + result = + ReportFailure("Parsing terminated before end of input.", + ParseErrorType::PARSING_TERMINATED_BEFORE_END_OF_INPUT); + } + } + return result; +} + +util::Status JsonStreamParser::ParseChunk(StringPiece chunk) { + // Do not do any work if the chunk is empty. + if (chunk.empty()) return util::Status(); + + p_ = json_ = chunk; + + finishing_ = false; + util::Status result = RunParser(); + if (!result.ok()) return result; + + SkipWhitespace(); + if (p_.empty()) { + // If we parsed everything we had, clear the leftover. + leftover_.clear(); + } else { + // If we do not expect anything i.e. stack is empty, and we have non-empty + // string left to parse, we report an error. + if (stack_.empty()) { + return ReportFailure( + "Parsing terminated before end of input.", + ParseErrorType::PARSING_TERMINATED_BEFORE_END_OF_INPUT); + } + // If we expect future data i.e. stack is non-empty, and we have some + // unparsed data left, we save it for later parse. + leftover_ = std::string(p_); + } + return util::Status(); +} + +bool JsonStreamParser::IsInputAllWhiteSpaces(TokenType type) { + // Conclude the whole input is full of white spaces by: + // - it is at the finishing stage + // - we have run out of the input data + // - haven't seen non-whitespace char so far + if (finishing_ && p_.empty() && type == UNKNOWN && !seen_non_whitespace_) { + return true; + } + return false; +} + +util::Status JsonStreamParser::RunParser() { + while (!stack_.empty()) { + ParseType type = stack_.top(); + TokenType t = (string_open_ == 0) ? GetNextTokenType() : BEGIN_STRING; + stack_.pop(); + util::Status result; + switch (type) { + case VALUE: + if (allow_no_root_element_ && IsInputAllWhiteSpaces(t)) { + return util::Status(); + } + result = ParseValue(t); + break; + + case OBJ_MID: + result = ParseObjectMid(t); + break; + + case ENTRY: + result = ParseEntry(t); + break; + + case ENTRY_MID: + result = ParseEntryMid(t); + break; + + case ARRAY_VALUE: + result = ParseArrayValue(t); + break; + + case ARRAY_MID: + result = ParseArrayMid(t); + break; + + default: + result = + util::InternalError(StrCat("Unknown parse type: ", type)); + break; + } + if (!result.ok()) { + // If we were cancelled, save our state and try again later. + if (!finishing_ && util::IsCancelled(result)) { + stack_.push(type); + // If we have a key we still need to render, make sure to save off the + // contents in our own storage. + if (!key_.empty() && key_storage_.empty()) { + StrAppend(&key_storage_, key_); + key_ = StringPiece(key_storage_); + } + result = util::Status(); + } + return result; + } + } + return util::Status(); +} + +util::Status JsonStreamParser::ParseValue(TokenType type) { + switch (type) { + case BEGIN_OBJECT: + return HandleBeginObject(); + case BEGIN_ARRAY: + return HandleBeginArray(); + case BEGIN_STRING: + return ParseString(); + case BEGIN_NUMBER: + return ParseNumber(); + case BEGIN_TRUE: + return ParseTrue(); + case BEGIN_FALSE: + return ParseFalse(); + case BEGIN_NULL: + return ParseNull(); + case UNKNOWN: + return ReportUnknown("Expected a value.", ParseErrorType::EXPECTED_VALUE); + default: { + // 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 + // else, making it invalid. + if (!finishing_ && p_.length() < kKeywordFalse.length()) { + return util::CancelledError(""); + } + + if (allow_empty_null_ && IsEmptyNullAllowed(type)) { + return ParseEmptyNull(); + } + return ReportFailure("Unexpected token.", + ParseErrorType::UNEXPECTED_TOKEN); + } + } +} + +util::Status JsonStreamParser::ParseString() { + util::Status result = ParseStringHelper(); + if (result.ok()) { + ow_->RenderString(key_, parsed_); + key_ = StringPiece(); + parsed_ = StringPiece(); + parsed_storage_.clear(); + } + return result; +} + +util::Status JsonStreamParser::ParseStringHelper() { + // If we haven't seen the start quote, grab it and remember it for later. + if (string_open_ == 0) { + string_open_ = *p_.data(); + GOOGLE_DCHECK(string_open_ == '\"' || string_open_ == '\''); + Advance(); + } + // Track where we last copied data from so we can minimize copying. + const char* last = p_.data(); + while (!p_.empty()) { + const char* data = p_.data(); + if (*data == '\\') { + // We're about to handle an escape, copy all bytes from last to data. + if (last < data) { + parsed_storage_.append(last, data - last); + } + // If we ran out of string after the \, cancel or report an error + // depending on if we expect more data later. + if (p_.length() == 1) { + if (!finishing_) { + return util::CancelledError(""); + } + return ReportFailure("Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } + // Parse a unicode escape if we found \u in the string. + if (data[1] == 'u') { + util::Status result = ParseUnicodeEscape(); + if (!result.ok()) { + return result; + } + // Move last pointer past the unicode escape and continue. + last = p_.data(); + continue; + } + // Handle the standard set of backslash-escaped characters. + switch (data[1]) { + case 'b': + parsed_storage_.push_back('\b'); + break; + case 'f': + parsed_storage_.push_back('\f'); + break; + case 'n': + parsed_storage_.push_back('\n'); + break; + case 'r': + parsed_storage_.push_back('\r'); + break; + case 't': + parsed_storage_.push_back('\t'); + break; + case 'v': + parsed_storage_.push_back('\v'); + break; + default: + parsed_storage_.push_back(data[1]); + } + // We handled two characters, so advance past them and continue. + p_.remove_prefix(2); + last = p_.data(); + continue; + } + // If we found the closing quote note it, advance past it, and return. + if (*data == string_open_) { + // If we didn't copy anything, reuse the input buffer. + if (parsed_storage_.empty()) { + parsed_ = StringPiece(last, data - last); + } else { + if (last < data) { + parsed_storage_.append(last, data - last); + } + parsed_ = StringPiece(parsed_storage_); + } + // Clear the quote char so next time we try to parse a string we'll + // start fresh. + string_open_ = 0; + Advance(); + return util::Status(); + } + // Normal character, just advance past it. + Advance(); + } + // If we ran out of characters, copy over what we have so far. + if (last < p_.data()) { + parsed_storage_.append(last, p_.data() - last); + } + // If we didn't find the closing quote but we expect more data, cancel for now + if (!finishing_) { + return util::CancelledError(""); + } + // End of string reached without a closing quote, report an error. + string_open_ = 0; + return ReportFailure("Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); +} + +// Converts a unicode escaped character to a decimal value stored in a char32 +// for use in UTF8 encoding utility. We assume that str begins with \uhhhh and +// convert that from the hex number to a decimal value. +// +// There are some security exploits with UTF-8 that we should be careful of: +// - http://www.unicode.org/reports/tr36/#UTF-8_Exploit +// - http://sites/intl-eng/design-guide/core-application +util::Status JsonStreamParser::ParseUnicodeEscape() { + if (p_.length() < kUnicodeEscapedLength) { + if (!finishing_) { + return util::CancelledError(""); + } + return ReportFailure("Illegal hex string.", + ParseErrorType::ILLEGAL_HEX_STRING); + } + GOOGLE_DCHECK_EQ('\\', p_.data()[0]); + GOOGLE_DCHECK_EQ('u', p_.data()[1]); + uint32 code = 0; + for (int i = 2; i < kUnicodeEscapedLength; ++i) { + if (!isxdigit(p_.data()[i])) { + return ReportFailure("Invalid escape sequence.", + ParseErrorType::INVALID_ESCAPE_SEQUENCE); + } + code = (code << 4) + hex_digit_to_int(p_.data()[i]); + } + if (code >= JsonEscaping::kMinHighSurrogate && + code <= JsonEscaping::kMaxHighSurrogate) { + if (p_.length() < 2 * kUnicodeEscapedLength) { + if (!finishing_) { + return util::CancelledError(""); + } + if (!coerce_to_utf8_) { + return ReportFailure("Missing low surrogate.", + ParseErrorType::MISSING_LOW_SURROGATE); + } + } else if (p_.data()[kUnicodeEscapedLength] == '\\' && + p_.data()[kUnicodeEscapedLength + 1] == 'u') { + uint32 low_code = 0; + for (int i = kUnicodeEscapedLength + 2; i < 2 * kUnicodeEscapedLength; + ++i) { + if (!isxdigit(p_.data()[i])) { + return ReportFailure("Invalid escape sequence.", + ParseErrorType::INVALID_ESCAPE_SEQUENCE); + } + low_code = (low_code << 4) + hex_digit_to_int(p_.data()[i]); + } + if (low_code >= JsonEscaping::kMinLowSurrogate && + low_code <= JsonEscaping::kMaxLowSurrogate) { + // Convert UTF-16 surrogate pair to 21-bit Unicode codepoint. + code = (((code & 0x3FF) << 10) | (low_code & 0x3FF)) + + JsonEscaping::kMinSupplementaryCodePoint; + // Advance past the first code unit escape. + p_.remove_prefix(kUnicodeEscapedLength); + } else if (!coerce_to_utf8_) { + return ReportFailure("Invalid low surrogate.", + ParseErrorType::INVALID_LOW_SURROGATE); + } + } else if (!coerce_to_utf8_) { + return ReportFailure("Missing low surrogate.", + ParseErrorType::MISSING_LOW_SURROGATE); + } + } + if (!coerce_to_utf8_ && !IsValidCodePoint(code)) { + return ReportFailure("Invalid unicode code point.", + ParseErrorType::INVALID_UNICODE); + } + char buf[UTFmax]; + int len = EncodeAsUTF8Char(code, buf); + // Advance past the [final] code unit escape. + p_.remove_prefix(kUnicodeEscapedLength); + parsed_storage_.append(buf, len); + return util::Status(); +} + +util::Status JsonStreamParser::ParseNumber() { + NumberResult number; + util::Status result = ParseNumberHelper(&number); + if (result.ok()) { + switch (number.type) { + case NumberResult::DOUBLE: + ow_->RenderDouble(key_, number.double_val); + key_ = StringPiece(); + break; + + case NumberResult::INT: + ow_->RenderInt64(key_, number.int_val); + key_ = StringPiece(); + break; + + case NumberResult::UINT: + ow_->RenderUint64(key_, number.uint_val); + key_ = StringPiece(); + break; + + default: + return ReportFailure("Unable to parse number.", + ParseErrorType::UNABLE_TO_PARSE_NUMBER); + } + } + return result; +} + +util::Status JsonStreamParser::ParseDoubleHelper(const std::string& number, + NumberResult* result) { + if (!safe_strtod(number, &result->double_val)) { + return ReportFailure("Unable to parse number.", + ParseErrorType::UNABLE_TO_PARSE_NUMBER); + } + if (!loose_float_number_conversion_ && !std::isfinite(result->double_val)) { + return ReportFailure("Number exceeds the range of double.", + ParseErrorType::NUMBER_EXCEEDS_RANGE_DOUBLE); + } + result->type = NumberResult::DOUBLE; + return util::Status(); +} + +util::Status JsonStreamParser::ParseNumberHelper(NumberResult* result) { + const char* data = p_.data(); + int length = p_.length(); + + // Look for the first non-numeric character, or the end of the string. + int index = 0; + bool floating = false; + bool negative = data[index] == '-'; + // Find the first character that cannot be part of the number. Along the way + // detect if the number needs to be parsed as a double. + // Note that this restricts numbers to the JSON specification, so for example + // we do not support hex or octal notations. + for (; index < length; ++index) { + char c = data[index]; + if (isdigit(c)) continue; + if (c == '.' || c == 'e' || c == 'E') { + floating = true; + continue; + } + if (c == '+' || c == '-' || c == 'x') continue; + // Not a valid number character, break out. + break; + } + + // If the entire input is a valid number, and we may have more content in the + // future, we abort for now and resume when we know more. + if (index == length && !finishing_) { + return util::CancelledError(""); + } + + // Create a string containing just the number, so we can use safe_strtoX + std::string number = std::string(p_.substr(0, index)); + + // Floating point number, parse as a double. + if (floating) { + util::Status status = ParseDoubleHelper(number, result); + if (status.ok()) { + p_.remove_prefix(index); + } + return status; + } + + // Positive non-floating point number, parse as a uint64. + if (!negative) { + // Octal/Hex numbers are not valid JSON values. + if (number.length() >= 2 && number[0] == '0') { + return ReportFailure( + "Octal/hex numbers are not valid JSON values.", + ParseErrorType::OCTAL_OR_HEX_ARE_NOT_VALID_JSON_VALUES); + } + if (safe_strtou64(number, &result->uint_val)) { + result->type = NumberResult::UINT; + p_.remove_prefix(index); + return util::Status(); + } else { + // If the value is too large, parse it as double. + util::Status status = ParseDoubleHelper(number, result); + if (status.ok()) { + p_.remove_prefix(index); + } + return status; + } + } + + // Octal/Hex numbers are not valid JSON values. + if (number.length() >= 3 && number[1] == '0') { + return ReportFailure( + "Octal/hex numbers are not valid JSON values.", + ParseErrorType::OCTAL_OR_HEX_ARE_NOT_VALID_JSON_VALUES); + } + // Negative non-floating point number, parse as an int64. + if (safe_strto64(number, &result->int_val)) { + result->type = NumberResult::INT; + p_.remove_prefix(index); + return util::Status(); + } else { + // If the value is too large, parse it as double. + util::Status status = ParseDoubleHelper(number, result); + if (status.ok()) { + p_.remove_prefix(index); + } + return status; + } +} + +util::Status JsonStreamParser::HandleBeginObject() { + GOOGLE_DCHECK_EQ('{', *p_.data()); + Advance(); + ow_->StartObject(key_); + auto status = IncrementRecursionDepth(key_); + if (!status.ok()) { + return status; + } + key_ = StringPiece(); + stack_.push(ENTRY); + return util::Status(); +} + +util::Status JsonStreamParser::ParseObjectMid(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected , or } after key:value pair.", + ParseErrorType::EXPECTED_COMMA_OR_BRACES); + } + + // Object is complete, advance past the comma and render the EndObject. + if (type == END_OBJECT) { + Advance(); + ow_->EndObject(); + --recursion_depth_; + return util::Status(); + } + // Found a comma, advance past it and get ready for an entry. + if (type == VALUE_SEPARATOR) { + Advance(); + stack_.push(ENTRY); + return util::Status(); + } + // Illegal token after key:value pair. + return ReportFailure("Expected , or } after key:value pair.", + ParseErrorType::EXPECTED_COMMA_OR_BRACES); +} + +util::Status JsonStreamParser::ParseEntry(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } + + // Close the object and return. This allows for trailing commas. + if (type == END_OBJECT) { + ow_->EndObject(); + Advance(); + --recursion_depth_; + return util::Status(); + } + + util::Status result; + if (type == BEGIN_STRING) { + // Key is a string (standard JSON), parse it and store the string. + result = ParseStringHelper(); + if (result.ok()) { + key_storage_.clear(); + if (!parsed_storage_.empty()) { + parsed_storage_.swap(key_storage_); + key_ = StringPiece(key_storage_); + } else { + key_ = parsed_; + } + parsed_ = StringPiece(); + } + } else if (type == BEGIN_KEY) { + // Key is a bare key (back compat), create a StringPiece pointing to it. + result = ParseKey(); + } else if (type == BEGIN_NULL || type == BEGIN_TRUE || type == BEGIN_FALSE) { + // Key may be a bare key that begins with a reserved word. + result = ParseKey(); + if (result.ok() && (key_ == kKeywordNull || key_ == kKeywordTrue || + key_ == kKeywordFalse)) { + result = ReportFailure("Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } + } else { + // Unknown key type, report an error. + result = ReportFailure("Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } + // On success we next expect an entry mid ':' then an object mid ',' or '}' + if (result.ok()) { + stack_.push(OBJ_MID); + stack_.push(ENTRY_MID); + } + return result; +} + +util::Status JsonStreamParser::ParseEntryMid(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected : between key:value pair.", + ParseErrorType::EXPECTED_COLON); + } + if (type == ENTRY_SEPARATOR) { + Advance(); + stack_.push(VALUE); + return util::Status(); + } + return ReportFailure("Expected : between key:value pair.", + ParseErrorType::EXPECTED_COLON); +} + +util::Status JsonStreamParser::HandleBeginArray() { + GOOGLE_DCHECK_EQ('[', *p_.data()); + Advance(); + ow_->StartList(key_); + key_ = StringPiece(); + stack_.push(ARRAY_VALUE); + return util::Status(); +} + +util::Status JsonStreamParser::ParseArrayValue(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected a value or ] within an array.", + ParseErrorType::EXPECTED_VALUE_OR_BRACKET); + } + + if (type == END_ARRAY) { + ow_->EndList(); + Advance(); + return util::Status(); + } + + // 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. 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 (util::IsCancelled(result)) { + // If we were cancelled, pop back off the ARRAY_MID so we don't try to + // push it on again when we try over. + stack_.pop(); + } + return result; +} + +util::Status JsonStreamParser::ParseArrayMid(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected , or ] after array value.", + ParseErrorType::EXPECTED_COMMA_OR_BRACKET); + } + + if (type == END_ARRAY) { + ow_->EndList(); + Advance(); + return util::Status(); + } + + // Found a comma, advance past it and expect an array value next. + if (type == VALUE_SEPARATOR) { + Advance(); + stack_.push(ARRAY_VALUE); + return util::Status(); + } + // Illegal token after array value. + return ReportFailure("Expected , or ] after array value.", + ParseErrorType::EXPECTED_COMMA_OR_BRACKET); +} + +util::Status JsonStreamParser::ParseTrue() { + ow_->RenderBool(key_, true); + key_ = StringPiece(); + p_.remove_prefix(kKeywordTrue.length()); + return util::Status(); +} + +util::Status JsonStreamParser::ParseFalse() { + ow_->RenderBool(key_, false); + key_ = StringPiece(); + p_.remove_prefix(kKeywordFalse.length()); + return util::Status(); +} + +util::Status JsonStreamParser::ParseNull() { + ow_->RenderNull(key_); + key_ = StringPiece(); + p_.remove_prefix(kKeywordNull.length()); + return util::Status(); +} + +util::Status JsonStreamParser::ParseEmptyNull() { + ow_->RenderNull(key_); + key_ = StringPiece(); + return util::Status(); +} + +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, + ParseErrorType parse_code) { + (void)parse_code; // Parameter is used in Google-internal code. + static const int kContextLength = 20; + const char* p_start = p_.data(); + const char* json_start = json_.data(); + const char* begin = std::max(p_start - kContextLength, json_start); + const char* end = + std::min(p_start + kContextLength, json_start + json_.size()); + StringPiece segment(begin, end - begin); + std::string location(p_start - begin, ' '); + location.push_back('^'); + auto status = util::InvalidArgumentError( + StrCat(message, "\n", segment, "\n", location)); + return status; +} + +util::Status JsonStreamParser::ReportUnknown(StringPiece message, + ParseErrorType parse_code) { + // If we aren't finishing the parse, cancel parsing and try later. + if (!finishing_) { + return util::CancelledError(""); + } + if (p_.empty()) { + return ReportFailure(StrCat("Unexpected end of string. ", message), + parse_code); + } + return ReportFailure(message, parse_code); +} + +util::Status JsonStreamParser::IncrementRecursionDepth( + StringPiece key) const { + if (++recursion_depth_ > max_recursion_depth_) { + return util::InvalidArgumentError(StrCat( + "Message too deep. Max recursion depth reached for key '", key, "'")); + } + return util::Status(); +} + +void JsonStreamParser::SkipWhitespace() { + while (!p_.empty() && ascii_isspace(*p_.data())) { + Advance(); + } + if (!p_.empty() && !ascii_isspace(*p_.data())) { + seen_non_whitespace_ = true; + } +} + +void JsonStreamParser::Advance() { + // Advance by moving one UTF8 character while making sure we don't go beyond + // the length of StringPiece. + p_.remove_prefix(std::min<int>( + p_.length(), UTF8FirstLetterNumBytes(p_.data(), p_.length()))); +} + +util::Status JsonStreamParser::ParseKey() { + StringPiece original = p_; + + if (allow_permissive_key_naming_) { + if (!ConsumeKeyPermissive(&p_, &key_)) { + return ReportFailure("Invalid key or variable name.", + ParseErrorType::INVALID_KEY_OR_VARIABLE_NAME); + } + } else { + if (!ConsumeKey(&p_, &key_)) { + return ReportFailure("Invalid key or variable name.", + ParseErrorType::INVALID_KEY_OR_VARIABLE_NAME); + } + } + + // If we consumed everything but expect more data, reset p_ and cancel since + // we can't know if the key was complete or not. + if (!finishing_ && p_.empty()) { + p_ = original; + return util::CancelledError(""); + } + // Since we aren't using the key storage, clear it out. + key_storage_.clear(); + return util::Status(); +} + +JsonStreamParser::TokenType JsonStreamParser::GetNextTokenType() { + SkipWhitespace(); + + int size = p_.size(); + if (size == 0) { + // If we ran out of data, report unknown and we'll place the previous parse + // type onto the stack and try again when we have more data. + return UNKNOWN; + } + // TODO(sven): Split this method based on context since different contexts + // support different tokens. Would slightly speed up processing? + const char* data = p_.data(); + StringPiece data_view = StringPiece(data, size); + if (*data == '\"' || *data == '\'') return BEGIN_STRING; + if (*data == '-' || ('0' <= *data && *data <= '9')) { + return BEGIN_NUMBER; + } + if (size >= kKeywordTrue.length() && + HasPrefixString(data_view, kKeywordTrue)) { + return BEGIN_TRUE; + } + if (size >= kKeywordFalse.length() && + HasPrefixString(data_view, kKeywordFalse)) { + return BEGIN_FALSE; + } + if (size >= kKeywordNull.length() && + HasPrefixString(data_view, kKeywordNull)) { + return BEGIN_NULL; + } + if (*data == '{') return BEGIN_OBJECT; + if (*data == '}') return END_OBJECT; + if (*data == '[') return BEGIN_ARRAY; + if (*data == ']') return END_ARRAY; + if (*data == ':') return ENTRY_SEPARATOR; + if (*data == ',') return VALUE_SEPARATOR; + if (MatchKey(p_)) { + return BEGIN_KEY; + } + + // We don't know that we necessarily have an invalid token here, just that we + // can't parse what we have so far. So we don't report an error and just + // return UNKNOWN so we can try again later when we have more data, or if we + // finish and we have leftovers. + return UNKNOWN; +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser.h b/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser.h new file mode 100644 index 00000000..ac90e413 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser.h @@ -0,0 +1,349 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_STREAM_PARSER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_STREAM_PARSER_H__ + +#include <cstdint> +#include <stack> +#include <string> + +#include <stubs/common.h> +#include <stubs/status.h> +#include <stubs/strutil.h> +#include <stubs/status.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +class ObjectWriter; + +// A JSON parser that can parse a stream of JSON chunks rather than needing the +// entire JSON string up front. It is a modified version of the parser in +// //net/proto/json/json-parser.h that has been changed in the following ways: +// - Changed from recursion to an explicit stack to allow resumption +// - Added support for int64 and uint64 numbers +// - Removed support for octal and decimal escapes +// - Removed support for numeric keys +// - Removed support for functions (javascript) +// - Removed some lax-comma support (but kept trailing comma support) +// - Writes directly to an ObjectWriter rather than using subclassing +// +// Here is an example usage: +// JsonStreamParser parser(ow_.get()); +// util::Status result = parser.Parse(chunk1); +// result.Update(parser.Parse(chunk2)); +// result.Update(parser.FinishParse()); +// GOOGLE_DCHECK(result.ok()) << "Failed to parse JSON"; +// +// This parser is thread-compatible as long as only one thread is calling a +// Parse() method at a time. +class PROTOBUF_EXPORT JsonStreamParser { + public: + // Creates a JsonStreamParser that will write to the given ObjectWriter. + explicit JsonStreamParser(ObjectWriter* ow); + virtual ~JsonStreamParser(); + + // Parses a UTF-8 encoded JSON string from a StringPiece. If the returned + // status is non-ok, the status might contain a payload ParseErrorType with + // type_url kParseErrorTypeUrl and a payload containing string snippet of the + // error with type_url kParseErrorSnippetUrl. + util::Status Parse(StringPiece json); + + + // Finish parsing the JSON string. If the returned status is non-ok, the + // status might contain a payload ParseErrorType with type_url + // kParseErrorTypeUrl and a payload containing string snippet of the error + // with type_url kParseErrorSnippetUrl. + util::Status FinishParse(); + + + // Sets the max recursion depth of JSON message to be deserialized. JSON + // messages over this depth will fail to be deserialized. + // Default value is 100. + void set_max_recursion_depth(int max_depth) { + max_recursion_depth_ = max_depth; + } + + // Denotes the cause of error. + enum ParseErrorType { + UNKNOWN_PARSE_ERROR, + OCTAL_OR_HEX_ARE_NOT_VALID_JSON_VALUES, + EXPECTED_COLON, + EXPECTED_COMMA_OR_BRACKET, + EXPECTED_VALUE, + EXPECTED_COMMA_OR_BRACES, + EXPECTED_OBJECT_KEY_OR_BRACES, + EXPECTED_VALUE_OR_BRACKET, + INVALID_KEY_OR_VARIABLE_NAME, + NON_UTF_8, + PARSING_TERMINATED_BEFORE_END_OF_INPUT, + UNEXPECTED_TOKEN, + EXPECTED_CLOSING_QUOTE, + ILLEGAL_HEX_STRING, + INVALID_ESCAPE_SEQUENCE, + MISSING_LOW_SURROGATE, + INVALID_LOW_SURROGATE, + INVALID_UNICODE, + UNABLE_TO_PARSE_NUMBER, + NUMBER_EXCEEDS_RANGE_DOUBLE + }; + + private: + friend class JsonStreamParserTest; + // Return the current recursion depth. + int recursion_depth() { return recursion_depth_; } + + enum TokenType { + BEGIN_STRING, // " or ' + BEGIN_NUMBER, // - or digit + BEGIN_TRUE, // true + BEGIN_FALSE, // false + BEGIN_NULL, // null + BEGIN_OBJECT, // { + END_OBJECT, // } + BEGIN_ARRAY, // [ + END_ARRAY, // ] + ENTRY_SEPARATOR, // : + VALUE_SEPARATOR, // , + BEGIN_KEY, // letter, _, $ or digit. Must begin with non-digit + UNKNOWN // Unknown token or we ran out of the stream. + }; + + enum ParseType { + VALUE, // Expects a {, [, true, false, null, string or number + OBJ_MID, // Expects a ',' or } + ENTRY, // Expects a key or } + ENTRY_MID, // Expects a : + ARRAY_VALUE, // Expects a value or ] + ARRAY_MID // Expects a ',' or ] + }; + + // Holds the result of parsing a number + struct NumberResult { + enum Type { DOUBLE, INT, UINT }; + Type type; + union { + double double_val; + int64_t int_val; + uint64_t uint_val; + }; + }; + + // Parses a single chunk of JSON, returning an error if the JSON was invalid. + util::Status ParseChunk(StringPiece chunk); + + // Runs the parser based on stack_ and p_, until the stack is empty or p_ runs + // out of data. If we unexpectedly run out of p_ we push the latest back onto + // the stack and return. + util::Status RunParser(); + + // Parses a value from p_ and writes it to ow_. + // A value may be an object, array, true, false, null, string or number. + util::Status ParseValue(TokenType type); + + // Parses a string and writes it out to the ow_. + util::Status ParseString(); + + // Parses a string, storing the result in parsed_. + util::Status ParseStringHelper(); + + // This function parses unicode escape sequences in strings. It returns an + // error when there's a parsing error, either the size is not the expected + // size or a character is not a hex digit. When it returns str will contain + // what has been successfully parsed so far. + util::Status ParseUnicodeEscape(); + + // Expects p_ to point to a JSON number, writes the number to the writer using + // the appropriate Render method based on the type of number. + util::Status ParseNumber(); + + // Parse a number into a NumberResult, reporting an error if no number could + // be parsed. This method will try to parse into a uint64, int64, or double + // based on whether the number was positive or negative or had a decimal + // component. + util::Status ParseNumberHelper(NumberResult* result); + + // Parse a number as double into a NumberResult. + util::Status ParseDoubleHelper(const std::string& number, + NumberResult* result); + + // Handles a { during parsing of a value. + util::Status HandleBeginObject(); + + // Parses from the ENTRY state. + util::Status ParseEntry(TokenType type); + + // Parses from the ENTRY_MID state. + util::Status ParseEntryMid(TokenType type); + + // Parses from the OBJ_MID state. + util::Status ParseObjectMid(TokenType type); + + // Handles a [ during parsing of a value. + util::Status HandleBeginArray(); + + // Parses from the ARRAY_VALUE state. + util::Status ParseArrayValue(TokenType type); + + // Parses from the ARRAY_MID state. + util::Status ParseArrayMid(TokenType type); + + // Expects p_ to point to an unquoted literal + 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); + + // Whether the whole input is all whitespaces. + bool IsInputAllWhiteSpaces(TokenType type); + + // Report a failure as a util::Status. + util::Status ReportFailure(StringPiece message, + ParseErrorType parse_code); + + // Report a failure due to an UNKNOWN token type. We check if we hit the + // end of the stream and if we're finishing or not to detect what type of + // status to return in this case. + util::Status ReportUnknown(StringPiece message, + ParseErrorType parse_code); + + // Helper function to check recursion depth and increment it. It will return + // OkStatus() if the current depth is allowed. Otherwise an error is returned. + // key is used for error reporting. + util::Status IncrementRecursionDepth(StringPiece key) const; + + // Advance p_ past all whitespace or until the end of the string. + void SkipWhitespace(); + + // Advance p_ one UTF-8 character + void Advance(); + + // Expects p_ to point to the beginning of a key. + util::Status ParseKey(); + + // Return the type of the next token at p_. + TokenType GetNextTokenType(); + + // The object writer to write parse events to. + ObjectWriter* ow_; + + // The stack of parsing we still need to do. When the stack runs empty we will + // have parsed a single value from the root (e.g. an object or list). + std::stack<ParseType> stack_; + + // Contains any leftover text from a previous chunk that we weren't able to + // fully parse, for example the start of a key or number. + std::string leftover_; + + // The current chunk of JSON being parsed. Primarily used for providing + // context during error reporting. + StringPiece json_; + + // A pointer within the current JSON being parsed, used to track location. + StringPiece p_; + + // Stores the last key read, as we separate parsing of keys and values. + StringPiece key_; + + // Storage for key_ if we need to keep ownership, for example between chunks + // or if the key was unescaped from a JSON string. + std::string key_storage_; + + // True during the FinishParse() call, so we know that any errors are fatal. + // For example an unterminated string will normally result in cancelling and + // trying during the next chunk, but during FinishParse() it is an error. + bool finishing_; + + // Whether non whitespace tokens have been seen during parsing. + // It is used to handle the case of a pure whitespace stream input. + bool seen_non_whitespace_; + + // The JsonStreamParser requires a root element by default and it will raise + // error if the root element is missing. If `allow_no_root_element_` is true, + // the JsonStreamParser can also handle this case. + bool allow_no_root_element_; + + // String we parsed during a call to ParseStringHelper(). + StringPiece parsed_; + + // Storage for the string we parsed. This may be empty if the string was able + // to be parsed directly from the input. + std::string parsed_storage_; + + // The character that opened the string, either ' or ". + // A value of 0 indicates that string parsing is not in process. + char string_open_; + + // Storage for the chunk that are being parsed in ParseChunk(). + std::string chunk_storage_; + + // Whether to allow non UTF-8 encoded input and replace invalid code points. + bool coerce_to_utf8_; + + // Replacement character for invalid UTF-8 code points. + std::string utf8_replacement_character_; + + // Whether allows empty string represented null array value or object entry + // value. + bool allow_empty_null_; + + // Whether unquoted object keys can contain embedded non-alphanumeric + // characters when this is unambiguous for parsing. + bool allow_permissive_key_naming_; + + // Whether allows out-of-range floating point numbers or reject them. + bool loose_float_number_conversion_; + + // Tracks current recursion depth. + mutable int recursion_depth_; + + // Maximum allowed recursion depth. + int max_recursion_depth_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonStreamParser); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_STREAM_PARSER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser_test.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser_test.cc new file mode 100644 index 00000000..af2ee741 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/json_stream_parser_test.cc @@ -0,0 +1,979 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/json_stream_parser.h> + +#include <cstdint> + +#include <stubs/logging.h> +#include <stubs/common.h> +#include <util/internal/expecting_objectwriter.h> +#include <util/internal/object_writer.h> +#include <gtest/gtest.h> +#include <stubs/time.h> +#include <stubs/status.h> + + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using ParseErrorType = + ::google::protobuf::util::converter::JsonStreamParser::ParseErrorType; + + +// Tests for the JSON Stream Parser. These tests are intended to be +// comprehensive and cover the following: +// +// Positive tests: +// - true, false, null +// - empty object or array. +// - negative and positive double and int, unsigned int +// - single and double quoted strings +// - string key, unquoted key, numeric key +// - array containing array, object, value +// - object containing array, object, value +// - unicode handling in strings +// - ascii escaping (\b, \f, \n, \r, \t, \v) +// - trailing commas +// +// Negative tests: +// - illegal literals +// - mismatched quotes failure on strings +// - unterminated string failure +// - unexpected end of string failure +// - mismatched object and array closing +// - Failure to close array or object +// - numbers too large +// - invalid unicode escapes. +// - invalid unicode sequences. +// - numbers as keys +// +// 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, + std::function<void(JsonStreamParser*)> setup) { + JsonStreamParser parser(&mock_); + setup(&parser); + + // Special case for split == length, test parsing one character at a time. + if (split == json.length()) { + GOOGLE_LOG(INFO) << "Testing split every char: " << json; + for (int i = 0; i < json.length(); ++i) { + StringPiece single = json.substr(i, 1); + util::Status result = parser.Parse(single); + if (!result.ok()) { + return result; + } + } + return parser.FinishParse(); + } + + // Normal case, split at the split point and parse two substrings. + StringPiece first = json.substr(0, split); + StringPiece rest = json.substr(split); + GOOGLE_LOG(INFO) << "Testing split: " << first << "><" << rest; + util::Status result = parser.Parse(first); + if (result.ok()) { + result = parser.Parse(rest); + if (result.ok()) { + result = parser.FinishParse(); + } + } + if (result.ok()) { + EXPECT_EQ(parser.recursion_depth(), 0); + } + return result; + } + + void DoTest( + StringPiece json, int split, + std::function<void(JsonStreamParser*)> setup = [](JsonStreamParser* p) { + }) { + util::Status result = RunTest(json, split, setup); + if (!result.ok()) { + GOOGLE_LOG(WARNING) << result; + } + EXPECT_TRUE(result.ok()); + } + + void DoErrorTest( + StringPiece json, int split, StringPiece error_prefix, + std::function<void(JsonStreamParser*)> setup = [](JsonStreamParser* p) { + }) { + util::Status result = RunTest(json, split, setup); + EXPECT_TRUE(util::IsInvalidArgument(result)); + StringPiece error_message(result.message()); + EXPECT_EQ(error_prefix, error_message.substr(0, error_prefix.size())); + } + + void DoErrorTest( + StringPiece json, int split, StringPiece error_prefix, + ParseErrorType expected_parse_error_type, + std::function<void(JsonStreamParser*)> setup = [](JsonStreamParser* p) { + }) { + util::Status result = RunTest(json, split, setup); + EXPECT_TRUE(util::IsInvalidArgument(result)); + StringPiece error_message(result.message()); + EXPECT_EQ(error_prefix, error_message.substr(0, error_prefix.size())); + } + + +#ifndef _MSC_VER + // TODO(xiaofeng): We have to disable InSequence check for MSVC because it + // causes stack overflow due to its use of a linked list that is destructed + // recursively. + ::testing::InSequence in_sequence_; +#endif // !_MSC_VER + MockObjectWriter mock_; + ExpectingObjectWriter ow_; +}; + + +// Positive tests + +// - true, false, null +TEST_F(JsonStreamParserTest, SimpleTrue) { + StringPiece str = "true"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderBool("", true); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleFalse) { + StringPiece str = "false"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderBool("", false); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleNull) { + StringPiece str = "null"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderNull(""); + DoTest(str, i); + } +} + +// - empty object and array. +TEST_F(JsonStreamParserTest, EmptyObject) { + StringPiece str = "{}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->EndObject(); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, EmptyList) { + StringPiece str = "[]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->EndList(); + DoTest(str, i); + } +} + +// - negative and positive double and int, unsigned int +TEST_F(JsonStreamParserTest, SimpleDouble) { + StringPiece str = "42.5"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderDouble("", 42.5); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, ScientificDouble) { + StringPiece str = "1.2345e-10"; + for (int i = 0; i < str.length(); ++i) { + ow_.RenderDouble("", 1.2345e-10); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleNegativeDouble) { + StringPiece str = "-1045.235"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderDouble("", -1045.235); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleInt) { + StringPiece str = "123456"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderUint64("", 123456); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleNegativeInt) { + StringPiece str = "-79497823553162765"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderInt64("", int64_t{-79497823553162765}); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleUnsignedInt) { + StringPiece str = "11779497823553162765"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderUint64("", uint64_t{11779497823553162765u}); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, OctalNumberIsInvalid) { + StringPiece str = "01234"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.", + ParseErrorType::OCTAL_OR_HEX_ARE_NOT_VALID_JSON_VALUES); + } + str = "-01234"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.", + ParseErrorType::OCTAL_OR_HEX_ARE_NOT_VALID_JSON_VALUES); + } +} + +TEST_F(JsonStreamParserTest, HexNumberIsInvalid) { + StringPiece str = "0x1234"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.", + ParseErrorType::OCTAL_OR_HEX_ARE_NOT_VALID_JSON_VALUES); + } + str = "-0x1234"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.", + ParseErrorType::OCTAL_OR_HEX_ARE_NOT_VALID_JSON_VALUES); + } + str = "12x34"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Unable to parse number.", + ParseErrorType::UNABLE_TO_PARSE_NUMBER); + } +} + +// - single and double quoted strings +TEST_F(JsonStreamParserTest, EmptyDoubleQuotedString) { + StringPiece str = "\"\""; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", ""); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, EmptySingleQuotedString) { + StringPiece str = "''"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", ""); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleDoubleQuotedString) { + StringPiece str = "\"Some String\""; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", "Some String"); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleSingleQuotedString) { + StringPiece str = "'Another String'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", "Another String"); + DoTest(str, i); + } +} + +// - string key, unquoted key, numeric key +TEST_F(JsonStreamParserTest, ObjectKeyTypes) { + StringPiece str = + "{'s': true, \"d\": false, key: null, snake_key: [], camelKey: {}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("") + ->RenderBool("s", true) + ->RenderBool("d", false) + ->RenderNull("key") + ->StartList("snake_key") + ->EndList() + ->StartObject("camelKey") + ->EndObject() + ->EndObject(); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, UnquotedObjectKeyWithReservedPrefxes) { + StringPiece str = "{ nullkey: \"a\", truekey: \"b\", falsekey: \"c\"}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("") + ->RenderString("nullkey", "a") + ->RenderString("truekey", "b") + ->RenderString("falsekey", "c") + ->EndObject(); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, UnquotedObjectKeyWithReservedKeyword) { + StringPiece str = "{ null: \"a\", true: \"b\", false: \"c\"}"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, UnquotedObjectKeyWithEmbeddedNonAlphanumeric) { + StringPiece str = "{ foo-bar-baz: \"a\"}"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Expected : between key:value pair.", + ParseErrorType::EXPECTED_COLON); + } +} + + +// - array containing primitive values (true, false, null, num, string) +TEST_F(JsonStreamParserTest, ArrayPrimitiveValues) { + StringPiece str = "[true, false, null, 'one', \"two\"]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->RenderBool("", true) + ->RenderBool("", false) + ->RenderNull("") + ->RenderString("", "one") + ->RenderString("", "two") + ->EndList(); + DoTest(str, i); + } +} + +// - array containing array, object +TEST_F(JsonStreamParserTest, ArrayComplexValues) { + StringPiece str = + "[[22, -127, 45.3, -1056.4, 11779497823553162765], {'key': true}]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->StartList("") + ->RenderUint64("", 22) + ->RenderInt64("", -127) + ->RenderDouble("", 45.3) + ->RenderDouble("", -1056.4) + ->RenderUint64("", uint64_t{11779497823553162765u}) + ->EndList() + ->StartObject("") + ->RenderBool("key", true) + ->EndObject() + ->EndList(); + DoTest(str, i); + } +} + + +// - object containing array, object, value (true, false, null, num, string) +TEST_F(JsonStreamParserTest, ObjectValues) { + StringPiece str = + "{t: true, f: false, n: null, s: 'a string', d: \"another string\", pi: " + "22, ni: -127, pd: 45.3, nd: -1056.4, pl: 11779497823553162765, l: [[]], " + "o: {'key': true}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("") + ->RenderBool("t", true) + ->RenderBool("f", false) + ->RenderNull("n") + ->RenderString("s", "a string") + ->RenderString("d", "another string") + ->RenderUint64("pi", 22) + ->RenderInt64("ni", -127) + ->RenderDouble("pd", 45.3) + ->RenderDouble("nd", -1056.4) + ->RenderUint64("pl", uint64_t{11779497823553162765u}) + ->StartList("l") + ->StartList("") + ->EndList() + ->EndList() + ->StartObject("o") + ->RenderBool("key", true) + ->EndObject() + ->EndObject(); + DoTest(str, i); + } +} + + +TEST_F(JsonStreamParserTest, RejectNonUtf8WhenNotCoerced) { + StringPiece json = "{\"address\":\xFF\"חרושת 23, רעננה, ישראל\"}"; + for (int i = 0; i <= json.length(); ++i) { + DoErrorTest(json, i, "Encountered non UTF-8 code points.", + ParseErrorType::NON_UTF_8); + } + json = "{\"address\": \"חרושת 23,\xFFרעננה, ישראל\"}"; + for (int i = 0; i <= json.length(); ++i) { + DoErrorTest(json, i, "Encountered non UTF-8 code points.", + ParseErrorType::NON_UTF_8); + } + DoErrorTest("\xFF{}", 0, "Encountered non UTF-8 code points.", + ParseErrorType::NON_UTF_8); +} + +// - unicode handling in strings +TEST_F(JsonStreamParserTest, UnicodeEscaping) { + StringPiece str = "[\"\\u0639\\u0631\\u0628\\u0649\"]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->RenderString("", "\xD8\xB9\xD8\xB1\xD8\xA8\xD9\x89") + ->EndList(); + DoTest(str, i); + } +} + +// - unicode UTF-16 surrogate pair handling in strings +TEST_F(JsonStreamParserTest, UnicodeSurrogatePairEscaping) { + StringPiece str = + "[\"\\u0bee\\ud800\\uddf1\\uD80C\\uDDA4\\uD83d\\udC1D\\uD83C\\uDF6F\"]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->RenderString("", + "\xE0\xAF\xAE\xF0\x90\x87\xB1\xF0\x93\x86\xA4\xF0" + "\x9F\x90\x9D\xF0\x9F\x8D\xAF") + ->EndList(); + DoTest(str, i); + } +} + + +TEST_F(JsonStreamParserTest, UnicodeEscapingInvalidCodePointWhenNotCoerced) { + // A low surrogate alone. + StringPiece str = "[\"\\ude36\"]"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Invalid unicode code point.", + ParseErrorType::INVALID_UNICODE); + } +} + +TEST_F(JsonStreamParserTest, UnicodeEscapingMissingLowSurrogateWhenNotCoerced) { + // A high surrogate alone. + StringPiece str = "[\"\\ud83d\"]"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Missing low surrogate.", + ParseErrorType::MISSING_LOW_SURROGATE); + } + // A high surrogate with some trailing characters. + str = "[\"\\ud83d|ude36\"]"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Missing low surrogate.", + ParseErrorType::MISSING_LOW_SURROGATE); + } + // A high surrogate with half a low surrogate. + str = "[\"\\ud83d\\ude--\"]"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Invalid escape sequence.", + ParseErrorType::INVALID_ESCAPE_SEQUENCE); + } + // Two high surrogates. + str = "[\"\\ud83d\\ud83d\"]"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Invalid low surrogate.", + ParseErrorType::INVALID_LOW_SURROGATE); + } +} + +// - ascii escaping (\b, \f, \n, \r, \t, \v) +TEST_F(JsonStreamParserTest, AsciiEscaping) { + StringPiece str = + "[\"\\b\", \"\\ning\", \"test\\f\", \"\\r\\t\", \"test\\\\\\ving\"]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->RenderString("", "\b") + ->RenderString("", "\ning") + ->RenderString("", "test\f") + ->RenderString("", "\r\t") + ->RenderString("", "test\\\ving") + ->EndList(); + DoTest(str, i); + } +} + +// - trailing commas, we support a single trailing comma but no internal commas. +TEST_F(JsonStreamParserTest, TrailingCommas) { + StringPiece str = "[['a',true,], {b: null,},]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->StartList("") + ->RenderString("", "a") + ->RenderBool("", true) + ->EndList() + ->StartObject("") + ->RenderNull("b") + ->EndObject() + ->EndList(); + DoTest(str, i); + } +} + +// Negative tests + +// illegal literals +TEST_F(JsonStreamParserTest, ExtraTextAfterTrue) { + StringPiece str = "truee"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderBool("", true); + DoErrorTest(str, i, "Parsing terminated before end of input.", + ParseErrorType::PARSING_TERMINATED_BEFORE_END_OF_INPUT); + } +} + +TEST_F(JsonStreamParserTest, InvalidNumberDashOnly) { + StringPiece str = "-"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Unable to parse number.", + ParseErrorType::UNABLE_TO_PARSE_NUMBER); + } +} + +TEST_F(JsonStreamParserTest, InvalidNumberDashName) { + StringPiece str = "-foo"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Unable to parse number.", + ParseErrorType::UNABLE_TO_PARSE_NUMBER); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralInArray) { + StringPiece str = "[nule]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Unexpected token.", ParseErrorType::UNEXPECTED_TOKEN); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralInObject) { + StringPiece str = "{123false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +// mismatched quotes failure on strings +TEST_F(JsonStreamParserTest, MismatchedSingleQuotedLiteral) { + StringPiece str = "'Some str\""; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } +} + +TEST_F(JsonStreamParserTest, MismatchedDoubleQuotedLiteral) { + StringPiece str = "\"Another string that ends poorly!'"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } +} + +// unterminated strings +TEST_F(JsonStreamParserTest, UnterminatedLiteralString) { + StringPiece str = "\"Forgot the rest of i"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedStringEscape) { + StringPiece str = "\"Forgot the rest of \\"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedStringInArray) { + StringPiece str = "[\"Forgot to close the string]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedStringInObject) { + StringPiece str = "{f: \"Forgot to close the string}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedObject) { + StringPiece str = "{"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Unexpected end of string.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + + +// mismatched object and array closing +TEST_F(JsonStreamParserTest, MismatchedCloseObject) { + StringPiece str = "{'key': true]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderBool("key", true); + DoErrorTest(str, i, "Expected , or } after key:value pair.", + ParseErrorType::EXPECTED_COMMA_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, MismatchedCloseArray) { + StringPiece str = "[true, null}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->RenderBool("", true)->RenderNull(""); + DoErrorTest(str, i, "Expected , or ] after array value.", + ParseErrorType::EXPECTED_COMMA_OR_BRACKET); + } +} + +// Invalid object keys. +TEST_F(JsonStreamParserTest, InvalidNumericObjectKey) { + StringPiece str = "{42: true}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralObjectInObject) { + StringPiece str = "{{bob: true}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralArrayInObject) { + StringPiece str = "{[null]}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralValueInObject) { + StringPiece str = "{false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, MissingColonAfterStringInObject) { + StringPiece str = "{\"key\"}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected : between key:value pair.", + ParseErrorType::EXPECTED_COLON); + } +} + +TEST_F(JsonStreamParserTest, MissingColonAfterKeyInObject) { + StringPiece str = "{key}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected : between key:value pair.", + ParseErrorType::EXPECTED_COLON); + } +} + +TEST_F(JsonStreamParserTest, EndOfTextAfterKeyInObject) { + StringPiece str = "{key"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Unexpected end of string.", + ParseErrorType::EXPECTED_COLON); + } +} + +TEST_F(JsonStreamParserTest, MissingValueAfterColonInObject) { + StringPiece str = "{key:}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Unexpected token.", ParseErrorType::UNEXPECTED_TOKEN); + } +} + +TEST_F(JsonStreamParserTest, MissingCommaBetweenObjectEntries) { + StringPiece str = "{key:20 'hello': true}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderUint64("key", 20); + DoErrorTest(str, i, "Expected , or } after key:value pair.", + ParseErrorType::EXPECTED_COMMA_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralAsObjectKey) { + StringPiece str = "{false: 20}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, ExtraCharactersAfterObject) { + StringPiece str = "{}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->EndObject(); + DoErrorTest(str, i, "Parsing terminated before end of input.", + ParseErrorType::PARSING_TERMINATED_BEFORE_END_OF_INPUT); + } +} + +TEST_F(JsonStreamParserTest, PositiveNumberTooBigIsDouble) { + StringPiece str = "18446744073709551616"; // 2^64 + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderDouble("", 18446744073709552000.0); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, NegativeNumberTooBigIsDouble) { + StringPiece str = "-18446744073709551616"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderDouble("", -18446744073709551616.0); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, DoubleTooBig) { + StringPiece str = "[1.89769e+308]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Number exceeds the range of double.", + ParseErrorType::NUMBER_EXCEEDS_RANGE_DOUBLE); + } + str = "[-1.89769e+308]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Number exceeds the range of double.", + ParseErrorType::NUMBER_EXCEEDS_RANGE_DOUBLE); + } +} + + +// invalid bare backslash. +TEST_F(JsonStreamParserTest, UnfinishedEscape) { + StringPiece str = "\"\\"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string.", + ParseErrorType::EXPECTED_CLOSING_QUOTE); + } +} + +// invalid bare backslash u. +TEST_F(JsonStreamParserTest, UnfinishedUnicodeEscape) { + StringPiece str = "\"\\u"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Illegal hex string.", + ParseErrorType::ILLEGAL_HEX_STRING); + } +} + +// invalid unicode sequence. +TEST_F(JsonStreamParserTest, UnicodeEscapeCutOff) { + StringPiece str = "\"\\u12"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Illegal hex string.", + ParseErrorType::ILLEGAL_HEX_STRING); + } +} + +// invalid unicode sequence (valid in modern EcmaScript but not in JSON). +TEST_F(JsonStreamParserTest, BracketedUnicodeEscape) { + StringPiece str = "\"\\u{1f36f}\""; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Invalid escape sequence.", + ParseErrorType::INVALID_ESCAPE_SEQUENCE); + } +} + + +TEST_F(JsonStreamParserTest, UnicodeEscapeInvalidCharacters) { + StringPiece str = "\"\\u12$4hello"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Invalid escape sequence.", + ParseErrorType::INVALID_ESCAPE_SEQUENCE); + } +} + +// invalid unicode sequence in low half surrogate: g is not a hex digit. +TEST_F(JsonStreamParserTest, UnicodeEscapeLowHalfSurrogateInvalidCharacters) { + StringPiece str = "\"\\ud800\\udcfg\""; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Invalid escape sequence.", + ParseErrorType::INVALID_ESCAPE_SEQUENCE); + } +} + +// Extra commas with an object or array. +TEST_F(JsonStreamParserTest, ExtraCommaInObject) { + StringPiece str = "{'k1': true,,'k2': false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderBool("k1", true); + DoErrorTest(str, i, "Expected an object key or }.", + ParseErrorType::EXPECTED_OBJECT_KEY_OR_BRACES); + } +} + +TEST_F(JsonStreamParserTest, ExtraCommaInArray) { + StringPiece str = "[true,,false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->RenderBool("", true); + DoErrorTest(str, i, "Unexpected token.", ParseErrorType::UNEXPECTED_TOKEN); + } +} + +// Extra text beyond end of value. +TEST_F(JsonStreamParserTest, ExtraTextAfterLiteral) { + StringPiece str = "'hello', 'world'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", "hello"); + DoErrorTest(str, i, "Parsing terminated before end of input.", + ParseErrorType::PARSING_TERMINATED_BEFORE_END_OF_INPUT); + } +} + +TEST_F(JsonStreamParserTest, ExtraTextAfterObject) { + StringPiece str = "{'key': true} 'oops'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderBool("key", true)->EndObject(); + DoErrorTest(str, i, "Parsing terminated before end of input.", + ParseErrorType::PARSING_TERMINATED_BEFORE_END_OF_INPUT); + } +} + +TEST_F(JsonStreamParserTest, ExtraTextAfterArray) { + StringPiece str = "[null] 'oops'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->RenderNull("")->EndList(); + DoErrorTest(str, i, "Parsing terminated before end of input.", + ParseErrorType::PARSING_TERMINATED_BEFORE_END_OF_INPUT); + } +} + +// Random unknown text in the value. +TEST_F(JsonStreamParserTest, UnknownCharactersAsValue) { + StringPiece str = "*"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Expected a value.", ParseErrorType::EXPECTED_VALUE); + } +} + +TEST_F(JsonStreamParserTest, UnknownCharactersInArray) { + StringPiece str = "[*]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Expected a value or ] within an array.", + ParseErrorType::EXPECTED_VALUE_OR_BRACKET); + } +} + +TEST_F(JsonStreamParserTest, UnknownCharactersInObject) { + StringPiece str = "{'key': *}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected a value.", ParseErrorType::EXPECTED_VALUE); + } +} + +TEST_F(JsonStreamParserTest, DeepNestJsonNotExceedLimit) { + int count = 99; + std::string str; + for (int i = 0; i < count; ++i) { + StrAppend(&str, "{'a':"); + } + StrAppend(&str, "{'nest64':'v1', 'nest64': false, 'nest64': ['v2']}"); + for (int i = 0; i < count; ++i) { + StrAppend(&str, "}"); + } + ow_.StartObject(""); + for (int i = 0; i < count; ++i) { + ow_.StartObject("a"); + } + ow_.RenderString("nest64", "v1") + ->RenderBool("nest64", false) + ->StartList("nest64") + ->RenderString("", "v2") + ->EndList(); + for (int i = 0; i < count; ++i) { + ow_.EndObject(); + } + ow_.EndObject(); + DoTest(str, 0); +} + +TEST_F(JsonStreamParserTest, DeepNestJsonExceedLimit) { + int count = 98; + std::string str; + for (int i = 0; i < count; ++i) { + StrAppend(&str, "{'a':"); + } + // Supports trailing commas. + StrAppend(&str, + "{'nest11' : [{'nest12' : null,},]," + "'nest21' : {'nest22' : {'nest23' : false}}}"); + for (int i = 0; i < count; ++i) { + StrAppend(&str, "}"); + } + DoErrorTest(str, 0, + "Message too deep. Max recursion depth reached for key 'nest22'"); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/location_tracker.h b/NorthstarDedicatedTest/include/protobuf/util/internal/location_tracker.h new file mode 100644 index 00000000..ae7875fd --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/location_tracker.h @@ -0,0 +1,69 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_LOCATION_TRACKER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_LOCATION_TRACKER_H__ + +#include <string> + +#include <stubs/common.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// LocationTrackerInterface is an interface for classes that track +// the location information for the purpose of error reporting. +class PROTOBUF_EXPORT LocationTrackerInterface { + public: + virtual ~LocationTrackerInterface() {} + + // Returns the object location as human readable string. + virtual std::string ToString() const = 0; + + protected: + LocationTrackerInterface() {} + + private: + // Please do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(LocationTrackerInterface); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_LOCATION_TRACKER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/mock_error_listener.h b/NorthstarDedicatedTest/include/protobuf/util/internal/mock_error_listener.h new file mode 100644 index 00000000..65376eaf --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/mock_error_listener.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_MOCK_ERROR_LISTENER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_MOCK_ERROR_LISTENER_H__ + +#include <util/internal/error_listener.h> +#include <util/internal/location_tracker.h> +#include <gmock/gmock.h> +#include <stubs/strutil.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class MockErrorListener : public ErrorListener { + public: + MockErrorListener() {} + virtual ~MockErrorListener() {} + + MOCK_METHOD(void, InvalidName, + (const LocationTrackerInterface& loc, + StringPiece unknown_name, StringPiece message), + (override)); + MOCK_METHOD(void, InvalidValue, + (const LocationTrackerInterface& loc, StringPiece type_name, + StringPiece value), + (override)); + MOCK_METHOD(void, MissingField, + (const LocationTrackerInterface& loc, + StringPiece missing_name), + (override)); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_MOCK_ERROR_LISTENER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/object_location_tracker.h b/NorthstarDedicatedTest/include/protobuf/util/internal/object_location_tracker.h new file mode 100644 index 00000000..d5eab1a9 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/object_location_tracker.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_LOCATION_TRACKER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_LOCATION_TRACKER_H__ + +#include <string> + +#include <stubs/common.h> +#include <util/internal/location_tracker.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// An empty concrete implementation of LocationTrackerInterface. +class ObjectLocationTracker : public LocationTrackerInterface { + public: + // Creates an empty location tracker. + ObjectLocationTracker() {} + + ~ObjectLocationTracker() override {} + + // Returns empty because nothing is tracked. + std::string ToString() const override { return ""; } + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ObjectLocationTracker); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_LOCATION_TRACKER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/object_source.h b/NorthstarDedicatedTest/include/protobuf/util/internal/object_source.h new file mode 100644 index 00000000..378e7350 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/object_source.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_SOURCE_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_SOURCE_H__ + +#include <stubs/common.h> +#include <stubs/status.h> +#include <stubs/strutil.h> +#include <stubs/status.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class ObjectWriter; + +// An ObjectSource is anything that can write to an ObjectWriter. +// Implementation of this interface typically provide constructors or +// factory methods to create an instance based on some source data, for +// example, a character stream, or protobuf. +// +// Derived classes could be thread-unsafe. +class PROTOBUF_EXPORT ObjectSource { + public: + virtual ~ObjectSource() {} + + // Writes to the ObjectWriter + virtual util::Status WriteTo(ObjectWriter* ow) const { + return NamedWriteTo("", ow); + } + + // Writes to the ObjectWriter with a custom name for the message. + // This is useful when you chain ObjectSource together by embedding one + // within another. + virtual util::Status NamedWriteTo(StringPiece name, + ObjectWriter* ow) const = 0; + + protected: + ObjectSource() {} + + private: + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ObjectSource); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_SOURCE_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/object_writer.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/object_writer.cc new file mode 100644 index 00000000..3d57f832 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/object_writer.cc @@ -0,0 +1,93 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/object_writer.h> + +#include <util/internal/datapiece.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// static +void ObjectWriter::RenderDataPieceTo(const DataPiece& data, + StringPiece name, ObjectWriter* ow) { + switch (data.type()) { + case DataPiece::TYPE_INT32: { + ow->RenderInt32(name, data.ToInt32().value()); + break; + } + case DataPiece::TYPE_INT64: { + ow->RenderInt64(name, data.ToInt64().value()); + break; + } + case DataPiece::TYPE_UINT32: { + ow->RenderUint32(name, data.ToUint32().value()); + break; + } + case DataPiece::TYPE_UINT64: { + ow->RenderUint64(name, data.ToUint64().value()); + break; + } + case DataPiece::TYPE_DOUBLE: { + ow->RenderDouble(name, data.ToDouble().value()); + break; + } + case DataPiece::TYPE_FLOAT: { + ow->RenderFloat(name, data.ToFloat().value()); + break; + } + case DataPiece::TYPE_BOOL: { + ow->RenderBool(name, data.ToBool().value()); + break; + } + case DataPiece::TYPE_STRING: { + ow->RenderString(name, data.ToString().value()); + break; + } + case DataPiece::TYPE_BYTES: { + ow->RenderBytes(name, data.ToBytes().value()); + break; + } + case DataPiece::TYPE_NULL: { + ow->RenderNull(name); + break; + } + default: + break; + } +} + + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/object_writer.h b/NorthstarDedicatedTest/include/protobuf/util/internal/object_writer.h new file mode 100644 index 00000000..abbaa520 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/object_writer.h @@ -0,0 +1,151 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_WRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_WRITER_H__ + +#include <cstdint> + +#include <stubs/common.h> +#include <stubs/strutil.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +class DataPiece; + +// An ObjectWriter is an interface for writing a stream of events +// representing objects and collections. Implementation of this +// interface can be used to write an object stream to an in-memory +// structure, protobufs, JSON, XML, or any other output format +// desired. The ObjectSource interface is typically used as the +// source of an object stream. +// +// See JsonObjectWriter for a sample implementation of ObjectWriter +// and its use. +// +// Derived classes could be thread-unsafe. +// +// TODO(xinb): seems like a prime candidate to apply the RAII paradigm +// and get rid the need to call EndXXX(). +class PROTOBUF_EXPORT ObjectWriter { + public: + virtual ~ObjectWriter() {} + + // Starts an object. If the name is empty, the object will not be named. + virtual ObjectWriter* StartObject(StringPiece name) = 0; + + // Ends an object. + virtual ObjectWriter* EndObject() = 0; + + // Starts a list. If the name is empty, the list will not be named. + virtual ObjectWriter* StartList(StringPiece name) = 0; + + // Ends a list. + virtual ObjectWriter* EndList() = 0; + + // Renders a boolean value. + virtual ObjectWriter* RenderBool(StringPiece name, bool value) = 0; + + // Renders an 32-bit integer value. + virtual ObjectWriter* RenderInt32(StringPiece name, int32_t value) = 0; + + // Renders an 32-bit unsigned integer value. + virtual ObjectWriter* RenderUint32(StringPiece name, + uint32_t value) = 0; + + // Renders a 64-bit integer value. + virtual ObjectWriter* RenderInt64(StringPiece name, int64_t value) = 0; + + // Renders an 64-bit unsigned integer value. + virtual ObjectWriter* RenderUint64(StringPiece name, + uint64_t value) = 0; + + + // Renders a double value. + virtual ObjectWriter* RenderDouble(StringPiece name, double value) = 0; + // Renders a float value. + virtual ObjectWriter* RenderFloat(StringPiece name, float value) = 0; + + // Renders a StringPiece value. This is for rendering strings. + virtual ObjectWriter* RenderString(StringPiece name, + StringPiece value) = 0; + + // Renders a bytes value. + virtual ObjectWriter* RenderBytes(StringPiece name, StringPiece value) = 0; + + // Renders a Null value. + virtual ObjectWriter* RenderNull(StringPiece name) = 0; + + + // Renders a DataPiece object to a ObjectWriter. + static void RenderDataPieceTo(const DataPiece& data, StringPiece name, + ObjectWriter* ow); + + + // Indicates whether this ObjectWriter has completed writing the root message, + // usually this means writing of one complete object. Subclasses must override + // this behavior appropriately. + virtual bool done() { return false; } + + void set_use_strict_base64_decoding(bool value) { + use_strict_base64_decoding_ = value; + } + + bool use_strict_base64_decoding() const { + return use_strict_base64_decoding_; + } + + protected: + ObjectWriter() : use_strict_base64_decoding_(true) {} + + private: + // If set to true, we use the stricter version of base64 decoding for byte + // fields by making sure decoded version encodes back to the original string. + bool use_strict_base64_decoding_; + + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_WRITER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/proto_writer.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/proto_writer.cc new file mode 100644 index 00000000..33b33262 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/proto_writer.cc @@ -0,0 +1,825 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/proto_writer.h> + +#include <cstdint> +#include <functional> +#include <stack> + +#include <stubs/once.h> +#include <wire_format_lite.h> +#include <util/internal/field_mask_utility.h> +#include <util/internal/object_location_tracker.h> +#include <util/internal/constants.h> +#include <util/internal/utility.h> +#include <stubs/strutil.h> +#include <stubs/statusor.h> +#include <stubs/time.h> +#include <stubs/map_util.h> + + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using io::CodedOutputStream; +using ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite; + +ProtoWriter::ProtoWriter(TypeResolver* type_resolver, + const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener) + : master_type_(type), + typeinfo_(TypeInfo::NewTypeInfo(type_resolver)), + own_typeinfo_(true), + done_(false), + ignore_unknown_fields_(false), + ignore_unknown_enum_values_(false), + use_lower_camel_for_enums_(false), + case_insensitive_enum_parsing_(true), + use_json_name_in_missing_fields_(false), + element_(nullptr), + size_insert_(), + output_(output), + buffer_(), + adapter_(&buffer_), + stream_(new CodedOutputStream(&adapter_)), + listener_(listener), + invalid_depth_(0), + tracker_(new ObjectLocationTracker()) {} + +ProtoWriter::ProtoWriter(const TypeInfo* typeinfo, + const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener) + : master_type_(type), + typeinfo_(typeinfo), + own_typeinfo_(false), + done_(false), + ignore_unknown_fields_(false), + ignore_unknown_enum_values_(false), + use_lower_camel_for_enums_(false), + case_insensitive_enum_parsing_(true), + use_json_name_in_missing_fields_(false), + element_(nullptr), + size_insert_(), + output_(output), + buffer_(), + adapter_(&buffer_), + stream_(new CodedOutputStream(&adapter_)), + listener_(listener), + invalid_depth_(0), + tracker_(new ObjectLocationTracker()) {} + +ProtoWriter::~ProtoWriter() { + if (own_typeinfo_) { + delete typeinfo_; + } + if (element_ == nullptr) return; + // Cleanup explicitly in order to avoid destructor stack overflow when input + // is deeply nested. + // Cast to BaseElement to avoid doing additional checks (like missing fields) + // during pop(). + std::unique_ptr<BaseElement> element( + static_cast<BaseElement*>(element_.get())->pop<BaseElement>()); + while (element != nullptr) { + element.reset(element->pop<BaseElement>()); + } +} + +namespace { + +// Writes an INT32 field, including tag to the stream. +inline util::Status WriteInt32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<int32_t> i32 = data.ToInt32(); + if (i32.ok()) { + WireFormatLite::WriteInt32(field_number, i32.value(), stream); + } + return i32.status(); +} + +// writes an SFIXED32 field, including tag, to the stream. +inline util::Status WriteSFixed32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<int32_t> i32 = data.ToInt32(); + if (i32.ok()) { + WireFormatLite::WriteSFixed32(field_number, i32.value(), stream); + } + return i32.status(); +} + +// Writes an SINT32 field, including tag, to the stream. +inline util::Status WriteSInt32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<int32_t> i32 = data.ToInt32(); + if (i32.ok()) { + WireFormatLite::WriteSInt32(field_number, i32.value(), stream); + } + return i32.status(); +} + +// Writes a FIXED32 field, including tag, to the stream. +inline util::Status WriteFixed32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<uint32_t> u32 = data.ToUint32(); + if (u32.ok()) { + WireFormatLite::WriteFixed32(field_number, u32.value(), stream); + } + return u32.status(); +} + +// Writes a UINT32 field, including tag, to the stream. +inline util::Status WriteUInt32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<uint32_t> u32 = data.ToUint32(); + if (u32.ok()) { + WireFormatLite::WriteUInt32(field_number, u32.value(), stream); + } + return u32.status(); +} + +// Writes an INT64 field, including tag, to the stream. +inline util::Status WriteInt64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<int64_t> i64 = data.ToInt64(); + if (i64.ok()) { + WireFormatLite::WriteInt64(field_number, i64.value(), stream); + } + return i64.status(); +} + +// Writes an SFIXED64 field, including tag, to the stream. +inline util::Status WriteSFixed64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<int64_t> i64 = data.ToInt64(); + if (i64.ok()) { + WireFormatLite::WriteSFixed64(field_number, i64.value(), stream); + } + return i64.status(); +} + +// Writes an SINT64 field, including tag, to the stream. +inline util::Status WriteSInt64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<int64_t> i64 = data.ToInt64(); + if (i64.ok()) { + WireFormatLite::WriteSInt64(field_number, i64.value(), stream); + } + return i64.status(); +} + +// Writes a FIXED64 field, including tag, to the stream. +inline util::Status WriteFixed64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<uint64_t> u64 = data.ToUint64(); + if (u64.ok()) { + WireFormatLite::WriteFixed64(field_number, u64.value(), stream); + } + return u64.status(); +} + +// Writes a UINT64 field, including tag, to the stream. +inline util::Status WriteUInt64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<uint64_t> u64 = data.ToUint64(); + if (u64.ok()) { + WireFormatLite::WriteUInt64(field_number, u64.value(), stream); + } + return u64.status(); +} + +// Writes a DOUBLE field, including tag, to the stream. +inline util::Status WriteDouble(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<double> d = data.ToDouble(); + if (d.ok()) { + WireFormatLite::WriteDouble(field_number, d.value(), stream); + } + return d.status(); +} + +// Writes a FLOAT field, including tag, to the stream. +inline util::Status WriteFloat(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<float> f = data.ToFloat(); + if (f.ok()) { + WireFormatLite::WriteFloat(field_number, f.value(), stream); + } + return f.status(); +} + +// Writes a BOOL field, including tag, to the stream. +inline util::Status WriteBool(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<bool> b = data.ToBool(); + if (b.ok()) { + WireFormatLite::WriteBool(field_number, b.value(), stream); + } + return b.status(); +} + +// Writes a BYTES field, including tag, to the stream. +inline util::Status WriteBytes(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<std::string> c = data.ToBytes(); + if (c.ok()) { + WireFormatLite::WriteBytes(field_number, c.value(), stream); + } + return c.status(); +} + +// Writes a STRING field, including tag, to the stream. +inline util::Status WriteString(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + util::StatusOr<std::string> s = data.ToString(); + if (s.ok()) { + WireFormatLite::WriteString(field_number, s.value(), stream); + } + return s.status(); +} + +// Given a google::protobuf::Type, returns the set of all required fields. +std::set<const google::protobuf::Field*> GetRequiredFields( + const google::protobuf::Type& type) { + std::set<const google::protobuf::Field*> required; + for (int i = 0; i < type.fields_size(); i++) { + const google::protobuf::Field& field = type.fields(i); + if (field.cardinality() == google::protobuf::Field::CARDINALITY_REQUIRED) { + required.insert(&field); + } + } + return required; +} + +} // namespace + +ProtoWriter::ProtoElement::ProtoElement(const TypeInfo* typeinfo, + const google::protobuf::Type& type, + ProtoWriter* enclosing) + : BaseElement(nullptr), + ow_(enclosing), + parent_field_(nullptr), + typeinfo_(typeinfo), + proto3_(type.syntax() == google::protobuf::SYNTAX_PROTO3), + type_(type), + size_index_(-1), + array_index_(-1), + // oneof_indices_ values are 1-indexed (0 means not present). + oneof_indices_(type.oneofs_size() + 1) { + if (!proto3_) { + required_fields_ = GetRequiredFields(type_); + } +} + +ProtoWriter::ProtoElement::ProtoElement(ProtoWriter::ProtoElement* parent, + const google::protobuf::Field* field, + const google::protobuf::Type& type, + bool is_list) + : BaseElement(parent), + ow_(this->parent()->ow_), + parent_field_(field), + typeinfo_(this->parent()->typeinfo_), + proto3_(type.syntax() == google::protobuf::SYNTAX_PROTO3), + type_(type), + size_index_(!is_list && + field->kind() == google::protobuf::Field::TYPE_MESSAGE + ? ow_->size_insert_.size() + : -1), + array_index_(is_list ? 0 : -1), + // oneof_indices_ values are 1-indexed (0 means not present). + oneof_indices_(type_.oneofs_size() + 1) { + if (!is_list) { + if (ow_->IsRepeated(*field)) { + // Update array_index_ if it is an explicit list. + if (this->parent()->array_index_ >= 0) this->parent()->array_index_++; + } else if (!proto3_) { + // For required fields tracking. + this->parent()->RegisterField(field); + } + + if (field->kind() == google::protobuf::Field::TYPE_MESSAGE) { + if (!proto3_) { + required_fields_ = GetRequiredFields(type_); + } + int start_pos = ow_->stream_->ByteCount(); + // length of serialized message is the final buffer position minus + // starting buffer position, plus length adjustments for size fields + // of any nested messages. We start with -start_pos here, so we only + // need to add the final buffer position to it at the end. + SizeInfo info = {start_pos, -start_pos}; + ow_->size_insert_.push_back(info); + } + } +} + +ProtoWriter::ProtoElement* ProtoWriter::ProtoElement::pop() { + if (!proto3_) { + // Calls the registered error listener for any required field(s) not yet + // seen. + for (std::set<const google::protobuf::Field*>::iterator it = + required_fields_.begin(); + it != required_fields_.end(); ++it) { + ow_->MissingField(ow_->use_json_name_in_missing_fields_ + ? (*it)->json_name() + : (*it)->name()); + } + } + // Computes the total number of proto bytes used by a message, also adjusts + // the size of all parent messages by the length of this size field. + // If size_index_ < 0, this is not a message, so no size field is added. + if (size_index_ >= 0) { + // Add the final buffer position to compute the total length of this + // serialized message. The stored value (before this addition) already + // contains the total length of the size fields of all nested messages + // minus the initial buffer position. + ow_->size_insert_[size_index_].size += ow_->stream_->ByteCount(); + // Calculate the length required to serialize the size field of the + // message, and propagate this additional size information upward to + // all enclosing messages. + int size = ow_->size_insert_[size_index_].size; + int length = CodedOutputStream::VarintSize32(size); + for (ProtoElement* e = parent(); e != nullptr; e = e->parent()) { + // Only nested messages have size field, lists do not have size field. + if (e->size_index_ >= 0) { + ow_->size_insert_[e->size_index_].size += length; + } + } + } + return BaseElement::pop<ProtoElement>(); +} + +void ProtoWriter::ProtoElement::RegisterField( + const google::protobuf::Field* field) { + if (!required_fields_.empty() && + field->cardinality() == google::protobuf::Field::CARDINALITY_REQUIRED) { + required_fields_.erase(field); + } +} + +std::string ProtoWriter::ProtoElement::ToString() const { + std::string loc = ""; + + // first populate a stack with the nodes since we need to process them + // from root to leaf when generating the string location + const ProtoWriter::ProtoElement* now = this; + std::stack<const ProtoWriter::ProtoElement*> element_stack; + while (now->parent() != nullptr) { + element_stack.push(now); + now = now->parent(); + } + + // now pop each node from the stack and append to the location string + while (!element_stack.empty()) { + now = element_stack.top(); + element_stack.pop(); + + if (!ow_->IsRepeated(*(now->parent_field_)) || + now->parent()->parent_field_ != now->parent_field_) { + std::string name = now->parent_field_->name(); + int i = 0; + while (i < name.size() && + (ascii_isalnum(name[i]) || name[i] == '_')) + ++i; + if (i > 0 && i == name.size()) { // safe field name + if (loc.empty()) { + loc = name; + } else { + StrAppend(&loc, ".", name); + } + } else { + StrAppend(&loc, "[\"", CEscape(name), "\"]"); + } + } + + int array_index_now = now->array_index_; + if (ow_->IsRepeated(*(now->parent_field_)) && array_index_now > 0) { + StrAppend(&loc, "[", array_index_now - 1, "]"); + } + } + + return loc; +} + +bool ProtoWriter::ProtoElement::IsOneofIndexTaken(int32_t index) { + return oneof_indices_[index]; +} + +void ProtoWriter::ProtoElement::TakeOneofIndex(int32_t index) { + oneof_indices_[index] = true; +} + +void ProtoWriter::InvalidName(StringPiece unknown_name, + StringPiece message) { + listener_->InvalidName(location(), unknown_name, message); +} + +void ProtoWriter::InvalidValue(StringPiece type_name, + StringPiece value) { + listener_->InvalidValue(location(), type_name, value); +} + +void ProtoWriter::MissingField(StringPiece missing_name) { + listener_->MissingField(location(), missing_name); +} + +ProtoWriter* ProtoWriter::StartObject( + StringPiece name) { + // Starting the root message. Create the root ProtoElement and return. + if (element_ == nullptr) { + if (!name.empty()) { + InvalidName(name, "Root element should not be named."); + } + element_.reset(new ProtoElement(typeinfo_, master_type_, this)); + return this; + } + + const google::protobuf::Field* field = BeginNamed(name, false); + + if (field == nullptr) return this; + + // Check to see if this field is a oneof and that no oneof in that group has + // already been set. + if (!ValidOneof(*field, name)) { + ++invalid_depth_; + return this; + } + + const google::protobuf::Type* type = LookupType(field); + if (type == nullptr) { + ++invalid_depth_; + InvalidName(name, StrCat("Missing descriptor for field: ", + field->type_url())); + return this; + } + + return StartObjectField(*field, *type); +} + + +ProtoWriter* ProtoWriter::EndObject() { + if (invalid_depth_ > 0) { + --invalid_depth_; + return this; + } + + if (element_ != nullptr) { + element_.reset(element_->pop()); + } + + + // If ending the root element, + // then serialize the full message with calculated sizes. + if (element_ == nullptr) { + WriteRootMessage(); + } + return this; +} + +ProtoWriter* ProtoWriter::StartList( + StringPiece name) { + + const google::protobuf::Field* field = BeginNamed(name, true); + + if (field == nullptr) return this; + + if (!ValidOneof(*field, name)) { + ++invalid_depth_; + return this; + } + + const google::protobuf::Type* type = LookupType(field); + if (type == nullptr) { + ++invalid_depth_; + InvalidName(name, StrCat("Missing descriptor for field: ", + field->type_url())); + return this; + } + + return StartListField(*field, *type); +} + + +ProtoWriter* ProtoWriter::EndList() { + if (invalid_depth_ > 0) { + --invalid_depth_; + } else if (element_ != nullptr) { + element_.reset(element_->pop()); + } + return this; +} + +ProtoWriter* ProtoWriter::RenderDataPiece( + StringPiece name, const DataPiece& data) { + util::Status status; + if (invalid_depth_ > 0) return this; + + const google::protobuf::Field* field = Lookup(name); + + if (field == nullptr) return this; + + if (!ValidOneof(*field, name)) return this; + + const google::protobuf::Type* type = LookupType(field); + if (type == nullptr) { + InvalidName(name, StrCat("Missing descriptor for field: ", + field->type_url())); + return this; + } + + return RenderPrimitiveField(*field, *type, data); +} + +bool ProtoWriter::ValidOneof(const google::protobuf::Field& field, + StringPiece unnormalized_name) { + if (element_ == nullptr) return true; + + if (field.oneof_index() > 0) { + if (element_->IsOneofIndexTaken(field.oneof_index())) { + InvalidValue( + "oneof", + StrCat( + "oneof field '", element_->type().oneofs(field.oneof_index() - 1), + "' is already set. Cannot set '", unnormalized_name, "'")); + return false; + } + element_->TakeOneofIndex(field.oneof_index()); + } + return true; +} + +bool ProtoWriter::IsRepeated(const google::protobuf::Field& field) { + return field.cardinality() == google::protobuf::Field::CARDINALITY_REPEATED; +} + +ProtoWriter* ProtoWriter::StartObjectField(const google::protobuf::Field& field, + const google::protobuf::Type& type) { + WriteTag(field); + element_.reset(new ProtoElement(element_.release(), &field, type, false)); + return this; +} + +ProtoWriter* ProtoWriter::StartListField(const google::protobuf::Field& field, + const google::protobuf::Type& type) { + element_.reset(new ProtoElement(element_.release(), &field, type, true)); + return this; +} + +util::Status ProtoWriter::WriteEnum(int field_number, const DataPiece& data, + const google::protobuf::Enum* enum_type, + CodedOutputStream* stream, + bool use_lower_camel_for_enums, + bool case_insensitive_enum_parsing, + bool ignore_unknown_values) { + bool is_unknown_enum_value = false; + util::StatusOr<int> e = data.ToEnum( + enum_type, use_lower_camel_for_enums, case_insensitive_enum_parsing, + ignore_unknown_values, &is_unknown_enum_value); + if (e.ok() && !is_unknown_enum_value) { + WireFormatLite::WriteEnum(field_number, e.value(), stream); + } + return e.status(); +} + +ProtoWriter* ProtoWriter::RenderPrimitiveField( + const google::protobuf::Field& field, const google::protobuf::Type& type, + const DataPiece& data) { + util::Status status; + + // Pushing a ProtoElement and then pop it off at the end for 2 purposes: + // error location reporting and required field accounting. + // + // For proto3, since there is no required field tracking, we only need to + // push ProtoElement for error cases. + if (!element_->proto3()) { + element_.reset(new ProtoElement(element_.release(), &field, type, false)); + } + + switch (field.kind()) { + case google::protobuf::Field::TYPE_INT32: { + status = WriteInt32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_SFIXED32: { + status = WriteSFixed32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_SINT32: { + status = WriteSInt32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_FIXED32: { + status = WriteFixed32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_UINT32: { + status = WriteUInt32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_INT64: { + status = WriteInt64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_SFIXED64: { + status = WriteSFixed64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_SINT64: { + status = WriteSInt64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_FIXED64: { + status = WriteFixed64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_UINT64: { + status = WriteUInt64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_DOUBLE: { + status = WriteDouble(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_FLOAT: { + status = WriteFloat(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_BOOL: { + status = WriteBool(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_BYTES: { + status = WriteBytes(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_STRING: { + status = WriteString(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field::TYPE_ENUM: { + status = WriteEnum( + field.number(), data, typeinfo_->GetEnumByTypeUrl(field.type_url()), + stream_.get(), use_lower_camel_for_enums_, + case_insensitive_enum_parsing_, ignore_unknown_enum_values_); + break; + } + default: // TYPE_GROUP, TYPE_MESSAGE, TYPE_UNKNOWN. + status = util::InvalidArgumentError(data.ValueAsStringOrDefault("")); + } + + if (!status.ok()) { + // Push a ProtoElement for location reporting purposes. + if (element_->proto3()) { + element_.reset(new ProtoElement(element_.release(), &field, type, false)); + } + InvalidValue(field.type_url().empty() + ? google::protobuf::Field_Kind_Name(field.kind()) + : field.type_url(), + status.message()); + element_.reset(element()->pop()); + return this; + } + + if (!element_->proto3()) element_.reset(element()->pop()); + + return this; +} + +const google::protobuf::Field* ProtoWriter::BeginNamed(StringPiece name, + bool is_list) { + if (invalid_depth_ > 0) { + ++invalid_depth_; + return nullptr; + } + const google::protobuf::Field* field = Lookup(name); + if (field == nullptr) { + ++invalid_depth_; + // InvalidName() already called in Lookup(). + return nullptr; + } + if (is_list && !IsRepeated(*field)) { + ++invalid_depth_; + InvalidName(name, "Proto field is not repeating, cannot start list."); + return nullptr; + } + return field; +} + +const google::protobuf::Field* ProtoWriter::Lookup( + StringPiece unnormalized_name) { + ProtoElement* e = element(); + if (e == nullptr) { + InvalidName(unnormalized_name, "Root element must be a message."); + return nullptr; + } + if (unnormalized_name.empty()) { + // Objects in repeated field inherit the same field descriptor. + if (e->parent_field() == nullptr) { + InvalidName(unnormalized_name, "Proto fields must have a name."); + } else if (!IsRepeated(*e->parent_field())) { + InvalidName(unnormalized_name, "Proto fields must have a name."); + return nullptr; + } + return e->parent_field(); + } + const google::protobuf::Field* field = + typeinfo_->FindField(&e->type(), unnormalized_name); + if (field == nullptr && !ignore_unknown_fields_) { + InvalidName(unnormalized_name, "Cannot find field."); + } + return field; +} + +const google::protobuf::Type* ProtoWriter::LookupType( + const google::protobuf::Field* field) { + return ((field->kind() == google::protobuf::Field::TYPE_MESSAGE || + field->kind() == google::protobuf::Field::TYPE_GROUP) + ? typeinfo_->GetTypeByTypeUrl(field->type_url()) + : &element_->type()); +} + +void ProtoWriter::WriteRootMessage() { + GOOGLE_DCHECK(!done_); + int curr_pos = 0; + // Calls the destructor of CodedOutputStream to remove any uninitialized + // memory from the Cord before we read it. + stream_.reset(nullptr); + const void* data; + int length; + io::ArrayInputStream input_stream(buffer_.data(), buffer_.size()); + while (input_stream.Next(&data, &length)) { + if (length == 0) continue; + int num_bytes = length; + // Write up to where we need to insert the size field. + // The number of bytes we may write is the smaller of: + // - the current fragment size + // - the distance to the next position where a size field needs to be + // inserted. + if (!size_insert_.empty() && + size_insert_.front().pos - curr_pos < num_bytes) { + num_bytes = size_insert_.front().pos - curr_pos; + } + output_->Append(static_cast<const char*>(data), num_bytes); + if (num_bytes < length) { + input_stream.BackUp(length - num_bytes); + } + curr_pos += num_bytes; + // Insert the size field. + // size_insert_.front(): the next <index, size> pair to be written. + // size_insert_.front().pos: position of the size field. + // size_insert_.front().size: the size (integer) to be inserted. + if (!size_insert_.empty() && curr_pos == size_insert_.front().pos) { + // Varint32 occupies at most 10 bytes. + uint8_t insert_buffer[10]; + uint8_t* insert_buffer_pos = CodedOutputStream::WriteVarint32ToArray( + size_insert_.front().size, insert_buffer); + output_->Append(reinterpret_cast<const char*>(insert_buffer), + insert_buffer_pos - insert_buffer); + size_insert_.pop_front(); + } + } + output_->Flush(); + stream_.reset(new CodedOutputStream(&adapter_)); + done_ = true; +} + +void ProtoWriter::WriteTag(const google::protobuf::Field& field) { + WireFormatLite::WireType wire_type = WireFormatLite::WireTypeForFieldType( + static_cast<WireFormatLite::FieldType>(field.kind())); + stream_->WriteTag(WireFormatLite::MakeTag(field.number(), wire_type)); +} + + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/proto_writer.h b/NorthstarDedicatedTest/include/protobuf/util/internal/proto_writer.h new file mode 100644 index 00000000..562ff8dd --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/proto_writer.h @@ -0,0 +1,388 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTO_WRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTO_WRITER_H__ + +#include <cstdint> +#include <deque> +#include <string> +#include <vector> + +#include <stubs/common.h> +#include <type.pb.h> +#include <io/coded_stream.h> +#include <io/zero_copy_stream_impl.h> +#include <descriptor.h> +#include <util/internal/type_info.h> +#include <util/internal/datapiece.h> +#include <util/internal/error_listener.h> +#include <util/internal/structured_objectwriter.h> +#include <util/type_resolver.h> +#include <stubs/bytestream.h> +#include <stubs/status.h> +#include <stubs/hash.h> +#include <stubs/status.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +class ObjectLocationTracker; + +// An ObjectWriter that can write protobuf bytes directly from writer events. +// This class does not support special types like Struct or Map. However, since +// this class supports raw protobuf, it can be used to provide support for +// special types by inheriting from it or by wrapping it. +// +// It also supports streaming. +class PROTOBUF_EXPORT ProtoWriter : public StructuredObjectWriter { + public: +// Constructor. Does not take ownership of any parameter passed in. + ProtoWriter(TypeResolver* type_resolver, const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener); + ~ProtoWriter() override; + + // ObjectWriter methods. + ProtoWriter* StartObject(StringPiece name) override; + ProtoWriter* EndObject() override; + ProtoWriter* StartList(StringPiece name) override; + ProtoWriter* EndList() override; + ProtoWriter* RenderBool(StringPiece name, bool value) override { + return RenderDataPiece(name, DataPiece(value)); + } + ProtoWriter* RenderInt32(StringPiece name, int32_t value) override { + return RenderDataPiece(name, DataPiece(value)); + } + ProtoWriter* RenderUint32(StringPiece name, uint32_t value) override { + return RenderDataPiece(name, DataPiece(value)); + } + ProtoWriter* RenderInt64(StringPiece name, int64_t value) override { + return RenderDataPiece(name, DataPiece(value)); + } + ProtoWriter* RenderUint64(StringPiece name, uint64_t value) override { + return RenderDataPiece(name, DataPiece(value)); + } + ProtoWriter* RenderDouble(StringPiece name, double value) override { + return RenderDataPiece(name, DataPiece(value)); + } + ProtoWriter* RenderFloat(StringPiece name, float value) override { + return RenderDataPiece(name, DataPiece(value)); + } + ProtoWriter* RenderString(StringPiece name, + StringPiece value) override { + return RenderDataPiece(name, + DataPiece(value, use_strict_base64_decoding())); + } + + ProtoWriter* RenderBytes(StringPiece name, StringPiece value) override { + return RenderDataPiece( + name, DataPiece(value, false, use_strict_base64_decoding())); + } + + ProtoWriter* RenderNull(StringPiece name) override { + return RenderDataPiece(name, DataPiece::NullData()); + } + + + // Renders a DataPiece 'value' into a field whose wire type is determined + // from the given field 'name'. + virtual ProtoWriter* RenderDataPiece(StringPiece name, + const DataPiece& data); + + + // Returns the location tracker to use for tracking locations for errors. + const LocationTrackerInterface& location() { + return element_ != nullptr ? *element_ : *tracker_; + } + + // When true, we finished writing to output a complete message. + bool done() override { return done_; } + + // Returns the proto stream object. + io::CodedOutputStream* stream() { return stream_.get(); } + + // Getters and mutators of invalid_depth_. + void IncrementInvalidDepth() { ++invalid_depth_; } + void DecrementInvalidDepth() { --invalid_depth_; } + int invalid_depth() { return invalid_depth_; } + + ErrorListener* listener() { return listener_; } + + const TypeInfo* typeinfo() { return typeinfo_; } + + void set_ignore_unknown_fields(bool ignore_unknown_fields) { + ignore_unknown_fields_ = ignore_unknown_fields; + } + + bool ignore_unknown_fields() { return ignore_unknown_fields_; } + + void set_ignore_unknown_enum_values(bool ignore_unknown_enum_values) { + ignore_unknown_enum_values_ = ignore_unknown_enum_values; + } + + void set_use_lower_camel_for_enums(bool use_lower_camel_for_enums) { + use_lower_camel_for_enums_ = use_lower_camel_for_enums; + } + + void set_case_insensitive_enum_parsing(bool case_insensitive_enum_parsing) { + case_insensitive_enum_parsing_ = case_insensitive_enum_parsing; + } + + void set_use_json_name_in_missing_fields( + bool use_json_name_in_missing_fields) { + use_json_name_in_missing_fields_ = use_json_name_in_missing_fields; + } + + protected: + class PROTOBUF_EXPORT ProtoElement : public BaseElement, + public LocationTrackerInterface { + public: + // Constructor for the root element. No parent nor field. + ProtoElement(const TypeInfo* typeinfo, const google::protobuf::Type& type, + ProtoWriter* enclosing); + + // Constructor for a field of an element. + ProtoElement(ProtoElement* parent, const google::protobuf::Field* field, + const google::protobuf::Type& type, bool is_list); + + ~ProtoElement() override {} + + // Called just before the destructor for clean up: + // - reports any missing required fields + // - computes the space needed by the size field, and augment the + // length of all parent messages by this additional space. + // - releases and returns the parent pointer. + ProtoElement* pop(); + + // Accessors + // parent_field() may be nullptr if we are at root. + const google::protobuf::Field* parent_field() const { + return parent_field_; + } + const google::protobuf::Type& type() const { return type_; } + + // Registers field for accounting required fields. + void RegisterField(const google::protobuf::Field* field); + + // To report location on error messages. + std::string ToString() const override; + + ProtoElement* parent() const override { + return static_cast<ProtoElement*>(BaseElement::parent()); + } + + // Returns true if the index is already taken by a preceding oneof input. + bool IsOneofIndexTaken(int32_t index); + + // Marks the oneof 'index' as taken. Future inputs to this oneof will + // generate an error. + void TakeOneofIndex(int32_t index); + + bool proto3() { return proto3_; } + + private: + // Used for access to variables of the enclosing instance of + // ProtoWriter. + ProtoWriter* ow_; + + // Describes the element as a field in the parent message. + // parent_field_ is nullptr if and only if this element is the root element. + const google::protobuf::Field* parent_field_; + + // TypeInfo to lookup types. + const TypeInfo* typeinfo_; + + // Whether the type_ is proto3 or not. + bool proto3_; + + // Additional variables if this element is a message: + // (Root element is always a message). + // type_ : the type of this element. + // required_fields_ : set of required fields. + // size_index_ : index into ProtoWriter::size_insert_ + // for later insertion of serialized message length. + const google::protobuf::Type& type_; + std::set<const google::protobuf::Field*> required_fields_; + const int size_index_; + + // Tracks position in repeated fields, needed for LocationTrackerInterface. + int array_index_; + + // Set of oneof indices already seen for the type_. Used to validate + // incoming messages so no more than one oneof is set. + std::vector<bool> oneof_indices_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoElement); + }; + + // Container for inserting 'size' information at the 'pos' position. + struct SizeInfo { + const int pos; + int size; + }; + + ProtoWriter(const TypeInfo* typeinfo, const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener); + + ProtoElement* element() override { return element_.get(); } + + // Helper methods for calling ErrorListener. See error_listener.h. + void InvalidName(StringPiece unknown_name, StringPiece message); + void InvalidValue(StringPiece type_name, StringPiece value); + void MissingField(StringPiece missing_name); + + // Common code for BeginObject() and BeginList() that does invalid_depth_ + // bookkeeping associated with name lookup. + const google::protobuf::Field* BeginNamed(StringPiece name, + bool is_list); + + // Lookup the field in the current element. Looks in the base descriptor + // and in any extension. This will report an error if the field cannot be + // found when ignore_unknown_names_ is false or if multiple matching + // extensions are found. + const google::protobuf::Field* Lookup(StringPiece name); + + // Lookup the field type in the type descriptor. Returns nullptr if the type + // is not known. + const google::protobuf::Type* LookupType( + const google::protobuf::Field* field); + + // Write serialized output to the final output ByteSink, inserting all + // the size information for nested messages that are missing from the + // intermediate Cord buffer. + void WriteRootMessage(); + + // Helper method to write proto tags based on the given field. + void WriteTag(const google::protobuf::Field& field); + + + // Returns true if the field for type_ can be set as a oneof. If field is not + // a oneof type, this function does nothing and returns true. + // If another field for this oneof is already set, this function returns + // false. It also calls the appropriate error callback. + // unnormalized_name is used for error string. + bool ValidOneof(const google::protobuf::Field& field, + StringPiece unnormalized_name); + + // Returns true if the field is repeated. + bool IsRepeated(const google::protobuf::Field& field); + + // Starts an object given the field and the enclosing type. + ProtoWriter* StartObjectField(const google::protobuf::Field& field, + const google::protobuf::Type& type); + + // Starts a list given the field and the enclosing type. + ProtoWriter* StartListField(const google::protobuf::Field& field, + const google::protobuf::Type& type); + + // Renders a primitive field given the field and the enclosing type. + ProtoWriter* RenderPrimitiveField(const google::protobuf::Field& field, + const google::protobuf::Type& type, + const DataPiece& data); + + private: + // Writes an ENUM field, including tag, to the stream. + static util::Status WriteEnum(int field_number, const DataPiece& data, + const google::protobuf::Enum* enum_type, + io::CodedOutputStream* stream, + bool use_lower_camel_for_enums, + bool case_insensitive_enum_parsing, + bool ignore_unknown_values); + + // Variables for describing the structure of the input tree: + // master_type_: descriptor for the whole protobuf message. + // typeinfo_ : the TypeInfo object to lookup types. + const google::protobuf::Type& master_type_; + const TypeInfo* typeinfo_; + // Whether we own the typeinfo_ object. + bool own_typeinfo_; + + // Indicates whether we finished writing root message completely. + bool done_; + + // If true, don't report unknown field names to the listener. + bool ignore_unknown_fields_; + + // If true, don't report unknown enum values to the listener. + bool ignore_unknown_enum_values_; + + // If true, check if enum name in camel case or without underscore matches the + // field name. + bool use_lower_camel_for_enums_; + + // If true, check if enum name in UPPER_CASE matches the field name. + bool case_insensitive_enum_parsing_; + + // If true, use the json name in missing fields errors. + bool use_json_name_in_missing_fields_; + + // Variable for internal state processing: + // element_ : the current element. + // size_insert_: sizes of nested messages. + // pos - position to insert the size field. + // size - size value to be inserted. + std::unique_ptr<ProtoElement> element_; + std::deque<SizeInfo> size_insert_; + + // Variables for output generation: + // output_ : pointer to an external ByteSink for final user-visible output. + // buffer_ : buffer holding partial message before being ready for output_. + // adapter_ : internal adapter between CodedOutputStream and buffer_. + // stream_ : wrapper for writing tags and other encodings in wire format. + strings::ByteSink* output_; + std::string buffer_; + io::StringOutputStream adapter_; + std::unique_ptr<io::CodedOutputStream> stream_; + + // Variables for error tracking and reporting: + // listener_ : a place to report any errors found. + // invalid_depth_: number of enclosing invalid nested messages. + // tracker_ : the root location tracker interface. + ErrorListener* listener_; + int invalid_depth_; + std::unique_ptr<LocationTrackerInterface> tracker_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTO_WRITER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource.cc new file mode 100644 index 00000000..801e90a3 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource.cc @@ -0,0 +1,1112 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/protostream_objectsource.h> + +#include <cstdint> +#include <unordered_map> +#include <utility> + +#include <stubs/logging.h> +#include <stubs/common.h> +#include <stubs/stringprintf.h> +#include <io/coded_stream.h> +#include <io/zero_copy_stream_impl.h> +#include <descriptor.h> +#include <stubs/once.h> +#include <unknown_field_set.h> +#include <wire_format.h> +#include <wire_format_lite.h> +#include <util/internal/field_mask_utility.h> +#include <util/internal/constants.h> +#include <util/internal/utility.h> +#include <stubs/strutil.h> +#include <stubs/casts.h> +#include <stubs/status.h> +#include <stubs/time.h> +#include <stubs/map_util.h> +#include <stubs/status_macros.h> + + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using ::PROTOBUF_NAMESPACE_ID::internal::WireFormat; +using ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite; + +namespace { + +static int kDefaultMaxRecursionDepth = 64; + +// Finds a field with the given number. nullptr if none found. +const google::protobuf::Field* FindFieldByNumber( + const google::protobuf::Type& type, int number); + +// Returns true if the field is packable. +bool IsPackable(const google::protobuf::Field& field); + +// Finds an enum value with the given number. nullptr if none found. +const google::protobuf::EnumValue* FindEnumValueByNumber( + const google::protobuf::Enum& tech_enum, int number); + +// Utility function to format nanos. +const std::string FormatNanos(uint32_t nanos, bool with_trailing_zeros); + +util::StatusOr<std::string> MapKeyDefaultValueAsString( + const google::protobuf::Field& field) { + switch (field.kind()) { + case google::protobuf::Field::TYPE_BOOL: + return std::string("false"); + case google::protobuf::Field::TYPE_INT32: + case google::protobuf::Field::TYPE_INT64: + case google::protobuf::Field::TYPE_UINT32: + case google::protobuf::Field::TYPE_UINT64: + case google::protobuf::Field::TYPE_SINT32: + case google::protobuf::Field::TYPE_SINT64: + case google::protobuf::Field::TYPE_SFIXED32: + case google::protobuf::Field::TYPE_SFIXED64: + case google::protobuf::Field::TYPE_FIXED32: + case google::protobuf::Field::TYPE_FIXED64: + return std::string("0"); + case google::protobuf::Field::TYPE_STRING: + return std::string(); + default: + return util::InternalError("Invalid map key type."); + } +} +} // namespace + + +ProtoStreamObjectSource::ProtoStreamObjectSource( + io::CodedInputStream* stream, TypeResolver* type_resolver, + const google::protobuf::Type& type, const RenderOptions& render_options) + : stream_(stream), + typeinfo_(TypeInfo::NewTypeInfo(type_resolver)), + own_typeinfo_(true), + type_(type), + render_options_(render_options), + recursion_depth_(0), + max_recursion_depth_(kDefaultMaxRecursionDepth) { + GOOGLE_LOG_IF(DFATAL, stream == nullptr) << "Input stream is nullptr."; +} + +ProtoStreamObjectSource::ProtoStreamObjectSource( + io::CodedInputStream* stream, const TypeInfo* typeinfo, + const google::protobuf::Type& type, const RenderOptions& render_options) + : stream_(stream), + typeinfo_(typeinfo), + own_typeinfo_(false), + type_(type), + render_options_(render_options), + recursion_depth_(0), + max_recursion_depth_(kDefaultMaxRecursionDepth) { + GOOGLE_LOG_IF(DFATAL, stream == nullptr) << "Input stream is nullptr."; +} + +ProtoStreamObjectSource::~ProtoStreamObjectSource() { + if (own_typeinfo_) { + delete typeinfo_; + } +} + +util::Status ProtoStreamObjectSource::NamedWriteTo(StringPiece name, + ObjectWriter* ow) const { + return WriteMessage(type_, name, 0, true, ow); +} + +const google::protobuf::Field* ProtoStreamObjectSource::FindAndVerifyField( + const google::protobuf::Type& type, uint32_t tag) const { + // Lookup the new field in the type by tag number. + const google::protobuf::Field* field = FindFieldByNumber(type, tag >> 3); + // Verify if the field corresponds to the wire type in tag. + // If there is any discrepancy, mark the field as not found. + if (field != nullptr) { + WireFormatLite::WireType expected_type = + WireFormatLite::WireTypeForFieldType( + static_cast<WireFormatLite::FieldType>(field->kind())); + WireFormatLite::WireType actual_type = WireFormatLite::GetTagWireType(tag); + if (actual_type != expected_type && + (!IsPackable(*field) || + actual_type != WireFormatLite::WIRETYPE_LENGTH_DELIMITED)) { + field = nullptr; + } + } + return field; +} + +util::Status ProtoStreamObjectSource::WriteMessage( + const google::protobuf::Type& type, StringPiece name, + const uint32_t end_tag, bool include_start_and_end, + ObjectWriter* ow) const { + + const TypeRenderer* type_renderer = FindTypeRenderer(type.name()); + if (type_renderer != nullptr) { + return (*type_renderer)(this, type, name, ow); + } + + const google::protobuf::Field* field = nullptr; + std::string field_name; + // last_tag set to dummy value that is different from tag. + uint32_t tag = stream_->ReadTag(), last_tag = tag + 1; + UnknownFieldSet unknown_fields; + + + if (include_start_and_end) { + ow->StartObject(name); + } + while (tag != end_tag && tag != 0) { + if (tag != last_tag) { // Update field only if tag is changed. + last_tag = tag; + field = FindAndVerifyField(type, tag); + if (field != nullptr) { + if (render_options_.preserve_proto_field_names) { + field_name = field->name(); + } else { + field_name = field->json_name(); + } + } + } + if (field == nullptr) { + // If we didn't find a field, skip this unknown tag. + // TODO(wpoon): Check return boolean value. + WireFormat::SkipField( + stream_, tag, + nullptr); + tag = stream_->ReadTag(); + continue; + } + + if (field->cardinality() == google::protobuf::Field::CARDINALITY_REPEATED) { + if (IsMap(*field)) { + ow->StartObject(field_name); + ASSIGN_OR_RETURN(tag, RenderMap(field, field_name, tag, ow)); + ow->EndObject(); + } else { + ASSIGN_OR_RETURN(tag, RenderList(field, field_name, tag, ow)); + } + } else { + // Render the field. + RETURN_IF_ERROR(RenderField(field, field_name, ow)); + tag = stream_->ReadTag(); + } + } + + + if (include_start_and_end) { + ow->EndObject(); + } + return util::Status(); +} + +util::StatusOr<uint32_t> ProtoStreamObjectSource::RenderList( + const google::protobuf::Field* field, StringPiece name, + uint32_t list_tag, ObjectWriter* ow) const { + uint32_t tag_to_return = 0; + ow->StartList(name); + if (IsPackable(*field) && + list_tag == + WireFormatLite::MakeTag(field->number(), + WireFormatLite::WIRETYPE_LENGTH_DELIMITED)) { + RETURN_IF_ERROR(RenderPacked(field, ow)); + // Since packed fields have a single tag, read another tag from stream to + // return. + tag_to_return = stream_->ReadTag(); + } else { + do { + RETURN_IF_ERROR(RenderField(field, "", ow)); + } while ((tag_to_return = stream_->ReadTag()) == list_tag); + } + ow->EndList(); + return tag_to_return; +} + +util::StatusOr<uint32_t> ProtoStreamObjectSource::RenderMap( + const google::protobuf::Field* field, StringPiece /* name */, + uint32_t list_tag, ObjectWriter* ow) const { + const google::protobuf::Type* field_type = + typeinfo_->GetTypeByTypeUrl(field->type_url()); + uint32_t tag_to_return = 0; + do { + // Render map entry message type. + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); // message length + int old_limit = stream_->PushLimit(buffer32); + std::string map_key; + for (uint32_t tag = stream_->ReadTag(); tag != 0; + tag = stream_->ReadTag()) { + const google::protobuf::Field* map_entry_field = + FindAndVerifyField(*field_type, tag); + if (map_entry_field == nullptr) { + WireFormat::SkipField(stream_, tag, nullptr); + continue; + } + // Map field numbers are key = 1 and value = 2 + if (map_entry_field->number() == 1) { + map_key = ReadFieldValueAsString(*map_entry_field); + } else if (map_entry_field->number() == 2) { + if (map_key.empty()) { + // An absent map key is treated as the default. + const google::protobuf::Field* key_field = + FindFieldByNumber(*field_type, 1); + if (key_field == nullptr) { + // The Type info for this map entry is incorrect. It should always + // have a field named "key" and with field number 1. + return util::InternalError("Invalid map entry."); + } + ASSIGN_OR_RETURN(map_key, MapKeyDefaultValueAsString(*key_field)); + } + RETURN_IF_ERROR(RenderField(map_entry_field, map_key, ow)); + } else { + // The Type info for this map entry is incorrect. It should contain + // exactly two fields with field number 1 and 2. + return util::InternalError("Invalid map entry."); + } + } + stream_->PopLimit(old_limit); + } while ((tag_to_return = stream_->ReadTag()) == list_tag); + return tag_to_return; +} + +util::Status ProtoStreamObjectSource::RenderPacked( + const google::protobuf::Field* field, ObjectWriter* ow) const { + uint32_t length; + stream_->ReadVarint32(&length); + int old_limit = stream_->PushLimit(length); + while (stream_->BytesUntilLimit() > 0) { + RETURN_IF_ERROR(RenderField(field, StringPiece(), ow)); + } + stream_->PopLimit(old_limit); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderTimestamp( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + std::pair<int64_t, int32_t> p = os->ReadSecondsAndNanos(type); + int64_t seconds = p.first; + int32_t nanos = p.second; + if (seconds > kTimestampMaxSeconds || seconds < kTimestampMinSeconds) { + return util::InternalError(StrCat( + "Timestamp seconds exceeds limit for field: ", field_name)); + } + + if (nanos < 0 || nanos >= kNanosPerSecond) { + return util::InternalError( + StrCat("Timestamp nanos exceeds limit for field: ", field_name)); + } + + ow->RenderString(field_name, + ::google::protobuf::internal::FormatTime(seconds, nanos)); + + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderDuration( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + std::pair<int64_t, int32_t> p = os->ReadSecondsAndNanos(type); + int64_t seconds = p.first; + int32_t nanos = p.second; + if (seconds > kDurationMaxSeconds || seconds < kDurationMinSeconds) { + return util::InternalError( + StrCat("Duration seconds exceeds limit for field: ", field_name)); + } + + if (nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + return util::InternalError( + StrCat("Duration nanos exceeds limit for field: ", field_name)); + } + + std::string sign = ""; + if (seconds < 0) { + if (nanos > 0) { + return util::InternalError( + StrCat("Duration nanos is non-negative, but seconds is " + "negative for field: ", + field_name)); + } + sign = "-"; + seconds = -seconds; + nanos = -nanos; + } else if (seconds == 0 && nanos < 0) { + sign = "-"; + nanos = -nanos; + } + std::string formatted_duration = StringPrintf( + "%s%lld%ss", sign.c_str(), static_cast<long long>(seconds), // NOLINT + FormatNanos( + nanos, + false + ) + .c_str()); + ow->RenderString(field_name, formatted_duration); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderDouble( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint64_t buffer64 = 0; // default value of Double wrapper value + if (tag != 0) { + os->stream_->ReadLittleEndian64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderDouble(field_name, bit_cast<double>(buffer64)); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderFloat( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint32_t buffer32 = 0; // default value of Float wrapper value + if (tag != 0) { + os->stream_->ReadLittleEndian32(&buffer32); + os->stream_->ReadTag(); + } + ow->RenderFloat(field_name, bit_cast<float>(buffer32)); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderInt64( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint64_t buffer64 = 0; // default value of Int64 wrapper value + if (tag != 0) { + os->stream_->ReadVarint64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderInt64(field_name, bit_cast<int64_t>(buffer64)); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderUInt64( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint64_t buffer64 = 0; // default value of UInt64 wrapper value + if (tag != 0) { + os->stream_->ReadVarint64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderUint64(field_name, bit_cast<uint64_t>(buffer64)); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderInt32( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint32_t buffer32 = 0; // default value of Int32 wrapper value + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); + os->stream_->ReadTag(); + } + ow->RenderInt32(field_name, bit_cast<int32_t>(buffer32)); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderUInt32( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint32_t buffer32 = 0; // default value of UInt32 wrapper value + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); + os->stream_->ReadTag(); + } + ow->RenderUint32(field_name, bit_cast<uint32_t>(buffer32)); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderBool( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint64_t buffer64 = 0; // results in 'false' value as default, which is the + // default value of Bool wrapper + if (tag != 0) { + os->stream_->ReadVarint64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderBool(field_name, buffer64 != 0); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderString( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint32_t buffer32; + std::string str; // default value of empty for String wrapper + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); // string size. + os->stream_->ReadString(&str, buffer32); + os->stream_->ReadTag(); + } + ow->RenderString(field_name, str); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderBytes( + const ProtoStreamObjectSource* os, const google::protobuf::Type& /*type*/, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + uint32_t buffer32; + std::string str; + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); + os->stream_->ReadString(&str, buffer32); + os->stream_->ReadTag(); + } + ow->RenderBytes(field_name, str); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderStruct( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + const google::protobuf::Field* field = nullptr; + uint32_t tag = os->stream_->ReadTag(); + ow->StartObject(field_name); + while (tag != 0) { + field = os->FindAndVerifyField(type, tag); + if (field == nullptr) { + WireFormat::SkipField(os->stream_, tag, nullptr); + tag = os->stream_->ReadTag(); + continue; + } + // google.protobuf.Struct has only one field that is a map. Hence we use + // RenderMap to render that field. + if (os->IsMap(*field)) { + ASSIGN_OR_RETURN(tag, os->RenderMap(field, field_name, tag, ow)); + } + } + ow->EndObject(); + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderStructValue( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + const google::protobuf::Field* field = nullptr; + for (uint32_t tag = os->stream_->ReadTag(); tag != 0; + tag = os->stream_->ReadTag()) { + field = os->FindAndVerifyField(type, tag); + if (field == nullptr) { + WireFormat::SkipField(os->stream_, tag, nullptr); + continue; + } + RETURN_IF_ERROR(os->RenderField(field, field_name, ow)); + } + return util::Status(); +} + +// TODO(skarvaje): Avoid code duplication of for loops and SkipField logic. +util::Status ProtoStreamObjectSource::RenderStructListValue( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + uint32_t tag = os->stream_->ReadTag(); + + // Render empty list when we find empty ListValue message. + if (tag == 0) { + ow->StartList(field_name); + ow->EndList(); + return util::Status(); + } + + while (tag != 0) { + const google::protobuf::Field* field = os->FindAndVerifyField(type, tag); + if (field == nullptr) { + WireFormat::SkipField(os->stream_, tag, nullptr); + tag = os->stream_->ReadTag(); + continue; + } + ASSIGN_OR_RETURN(tag, os->RenderList(field, field_name, tag, ow)); + } + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderAny( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + // An Any is of the form { string type_url = 1; bytes value = 2; } + uint32_t tag; + std::string type_url; + std::string value; + + // First read out the type_url and value from the proto stream + for (tag = os->stream_->ReadTag(); tag != 0; tag = os->stream_->ReadTag()) { + const google::protobuf::Field* field = os->FindAndVerifyField(type, tag); + if (field == nullptr) { + WireFormat::SkipField(os->stream_, tag, nullptr); + continue; + } + // 'type_url' has field number of 1 and 'value' has field number 2 + // //google/protobuf/any.proto + if (field->number() == 1) { + // read type_url + uint32_t type_url_size; + os->stream_->ReadVarint32(&type_url_size); + os->stream_->ReadString(&type_url, type_url_size); + } else if (field->number() == 2) { + // read value + uint32_t value_size; + os->stream_->ReadVarint32(&value_size); + os->stream_->ReadString(&value, value_size); + } + } + + // If there is no value, we don't lookup the type, we just output it (if + // present). If both type and value are empty we output an empty object. + if (value.empty()) { + ow->StartObject(field_name); + if (!type_url.empty()) { + ow->RenderString("@type", type_url); + } + ow->EndObject(); + return util::Status(); + } + + // If there is a value but no type, we cannot render it, so report an error. + if (type_url.empty()) { + // TODO(sven): Add an external message once those are ready. + return util::InternalError("Invalid Any, the type_url is missing."); + } + + util::StatusOr<const google::protobuf::Type*> resolved_type = + os->typeinfo_->ResolveTypeUrl(type_url); + + if (!resolved_type.ok()) { + // Convert into an internal error, since this means the backend gave us + // an invalid response (missing or invalid type information). + return util::InternalError(resolved_type.status().message()); + } + // nested_type cannot be null at this time. + const google::protobuf::Type* nested_type = resolved_type.value(); + + io::ArrayInputStream zero_copy_stream(value.data(), value.size()); + io::CodedInputStream in_stream(&zero_copy_stream); + // We know the type so we can render it. Recursively parse the nested stream + // using a nested ProtoStreamObjectSource using our nested type information. + ProtoStreamObjectSource nested_os(&in_stream, os->typeinfo_, *nested_type, + os->render_options_); + + // We manually call start and end object here so we can inject the @type. + ow->StartObject(field_name); + ow->RenderString("@type", type_url); + util::Status result = + nested_os.WriteMessage(nested_os.type_, "value", 0, false, ow); + ow->EndObject(); + return result; +} + +util::Status ProtoStreamObjectSource::RenderFieldMask( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + std::string combined; + uint32_t buffer32; + uint32_t paths_field_tag = 0; + for (uint32_t tag = os->stream_->ReadTag(); tag != 0; + tag = os->stream_->ReadTag()) { + if (paths_field_tag == 0) { + const google::protobuf::Field* field = os->FindAndVerifyField(type, tag); + if (field != nullptr && field->number() == 1 && + field->name() == "paths") { + paths_field_tag = tag; + } + } + if (paths_field_tag != tag) { + return util::InternalError("Invalid FieldMask, unexpected field."); + } + std::string str; + os->stream_->ReadVarint32(&buffer32); // string size. + os->stream_->ReadString(&str, buffer32); + if (!combined.empty()) { + combined.append(","); + } + combined.append(ConvertFieldMaskPath(str, &ToCamelCase)); + } + ow->RenderString(field_name, combined); + return util::Status(); +} + + +std::unordered_map<std::string, ProtoStreamObjectSource::TypeRenderer>* + ProtoStreamObjectSource::renderers_ = nullptr; +PROTOBUF_NAMESPACE_ID::internal::once_flag source_renderers_init_; + + +void ProtoStreamObjectSource::InitRendererMap() { + renderers_ = new std::unordered_map<std::string, + ProtoStreamObjectSource::TypeRenderer>(); + (*renderers_)["google.protobuf.Timestamp"] = + &ProtoStreamObjectSource::RenderTimestamp; + (*renderers_)["google.protobuf.Duration"] = + &ProtoStreamObjectSource::RenderDuration; + (*renderers_)["google.protobuf.DoubleValue"] = + &ProtoStreamObjectSource::RenderDouble; + (*renderers_)["google.protobuf.FloatValue"] = + &ProtoStreamObjectSource::RenderFloat; + (*renderers_)["google.protobuf.Int64Value"] = + &ProtoStreamObjectSource::RenderInt64; + (*renderers_)["google.protobuf.UInt64Value"] = + &ProtoStreamObjectSource::RenderUInt64; + (*renderers_)["google.protobuf.Int32Value"] = + &ProtoStreamObjectSource::RenderInt32; + (*renderers_)["google.protobuf.UInt32Value"] = + &ProtoStreamObjectSource::RenderUInt32; + (*renderers_)["google.protobuf.BoolValue"] = + &ProtoStreamObjectSource::RenderBool; + (*renderers_)["google.protobuf.StringValue"] = + &ProtoStreamObjectSource::RenderString; + (*renderers_)["google.protobuf.BytesValue"] = + &ProtoStreamObjectSource::RenderBytes; + (*renderers_)["google.protobuf.Any"] = &ProtoStreamObjectSource::RenderAny; + (*renderers_)["google.protobuf.Struct"] = + &ProtoStreamObjectSource::RenderStruct; + (*renderers_)["google.protobuf.Value"] = + &ProtoStreamObjectSource::RenderStructValue; + (*renderers_)["google.protobuf.ListValue"] = + &ProtoStreamObjectSource::RenderStructListValue; + (*renderers_)["google.protobuf.FieldMask"] = + &ProtoStreamObjectSource::RenderFieldMask; + ::google::protobuf::internal::OnShutdown(&DeleteRendererMap); +} + +void ProtoStreamObjectSource::DeleteRendererMap() { + delete ProtoStreamObjectSource::renderers_; + renderers_ = nullptr; +} + +// static +ProtoStreamObjectSource::TypeRenderer* +ProtoStreamObjectSource::FindTypeRenderer(const std::string& type_url) { + PROTOBUF_NAMESPACE_ID::internal::call_once(source_renderers_init_, + InitRendererMap); + return FindOrNull(*renderers_, type_url); +} + +util::Status ProtoStreamObjectSource::RenderField( + const google::protobuf::Field* field, StringPiece field_name, + ObjectWriter* ow) const { + // Short-circuit message types as it tends to call WriteMessage recursively + // and ends up using a lot of stack space. Keep the stack usage of this + // message small in order to preserve stack space and not crash. + if (field->kind() == google::protobuf::Field::TYPE_MESSAGE) { + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); // message length + int old_limit = stream_->PushLimit(buffer32); + // Get the nested message type for this field. + const google::protobuf::Type* type = + typeinfo_->GetTypeByTypeUrl(field->type_url()); + if (type == nullptr) { + return util::InternalError( + StrCat("Invalid configuration. Could not find the type: ", + field->type_url())); + } + + // Short-circuit any special type rendering to save call-stack space. + const TypeRenderer* type_renderer = FindTypeRenderer(type->name()); + + RETURN_IF_ERROR(IncrementRecursionDepth(type->name(), field_name)); + if (type_renderer != nullptr) { + RETURN_IF_ERROR((*type_renderer)(this, *type, field_name, ow)); + } else { + RETURN_IF_ERROR(WriteMessage(*type, field_name, 0, true, ow)); + } + --recursion_depth_; + + if (!stream_->ConsumedEntireMessage()) { + return util::InvalidArgumentError( + "Nested protocol message not parsed in its entirety."); + } + stream_->PopLimit(old_limit); + } else { + // Render all other non-message types. + return RenderNonMessageField(field, field_name, ow); + } + return util::Status(); +} + +util::Status ProtoStreamObjectSource::RenderNonMessageField( + const google::protobuf::Field* field, StringPiece field_name, + ObjectWriter* ow) const { + // Temporary buffers of different types. + uint32_t buffer32 = 0; + uint64_t buffer64 = 0; + std::string strbuffer; + switch (field->kind()) { + case google::protobuf::Field::TYPE_BOOL: { + stream_->ReadVarint64(&buffer64); + ow->RenderBool(field_name, buffer64 != 0); + break; + } + case google::protobuf::Field::TYPE_INT32: { + stream_->ReadVarint32(&buffer32); + ow->RenderInt32(field_name, bit_cast<int32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_INT64: { + stream_->ReadVarint64(&buffer64); + ow->RenderInt64(field_name, bit_cast<int64>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_UINT32: { + stream_->ReadVarint32(&buffer32); + ow->RenderUint32(field_name, bit_cast<uint32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_UINT64: { + stream_->ReadVarint64(&buffer64); + ow->RenderUint64(field_name, bit_cast<uint64>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_SINT32: { + stream_->ReadVarint32(&buffer32); + ow->RenderInt32(field_name, WireFormatLite::ZigZagDecode32(buffer32)); + break; + } + case google::protobuf::Field::TYPE_SINT64: { + stream_->ReadVarint64(&buffer64); + ow->RenderInt64(field_name, WireFormatLite::ZigZagDecode64(buffer64)); + break; + } + case google::protobuf::Field::TYPE_SFIXED32: { + stream_->ReadLittleEndian32(&buffer32); + ow->RenderInt32(field_name, bit_cast<int32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_SFIXED64: { + stream_->ReadLittleEndian64(&buffer64); + ow->RenderInt64(field_name, bit_cast<int64>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_FIXED32: { + stream_->ReadLittleEndian32(&buffer32); + ow->RenderUint32(field_name, bit_cast<uint32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_FIXED64: { + stream_->ReadLittleEndian64(&buffer64); + ow->RenderUint64(field_name, bit_cast<uint64>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_FLOAT: { + stream_->ReadLittleEndian32(&buffer32); + ow->RenderFloat(field_name, bit_cast<float>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_DOUBLE: { + stream_->ReadLittleEndian64(&buffer64); + ow->RenderDouble(field_name, bit_cast<double>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_ENUM: { + stream_->ReadVarint32(&buffer32); + + // If the field represents an explicit NULL value, render null. + if (field->type_url() == kStructNullValueTypeUrl) { + ow->RenderNull(field_name); + break; + } + + // Get the nested enum type for this field. + // TODO(skarvaje): Avoid string manipulation. Find ways to speed this + // up. + const google::protobuf::Enum* en = + typeinfo_->GetEnumByTypeUrl(field->type_url()); + // Lookup the name of the enum, and render that. Unknown enum values + // are printed as integers. + if (en != nullptr) { + const google::protobuf::EnumValue* enum_value = + FindEnumValueByNumber(*en, buffer32); + if (enum_value != nullptr) { + if (render_options_.use_ints_for_enums) { + ow->RenderInt32(field_name, buffer32); + } else if (render_options_.use_lower_camel_for_enums) { + ow->RenderString(field_name, + EnumValueNameToLowerCamelCase(enum_value->name())); + } else { + ow->RenderString(field_name, enum_value->name()); + } + } else { + ow->RenderInt32(field_name, buffer32); + } + } else { + ow->RenderInt32(field_name, buffer32); + } + break; + } + case google::protobuf::Field::TYPE_STRING: { + stream_->ReadVarint32(&buffer32); // string size. + stream_->ReadString(&strbuffer, buffer32); + ow->RenderString(field_name, strbuffer); + break; + } + case google::protobuf::Field::TYPE_BYTES: { + stream_->ReadVarint32(&buffer32); // bytes size. + stream_->ReadString(&strbuffer, buffer32); + ow->RenderBytes(field_name, strbuffer); + break; + } + default: + break; + } + return util::Status(); +} + +// TODO(skarvaje): Fix this to avoid code duplication. +const std::string ProtoStreamObjectSource::ReadFieldValueAsString( + const google::protobuf::Field& field) const { + std::string result; + switch (field.kind()) { + case google::protobuf::Field::TYPE_BOOL: { + uint64_t buffer64; + stream_->ReadVarint64(&buffer64); + result = buffer64 != 0 ? "true" : "false"; + break; + } + case google::protobuf::Field::TYPE_INT32: { + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); + result = StrCat(bit_cast<int32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_INT64: { + uint64_t buffer64; + stream_->ReadVarint64(&buffer64); + result = StrCat(bit_cast<int64_t>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_UINT32: { + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); + result = StrCat(bit_cast<uint32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_UINT64: { + uint64_t buffer64; + stream_->ReadVarint64(&buffer64); + result = StrCat(bit_cast<uint64_t>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_SINT32: { + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); + result = StrCat(WireFormatLite::ZigZagDecode32(buffer32)); + break; + } + case google::protobuf::Field::TYPE_SINT64: { + uint64_t buffer64; + stream_->ReadVarint64(&buffer64); + result = StrCat(WireFormatLite::ZigZagDecode64(buffer64)); + break; + } + case google::protobuf::Field::TYPE_SFIXED32: { + uint32_t buffer32; + stream_->ReadLittleEndian32(&buffer32); + result = StrCat(bit_cast<int32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_SFIXED64: { + uint64_t buffer64; + stream_->ReadLittleEndian64(&buffer64); + result = StrCat(bit_cast<int64_t>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_FIXED32: { + uint32_t buffer32; + stream_->ReadLittleEndian32(&buffer32); + result = StrCat(bit_cast<uint32_t>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_FIXED64: { + uint64_t buffer64; + stream_->ReadLittleEndian64(&buffer64); + result = StrCat(bit_cast<uint64_t>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_FLOAT: { + uint32_t buffer32; + stream_->ReadLittleEndian32(&buffer32); + result = SimpleFtoa(bit_cast<float>(buffer32)); + break; + } + case google::protobuf::Field::TYPE_DOUBLE: { + uint64_t buffer64; + stream_->ReadLittleEndian64(&buffer64); + result = SimpleDtoa(bit_cast<double>(buffer64)); + break; + } + case google::protobuf::Field::TYPE_ENUM: { + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); + // Get the nested enum type for this field. + // TODO(skarvaje): Avoid string manipulation. Find ways to speed this + // up. + const google::protobuf::Enum* en = + typeinfo_->GetEnumByTypeUrl(field.type_url()); + // Lookup the name of the enum, and render that. Skips unknown enums. + if (en != nullptr) { + const google::protobuf::EnumValue* enum_value = + FindEnumValueByNumber(*en, buffer32); + if (enum_value != nullptr) { + result = enum_value->name(); + } + } + break; + } + case google::protobuf::Field::TYPE_STRING: { + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); // string size. + stream_->ReadString(&result, buffer32); + break; + } + case google::protobuf::Field::TYPE_BYTES: { + uint32_t buffer32; + stream_->ReadVarint32(&buffer32); // bytes size. + stream_->ReadString(&result, buffer32); + break; + } + default: + break; + } + return result; +} + +// Field is a map if it is a repeated message and it has an option "map_type". +// TODO(skarvaje): Consider pre-computing the IsMap() into Field directly. +bool ProtoStreamObjectSource::IsMap( + const google::protobuf::Field& field) const { + const google::protobuf::Type* field_type = + typeinfo_->GetTypeByTypeUrl(field.type_url()); + return field.kind() == google::protobuf::Field::TYPE_MESSAGE && + util::converter::IsMap(field, *field_type); +} + +std::pair<int64_t, int32_t> ProtoStreamObjectSource::ReadSecondsAndNanos( + const google::protobuf::Type& type) const { + uint64_t seconds = 0; + uint32_t nanos = 0; + uint32_t tag = 0; + int64_t signed_seconds = 0; + int32_t signed_nanos = 0; + + for (tag = stream_->ReadTag(); tag != 0; tag = stream_->ReadTag()) { + const google::protobuf::Field* field = FindAndVerifyField(type, tag); + if (field == nullptr) { + WireFormat::SkipField(stream_, tag, nullptr); + continue; + } + // 'seconds' has field number of 1 and 'nanos' has field number 2 + // //google/protobuf/timestamp.proto & duration.proto + if (field->number() == 1) { + // read seconds + stream_->ReadVarint64(&seconds); + signed_seconds = bit_cast<int64_t>(seconds); + } else if (field->number() == 2) { + // read nanos + stream_->ReadVarint32(&nanos); + signed_nanos = bit_cast<int32_t>(nanos); + } + } + return std::pair<int64_t, int32_t>(signed_seconds, signed_nanos); +} + +util::Status ProtoStreamObjectSource::IncrementRecursionDepth( + StringPiece type_name, StringPiece field_name) const { + if (++recursion_depth_ > max_recursion_depth_) { + return util::InvalidArgumentError( + StrCat("Message too deep. Max recursion depth reached for type '", + type_name, "', field '", field_name, "'")); + } + return util::Status(); +} + +namespace { +// TODO(skarvaje): Speed this up by not doing a linear scan. +const google::protobuf::Field* FindFieldByNumber( + const google::protobuf::Type& type, int number) { + for (int i = 0; i < type.fields_size(); ++i) { + if (type.fields(i).number() == number) { + return &type.fields(i); + } + } + return nullptr; +} + +// TODO(skarvaje): Replace FieldDescriptor by implementing IsTypePackable() +// using tech Field. +bool IsPackable(const google::protobuf::Field& field) { + return field.cardinality() == google::protobuf::Field::CARDINALITY_REPEATED && + FieldDescriptor::IsTypePackable( + static_cast<FieldDescriptor::Type>(field.kind())); +} + +// TODO(skarvaje): Speed this up by not doing a linear scan. +const google::protobuf::EnumValue* FindEnumValueByNumber( + const google::protobuf::Enum& tech_enum, int number) { + for (int i = 0; i < tech_enum.enumvalue_size(); ++i) { + const google::protobuf::EnumValue& ev = tech_enum.enumvalue(i); + if (ev.number() == number) { + return &ev; + } + } + return nullptr; +} + +// TODO(skarvaje): Look into optimizing this by not doing computation on +// double. +const std::string FormatNanos(uint32_t nanos, bool with_trailing_zeros) { + if (nanos == 0) { + return with_trailing_zeros ? ".000" : ""; + } + + const char* format = (nanos % 1000 != 0) ? "%.9f" + : (nanos % 1000000 != 0) ? "%.6f" + : "%.3f"; + std::string formatted = + StringPrintf(format, static_cast<double>(nanos) / kNanosPerSecond); + // remove the leading 0 before decimal. + return formatted.substr(1); +} +} // namespace + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource.h b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource.h new file mode 100644 index 00000000..d3e4d8cb --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource.h @@ -0,0 +1,328 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTSOURCE_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTSOURCE_H__ + +#include <cstdint> +#include <functional> +#include <string> +#include <unordered_map> + +#include <stubs/status.h> + +#include <stubs/common.h> +#include <type.pb.h> +#include <util/internal/type_info.h> +#include <util/internal/object_source.h> +#include <util/internal/object_writer.h> +#include <util/type_resolver.h> +#include <stubs/statusor.h> +#include <stubs/strutil.h> +#include <stubs/hash.h> +#include <stubs/status.h> + + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class TypeInfo; + +// An ObjectSource that can parse a stream of bytes as a protocol buffer. +// Its WriteTo() method can be given an ObjectWriter. +// This implementation uses a google.protobuf.Type for tag and name lookup. +// The field names are converted into lower camel-case when writing to the +// ObjectWriter. +// +// Sample usage: (suppose input is: string proto) +// ArrayInputStream arr_stream(proto.data(), proto.size()); +// CodedInputStream in_stream(&arr_stream); +// ProtoStreamObjectSource os(&in_stream, /*ServiceTypeInfo*/ typeinfo, +// <your message google::protobuf::Type>); +// +// Status status = os.WriteTo(<some ObjectWriter>); +class PROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource { + public: + + struct RenderOptions { + RenderOptions() = default; + RenderOptions(const RenderOptions&) = default; + + // Sets whether or not to use lowerCamelCase casing for enum values. If set + // to false, enum values are output without any case conversions. + // + // For example, if we have an enum: + // enum Type { + // ACTION_AND_ADVENTURE = 1; + // } + // Type type = 20; + // + // And this option is set to true. Then the rendered "type" field will have + // the string "actionAndAdventure". + // { + // ... + // "type": "actionAndAdventure", + // ... + // } + // + // If set to false, the rendered "type" field will have the string + // "ACTION_AND_ADVENTURE". + // { + // ... + // "type": "ACTION_AND_ADVENTURE", + // ... + // } + bool use_lower_camel_for_enums = false; + + // Sets whether to always output enums as ints, by default this is off, and + // enums are rendered as strings. + bool use_ints_for_enums = false; + + // Whether to preserve proto field names + bool preserve_proto_field_names = false; + + }; + + ProtoStreamObjectSource(io::CodedInputStream* stream, + TypeResolver* type_resolver, + const google::protobuf::Type& type) + : ProtoStreamObjectSource(stream, type_resolver, type, RenderOptions()) {} + ProtoStreamObjectSource(io::CodedInputStream* stream, + TypeResolver* type_resolver, + const google::protobuf::Type& type, + const RenderOptions& render_options); + + ~ProtoStreamObjectSource() override; + + util::Status NamedWriteTo(StringPiece name, + ObjectWriter* ow) const override; + + // Sets the max recursion depth of proto message to be deserialized. Proto + // messages over this depth will fail to be deserialized. + // Default value is 64. + void set_max_recursion_depth(int max_depth) { + max_recursion_depth_ = max_depth; + } + + protected: + // Writes a proto2 Message to the ObjectWriter. When the given end_tag is + // found this method will complete, allowing it to be used for parsing both + // nested messages (end with 0) and nested groups (end with group end tag). + // The include_start_and_end parameter allows this method to be called when + // already inside of an object, and skip calling StartObject and EndObject. + virtual util::Status WriteMessage(const google::protobuf::Type& type, + StringPiece name, + const uint32_t end_tag, + 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_t> RenderList( + const google::protobuf::Field* field, StringPiece name, + uint32_t 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_t tag) const; + + // Renders a field value to the ObjectWriter. + virtual 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 std::string ReadFieldValueAsString( + const google::protobuf::Field& field) const; + + + // Returns the input stream. + io::CodedInputStream* stream() const { return stream_; } + + private: + ProtoStreamObjectSource(io::CodedInputStream* stream, + const TypeInfo* typeinfo, + const google::protobuf::Type& type, + const RenderOptions& render_options); + // Function that renders a well known type with a modified behavior. + typedef util::Status (*TypeRenderer)(const ProtoStreamObjectSource*, + const google::protobuf::Type&, + StringPiece, ObjectWriter*); + + // TODO(skarvaje): Mark these methods as non-const as they modify internal + // state (stream_). + // + // 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. + util::StatusOr<uint32_t> RenderMap(const google::protobuf::Field* field, + StringPiece name, uint32_t list_tag, + ObjectWriter* ow) const; + + // Renders a packed repeating field. A packed field is stored as: + // {tag length item1 item2 item3} instead of the less efficient + // {tag item1 tag item2 tag item3}. + util::Status RenderPacked(const google::protobuf::Field* field, + ObjectWriter* ow) const; + + // Renders a google.protobuf.Timestamp value to ObjectWriter + static util::Status RenderTimestamp(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Renders a google.protobuf.Duration value to ObjectWriter + static util::Status RenderDuration(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Following RenderTYPE functions render well known types in + // google/protobuf/wrappers.proto corresponding to TYPE. + static util::Status RenderDouble(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderFloat(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderInt64(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderUInt64(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderInt32(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderUInt32(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderBool(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderString(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderBytes(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Renders a google.protobuf.Struct to ObjectWriter. + static util::Status RenderStruct(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Helper to render google.protobuf.Struct's Value fields to ObjectWriter. + static util::Status RenderStructValue(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, + ObjectWriter* ow); + + // Helper to render google.protobuf.Struct's ListValue fields to ObjectWriter. + static util::Status RenderStructListValue(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, + ObjectWriter* ow); + + // Render the "Any" type. + static util::Status RenderAny(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Render the "FieldMask" type. + static util::Status RenderFieldMask(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + static std::unordered_map<std::string, TypeRenderer>* renderers_; + static void InitRendererMap(); + static void DeleteRendererMap(); + static TypeRenderer* FindTypeRenderer(const std::string& type_url); + + // 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, + StringPiece field_name, + ObjectWriter* ow) const; + + + // Utility function to detect proto maps. The 'field' MUST be repeated. + bool IsMap(const google::protobuf::Field& field) const; + + // Utility to read int64 and int32 values from a message type in stream_. + // Used for reading google.protobuf.Timestamp and Duration messages. + std::pair<int64_t, int32_t> ReadSecondsAndNanos( + const google::protobuf::Type& type) const; + + // Helper function to check recursion depth and increment it. It will return + // OkStatus() if the current depth is allowed. Otherwise an error is returned. + // type_name and field_name are used for error reporting. + util::Status IncrementRecursionDepth(StringPiece type_name, + StringPiece field_name) const; + + // Input stream to read from. Ownership rests with the caller. + mutable io::CodedInputStream* stream_; + + // 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_; + + // google::protobuf::Type of the message source. + const google::protobuf::Type& type_; + + + const RenderOptions render_options_; + + // Tracks current recursion depth. + mutable int recursion_depth_; + + // Maximum allowed recursion depth. + int max_recursion_depth_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoStreamObjectSource); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTSOURCE_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource_test.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource_test.cc new file mode 100644 index 00000000..ec35789c --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectsource_test.cc @@ -0,0 +1,1165 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/protostream_objectsource.h> + +#include <cstdint> +#include <memory> +#include <sstream> + +#include <any.pb.h> +#include <io/coded_stream.h> +#include <io/zero_copy_stream_impl_lite.h> +#include <descriptor.h> +#include <util/internal/expecting_objectwriter.h> +#include <util/internal/testdata/anys.pb.h> +#include <util/internal/testdata/books.pb.h> +#include <util/internal/testdata/field_mask.pb.h> +#include <util/internal/testdata/maps.pb.h> +#include <util/internal/testdata/proto3.pb.h> +#include <util/internal/testdata/struct.pb.h> +#include <util/internal/testdata/timestamp_duration.pb.h> +#include <util/internal/type_info_test_helper.h> +#include <util/internal/constants.h> +#include <gtest/gtest.h> +#include <stubs/casts.h> +#include <stubs/status.h> + + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using ::google::protobuf::Any; +using io::ArrayInputStream; +using io::CodedInputStream; +using proto_util_converter::testing::AnyM; +using proto_util_converter::testing::AnyOut; +using proto_util_converter::testing::Author; +using proto_util_converter::testing::BadAuthor; +using proto_util_converter::testing::BadNestedBook; +using proto_util_converter::testing::Book; +using proto_util_converter::testing::Book_Label; +using proto_util_converter::testing::Cyclic; +using proto_util_converter::testing::FieldMaskTest; +using proto_util_converter::testing::MapOut; +using proto_util_converter::testing::MapOutWireFormat; +using proto_util_converter::testing::NestedBook; +using proto_util_converter::testing::NestedFieldMask; +using proto_util_converter::testing::PackedPrimitive; +using proto_util_converter::testing::Primitive; +using proto_util_converter::testing::Proto3Message; +using proto_util_converter::testing::StructType; +using proto_util_converter::testing::TimestampDuration; +using ::testing::_; + + +namespace { +std::string GetTypeUrl(const Descriptor* descriptor) { + return std::string(kTypeServiceBaseUrl) + "/" + descriptor->full_name(); +} +} // namespace + +class ProtostreamObjectSourceTest + : public ::testing::TestWithParam<testing::TypeInfoSource> { + protected: + ProtostreamObjectSourceTest() + : helper_(GetParam()), + mock_(), + ow_(&mock_), + use_lower_camel_for_enums_(false), + use_ints_for_enums_(false), + use_preserve_proto_field_names_(false), + add_trailing_zeros_(false), + render_unknown_enum_values_(true) { + helper_.ResetTypeInfo(Book::descriptor(), Proto3Message::descriptor()); + } + + virtual ~ProtostreamObjectSourceTest() {} + + void DoTest(const Message& msg, const Descriptor* descriptor) { + util::Status status = ExecuteTest(msg, descriptor); + EXPECT_EQ(util::Status(), status); + } + + util::Status ExecuteTest(const Message& msg, const Descriptor* descriptor) { + std::ostringstream oss; + msg.SerializePartialToOstream(&oss); + std::string proto = oss.str(); + ArrayInputStream arr_stream(proto.data(), proto.size()); + CodedInputStream in_stream(&arr_stream); + + ProtoStreamObjectSource::RenderOptions render_options; + render_options.use_lower_camel_for_enums = use_lower_camel_for_enums_; + render_options.use_ints_for_enums = use_ints_for_enums_; + render_options.preserve_proto_field_names = use_preserve_proto_field_names_; + std::unique_ptr<ProtoStreamObjectSource> os(helper_.NewProtoSource( + &in_stream, GetTypeUrl(descriptor), render_options)); + os->set_max_recursion_depth(64); + return os->WriteTo(&mock_); + } + + void PrepareExpectingObjectWriterForRepeatedPrimitive() { + ow_.StartObject("") + ->StartList("repFix32") + ->RenderUint32("", bit_cast<uint32_t>(3201)) + ->RenderUint32("", bit_cast<uint32_t>(0)) + ->RenderUint32("", bit_cast<uint32_t>(3202)) + ->EndList() + ->StartList("repU32") + ->RenderUint32("", bit_cast<uint32_t>(3203)) + ->RenderUint32("", bit_cast<uint32_t>(0)) + ->EndList() + ->StartList("repI32") + ->RenderInt32("", 0) + ->RenderInt32("", 3204) + ->RenderInt32("", 3205) + ->EndList() + ->StartList("repSf32") + ->RenderInt32("", 3206) + ->RenderInt32("", 0) + ->EndList() + ->StartList("repS32") + ->RenderInt32("", 0) + ->RenderInt32("", 3207) + ->RenderInt32("", 3208) + ->EndList() + ->StartList("repFix64") + ->RenderUint64("", bit_cast<uint64_t>(int64_t{6401})) + ->RenderUint64("", bit_cast<uint64_t>(int64_t{0})) + ->EndList() + ->StartList("repU64") + ->RenderUint64("", bit_cast<uint64_t>(int64_t{0})) + ->RenderUint64("", bit_cast<uint64_t>(int64_t{6402})) + ->RenderUint64("", bit_cast<uint64_t>(int64_t{6403})) + ->EndList() + ->StartList("repI64") + ->RenderInt64("", 6404L) + ->RenderInt64("", 0L) + ->EndList() + ->StartList("repSf64") + ->RenderInt64("", 0L) + ->RenderInt64("", 6405L) + ->RenderInt64("", 6406L) + ->EndList() + ->StartList("repS64") + ->RenderInt64("", 6407L) + ->RenderInt64("", 0L) + ->EndList() + ->StartList("repFloat") + ->RenderFloat("", 0.0f) + ->RenderFloat("", 32.1f) + ->RenderFloat("", 32.2f) + ->EndList() + ->StartList("repDouble") + ->RenderDouble("", 64.1L) + ->RenderDouble("", 0.0L) + ->EndList() + ->StartList("repBool") + ->RenderBool("", true) + ->RenderBool("", false) + ->EndList() + ->EndObject(); + } + + Primitive PrepareRepeatedPrimitive() { + Primitive primitive; + primitive.add_rep_fix32(3201); + primitive.add_rep_fix32(0); + primitive.add_rep_fix32(3202); + primitive.add_rep_u32(3203); + primitive.add_rep_u32(0); + primitive.add_rep_i32(0); + primitive.add_rep_i32(3204); + primitive.add_rep_i32(3205); + primitive.add_rep_sf32(3206); + primitive.add_rep_sf32(0); + primitive.add_rep_s32(0); + primitive.add_rep_s32(3207); + primitive.add_rep_s32(3208); + primitive.add_rep_fix64(6401L); + primitive.add_rep_fix64(0L); + primitive.add_rep_u64(0L); + primitive.add_rep_u64(6402L); + primitive.add_rep_u64(6403L); + primitive.add_rep_i64(6404L); + primitive.add_rep_i64(0L); + primitive.add_rep_sf64(0L); + primitive.add_rep_sf64(6405L); + primitive.add_rep_sf64(6406L); + primitive.add_rep_s64(6407L); + primitive.add_rep_s64(0L); + primitive.add_rep_float(0.0f); + primitive.add_rep_float(32.1f); + primitive.add_rep_float(32.2f); + primitive.add_rep_double(64.1L); + primitive.add_rep_double(0.0); + primitive.add_rep_bool(true); + primitive.add_rep_bool(false); + + PrepareExpectingObjectWriterForRepeatedPrimitive(); + return primitive; + } + + PackedPrimitive PreparePackedPrimitive() { + PackedPrimitive primitive; + primitive.add_rep_fix32(3201); + primitive.add_rep_fix32(0); + primitive.add_rep_fix32(3202); + primitive.add_rep_u32(3203); + primitive.add_rep_u32(0); + primitive.add_rep_i32(0); + primitive.add_rep_i32(3204); + primitive.add_rep_i32(3205); + primitive.add_rep_sf32(3206); + primitive.add_rep_sf32(0); + primitive.add_rep_s32(0); + primitive.add_rep_s32(3207); + primitive.add_rep_s32(3208); + primitive.add_rep_fix64(6401L); + primitive.add_rep_fix64(0L); + primitive.add_rep_u64(0L); + primitive.add_rep_u64(6402L); + primitive.add_rep_u64(6403L); + primitive.add_rep_i64(6404L); + primitive.add_rep_i64(0L); + primitive.add_rep_sf64(0L); + primitive.add_rep_sf64(6405L); + primitive.add_rep_sf64(6406L); + primitive.add_rep_s64(6407L); + primitive.add_rep_s64(0L); + primitive.add_rep_float(0.0f); + primitive.add_rep_float(32.1f); + primitive.add_rep_float(32.2f); + primitive.add_rep_double(64.1L); + primitive.add_rep_double(0.0); + primitive.add_rep_bool(true); + primitive.add_rep_bool(false); + + PrepareExpectingObjectWriterForRepeatedPrimitive(); + return primitive; + } + + void UseLowerCamelForEnums() { use_lower_camel_for_enums_ = true; } + + void UseIntsForEnums() { use_ints_for_enums_ = true; } + + void UsePreserveProtoFieldNames() { use_preserve_proto_field_names_ = true; } + + void AddTrailingZeros() { add_trailing_zeros_ = true; } + + void SetRenderUnknownEnumValues(bool value) { + render_unknown_enum_values_ = value; + } + + testing::TypeInfoTestHelper helper_; + + ::testing::NiceMock<MockObjectWriter> mock_; + ExpectingObjectWriter ow_; + bool use_lower_camel_for_enums_; + bool use_ints_for_enums_; + bool use_preserve_proto_field_names_; + bool add_trailing_zeros_; + bool render_unknown_enum_values_; +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtostreamObjectSourceTest, EmptyMessage) { + Book empty; + ow_.StartObject("")->EndObject(); + DoTest(empty, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, Primitives) { + Primitive primitive; + primitive.set_fix32(3201); + primitive.set_u32(3202); + primitive.set_i32(3203); + primitive.set_sf32(3204); + primitive.set_s32(3205); + primitive.set_fix64(6401L); + primitive.set_u64(6402L); + primitive.set_i64(6403L); + primitive.set_sf64(6404L); + primitive.set_s64(6405L); + primitive.set_str("String Value"); + primitive.set_bytes("Some Bytes"); + primitive.set_float_(32.1f); + primitive.set_double_(64.1L); + primitive.set_bool_(true); + + ow_.StartObject("") + ->RenderUint32("fix32", bit_cast<uint32_t>(3201)) + ->RenderUint32("u32", bit_cast<uint32_t>(3202)) + ->RenderInt32("i32", 3203) + ->RenderInt32("sf32", 3204) + ->RenderInt32("s32", 3205) + ->RenderUint64("fix64", bit_cast<uint64_t>(int64_t{6401})) + ->RenderUint64("u64", bit_cast<uint64_t>(int64_t{6402})) + ->RenderInt64("i64", 6403L) + ->RenderInt64("sf64", 6404L) + ->RenderInt64("s64", 6405L) + ->RenderString("str", "String Value") + ->RenderBytes("bytes", "Some Bytes") + ->RenderFloat("float", 32.1f) + ->RenderDouble("double", 64.1L) + ->RenderBool("bool", true) + ->EndObject(); + DoTest(primitive, Primitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, RepeatingPrimitives) { + Primitive primitive = PrepareRepeatedPrimitive(); + primitive.add_rep_str("String One"); + primitive.add_rep_str("String Two"); + primitive.add_rep_bytes("Some Bytes"); + + ow_.StartList("repStr") + ->RenderString("", "String One") + ->RenderString("", "String Two") + ->EndList() + ->StartList("repBytes") + ->RenderBytes("", "Some Bytes") + ->EndList(); + DoTest(primitive, Primitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, CustomJsonName) { + Author author; + author.set_id(12345); + + ow_.StartObject("")->RenderUint64("@id", 12345)->EndObject(); + DoTest(author, Author::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, NestedMessage) { + Author* author = new Author(); + author->set_name("Tolstoy"); + Book book; + book.set_title("My Book"); + book.set_allocated_author(author); + + ow_.StartObject("") + ->RenderString("title", "My Book") + ->StartObject("author") + ->RenderString("name", "Tolstoy") + ->EndObject() + ->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, RepeatingField) { + Author author; + author.set_alive(false); + author.set_name("john"); + author.add_pseudonym("phil"); + author.add_pseudonym("bob"); + + ow_.StartObject("") + ->RenderBool("alive", false) + ->RenderString("name", "john") + ->StartList("pseudonym") + ->RenderString("", "phil") + ->RenderString("", "bob") + ->EndList() + ->EndObject(); + DoTest(author, Author::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, PackedRepeatingFields) { + DoTest(PreparePackedPrimitive(), PackedPrimitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, NonPackedPackableFieldsActuallyPacked) { + // Protostream is packed, but parse with non-packed Primitive. + DoTest(PreparePackedPrimitive(), Primitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, PackedPackableFieldNotActuallyPacked) { + // Protostream is not packed, but parse with PackedPrimitive. + DoTest(PrepareRepeatedPrimitive(), PackedPrimitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, BadAuthor) { + Author author; + author.set_alive(false); + author.set_name("john"); + author.set_id(1234L); + author.add_pseudonym("phil"); + author.add_pseudonym("bob"); + + ow_.StartObject("") + ->StartList("alive") + ->RenderBool("", false) + ->EndList() + ->StartList("name") + ->RenderUint64("", static_cast<uint64_t>('j')) + ->RenderUint64("", static_cast<uint64_t>('o')) + ->RenderUint64("", static_cast<uint64_t>('h')) + ->RenderUint64("", static_cast<uint64_t>('n')) + ->EndList() + ->RenderString("pseudonym", "phil") + ->RenderString("pseudonym", "bob") + ->EndObject(); + // Protostream created with Author, but parsed with BadAuthor. + DoTest(author, BadAuthor::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, NestedBookToBadNestedBook) { + Book* book = new Book(); + book->set_length(250); + book->set_published(2014L); + NestedBook nested; + nested.set_allocated_book(book); + + ow_.StartObject("") + ->StartList("book") + ->RenderUint32("", 24) // tag for field length (3 << 3) + ->RenderUint32("", 250) + ->RenderUint32("", 32) // tag for field published (4 << 3) + ->RenderUint32("", 2014) + ->EndList() + ->EndObject(); + // Protostream created with NestedBook, but parsed with BadNestedBook. + DoTest(nested, BadNestedBook::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, BadNestedBookToNestedBook) { + BadNestedBook nested; + nested.add_book(1); + nested.add_book(2); + nested.add_book(3); + nested.add_book(4); + nested.add_book(5); + nested.add_book(6); + nested.add_book(7); + + ow_.StartObject("")->StartObject("book")->EndObject()->EndObject(); + // Protostream created with BadNestedBook, but parsed with NestedBook. + DoTest(nested, NestedBook::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, + LongRepeatedListDoesNotBreakIntoMultipleJsonLists) { + Book book; + + int repeat = 10000; + for (int i = 0; i < repeat; ++i) { + Book_Label* label = book.add_labels(); + label->set_key(StrCat("i", i)); + label->set_value(StrCat("v", i)); + } + + // Make sure StartList and EndList are called exactly once (see b/18227499 for + // problems when this doesn't happen) + EXPECT_CALL(mock_, StartList(_)).Times(1); + EXPECT_CALL(mock_, EndList()).Times(1); + + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, LowerCamelEnumOutputMacroCase) { + Book book; + book.set_type(Book::ACTION_AND_ADVENTURE); + + UseLowerCamelForEnums(); + + ow_.StartObject("")->RenderString("type", "actionAndAdventure")->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, LowerCamelEnumOutputSnakeCase) { + Book book; + book.set_type(Book::arts_and_photography); + + UseLowerCamelForEnums(); + + ow_.StartObject("")->RenderString("type", "artsAndPhotography")->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, LowerCamelEnumOutputWithNumber) { + Book book; + book.set_type(Book::I18N_Tech); + + UseLowerCamelForEnums(); + + ow_.StartObject("")->RenderString("type", "i18nTech")->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, EnumCaseIsUnchangedByDefault) { + Book book; + book.set_type(Book::ACTION_AND_ADVENTURE); + ow_.StartObject("") + ->RenderString("type", "ACTION_AND_ADVENTURE") + ->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, UseIntsForEnumsTest) { + Book book; + book.set_type(Book::ACTION_AND_ADVENTURE); + + UseIntsForEnums(); + + ow_.StartObject("")->RenderInt32("type", 3)->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, UsePreserveProtoFieldNames) { + Book book; + book.set_snake_field("foo"); + + UsePreserveProtoFieldNames(); + + ow_.StartObject("")->RenderString("snake_field", "foo")->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, + UnknownEnumAreDroppedWhenRenderUnknownEnumValuesIsUnset) { + Proto3Message message; + message.set_enum_value(static_cast<Proto3Message::NestedEnum>(1234)); + + SetRenderUnknownEnumValues(false); + + // Unknown enum values are not output. + ow_.StartObject("")->EndObject(); + DoTest(message, Proto3Message::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, + UnknownEnumAreOutputWhenRenderUnknownEnumValuesIsSet) { + Proto3Message message; + message.set_enum_value(static_cast<Proto3Message::NestedEnum>(1234)); + + SetRenderUnknownEnumValues(true); + + // Unknown enum values are output. + ow_.StartObject("")->RenderInt32("enumValue", 1234)->EndObject(); + DoTest(message, Proto3Message::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, CyclicMessageDepthTest) { + Cyclic cyclic; + cyclic.set_m_int(123); + + Book* book = cyclic.mutable_m_book(); + book->set_title("book title"); + Cyclic* current = cyclic.mutable_m_cyclic(); + Author* current_author = cyclic.add_m_author(); + for (int i = 0; i < 63; ++i) { + Author* next = current_author->add_friend_(); + next->set_id(i); + next->set_name(StrCat("author_name_", i)); + next->set_alive(true); + current_author = next; + } + + // Recursive message with depth (65) > max (max is 64). + for (int i = 0; i < 64; ++i) { + Cyclic* next = current->mutable_m_cyclic(); + next->set_m_str(StrCat("count_", i)); + current = next; + } + + util::Status status = ExecuteTest(cyclic, Cyclic::descriptor()); + EXPECT_TRUE(util::IsInvalidArgument(status)); +} + +class ProtostreamObjectSourceMapsTest : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceMapsTest() { + helper_.ResetTypeInfo(MapOut::descriptor()); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceMapsTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +// Tests JSON map. +// +// This is the example expected output. +// { +// "map1": { +// "key1": { +// "foo": "foovalue" +// }, +// "key2": { +// "foo": "barvalue" +// } +// }, +// "map2": { +// "nestedself": { +// "map1": { +// "nested_key1": { +// "foo": "nested_foo" +// } +// }, +// "bar": "nested_bar_string" +// } +// }, +// "map3": { +// "111": "one one one" +// }, +// "bar": "top bar" +// } +TEST_P(ProtostreamObjectSourceMapsTest, MapsTest) { + MapOut out; + (*out.mutable_map1())["key1"].set_foo("foovalue"); + (*out.mutable_map1())["key2"].set_foo("barvalue"); + + MapOut* nested_value = &(*out.mutable_map2())["nestedself"]; + (*nested_value->mutable_map1())["nested_key1"].set_foo("nested_foo"); + nested_value->set_bar("nested_bar_string"); + + (*out.mutable_map3())[111] = "one one one"; + + out.set_bar("top bar"); + + ow_.StartObject("") + ->StartObject("map1") + ->StartObject("key1") + ->RenderString("foo", "foovalue") + ->EndObject() + ->StartObject("key2") + ->RenderString("foo", "barvalue") + ->EndObject() + ->StartObject("map2") + ->StartObject("nestedself") + ->StartObject("map1") + ->StartObject("nested_key1") + ->RenderString("foo", "nested_foo") + ->EndObject() + ->EndObject() + ->RenderString("bar", "nested_bar_string") + ->EndObject() + ->EndObject() + ->StartObject("map3") + ->RenderString("111", "one one one") + ->EndObject() + ->EndObject() + ->RenderString("bar", "top bar") + ->EndObject(); + + DoTest(out, MapOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceMapsTest, MissingKeysTest) { + // MapOutWireFormat has the same wire representation with MapOut but uses + // repeated message fields to represent map fields so we can intentionally + // leave out the key field or the value field of a map entry. + MapOutWireFormat out; + // Create some map entries without keys. They will be rendered with the + // default values ("" for strings, "0" for integers, etc.). + // { + // "map1": { + // "": { + // "foo": "foovalue" + // } + // }, + // "map2": { + // "": { + // "map1": { + // "nested_key1": { + // "foo": "nested_foo" + // } + // } + // } + // }, + // "map3": { + // "0": "one one one" + // }, + // "map4": { + // "false": "bool" + // } + // } + out.add_map1()->mutable_value()->set_foo("foovalue"); + MapOut* nested = out.add_map2()->mutable_value(); + (*nested->mutable_map1())["nested_key1"].set_foo("nested_foo"); + out.add_map3()->set_value("one one one"); + out.add_map4()->set_value("bool"); + + ow_.StartObject("") + ->StartObject("map1") + ->StartObject("") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->StartObject("map2") + ->StartObject("") + ->StartObject("map1") + ->StartObject("nested_key1") + ->RenderString("foo", "nested_foo") + ->EndObject() + ->EndObject() + ->EndObject() + ->EndObject() + ->StartObject("map3") + ->RenderString("0", "one one one") + ->EndObject() + ->StartObject("map4") + ->RenderString("false", "bool") + ->EndObject() + ->EndObject(); + + DoTest(out, MapOut::descriptor()); +} + +class ProtostreamObjectSourceAnysTest : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceAnysTest() { + helper_.ResetTypeInfo({AnyOut::descriptor(), Book::descriptor(), + google::protobuf::Any::descriptor()}); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceAnysTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +// Tests JSON any support. +// +// This is the example expected output. +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.testing.AnyM" +// "foo": "foovalue" +// } +// } +TEST_P(ProtostreamObjectSourceAnysTest, BasicAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + AnyM m; + m.set_foo("foovalue"); + any->PackFrom(m); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, LowerCamelEnumOutputSnakeCase) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + Book book; + book.set_type(Book::arts_and_photography); + any->PackFrom(book); + + UseLowerCamelForEnums(); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.Book") + ->RenderString("type", "artsAndPhotography") + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, UseIntsForEnumsTest) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + Book book; + book.set_type(Book::ACTION_AND_ADVENTURE); + any->PackFrom(book); + + UseIntsForEnums(); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.Book") + ->RenderInt32("type", 3) + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, UsePreserveProtoFieldNames) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + Book book; + book.set_snake_field("foo"); + any->PackFrom(book); + + UsePreserveProtoFieldNames(); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.Book") + ->RenderString("snake_field", "foo") + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, RecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url( + "type.googleapis.com/proto_util_converter.testing.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + nested_any.set_value(m.SerializeAsString()); + + any->set_value(nested_any.SerializeAsString()); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, DoubleRecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + 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.Any"); + + ::google::protobuf::Any second_nested_any; + second_nested_any.set_type_url( + "type.googleapis.com/proto_util_converter.testing.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + second_nested_any.set_value(m.SerializeAsString()); + nested_any.set_value(second_nested_any.SerializeAsString()); + any->set_value(nested_any.SerializeAsString()); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, EmptyAnyOutputsEmptyObject) { + AnyOut out; + out.mutable_any(); + + ow_.StartObject("")->StartObject("any")->EndObject()->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, EmptyWithTypeAndNoValueOutputsType) { + AnyOut out; + out.mutable_any()->set_type_url("foo.googleapis.com/my.Type"); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", "foo.googleapis.com/my.Type") + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, MissingTypeUrlError) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + AnyM m; + m.set_foo("foovalue"); + any->set_value(m.SerializeAsString()); + + // We start the "AnyOut" part and then fail when we hit the Any object. + ow_.StartObject(""); + + util::Status status = ExecuteTest(out, AnyOut::descriptor()); + EXPECT_TRUE(util::IsInternal(status)); +} + +TEST_P(ProtostreamObjectSourceAnysTest, UnknownTypeServiceError) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("foo.googleapis.com/my.own.Type"); + + AnyM m; + m.set_foo("foovalue"); + any->set_value(m.SerializeAsString()); + + // We start the "AnyOut" part and then fail when we hit the Any object. + ow_.StartObject(""); + + util::Status status = ExecuteTest(out, AnyOut::descriptor()); + EXPECT_TRUE(util::IsInternal(status)); +} + +TEST_P(ProtostreamObjectSourceAnysTest, UnknownTypeError) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/unknown.Type"); + + AnyM m; + m.set_foo("foovalue"); + any->set_value(m.SerializeAsString()); + + // We start the "AnyOut" part and then fail when we hit the Any object. + ow_.StartObject(""); + + util::Status status = ExecuteTest(out, AnyOut::descriptor()); + EXPECT_TRUE(util::IsInternal(status)); +} + +class ProtostreamObjectSourceStructTest : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceStructTest() { + helper_.ResetTypeInfo(StructType::descriptor(), + google::protobuf::Struct::descriptor()); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceStructTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +// Tests struct +// +// "object": { +// "k1": 123, +// "k2": true +// } +TEST_P(ProtostreamObjectSourceStructTest, StructRenderSuccess) { + StructType out; + google::protobuf::Struct* s = out.mutable_object(); + s->mutable_fields()->operator[]("k1").set_number_value(123); + s->mutable_fields()->operator[]("k2").set_bool_value(true); + + ow_.StartObject("") + ->StartObject("object") + ->RenderDouble("k1", 123) + ->RenderBool("k2", true) + ->EndObject() + ->EndObject(); + + DoTest(out, StructType::descriptor()); +} + +TEST_P(ProtostreamObjectSourceStructTest, MissingValueSkipsField) { + StructType out; + google::protobuf::Struct* s = out.mutable_object(); + s->mutable_fields()->operator[]("k1"); + + ow_.StartObject("")->StartObject("object")->EndObject()->EndObject(); + + DoTest(out, StructType::descriptor()); +} + +class ProtostreamObjectSourceFieldMaskTest + : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceFieldMaskTest() { + helper_.ResetTypeInfo(FieldMaskTest::descriptor(), + google::protobuf::FieldMask::descriptor()); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceFieldMaskTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtostreamObjectSourceFieldMaskTest, FieldMaskRenderSuccess) { + FieldMaskTest out; + out.set_id("1"); + out.mutable_single_mask()->add_paths("path1"); + out.mutable_single_mask()->add_paths("snake_case_path2"); + ::google::protobuf::FieldMask* mask = out.add_repeated_mask(); + mask->add_paths("path3"); + mask = out.add_repeated_mask(); + mask->add_paths("snake_case_path4"); + mask->add_paths("path5"); + NestedFieldMask* nested = out.add_nested_mask(); + nested->set_data("data"); + nested->mutable_single_mask()->add_paths("nested.path1"); + nested->mutable_single_mask()->add_paths("nested_field.snake_case_path2"); + mask = nested->add_repeated_mask(); + mask->add_paths("nested_field.path3"); + mask->add_paths("nested.snake_case_path4"); + mask = nested->add_repeated_mask(); + mask->add_paths("nested.path5"); + mask = nested->add_repeated_mask(); + mask->add_paths( + "snake_case.map_field[\"map_key_should_be_ignored\"].nested_snake_case." + "map_field[\"map_key_sho\\\"uld_be_ignored\"]"); + + ow_.StartObject("") + ->RenderString("id", "1") + ->RenderString("singleMask", "path1,snakeCasePath2") + ->StartList("repeatedMask") + ->RenderString("", "path3") + ->RenderString("", "snakeCasePath4,path5") + ->EndList() + ->StartList("nestedMask") + ->StartObject("") + ->RenderString("data", "data") + ->RenderString("singleMask", "nested.path1,nestedField.snakeCasePath2") + ->StartList("repeatedMask") + ->RenderString("", "nestedField.path3,nested.snakeCasePath4") + ->RenderString("", "nested.path5") + ->RenderString("", + "snakeCase.mapField[\"map_key_should_be_ignored\"]." + "nestedSnakeCase.mapField[\"map_key_sho\\\"uld_be_" + "ignored\"]") + ->EndList() + ->EndObject() + ->EndList() + ->EndObject(); + + DoTest(out, FieldMaskTest::descriptor()); +} + +class ProtostreamObjectSourceTimestampTest + : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceTimestampTest() { + helper_.ResetTypeInfo(TimestampDuration::descriptor()); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceTimestampTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtostreamObjectSourceTimestampTest, InvalidTimestampBelowMinTest) { + TimestampDuration out; + google::protobuf::Timestamp* ts = out.mutable_ts(); + // Min allowed seconds - 1 + ts->set_seconds(kTimestampMinSeconds - 1); + ow_.StartObject(""); + + util::Status status = ExecuteTest(out, TimestampDuration::descriptor()); + EXPECT_TRUE(util::IsInternal(status)); +} + +TEST_P(ProtostreamObjectSourceTimestampTest, InvalidTimestampAboveMaxTest) { + TimestampDuration out; + google::protobuf::Timestamp* ts = out.mutable_ts(); + // Max allowed seconds + 1 + ts->set_seconds(kTimestampMaxSeconds + 1); + ow_.StartObject(""); + + util::Status status = ExecuteTest(out, TimestampDuration::descriptor()); + EXPECT_TRUE(util::IsInternal(status)); +} + +TEST_P(ProtostreamObjectSourceTimestampTest, InvalidDurationBelowMinTest) { + TimestampDuration out; + google::protobuf::Duration* dur = out.mutable_dur(); + // Min allowed seconds - 1 + dur->set_seconds(kDurationMinSeconds - 1); + ow_.StartObject(""); + + util::Status status = ExecuteTest(out, TimestampDuration::descriptor()); + EXPECT_TRUE(util::IsInternal(status)); +} + +TEST_P(ProtostreamObjectSourceTimestampTest, InvalidDurationAboveMaxTest) { + TimestampDuration out; + google::protobuf::Duration* dur = out.mutable_dur(); + // Min allowed seconds + 1 + dur->set_seconds(kDurationMaxSeconds + 1); + ow_.StartObject(""); + + util::Status status = ExecuteTest(out, TimestampDuration::descriptor()); + EXPECT_TRUE(util::IsInternal(status)); +} + +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 +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter.cc new file mode 100644 index 00000000..5f3a1a68 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter.cc @@ -0,0 +1,1400 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/protostream_objectwriter.h> + +#include <cstdint> +#include <functional> +#include <stack> +#include <unordered_map> +#include <unordered_set> + +#include <stubs/once.h> +#include <wire_format_lite.h> +#include <util/internal/field_mask_utility.h> +#include <util/internal/object_location_tracker.h> +#include <util/internal/constants.h> +#include <util/internal/utility.h> +#include <stubs/strutil.h> +#include <stubs/status.h> +#include <stubs/statusor.h> +#include <stubs/time.h> +#include <stubs/map_util.h> + + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite; +using std::placeholders::_1; +using util::Status; + + +ProtoStreamObjectWriter::ProtoStreamObjectWriter( + TypeResolver* type_resolver, const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener, + const ProtoStreamObjectWriter::Options& options) + : ProtoWriter(type_resolver, type, output, listener), + master_type_(type), + current_(nullptr), + options_(options) { + set_ignore_unknown_fields(options_.ignore_unknown_fields); + set_ignore_unknown_enum_values(options_.ignore_unknown_enum_values); + set_use_lower_camel_for_enums(options_.use_lower_camel_for_enums); + set_case_insensitive_enum_parsing(options_.case_insensitive_enum_parsing); + set_use_json_name_in_missing_fields(options.use_json_name_in_missing_fields); +} + +ProtoStreamObjectWriter::ProtoStreamObjectWriter( + const TypeInfo* typeinfo, const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener, + const ProtoStreamObjectWriter::Options& options) + : ProtoWriter(typeinfo, type, output, listener), + master_type_(type), + current_(nullptr), + options_(options) { + set_ignore_unknown_fields(options_.ignore_unknown_fields); + set_use_lower_camel_for_enums(options.use_lower_camel_for_enums); + set_case_insensitive_enum_parsing(options_.case_insensitive_enum_parsing); + set_use_json_name_in_missing_fields(options.use_json_name_in_missing_fields); +} + +ProtoStreamObjectWriter::ProtoStreamObjectWriter( + const TypeInfo* typeinfo, const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener) + : ProtoWriter(typeinfo, type, output, listener), + master_type_(type), + current_(nullptr), + options_(ProtoStreamObjectWriter::Options::Defaults()) {} + +ProtoStreamObjectWriter::~ProtoStreamObjectWriter() { + if (current_ == nullptr) return; + // Cleanup explicitly in order to avoid destructor stack overflow when input + // is deeply nested. + // Cast to BaseElement to avoid doing additional checks (like missing fields) + // during pop(). + std::unique_ptr<BaseElement> element( + static_cast<BaseElement*>(current_.get())->pop<BaseElement>()); + while (element != nullptr) { + element.reset(element->pop<BaseElement>()); + } +} + +namespace { +// Utility method to split a string representation of Timestamp or Duration and +// return the parts. +void SplitSecondsAndNanos(StringPiece input, StringPiece* seconds, + StringPiece* nanos) { + size_t idx = input.rfind('.'); + if (idx != std::string::npos) { + *seconds = input.substr(0, idx); + *nanos = input.substr(idx + 1); + } else { + *seconds = input; + *nanos = StringPiece(); + } +} + +Status GetNanosFromStringPiece(StringPiece s_nanos, + const char* parse_failure_message, + const char* exceeded_limit_message, + int32_t* nanos) { + *nanos = 0; + + // Count the number of leading 0s and consume them. + int num_leading_zeros = 0; + while (s_nanos.Consume("0")) { + num_leading_zeros++; + } + int32_t i_nanos = 0; + // 's_nanos' contains fractional seconds -- i.e. 'nanos' is equal to + // "0." + s_nanos.ToString() seconds. An int32 is used for the + // conversion to 'nanos', rather than a double, so that there is no + // loss of precision. + if (!s_nanos.empty() && !safe_strto32(s_nanos, &i_nanos)) { + return util::InvalidArgumentError(parse_failure_message); + } + if (i_nanos > kNanosPerSecond || i_nanos < 0) { + return util::InvalidArgumentError(exceeded_limit_message); + } + // s_nanos should only have digits. No whitespace. + if (s_nanos.find_first_not_of("0123456789") != StringPiece::npos) { + return util::InvalidArgumentError(parse_failure_message); + } + + if (i_nanos > 0) { + // 'scale' is the number of digits to the right of the decimal + // point in "0." + s_nanos.ToString() + int32_t scale = num_leading_zeros + s_nanos.size(); + // 'conversion' converts i_nanos into nanoseconds. + // conversion = kNanosPerSecond / static_cast<int32>(std::pow(10, scale)) + // For efficiency, we precompute the conversion factor. + int32_t conversion = 0; + switch (scale) { + case 1: + conversion = 100000000; + break; + case 2: + conversion = 10000000; + break; + case 3: + conversion = 1000000; + break; + case 4: + conversion = 100000; + break; + case 5: + conversion = 10000; + break; + case 6: + conversion = 1000; + break; + case 7: + conversion = 100; + break; + case 8: + conversion = 10; + break; + case 9: + conversion = 1; + break; + default: + return util::InvalidArgumentError(exceeded_limit_message); + } + *nanos = i_nanos * conversion; + } + + return Status(); +} + +} // namespace + +ProtoStreamObjectWriter::AnyWriter::AnyWriter(ProtoStreamObjectWriter* parent) + : parent_(parent), + ow_(), + invalid_(false), + data_(), + output_(&data_), + depth_(0), + is_well_known_type_(false), + well_known_type_render_(nullptr) {} + +ProtoStreamObjectWriter::AnyWriter::~AnyWriter() {} + +void ProtoStreamObjectWriter::AnyWriter::StartObject(StringPiece name) { + ++depth_; + // If an object writer is absent, that means we have not called StartAny() + // before reaching here, which happens when we have data before the "@type" + // field. + if (ow_ == nullptr) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::START_OBJECT, name)); + } else if (is_well_known_type_ && depth_ == 1) { + // For well-known types, the only other field besides "@type" should be a + // "value" field. + if (name != "value" && !invalid_) { + parent_->InvalidValue("Any", + "Expect a \"value\" field for well-known types."); + invalid_ = true; + } + ow_->StartObject(""); + } else { + // Forward the call to the child writer if: + // 1. the type is not a well-known type. + // 2. or, we are in a nested Any, Struct, or Value object. + ow_->StartObject(name); + } +} + +bool ProtoStreamObjectWriter::AnyWriter::EndObject() { + --depth_; + if (ow_ == nullptr) { + if (depth_ >= 0) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::END_OBJECT)); + } + } else if (depth_ >= 0 || !is_well_known_type_) { + // As long as depth_ >= 0, we know we haven't reached the end of Any. + // Propagate these EndObject() calls to the contained ow_. For regular + // message types, we propagate the end of Any as well. + ow_->EndObject(); + } + // A negative depth_ implies that we have reached the end of Any + // object. Now we write out its contents. + if (depth_ < 0) { + WriteAny(); + return false; + } + return true; +} + +void ProtoStreamObjectWriter::AnyWriter::StartList(StringPiece name) { + ++depth_; + if (ow_ == nullptr) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::START_LIST, name)); + } else if (is_well_known_type_ && depth_ == 1) { + if (name != "value" && !invalid_) { + parent_->InvalidValue("Any", + "Expect a \"value\" field for well-known types."); + invalid_ = true; + } + ow_->StartList(""); + } else { + ow_->StartList(name); + } +} + +void ProtoStreamObjectWriter::AnyWriter::EndList() { + --depth_; + if (depth_ < 0) { + GOOGLE_LOG(DFATAL) << "Mismatched EndList found, should not be possible"; + depth_ = 0; + } + if (ow_ == nullptr) { + // Save data before the "@type" field for later replay. + uninterpreted_events_.push_back(Event(Event::END_LIST)); + } else { + ow_->EndList(); + } +} + +void ProtoStreamObjectWriter::AnyWriter::RenderDataPiece( + StringPiece name, const DataPiece& value) { + // Start an Any only at depth_ 0. Other RenderDataPiece calls with "@type" + // should go to the contained ow_ as they indicate nested Anys. + if (depth_ == 0 && ow_ == nullptr && name == "@type") { + StartAny(value); + } else if (ow_ == nullptr) { + // Save data before the "@type" field. + uninterpreted_events_.push_back(Event(name, value)); + } else if (depth_ == 0 && is_well_known_type_) { + if (name != "value" && !invalid_) { + parent_->InvalidValue("Any", + "Expect a \"value\" field for well-known types."); + invalid_ = true; + } + if (well_known_type_render_ == nullptr) { + // Only Any and Struct don't have a special type render but both of + // them expect a JSON object (i.e., a StartObject() call). + if (value.type() != DataPiece::TYPE_NULL && !invalid_) { + parent_->InvalidValue("Any", "Expect a JSON object."); + invalid_ = true; + } + } else { + ow_->ProtoWriter::StartObject(""); + Status status = (*well_known_type_render_)(ow_.get(), value); + if (!status.ok()) ow_->InvalidValue("Any", status.message()); + ow_->ProtoWriter::EndObject(); + } + } else { + ow_->RenderDataPiece(name, value); + } +} + +void ProtoStreamObjectWriter::AnyWriter::StartAny(const DataPiece& value) { + // Figure out the type url. This is a copy-paste from WriteString but we also + // need the value, so we can't just call through to that. + if (value.type() == DataPiece::TYPE_STRING) { + type_url_ = std::string(value.str()); + } else { + util::StatusOr<std::string> s = value.ToString(); + if (!s.ok()) { + parent_->InvalidValue("String", s.status().message()); + invalid_ = true; + return; + } + type_url_ = s.value(); + } + // Resolve the type url, and report an error if we failed to resolve it. + util::StatusOr<const google::protobuf::Type*> resolved_type = + parent_->typeinfo()->ResolveTypeUrl(type_url_); + if (!resolved_type.ok()) { + parent_->InvalidValue("Any", resolved_type.status().message()); + invalid_ = true; + return; + } + // At this point, type is never null. + const google::protobuf::Type* type = resolved_type.value(); + + well_known_type_render_ = FindTypeRenderer(type_url_); + if (well_known_type_render_ != nullptr || + // Explicitly list Any and Struct here because they don't have a + // custom renderer. + type->name() == kAnyType || type->name() == kStructType) { + is_well_known_type_ = true; + } + + // Create our object writer and initialize it with the first StartObject + // call. + ow_.reset(new ProtoStreamObjectWriter(parent_->typeinfo(), *type, &output_, + parent_->listener(), + parent_->options_)); + + // Don't call StartObject() for well-known types yet. Depending on the + // type of actual data, we may not need to call StartObject(). For + // example: + // { + // "@type": "type.googleapis.com/google.protobuf.Value", + // "value": [1, 2, 3], + // } + // With the above JSON representation, we will only call StartList() on the + // contained ow_. + 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_ == nullptr) { + if (uninterpreted_events_.empty()) { + // We never got any content, so just return immediately, which is + // equivalent to writing an empty Any. + return; + } else { + // There are uninterpreted data, but we never got a "@type" field. + if (!invalid_) { + parent_->InvalidValue("Any", + StrCat("Missing @type for any field in ", + parent_->master_type_.name())); + invalid_ = true; + } + return; + } + } + // Render the type_url and value fields directly to the stream. + // type_url has tag 1 and value has tag 2. + WireFormatLite::WriteString(1, type_url_, parent_->stream()); + if (!data_.empty()) { + WireFormatLite::WriteBytes(2, data_, parent_->stream()); + } +} + +void ProtoStreamObjectWriter::AnyWriter::Event::Replay( + AnyWriter* writer) const { + switch (type_) { + case START_OBJECT: + writer->StartObject(name_); + break; + case END_OBJECT: + writer->EndObject(); + break; + case START_LIST: + writer->StartList(name_); + break; + case END_LIST: + writer->EndList(); + break; + case RENDER_DATA_PIECE: + writer->RenderDataPiece(name_, value_); + break; + } +} + +void ProtoStreamObjectWriter::AnyWriter::Event::DeepCopy() { + // DataPiece only contains a string reference. To make sure the referenced + // string value stays valid, we make a copy of the string value and update + // DataPiece to reference our own copy. + if (value_.type() == DataPiece::TYPE_STRING) { + StrAppend(&value_storage_, value_.str()); + value_ = DataPiece(value_storage_, value_.use_strict_base64_decoding()); + } else if (value_.type() == DataPiece::TYPE_BYTES) { + value_storage_ = value_.ToBytes().value(); + value_ = + DataPiece(value_storage_, true, value_.use_strict_base64_decoding()); + } +} + +ProtoStreamObjectWriter::Item::Item(ProtoStreamObjectWriter* enclosing, + ItemType item_type, bool is_placeholder, + bool is_list) + : BaseElement(nullptr), + ow_(enclosing), + any_(), + item_type_(item_type), + is_placeholder_(is_placeholder), + is_list_(is_list) { + if (item_type_ == ANY) { + any_.reset(new AnyWriter(ow_)); + } + if (item_type == MAP) { + map_keys_.reset(new std::unordered_set<std::string>); + } +} + +ProtoStreamObjectWriter::Item::Item(ProtoStreamObjectWriter::Item* parent, + ItemType item_type, bool is_placeholder, + bool is_list) + : BaseElement(parent), + ow_(this->parent()->ow_), + any_(), + item_type_(item_type), + is_placeholder_(is_placeholder), + is_list_(is_list) { + if (item_type == ANY) { + any_.reset(new AnyWriter(ow_)); + } + if (item_type == MAP) { + map_keys_.reset(new std::unordered_set<std::string>); + } +} + +bool ProtoStreamObjectWriter::Item::InsertMapKeyIfNotPresent( + StringPiece map_key) { + return InsertIfNotPresent(map_keys_.get(), std::string(map_key)); +} + + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartObject( + StringPiece name) { + if (invalid_depth() > 0) { + IncrementInvalidDepth(); + return this; + } + + // Starting the root message. Create the root Item and return. + // ANY message type does not need special handling, just set the ItemType + // to ANY. + if (current_ == nullptr) { + ProtoWriter::StartObject(name); + current_.reset(new Item( + this, master_type_.name() == kAnyType ? Item::ANY : Item::MESSAGE, + false, false)); + + // If master type is a special type that needs extra values to be written to + // stream, we write those values. + if (master_type_.name() == kStructType) { + // Struct has a map<string, Value> field called "fields". + // https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/struct.proto + // "fields": [ + Push("fields", Item::MAP, true, true); + return this; + } + + if (master_type_.name() == kStructValueType) { + // We got a StartObject call with google.protobuf.Value field. The only + // object within that type is a struct type. So start a struct. + // + // The struct field in Value type is named "struct_value" + // https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/struct.proto + // Also start the map field "fields" within the struct. + // "struct_value": { + // "fields": [ + Push("struct_value", Item::MESSAGE, true, false); + Push("fields", Item::MAP, true, true); + return this; + } + + if (master_type_.name() == kStructListValueType) { + InvalidValue(kStructListValueType, + "Cannot start root message with ListValue."); + } + + return this; + } + + // Send all ANY events to AnyWriter. + if (current_->IsAny()) { + current_->any()->StartObject(name); + return this; + } + + // If we are within a map, we render name as keys and send StartObject to the + // value field. + if (current_->IsMap()) { + if (!ValidMapKey(name)) { + IncrementInvalidDepth(); + return this; + } + + // Map is a repeated field of message type with a "key" and a "value" field. + // https://developers.google.com/protocol-buffers/docs/proto3?hl=en#maps + // message MapFieldEntry { + // key_type key = 1; + // value_type value = 2; + // } + // + // repeated MapFieldEntry map_field = N; + // + // That means, we render the following element within a list (hence no + // name): + // { "key": "<name>", "value": { + Push("", Item::MESSAGE, false, false); + ProtoWriter::RenderDataPiece("key", + DataPiece(name, use_strict_base64_decoding())); + Push("value", IsAny(*Lookup("value")) ? Item::ANY : Item::MESSAGE, true, + false); + + // Make sure we are valid so far after starting map fields. + if (invalid_depth() > 0) return this; + + // If top of stack is g.p.Struct type, start the struct the map field within + // it. + if (element() != nullptr && IsStruct(*element()->parent_field())) { + // Render "fields": [ + Push("fields", Item::MAP, true, true); + return this; + } + + // If top of stack is g.p.Value type, start the Struct within it. + if (element() != nullptr && IsStructValue(*element()->parent_field())) { + // Render + // "struct_value": { + // "fields": [ + Push("struct_value", Item::MESSAGE, true, false); + Push("fields", Item::MAP, true, true); + } + return this; + } + + const google::protobuf::Field* field = BeginNamed(name, false); + + if (field == nullptr) return this; + + // Legacy JSON map is a list of key value pairs. Starts a map entry object. + if (options_.use_legacy_json_map_format && name.empty()) { + Push(name, IsAny(*field) ? Item::ANY : Item::MESSAGE, false, false); + return this; + } + + if (IsMap(*field)) { + // Begin a map. A map is triggered by a StartObject() call if the current + // field has a map type. + // A map type is always repeated, hence set is_list to true. + // Render + // "<name>": [ + Push(name, Item::MAP, false, true); + return this; + } + + if (options_.disable_implicit_message_list) { + // If the incoming object is repeated, the top-level object on stack should + // be list. Report an error otherwise. + if (IsRepeated(*field) && !current_->is_list()) { + IncrementInvalidDepth(); + + if (!options_.suppress_implicit_message_list_error) { + InvalidValue( + field->name(), + "Starting an object in a repeated field but the parent object " + "is not a list"); + } + return this; + } + } + + if (IsStruct(*field)) { + // Start a struct object. + // Render + // "<name>": { + // "fields": { + Push(name, Item::MESSAGE, false, false); + Push("fields", Item::MAP, true, true); + return this; + } + + if (IsStructValue(*field)) { + // We got a StartObject call with google.protobuf.Value field. The only + // object within that type is a struct type. So start a struct. + // Render + // "<name>": { + // "struct_value": { + // "fields": { + Push(name, Item::MESSAGE, false, false); + Push("struct_value", Item::MESSAGE, true, false); + Push("fields", Item::MAP, true, true); + return this; + } + + if (field->kind() != google::protobuf::Field::TYPE_GROUP && + field->kind() != google::protobuf::Field::TYPE_MESSAGE) { + IncrementInvalidDepth(); + if (!options_.suppress_object_to_scalar_error) { + InvalidValue(field->name(), "Starting an object on a scalar field"); + } + + return this; + } + + // A regular message type. Pass it directly to ProtoWriter. + // Render + // "<name>": { + Push(name, IsAny(*field) ? Item::ANY : Item::MESSAGE, false, false); + return this; +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndObject() { + if (invalid_depth() > 0) { + DecrementInvalidDepth(); + return this; + } + + if (current_ == nullptr) return this; + + if (current_->IsAny()) { + if (current_->any()->EndObject()) return this; + } + + Pop(); + + return this; +} + + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList( + StringPiece name) { + if (invalid_depth() > 0) { + IncrementInvalidDepth(); + return this; + } + + // Since we cannot have a top-level repeated item in protobuf, the only way + // this is valid is if we start a special type google.protobuf.ListValue or + // google.protobuf.Value. + if (current_ == nullptr) { + if (!name.empty()) { + InvalidName(name, "Root element should not be named."); + IncrementInvalidDepth(); + return this; + } + + // If master type is a special type that needs extra values to be written to + // stream, we write those values. + if (master_type_.name() == kStructValueType) { + // We got a StartList with google.protobuf.Value master type. This means + // we have to start the "list_value" within google.protobuf.Value. + // + // See + // https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/struct.proto + // + // Render + // "<name>": { + // "list_value": { + // "values": [ // Start this list. + ProtoWriter::StartObject(name); + current_.reset(new Item(this, Item::MESSAGE, false, false)); + Push("list_value", Item::MESSAGE, true, false); + Push("values", Item::MESSAGE, true, true); + return this; + } + + if (master_type_.name() == kStructListValueType) { + // We got a StartList with google.protobuf.ListValue master type. This + // means we have to start the "values" within google.protobuf.ListValue. + // + // Render + // "<name>": { + // "values": [ // Start this list. + ProtoWriter::StartObject(name); + current_.reset(new Item(this, Item::MESSAGE, false, false)); + Push("values", Item::MESSAGE, true, true); + return this; + } + + // Send the event to ProtoWriter so proper errors can be reported. + // + // Render a regular list: + // "<name>": [ + ProtoWriter::StartList(name); + current_.reset(new Item(this, Item::MESSAGE, false, true)); + return this; + } + + if (current_->IsAny()) { + current_->any()->StartList(name); + return this; + } + + // If the top of stack is a map, we are starting a list value within a map. + // Since map does not allow repeated values, this can only happen when the map + // value is of a special type that renders a list in JSON. These can be one + // of 3 cases: + // i. We are rendering a list value within google.protobuf.Struct + // ii. We are rendering a list value within google.protobuf.Value + // iii. We are rendering a list value with type google.protobuf.ListValue. + if (current_->IsMap()) { + if (!ValidMapKey(name)) { + IncrementInvalidDepth(); + return this; + } + + // Start the repeated map entry object. + // Render + // { "key": "<name>", "value": { + Push("", Item::MESSAGE, false, false); + ProtoWriter::RenderDataPiece("key", + DataPiece(name, use_strict_base64_decoding())); + Push("value", Item::MESSAGE, true, false); + + // Make sure we are valid after pushing all above items. + if (invalid_depth() > 0) return this; + + // case i and ii above. Start "list_value" field within g.p.Value + if (element() != nullptr && element()->parent_field() != nullptr) { + // Render + // "list_value": { + // "values": [ // Start this list + if (IsStructValue(*element()->parent_field())) { + Push("list_value", Item::MESSAGE, true, false); + Push("values", Item::MESSAGE, true, true); + return this; + } + + // Render + // "values": [ + if (IsStructListValue(*element()->parent_field())) { + // case iii above. Bind directly to g.p.ListValue + Push("values", Item::MESSAGE, true, true); + return this; + } + } + + // Report an error. + InvalidValue("Map", StrCat("Cannot have repeated items ('", name, + "') within a map.")); + return this; + } + + // When name is empty and stack is not empty, we are rendering an item within + // a list. + if (name.empty()) { + if (element() != nullptr && element()->parent_field() != nullptr) { + if (IsStructValue(*element()->parent_field())) { + // Since it is g.p.Value, we bind directly to the list_value. + // Render + // { // g.p.Value item within the list + // "list_value": { + // "values": [ + Push("", Item::MESSAGE, false, false); + Push("list_value", Item::MESSAGE, true, false); + Push("values", Item::MESSAGE, true, true); + return this; + } + + if (IsStructListValue(*element()->parent_field())) { + // Since it is g.p.ListValue, we bind to it directly. + // Render + // { // g.p.ListValue item within the list + // "values": [ + Push("", Item::MESSAGE, false, false); + Push("values", Item::MESSAGE, true, true); + return this; + } + } + + // Pass the event to underlying ProtoWriter. + Push(name, Item::MESSAGE, false, true); + return this; + } + + // name is not empty + const google::protobuf::Field* field = Lookup(name); + + if (field == nullptr) { + IncrementInvalidDepth(); + return this; + } + + if (IsStructValue(*field)) { + // If g.p.Value is repeated, start that list. Otherwise, start the + // "list_value" within it. + if (IsRepeated(*field)) { + // Render it just like a regular repeated field. + // "<name>": [ + Push(name, Item::MESSAGE, false, true); + return this; + } + + // Start the "list_value" field. + // Render + // "<name>": { + // "list_value": { + // "values": [ + Push(name, Item::MESSAGE, false, false); + Push("list_value", Item::MESSAGE, true, false); + Push("values", Item::MESSAGE, true, true); + return this; + } + + if (IsStructListValue(*field)) { + // If g.p.ListValue is repeated, start that list. Otherwise, start the + // "values" within it. + if (IsRepeated(*field)) { + // Render it just like a regular repeated field. + // "<name>": [ + Push(name, Item::MESSAGE, false, true); + return this; + } + + // Start the "values" field within g.p.ListValue. + // Render + // "<name>": { + // "values": [ + Push(name, Item::MESSAGE, false, false); + Push("values", Item::MESSAGE, true, true); + return this; + } + + // If we are here, the field should be repeated. Report an error otherwise. + if (!IsRepeated(*field)) { + IncrementInvalidDepth(); + InvalidName(name, "Proto field is not repeating, cannot start list."); + return this; + } + + if (IsMap(*field)) { + if (options_.use_legacy_json_map_format) { + Push(name, Item::MESSAGE, false, true); + return this; + } + InvalidValue("Map", StrCat("Cannot bind a list to map for field '", + name, "'.")); + IncrementInvalidDepth(); + return this; + } + + // Pass the event to ProtoWriter. + // Render + // "<name>": [ + Push(name, Item::MESSAGE, false, true); + return this; +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndList() { + if (invalid_depth() > 0) { + DecrementInvalidDepth(); + return this; + } + + if (current_ == nullptr) return this; + + if (current_->IsAny()) { + current_->any()->EndList(); + return this; + } + + Pop(); + return this; +} + +Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + std::string struct_field_name; + switch (data.type()) { + case DataPiece::TYPE_INT32: { + if (ow->options_.struct_integers_as_strings) { + util::StatusOr<int32_t> int_value = data.ToInt32(); + if (int_value.ok()) { + ow->ProtoWriter::RenderDataPiece( + "string_value", + DataPiece(SimpleDtoa(int_value.value()), true)); + return Status(); + } + } + struct_field_name = "number_value"; + break; + } + case DataPiece::TYPE_UINT32: { + if (ow->options_.struct_integers_as_strings) { + util::StatusOr<uint32_t> int_value = data.ToUint32(); + if (int_value.ok()) { + ow->ProtoWriter::RenderDataPiece( + "string_value", + DataPiece(SimpleDtoa(int_value.value()), true)); + return Status(); + } + } + struct_field_name = "number_value"; + break; + } + case DataPiece::TYPE_INT64: { + // If the option to treat integers as strings is set, then render them as + // strings. Otherwise, fallback to rendering them as double. + if (ow->options_.struct_integers_as_strings) { + util::StatusOr<int64_t> int_value = data.ToInt64(); + if (int_value.ok()) { + ow->ProtoWriter::RenderDataPiece( + "string_value", DataPiece(StrCat(int_value.value()), true)); + return Status(); + } + } + struct_field_name = "number_value"; + break; + } + case DataPiece::TYPE_UINT64: { + // If the option to treat integers as strings is set, then render them as + // strings. Otherwise, fallback to rendering them as double. + if (ow->options_.struct_integers_as_strings) { + util::StatusOr<uint64_t> int_value = data.ToUint64(); + if (int_value.ok()) { + ow->ProtoWriter::RenderDataPiece( + "string_value", DataPiece(StrCat(int_value.value()), true)); + return Status(); + } + } + struct_field_name = "number_value"; + break; + } + case DataPiece::TYPE_FLOAT: { + if (ow->options_.struct_integers_as_strings) { + util::StatusOr<float> float_value = data.ToFloat(); + if (float_value.ok()) { + ow->ProtoWriter::RenderDataPiece( + "string_value", + DataPiece(SimpleDtoa(float_value.value()), true)); + return Status(); + } + } + struct_field_name = "number_value"; + break; + } + case DataPiece::TYPE_DOUBLE: { + if (ow->options_.struct_integers_as_strings) { + util::StatusOr<double> double_value = data.ToDouble(); + if (double_value.ok()) { + ow->ProtoWriter::RenderDataPiece( + "string_value", + DataPiece(SimpleDtoa(double_value.value()), true)); + return Status(); + } + } + struct_field_name = "number_value"; + break; + } + case DataPiece::TYPE_STRING: { + struct_field_name = "string_value"; + break; + } + case DataPiece::TYPE_BOOL: { + struct_field_name = "bool_value"; + break; + } + case DataPiece::TYPE_NULL: { + struct_field_name = "null_value"; + break; + } + default: { + return util::InvalidArgumentError( + "Invalid struct data type. Only number, string, boolean or null " + "values are supported."); + } + } + ow->ProtoWriter::RenderDataPiece(struct_field_name, data); + return Status(); +} + +Status ProtoStreamObjectWriter::RenderTimestamp(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); + if (data.type() != DataPiece::TYPE_STRING) { + return util::InvalidArgumentError( + StrCat("Invalid data type for timestamp, value is ", + data.ValueAsStringOrDefault(""))); + } + + StringPiece value(data.str()); + + int64 seconds; + int32 nanos; + if (!::google::protobuf::internal::ParseTime(value.ToString(), &seconds, + &nanos)) { + return util::InvalidArgumentError(StrCat("Invalid time format: ", value)); + } + + + ow->ProtoWriter::RenderDataPiece("seconds", DataPiece(seconds)); + ow->ProtoWriter::RenderDataPiece("nanos", DataPiece(nanos)); + return Status(); +} + +static inline util::Status RenderOneFieldPath(ProtoStreamObjectWriter* ow, + StringPiece path) { + ow->ProtoWriter::RenderDataPiece( + "paths", DataPiece(ConvertFieldMaskPath(path, &ToSnakeCase), true)); + return Status(); +} + +Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); + if (data.type() != DataPiece::TYPE_STRING) { + return util::InvalidArgumentError( + StrCat("Invalid data type for field mask, value is ", + data.ValueAsStringOrDefault(""))); + } + + // TODO(tsun): figure out how to do proto descriptor based snake case + // conversions as much as possible. Because ToSnakeCase sometimes returns the + // wrong value. + return DecodeCompactFieldMaskPaths(data.str(), + std::bind(&RenderOneFieldPath, ow, _1)); +} + +Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); + if (data.type() != DataPiece::TYPE_STRING) { + return util::InvalidArgumentError( + StrCat("Invalid data type for duration, value is ", + data.ValueAsStringOrDefault(""))); + } + + StringPiece value(data.str()); + + if (!HasSuffixString(value, "s")) { + return util::InvalidArgumentError( + "Illegal duration format; duration must end with 's'"); + } + value = value.substr(0, value.size() - 1); + int sign = 1; + if (HasPrefixString(value, "-")) { + sign = -1; + value = value.substr(1); + } + + StringPiece s_secs, s_nanos; + SplitSecondsAndNanos(value, &s_secs, &s_nanos); + uint64_t unsigned_seconds; + if (!safe_strtou64(s_secs, &unsigned_seconds)) { + return util::InvalidArgumentError( + "Invalid duration format, failed to parse seconds"); + } + + int32_t nanos = 0; + Status nanos_status = GetNanosFromStringPiece( + s_nanos, "Invalid duration format, failed to parse nano seconds", + "Duration value exceeds limits", &nanos); + if (!nanos_status.ok()) { + return nanos_status; + } + nanos = sign * nanos; + + int64_t seconds = sign * unsigned_seconds; + if (seconds > kDurationMaxSeconds || seconds < kDurationMinSeconds || + nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + return util::InvalidArgumentError("Duration value exceeds limits"); + } + + ow->ProtoWriter::RenderDataPiece("seconds", DataPiece(seconds)); + ow->ProtoWriter::RenderDataPiece("nanos", DataPiece(nanos)); + return Status(); +} + +Status ProtoStreamObjectWriter::RenderWrapperType(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + if (data.type() == DataPiece::TYPE_NULL) return Status(); + ow->ProtoWriter::RenderDataPiece("value", data); + return Status(); +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( + StringPiece name, const DataPiece& data) { + Status status; + if (invalid_depth() > 0) return this; + + if (current_ == nullptr) { + const TypeRenderer* type_renderer = + FindTypeRenderer(GetFullTypeWithUrl(master_type_.name())); + if (type_renderer == nullptr) { + InvalidName(name, "Root element must be a message."); + return this; + } + // Render the special type. + // "<name>": { + // ... Render special type ... + // } + ProtoWriter::StartObject(name); + status = (*type_renderer)(this, data); + if (!status.ok()) { + InvalidValue(master_type_.name(), + StrCat("Field '", name, "', ", status.message())); + } + ProtoWriter::EndObject(); + return this; + } + + if (current_->IsAny()) { + current_->any()->RenderDataPiece(name, data); + return this; + } + + const google::protobuf::Field* field = nullptr; + if (current_->IsMap()) { + if (!ValidMapKey(name)) return this; + + field = Lookup("value"); + if (field == nullptr) { + GOOGLE_LOG(DFATAL) << "Map does not have a value field."; + return this; + } + + if (options_.ignore_null_value_map_entry) { + // If we are rendering explicit null values and the backend proto field is + // not of the google.protobuf.NullType type, interpret null as absence. + if (data.type() == DataPiece::TYPE_NULL && + field->type_url() != kStructNullValueTypeUrl) { + return this; + } + } + + // Render an item in repeated map list. + // { "key": "<name>", "value": + Push("", Item::MESSAGE, false, false); + ProtoWriter::RenderDataPiece("key", + DataPiece(name, use_strict_base64_decoding())); + + const TypeRenderer* type_renderer = FindTypeRenderer(field->type_url()); + if (type_renderer != nullptr) { + // Map's value type is a special type. Render it like a message: + // "value": { + // ... Render special type ... + // } + Push("value", Item::MESSAGE, true, false); + status = (*type_renderer)(this, data); + if (!status.ok()) { + InvalidValue(field->type_url(), + StrCat("Field '", name, "', ", status.message())); + } + Pop(); + return this; + } + + // If we are rendering explicit null values and the backend proto field is + // not of the google.protobuf.NullType type, we do nothing. + if (data.type() == DataPiece::TYPE_NULL && + field->type_url() != kStructNullValueTypeUrl) { + Pop(); + return this; + } + + // Render the map value as a primitive type. + ProtoWriter::RenderDataPiece("value", data); + Pop(); + return this; + } + + field = Lookup(name); + if (field == nullptr) return this; + + // Check if the field is of special type. Render it accordingly if so. + const TypeRenderer* type_renderer = FindTypeRenderer(field->type_url()); + if (type_renderer != nullptr) { + // Pass through null value only for google.protobuf.Value. For other + // types we ignore null value just like for regular field types. + if (data.type() != DataPiece::TYPE_NULL || + field->type_url() == kStructValueTypeUrl) { + Push(name, Item::MESSAGE, false, false); + status = (*type_renderer)(this, data); + if (!status.ok()) { + InvalidValue(field->type_url(), + StrCat("Field '", name, "', ", status.message())); + } + Pop(); + } + return this; + } + + // If we are rendering explicit null values and the backend proto field is + // not of the google.protobuf.NullType type, we do nothing. + if (data.type() == DataPiece::TYPE_NULL && + field->type_url() != kStructNullValueTypeUrl) { + return this; + } + + if (IsRepeated(*field) && !current_->is_list()) { + if (options_.disable_implicit_scalar_list) { + if (!options_.suppress_implicit_scalar_list_error) { + InvalidValue( + field->name(), + "Starting an primitive in a repeated field but the parent field " + "is not a list"); + } + + return this; + } + } + + ProtoWriter::RenderDataPiece(name, data); + return this; +} + +// Map of functions that are responsible for rendering well known type +// represented by the key. +std::unordered_map<std::string, ProtoStreamObjectWriter::TypeRenderer>* + ProtoStreamObjectWriter::renderers_ = nullptr; +PROTOBUF_NAMESPACE_ID::internal::once_flag writer_renderers_init_; + +void ProtoStreamObjectWriter::InitRendererMap() { + renderers_ = new std::unordered_map<std::string, + ProtoStreamObjectWriter::TypeRenderer>(); + (*renderers_)["type.googleapis.com/google.protobuf.Timestamp"] = + &ProtoStreamObjectWriter::RenderTimestamp; + (*renderers_)["type.googleapis.com/google.protobuf.Duration"] = + &ProtoStreamObjectWriter::RenderDuration; + (*renderers_)["type.googleapis.com/google.protobuf.FieldMask"] = + &ProtoStreamObjectWriter::RenderFieldMask; + (*renderers_)["type.googleapis.com/google.protobuf.Double"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Float"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Int64"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.UInt64"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Int32"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.UInt32"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Bool"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.String"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Bytes"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.DoubleValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.FloatValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Int64Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.UInt64Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Int32Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.UInt32Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.BoolValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.StringValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.BytesValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*renderers_)["type.googleapis.com/google.protobuf.Value"] = + &ProtoStreamObjectWriter::RenderStructValue; + ::google::protobuf::internal::OnShutdown(&DeleteRendererMap); +} + +void ProtoStreamObjectWriter::DeleteRendererMap() { + delete ProtoStreamObjectWriter::renderers_; + renderers_ = nullptr; +} + +ProtoStreamObjectWriter::TypeRenderer* +ProtoStreamObjectWriter::FindTypeRenderer(const std::string& type_url) { + PROTOBUF_NAMESPACE_ID::internal::call_once(writer_renderers_init_, + InitRendererMap); + return FindOrNull(*renderers_, type_url); +} + +bool ProtoStreamObjectWriter::ValidMapKey(StringPiece unnormalized_name) { + if (current_ == nullptr) return true; + + if (!current_->InsertMapKeyIfNotPresent(unnormalized_name)) { + listener()->InvalidName( + location(), unnormalized_name, + StrCat("Repeated map key: '", unnormalized_name, + "' is already set.")); + return false; + } + + return true; +} + +void ProtoStreamObjectWriter::Push( + StringPiece name, Item::ItemType item_type, bool is_placeholder, + bool is_list) { + is_list ? ProtoWriter::StartList(name) : ProtoWriter::StartObject(name); + + // invalid_depth == 0 means it is a successful StartObject or StartList. + if (invalid_depth() == 0) + current_.reset( + new Item(current_.release(), item_type, is_placeholder, is_list)); +} + +void ProtoStreamObjectWriter::Pop() { + // Pop all placeholder items sending StartObject or StartList events to + // ProtoWriter according to is_list value. + while (current_ != nullptr && current_->is_placeholder()) { + PopOneElement(); + } + if (current_ != nullptr) { + PopOneElement(); + } +} + +void ProtoStreamObjectWriter::PopOneElement() { + current_->is_list() ? ProtoWriter::EndList() : ProtoWriter::EndObject(); + current_.reset(current_->pop<Item>()); +} + +bool ProtoStreamObjectWriter::IsMap(const google::protobuf::Field& field) { + if (field.type_url().empty() || + field.kind() != google::protobuf::Field::TYPE_MESSAGE || + field.cardinality() != google::protobuf::Field::CARDINALITY_REPEATED) { + return false; + } + const google::protobuf::Type* field_type = + typeinfo()->GetTypeByTypeUrl(field.type_url()); + + return converter::IsMap(field, *field_type); +} + +bool ProtoStreamObjectWriter::IsAny(const google::protobuf::Field& field) { + return GetTypeWithoutUrl(field.type_url()) == kAnyType; +} + +bool ProtoStreamObjectWriter::IsStruct(const google::protobuf::Field& field) { + return GetTypeWithoutUrl(field.type_url()) == kStructType; +} + +bool ProtoStreamObjectWriter::IsStructValue( + const google::protobuf::Field& field) { + return GetTypeWithoutUrl(field.type_url()) == kStructValueType; +} + +bool ProtoStreamObjectWriter::IsStructListValue( + const google::protobuf::Field& field) { + return GetTypeWithoutUrl(field.type_url()) == kStructListValueType; +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter.h b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter.h new file mode 100644 index 00000000..ac5f8a0c --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter.h @@ -0,0 +1,452 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTWRITER_H__ + +#include <deque> +#include <string> +#include <unordered_map> +#include <unordered_set> + +#include <stubs/common.h> +#include <type.pb.h> +#include <io/coded_stream.h> +#include <io/zero_copy_stream_impl.h> +#include <descriptor.h> +#include <util/internal/type_info.h> +#include <util/internal/datapiece.h> +#include <util/internal/error_listener.h> +#include <util/internal/proto_writer.h> +#include <util/internal/structured_objectwriter.h> +#include <util/type_resolver.h> +#include <stubs/bytestream.h> +#include <stubs/status.h> +#include <stubs/hash.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class ObjectLocationTracker; + +// An ObjectWriter that can write protobuf bytes directly from writer events. +// This class supports all special types like Struct and Map. It uses +// the ProtoWriter class to write raw proto bytes. +// +// It also supports streaming. +class PROTOBUF_EXPORT ProtoStreamObjectWriter : public ProtoWriter { + public: + // Options that control ProtoStreamObjectWriter class's behavior. + struct Options { + // Treats numeric inputs in google.protobuf.Struct as strings. Normally, + // numeric values are returned in double field "number_value" of + // google.protobuf.Struct. However, this can cause precision loss for + // int64/uint64/double inputs. This option is provided for cases that want + // to preserve number precision. + // + // TODO(skarvaje): Rename to struct_numbers_as_strings as it covers double + // as well. + bool struct_integers_as_strings; + + // Not treat unknown fields as an error. If there is an unknown fields, + // just ignore it and continue to process the rest. Note that this doesn't + // apply to unknown enum values. + bool ignore_unknown_fields; + + // Ignore unknown enum values. + bool ignore_unknown_enum_values; + + // If true, check if enum name in camel case or without underscore matches + // the field name. + bool use_lower_camel_for_enums; + + // If true, check if enum name in UPPER_CASE matches the field name. + bool case_insensitive_enum_parsing; + + // If true, skips rendering the map entry if map value is null unless the + // value type is google.protobuf.NullType. + bool ignore_null_value_map_entry; + + // If true, accepts repeated key/value pair for a map proto field. + bool use_legacy_json_map_format; + + // If true, disable implicitly creating message list. + bool disable_implicit_message_list; + + // If true, suppress the error of implicitly creating message list when it + // is disabled. + bool suppress_implicit_message_list_error; + + // If true, disable implicitly creating scalar list. + bool disable_implicit_scalar_list; + + // If true, suppress the error of implicitly creating scalar list when it + // is disabled. + bool suppress_implicit_scalar_list_error; + + // If true, suppress the error of rendering scalar field if the source is an + // object. + bool suppress_object_to_scalar_error; + + // If true, use the json name in missing fields errors. + bool use_json_name_in_missing_fields; + + Options() + : struct_integers_as_strings(false), + ignore_unknown_fields(false), + ignore_unknown_enum_values(false), + use_lower_camel_for_enums(false), + case_insensitive_enum_parsing(false), + ignore_null_value_map_entry(false), + use_legacy_json_map_format(false), + disable_implicit_message_list(false), + suppress_implicit_message_list_error(false), + disable_implicit_scalar_list(false), + suppress_implicit_scalar_list_error(false), + suppress_object_to_scalar_error(false), + use_json_name_in_missing_fields(false) {} + + // Default instance of Options with all options set to defaults. + static const Options& Defaults() { + static Options defaults; + return defaults; + } + }; + + // Constructor. Does not take ownership of any parameter passed in. + ProtoStreamObjectWriter(TypeResolver* type_resolver, + const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener, + const ProtoStreamObjectWriter::Options& options = + ProtoStreamObjectWriter::Options::Defaults()); + ~ProtoStreamObjectWriter() override; + + // ObjectWriter methods. + ProtoStreamObjectWriter* StartObject(StringPiece name) override; + ProtoStreamObjectWriter* EndObject() override; + ProtoStreamObjectWriter* StartList(StringPiece name) override; + ProtoStreamObjectWriter* EndList() override; + + // Renders a DataPiece 'value' into a field whose wire type is determined + // from the given field 'name'. + ProtoStreamObjectWriter* RenderDataPiece(StringPiece name, + const DataPiece& data) override; + + protected: + // Function that renders a well known type with modified behavior. + typedef util::Status (*TypeRenderer)(ProtoStreamObjectWriter*, + const DataPiece&); + + // Handles writing Anys out using nested object writers and the like. + class PROTOBUF_EXPORT AnyWriter { + public: + explicit AnyWriter(ProtoStreamObjectWriter* parent); + ~AnyWriter(); + + // Passes a StartObject call through to the Any writer. + void StartObject(StringPiece name); + + // Passes an EndObject call through to the Any. Returns true if the any + // handled the EndObject call, false if the Any is now all done and is no + // longer needed. + bool EndObject(); + + // Passes a StartList call through to the Any writer. + void StartList(StringPiece name); + + // Passes an EndList call through to the Any writer. + void EndList(); + + // Renders a data piece on the any. + 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 PROTOBUF_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), value_(DataPiece::NullData()) {} + + // Constructor for RENDER_DATA_PIECE events. + explicit Event(StringPiece name, const DataPiece& value) + : type_(RENDER_DATA_PIECE), name_(name), 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_; + std::string name_; + DataPiece value_; + std::string value_storage_; + }; + + // Handles starting up the any once we have a type. + void StartAny(const DataPiece& value); + + // Writes the Any out to the parent writer in its serialized form. + void WriteAny(); + + // The parent of this writer, needed for various bits such as type info and + // the listeners. + ProtoStreamObjectWriter* parent_; + + // The nested object writer, used to write events. + std::unique_ptr<ProtoStreamObjectWriter> ow_; + + // The type_url_ that this Any represents. + std::string type_url_; + + // Whether this any is invalid. This allows us to only report an invalid + // Any message a single time rather than every time we get a nested field. + bool invalid_; + + // The output data and wrapping ByteSink. + std::string data_; + strings::StringByteSink output_; + + // The depth within the Any, so we can track when we're done. + int depth_; + + // True if the type is a well-known type. Well-known types in Any + // has a special formatting: + // { + // "@type": "type.googleapis.com/google.protobuf.XXX", + // "value": <JSON representation of the type>, + // } + 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 + // ObjectWrier events. + class PROTOBUF_EXPORT Item : public BaseElement { + public: + // Indicates the type of item. + enum ItemType { + MESSAGE, // Simple message + MAP, // Proto3 map type + ANY, // Proto3 Any type + }; + + // Constructor for the root item. + Item(ProtoStreamObjectWriter* enclosing, ItemType item_type, + bool is_placeholder, bool is_list); + + // Constructor for a field of a message. + Item(Item* parent, ItemType item_type, bool is_placeholder, bool is_list); + + ~Item() override {} + + // These functions return true if the element type is corresponding to the + // type in function name. + bool IsMap() { return item_type_ == MAP; } + bool IsAny() { return item_type_ == ANY; } + + AnyWriter* any() const { return any_.get(); } + + Item* parent() const override { + return static_cast<Item*>(BaseElement::parent()); + } + + // Inserts map key into hash set if and only if the key did NOT already + // exist in hash set. + // The hash set (map_keys_) is ONLY used to keep track of map keys. + // Return true if insert successfully; returns false if the map key was + // already present. + bool InsertMapKeyIfNotPresent(StringPiece map_key); + + bool is_placeholder() const { return is_placeholder_; } + bool is_list() const { return is_list_; } + + private: + // Used for access to variables of the enclosing instance of + // ProtoStreamObjectWriter. + ProtoStreamObjectWriter* ow_; + + // A writer for Any objects, handles all Any-related nonsense. + std::unique_ptr<AnyWriter> any_; + + // The type of this element, see enum for permissible types. + ItemType item_type_; + + // Set of map keys already seen for the type_. Used to validate incoming + // messages so no map key appears more than once. + std::unique_ptr<std::unordered_set<std::string> > map_keys_; + + // Conveys whether this Item is a placeholder or not. Placeholder items are + // pushed to stack to account for special types. + bool is_placeholder_; + + // Conveys whether this Item is a list or not. This is used to send + // StartList or EndList calls to underlying ObjectWriter. + bool is_list_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(Item); + }; + + ProtoStreamObjectWriter(const TypeInfo* typeinfo, + const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener); + + ProtoStreamObjectWriter(const TypeInfo* typeinfo, + const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener, + const ProtoStreamObjectWriter::Options& options); + + // Returns true if the field is a map. + inline bool IsMap(const google::protobuf::Field& field); + + // Returns true if the field is an any. + inline bool IsAny(const google::protobuf::Field& field); + + // Returns true if the field is google.protobuf.Struct. + inline bool IsStruct(const google::protobuf::Field& field); + + // Returns true if the field is google.protobuf.Value. + inline bool IsStructValue(const google::protobuf::Field& field); + + // Returns true if the field is google.protobuf.ListValue. + inline bool IsStructListValue(const google::protobuf::Field& field); + + // Renders google.protobuf.Value in struct.proto. It picks the right oneof + // type based on value's type. + static util::Status RenderStructValue(ProtoStreamObjectWriter* ow, + const DataPiece& data); + + // Renders google.protobuf.Timestamp value. + static util::Status RenderTimestamp(ProtoStreamObjectWriter* ow, + const DataPiece& data); + + // Renders google.protobuf.FieldMask value. + static util::Status RenderFieldMask(ProtoStreamObjectWriter* ow, + const DataPiece& data); + + // Renders google.protobuf.Duration value. + static util::Status RenderDuration(ProtoStreamObjectWriter* ow, + const DataPiece& data); + + // Renders wrapper message types for primitive types in + // google/protobuf/wrappers.proto. + static util::Status RenderWrapperType(ProtoStreamObjectWriter* ow, + const DataPiece& data); + + static void InitRendererMap(); + static void DeleteRendererMap(); + static TypeRenderer* FindTypeRenderer(const std::string& type_url); + + // Returns true if the map key for type_ is not duplicated key. + // If map key is duplicated key, this function returns false. + // Note that caller should make sure that the current proto element (current_) + // is of element type MAP or STRUCT_MAP. + // It also calls the appropriate error callback and unnormalzied_name is used + // for error string. + bool ValidMapKey(StringPiece unnormalized_name); + + // Pushes an item on to the stack. Also calls either StartObject or StartList + // on the underlying ObjectWriter depending on whether is_list is false or + // not. + // is_placeholder conveys whether the item is a placeholder item or not. + // Placeholder items are pushed when adding auxiliary types' StartObject or + // StartList calls. + void Push(StringPiece name, Item::ItemType item_type, + bool is_placeholder, bool is_list); + + + // Pops items from the stack. All placeholder items are popped until a + // non-placeholder item is found. + void Pop(); + + // Pops one element from the stack. Calls EndObject() or EndList() on the + // underlying ObjectWriter depending on the value of is_list_. + void PopOneElement(); + + private: + // Helper functions to create the map and find functions responsible for + // rendering well known types, keyed by type URL. + static std::unordered_map<std::string, TypeRenderer>* renderers_; + + // Variables for describing the structure of the input tree: + // master_type_: descriptor for the whole protobuf message. + const google::protobuf::Type& master_type_; + + // The current element, variable for internal state processing. + std::unique_ptr<Item> current_; + + // Reference to the options that control this class's behavior. + const ProtoStreamObjectWriter::Options options_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoStreamObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTWRITER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter_test.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter_test.cc new file mode 100644 index 00000000..60501752 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/protostream_objectwriter_test.cc @@ -0,0 +1,3032 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/protostream_objectwriter.h> + +#include <stddef.h> // For size_t + +#include <field_mask.pb.h> +#include <timestamp.pb.h> +#include <type.pb.h> +#include <wrappers.pb.h> +#include <io/zero_copy_stream_impl_lite.h> +#include <descriptor.pb.h> +#include <descriptor.h> +#include <dynamic_message.h> +#include <message.h> +#include <util/internal/mock_error_listener.h> +#include <util/internal/testdata/anys.pb.h> +#include <util/internal/testdata/books.pb.h> +#include <util/internal/testdata/field_mask.pb.h> +#include <util/internal/testdata/maps.pb.h> +#include <util/internal/testdata/oneofs.pb.h> +#include <util/internal/testdata/proto3.pb.h> +#include <util/internal/testdata/struct.pb.h> +#include <util/internal/testdata/timestamp_duration.pb.h> +#include <util/internal/testdata/wrappers.pb.h> +#include <util/internal/type_info_test_helper.h> +#include <util/internal/constants.h> +#include <util/message_differencer.h> +#include <util/type_resolver_util.h> +#include <stubs/bytestream.h> +#include <stubs/strutil.h> +#include <gtest/gtest.h> + + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +using proto_util_converter::testing::AnyM; +using proto_util_converter::testing::AnyOut; +using proto_util_converter::testing::Author; +using proto_util_converter::testing::Book; +using proto_util_converter::testing::FieldMaskTest; +using proto_util_converter::testing::Int32Wrapper; +using proto_util_converter::testing::MapIn; +using proto_util_converter::testing::Primitive; +using proto_util_converter::testing::Proto3Message; +using proto_util_converter::testing::Publisher; +using proto_util_converter::testing::StructType; +using proto_util_converter::testing::TestJsonName1; +using proto_util_converter::testing::TestJsonName2; +using proto_util_converter::testing::TimestampDuration; +using proto_util_converter::testing::ValueWrapper; +using proto_util_converter::testing::oneofs::OneOfsRequest; +using strings::GrowingArrayByteSink; +using ::testing::_; +using ::testing::Args; + + +namespace { +std::string GetTypeUrl(const Descriptor* descriptor) { + return std::string(kTypeServiceBaseUrl) + "/" + descriptor->full_name(); +} +} // namespace + +class BaseProtoStreamObjectWriterTest + : public ::testing::TestWithParam<testing::TypeInfoSource> { + protected: + BaseProtoStreamObjectWriterTest() + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() {} + + explicit BaseProtoStreamObjectWriterTest(const Descriptor* descriptor) + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(descriptor); + ResetTypeInfo(descriptors); + } + + explicit BaseProtoStreamObjectWriterTest( + std::vector<const Descriptor*> descriptors) + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() { + ResetTypeInfo(descriptors); + } + + void ResetTypeInfo(std::vector<const Descriptor*> descriptors) { + GOOGLE_CHECK(!descriptors.empty()) << "Must have at least one descriptor!"; + helper_.ResetTypeInfo(descriptors); + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(descriptors[0]), output_.get(), + &listener_, options_)); + } + + void ResetTypeInfo(const Descriptor* descriptor) { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(descriptor); + ResetTypeInfo(descriptors); + } + + virtual ~BaseProtoStreamObjectWriterTest() {} + + void CheckOutput(const Message& expected, int expected_length) { + size_t nbytes; + std::unique_ptr<char[]> buffer(output_->GetBuffer(&nbytes)); + if (expected_length >= 0) { + EXPECT_EQ(expected_length, nbytes); + } + std::string str(buffer.get(), nbytes); + + std::stringbuf str_buf(str, std::ios_base::in); + std::istream istream(&str_buf); + std::unique_ptr<Message> message(expected.New()); + message->ParsePartialFromIstream(&istream); + + if (!MessageDifferencer::Equivalent(expected, *message)) { + EXPECT_EQ(expected.DebugString(), message->DebugString()); + } + } + + void CheckOutput(const Message& expected) { CheckOutput(expected, -1); } + + testing::TypeInfoTestHelper helper_; + MockErrorListener listener_; + std::unique_ptr<GrowingArrayByteSink> output_; + std::unique_ptr<ProtoStreamObjectWriter> ow_; + ProtoStreamObjectWriter::Options options_; +}; + +MATCHER_P(HasObjectLocation, expected, + "Verifies the expected object location") { + std::string actual = std::get<0>(arg).ToString(); + if (actual == expected) return true; + *result_listener << "actual location is: " << actual; + return false; +} + +class ProtoStreamObjectWriterTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterTest() + : BaseProtoStreamObjectWriterTest(Book::descriptor()) {} + + void ResetProtoWriter() { ResetTypeInfo(Book::descriptor()); } + + virtual ~ProtoStreamObjectWriterTest() {} +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterTest, EmptyObject) { + Book empty; + ow_->StartObject("")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, SimpleObject) { + std::string content("My content"); + + Book book; + book.set_title("My Title"); + book.set_length(222); + book.set_content(content); + + ow_->StartObject("") + ->RenderString("title", "My Title") + ->RenderInt32("length", 222) + ->RenderBytes("content", content) + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, SimpleMessage) { + Book book; + book.set_title("Some Book"); + book.set_length(102); + Publisher* publisher = book.mutable_publisher(); + publisher->set_name("My Publisher"); + Author* robert = book.mutable_author(); + robert->set_alive(true); + robert->set_name("robert"); + robert->add_pseudonym("bob"); + robert->add_pseudonym("bobby"); + robert->add_friend_()->set_name("john"); + + ow_->StartObject("") + ->RenderString("title", "Some Book") + ->RenderInt32("length", 102) + ->StartObject("publisher") + ->RenderString("name", "My Publisher") + ->EndObject() + ->StartObject("author") + ->RenderBool("alive", true) + ->RenderString("name", "robert") + ->StartList("pseudonym") + ->RenderString("", "bob") + ->RenderString("", "bobby") + ->EndList() + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "john") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, CustomJsonName) { + Book book; + Author* robert = book.mutable_author(); + robert->set_id(12345); + robert->set_name("robert"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderUint64("@id", 12345) + ->RenderString("name", "robert") + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +// Test that two messages can have different fields mapped to the same JSON +// name. See: https://github.com/protocolbuffers/protobuf/issues/1415 +TEST_P(ProtoStreamObjectWriterTest, ConflictingJsonName) { + ResetTypeInfo(TestJsonName1::descriptor()); + TestJsonName1 message1; + message1.set_one_value(12345); + ow_->StartObject("")->RenderInt32("value", 12345)->EndObject(); + CheckOutput(message1); + + ResetTypeInfo(TestJsonName2::descriptor()); + TestJsonName2 message2; + message2.set_another_value(12345); + ow_->StartObject("")->RenderInt32("value", 12345)->EndObject(); + CheckOutput(message2); +} + + +TEST_P(ProtoStreamObjectWriterTest, IntEnumValuesAreAccepted) { + Book book; + book.set_title("Some Book"); + book.set_type(proto_util_converter::testing::Book::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, EnumValuesWithDifferentCaseIsRejected) { + Book book; + book.set_title("Some Book"); + Author* robert = book.mutable_author(); + robert->set_name("robert"); + + options_.case_insensitive_enum_parsing = false; + ResetProtoWriter(); + + ow_->StartObject("") + ->RenderString("title", "Some Book") + ->RenderString("type", "action_and_adventure") + ->StartObject("author") + ->RenderString("name", "robert") + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, EnumValuesWithSameCaseIsAccepted) { + Book book; + book.set_title("Some Book"); + book.set_type(proto_util_converter::testing::Book::ACTION_AND_ADVENTURE); + Author* robert = book.mutable_author(); + robert->set_name("robert"); + + options_.case_insensitive_enum_parsing = false; + ResetProtoWriter(); + + ow_->StartObject("") + ->RenderString("title", "Some Book") + ->RenderString("type", "ACTION_AND_ADVENTURE") + ->StartObject("author") + ->RenderString("name", "robert") + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, EnumValuesWithDifferentCaseIsAccepted) { + Book book; + book.set_title("Some Book"); + book.set_type(proto_util_converter::testing::Book::ACTION_AND_ADVENTURE); + Author* robert = book.mutable_author(); + robert->set_name("robert"); + + options_.case_insensitive_enum_parsing = true; + ResetProtoWriter(); + + ow_->StartObject("") + ->RenderString("title", "Some Book") + ->RenderString("type", "action_AND_adventure") + ->StartObject("author") + ->RenderString("name", "robert") + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, EnumValuesWithoutUnderscoreAreAccepted) { + Book book; + book.set_title("Some Book"); + book.set_type(proto_util_converter::testing::Book::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(proto_util_converter::testing::Book::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, + EnumValuesInCamelCaseRemoveDashAndUnderscoreAreAccepted) { + Book book; + book.set_title("Some Book"); + book.set_type(proto_util_converter::testing::Book::ACTION_AND_ADVENTURE); + Author* robert = book.mutable_author(); + robert->set_name("robert"); + + options_.use_lower_camel_for_enums = true; + options_.case_insensitive_enum_parsing = false; + ResetProtoWriter(); + + ow_->StartObject("") + ->RenderString("title", "Some Book") + ->RenderString("type", "action-And_Adventure") + ->StartObject("author") + ->RenderString("name", "robert") + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, + EnumValuesInCamelCaseWithNameNotUppercaseAreAccepted) { + Book book; + book.set_title("Some Book"); + book.set_type(proto_util_converter::testing::Book::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); + full.set_u32(102); + full.set_i32(-103); + full.set_sf32(-104); + full.set_s32(-105); + full.set_fix64(40000000001L); + full.set_u64(40000000002L); + full.set_i64(-40000000003L); + full.set_sf64(-40000000004L); + full.set_s64(-40000000005L); + full.set_str("string1"); + full.set_bytes("Some Bytes"); + full.set_float_(3.14f); + full.set_double_(-4.05L); + full.set_bool_(true); + full.add_rep_fix32(201); + full.add_rep_u32(202); + full.add_rep_i32(-203); + full.add_rep_sf32(-204); + full.add_rep_s32(-205); + full.add_rep_fix64(80000000001L); + full.add_rep_u64(80000000002L); + full.add_rep_i64(-80000000003L); + full.add_rep_sf64(-80000000004L); + full.add_rep_s64(-80000000005L); + full.add_rep_str("string2"); + full.add_rep_bytes("More Bytes"); + full.add_rep_float(6.14f); + full.add_rep_double(-8.05L); + full.add_rep_bool(false); + + ResetTypeInfo(Primitive::descriptor()); + + ow_->StartObject("") + ->RenderString("fix32", "101") + ->RenderString("u32", "102") + ->RenderString("i32", "-103") + ->RenderString("sf32", "-104") + ->RenderString("s32", "-105") + ->RenderString("fix64", "40000000001") + ->RenderString("u64", "40000000002") + ->RenderString("i64", "-40000000003") + ->RenderString("sf64", "-40000000004") + ->RenderString("s64", "-40000000005") + ->RenderString("str", "string1") + ->RenderString("bytes", "U29tZSBCeXRlcw==") // "Some Bytes" + ->RenderString("float", "3.14") + ->RenderString("double", "-4.05") + ->RenderString("bool", "true") + ->StartList("rep_fix32") + ->RenderString("", "201") + ->EndList() + ->StartList("rep_u32") + ->RenderString("", "202") + ->EndList() + ->StartList("rep_i32") + ->RenderString("", "-203") + ->EndList() + ->StartList("rep_sf32") + ->RenderString("", "-204") + ->EndList() + ->StartList("rep_s32") + ->RenderString("", "-205") + ->EndList() + ->StartList("rep_fix64") + ->RenderString("", "80000000001") + ->EndList() + ->StartList("rep_u64") + ->RenderString("", "80000000002") + ->EndList() + ->StartList("rep_i64") + ->RenderString("", "-80000000003") + ->EndList() + ->StartList("rep_sf64") + ->RenderString("", "-80000000004") + ->EndList() + ->StartList("rep_s64") + ->RenderString("", "-80000000005") + ->EndList() + ->StartList("rep_str") + ->RenderString("", "string2") + ->EndList() + ->StartList("rep_bytes") + ->RenderString("", "TW9yZSBCeXRlcw==") // "More Bytes" + ->EndList() + ->StartList("rep_float") + ->RenderString("", "6.14") + ->EndList() + ->StartList("rep_double") + ->RenderString("", "-8.05") + ->EndList() + ->StartList("rep_bool") + ->RenderString("", "false") + ->EndList() + ->EndObject(); + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, InfinityInputTest) { + Primitive full; + full.set_double_(std::numeric_limits<double>::infinity()); + full.set_float_(std::numeric_limits<float>::infinity()); + full.set_str("-Infinity"); + + ResetTypeInfo(Primitive::descriptor()); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("i32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("u32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_SFIXED64"), + StringPiece("\"-Infinity\""))) + .With(Args<0>(HasObjectLocation("sf64"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_BOOL"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("bool"))); + + ow_->StartObject("") + ->RenderString("double", "Infinity") + ->RenderString("float", "Infinity") + ->RenderString("i32", "Infinity") + ->RenderString("u32", "Infinity") + ->RenderString("sf64", "-Infinity") + ->RenderString("str", "-Infinity") + ->RenderString("bool", "Infinity") + ->EndObject(); + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, NaNInputTest) { + Primitive full; + full.set_double_(std::numeric_limits<double>::quiet_NaN()); + full.set_float_(std::numeric_limits<float>::quiet_NaN()); + full.set_str("NaN"); + + ResetTypeInfo(Primitive::descriptor()); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("i32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("u32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_SFIXED64"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("sf64"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_BOOL"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("bool"))); + + ow_->StartObject("") + ->RenderString("double", "NaN") + ->RenderString("float", "NaN") + ->RenderString("i32", "NaN") + ->RenderString("u32", "NaN") + ->RenderString("sf64", "NaN") + ->RenderString("str", "NaN") + ->RenderString("bool", "NaN") + ->EndObject(); + + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, ImplicitPrimitiveList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + author->add_pseudonym("first"); + author->add_pseudonym("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->RenderString("pseudonym", "first") + ->RenderString("pseudonym", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + LastWriteWinsOnNonRepeatedPrimitiveFieldWithDuplicates) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "first") + ->RenderString("name", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ExplicitPrimitiveList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + author->add_pseudonym("first"); + author->add_pseudonym("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->StartList("pseudonym") + ->RenderString("", "first") + ->RenderString("", "second") + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, NonRepeatedExplicitPrimitiveList) { + Book expected; + expected.set_allocated_author(new Author()); + + EXPECT_CALL( + listener_, + InvalidName(_, StringPiece("name"), + StringPiece( + "Proto field is not repeating, cannot start list."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->StartList("name") + ->RenderString("", "first") + ->RenderString("", "second") + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ImplicitMessageList) { + Book expected; + Author* outer = expected.mutable_author(); + outer->set_name("outer"); + outer->set_alive(true); + Author* first = outer->add_friend_(); + first->set_name("first"); + Author* second = outer->add_friend_(); + second->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "outer") + ->RenderBool("alive", true) + ->StartObject("friend") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("friend") + ->RenderString("name", "second") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, DisableImplicitMessageList) { + options_.disable_implicit_message_list = true; + options_.suppress_implicit_message_list_error = true; + ResetProtoWriter(); + + Book expected; + // The repeated friend field of the author is empty. + expected.mutable_author(); + + EXPECT_CALL(listener_, InvalidValue(_, _, _)).Times(0); + + ow_->StartObject("") + ->StartObject("author") + ->StartObject("friend") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("friend") + ->RenderString("name", "second") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + DisableImplicitMessageListWithoutErrorSuppressed) { + options_.disable_implicit_message_list = true; + ResetProtoWriter(); + + Book expected; + // The repeated friend field of the author is empty. + expected.mutable_author(); + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("friend"), + StringPiece( + "Starting an object in a repeated field but the parent object " + "is not a list"))) + .With(Args<0>(HasObjectLocation("author"))) + .Times(2); + + ow_->StartObject("") + ->StartObject("author") + ->StartObject("friend") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("friend") + ->RenderString("name", "second") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + LastWriteWinsOnNonRepeatedMessageFieldWithDuplicates) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + Publisher* publisher = expected.mutable_publisher(); + publisher->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->EndObject() + ->StartObject("publisher") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("publisher") + ->RenderString("name", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ExplicitMessageList) { + Book expected; + Author* outer = expected.mutable_author(); + outer->set_name("outer"); + outer->set_alive(true); + Author* first = outer->add_friend_(); + first->set_name("first"); + Author* second = outer->add_friend_(); + second->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "outer") + ->RenderBool("alive", true) + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("") + ->RenderString("name", "second") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, NonRepeatedExplicitMessageList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + + EXPECT_CALL( + listener_, + InvalidName(_, StringPiece("publisher"), + StringPiece( + "Proto field is not repeating, cannot start list."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->EndObject() + ->StartList("publisher") + ->StartObject("") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("") + ->RenderString("name", "second") + ->EndObject() + ->EndList() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownFieldAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderString("unknown", "Nope!")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownFieldAtAuthorFriend) { + Book expected; + Author* paul = expected.mutable_author(); + paul->set_name("Paul"); + Author* mark = paul->add_friend_(); + mark->set_name("Mark"); + Author* john = paul->add_friend_(); + john->set_name("John"); + Author* luke = paul->add_friend_(); + luke->set_name("Luke"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("address"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("author.friend[1]"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "Paul") + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "Mark") + ->EndObject() + ->StartObject("") + ->RenderString("name", "John") + ->RenderString("address", "Patmos") + ->EndObject() + ->StartObject("") + ->RenderString("name", "Luke") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownObjectAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->StartObject("unknown")->EndObject()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownObjectAtAuthor) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("William"); + author->add_pseudonym("Bill"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("wife"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "William") + ->StartObject("wife") + ->RenderString("name", "Hilary") + ->EndObject() + ->RenderString("pseudonym", "Bill") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownListAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->StartList("unknown")->EndList()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownListAtPublisher) { + Book expected; + expected.set_title("Brainwashing"); + Publisher* publisher = expected.mutable_publisher(); + publisher->set_name("propaganda"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("alliance"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("publisher"))); + ow_->StartObject("") + ->StartObject("publisher") + ->RenderString("name", "propaganda") + ->StartList("alliance") + ->EndList() + ->EndObject() + ->RenderString("title", "Brainwashing") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, IgnoreUnknownFieldAtRoot) { + Book empty; + + options_.ignore_unknown_fields = true; + ResetProtoWriter(); + + EXPECT_CALL(listener_, InvalidName(_, _, _)).Times(0); + ow_->StartObject("")->RenderString("unknown", "Nope!")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, IgnoreUnknownFieldAtAuthorFriend) { + Book expected; + Author* paul = expected.mutable_author(); + paul->set_name("Paul"); + Author* mark = paul->add_friend_(); + mark->set_name("Mark"); + Author* john = paul->add_friend_(); + john->set_name("John"); + Author* luke = paul->add_friend_(); + luke->set_name("Luke"); + + options_.ignore_unknown_fields = true; + ResetProtoWriter(); + + EXPECT_CALL(listener_, InvalidName(_, _, _)).Times(0); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "Paul") + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "Mark") + ->EndObject() + ->StartObject("") + ->RenderString("name", "John") + ->RenderString("address", "Patmos") + ->EndObject() + ->StartObject("") + ->RenderString("name", "Luke") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, IgnoreUnknownObjectAtRoot) { + Book empty; + + options_.ignore_unknown_fields = true; + ResetProtoWriter(); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .Times(0); + ow_->StartObject("")->StartObject("unknown")->EndObject()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, IgnoreUnknownObjectAtAuthor) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("William"); + author->add_pseudonym("Bill"); + + options_.ignore_unknown_fields = true; + ResetProtoWriter(); + + EXPECT_CALL(listener_, InvalidName(_, _, _)).Times(0); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "William") + ->StartObject("wife") + ->RenderString("name", "Hilary") + ->EndObject() + ->RenderString("pseudonym", "Bill") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, IgnoreUnknownListAtRoot) { + Book empty; + + options_.ignore_unknown_fields = true; + ResetProtoWriter(); + + EXPECT_CALL(listener_, InvalidName(_, _, _)).Times(0); + ow_->StartObject("")->StartList("unknown")->EndList()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, IgnoreUnknownListAtPublisher) { + Book expected; + expected.set_title("Brainwashing"); + Publisher* publisher = expected.mutable_publisher(); + publisher->set_name("propaganda"); + + options_.ignore_unknown_fields = true; + ResetProtoWriter(); + + EXPECT_CALL(listener_, InvalidName(_, _, _)).Times(0); + ow_->StartObject("") + ->StartObject("publisher") + ->RenderString("name", "propaganda") + ->StartList("alliance") + ->EndList() + ->EndObject() + ->RenderString("title", "Brainwashing") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + IgnoreUnknownFieldsDontIgnoreUnknownEnumValues) { + ResetTypeInfo(Proto3Message::descriptor()); + + Proto3Message expected; + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece( + "type.googleapis.com/" + "proto_util_converter.testing.Proto3Message.NestedEnum"), + StringPiece("\"someunknownvalueyouwillneverknow\""))) + .With(Args<0>(HasObjectLocation("enum_value"))); + ow_->StartObject("") + ->RenderString("enumValue", "someunknownvalueyouwillneverknow") + ->EndObject(); + 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"); + expected.set_allocated_publisher(new Publisher()); + + EXPECT_CALL(listener_, MissingField(_, StringPiece("name"))) + .With(Args<0>(HasObjectLocation("publisher"))); + ow_->StartObject("") + ->StartObject("publisher") + ->EndObject() + ->RenderString("title", "My Title") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, InvalidFieldValueAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"garbage\""))) + .With(Args<0>(HasObjectLocation("length"))); + ow_->StartObject("")->RenderString("length", "garbage")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, MultipleInvalidFieldValues) { + Book expected; + expected.set_title("My Title"); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"-400\""))) + .With(Args<0>(HasObjectLocation("length"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT64"), + StringPiece("\"3.14\""))) + .With(Args<0>(HasObjectLocation("published"))); + ow_->StartObject("") + ->RenderString("length", "-400") + ->RenderString("published", "3.14") + ->RenderString("title", "My Title") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedFieldAtRoot) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderFloat("", 3.14)->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedFieldAtAuthor) { + Book expected; + expected.set_title("noname"); + expected.set_allocated_author(new Author()); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderInt32("", 123) + ->EndObject() + ->RenderString("title", "noname") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedListAtRoot) { + Book expected; + expected.set_title("noname"); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("") + ->StartList("") + ->EndList() + ->RenderString("title", "noname") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedObject) { + Book expected; + expected.set_title("Annie"); + + EXPECT_CALL( + listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element should not be named."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("oops")->RenderString("title", "Annie")->EndObject(); + CheckOutput(expected, 7); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedList) { + Book empty; + + EXPECT_CALL( + listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element should not be named."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartList("oops")->RenderString("", "item")->EndList(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, RootUnnamedField) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Root element must be a message."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->RenderBool("", true); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedField) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element must be a message."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->RenderBool("oops", true); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValue) { + Book empty; + + ow_->RenderNull(""); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValueForMessageField) { + Book empty; + + ow_->RenderNull("author"); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValueForPrimitiveField) { + Book empty; + + ow_->RenderNull("length"); + CheckOutput(empty, 0); +} + +class ProtoStreamObjectWriterTimestampDurationTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterTimestampDurationTest() { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(TimestampDuration::descriptor()); + descriptors.push_back(google::protobuf::Timestamp::descriptor()); + descriptors.push_back(google::protobuf::Duration::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterTimestampDurationTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, ParseTimestamp) { + TimestampDuration timestamp; + google::protobuf::Timestamp* ts = timestamp.mutable_ts(); + ts->set_seconds(1448249855); + ts->set_nanos(33155000); + + ow_->StartObject("") + ->RenderString("ts", "2015-11-23T03:37:35.033155Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + ParseTimestampYearNotZeroPadded) { + TimestampDuration timestamp; + google::protobuf::Timestamp* ts = timestamp.mutable_ts(); + ts->set_seconds(-61665654145); + ts->set_nanos(33155000); + + ow_->StartObject("") + ->RenderString("ts", "15-11-23T03:37:35.033155Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + ParseTimestampYearZeroPadded) { + TimestampDuration timestamp; + google::protobuf::Timestamp* ts = timestamp.mutable_ts(); + ts->set_seconds(-61665654145); + ts->set_nanos(33155000); + + ow_->StartObject("") + ->RenderString("ts", "0015-11-23T03:37:35.033155Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + ParseTimestampWithPositiveOffset) { + TimestampDuration timestamp; + google::protobuf::Timestamp* ts = timestamp.mutable_ts(); + ts->set_seconds(1448249855); + ts->set_nanos(33155000); + + ow_->StartObject("") + ->RenderString("ts", "2015-11-23T11:47:35.033155+08:10") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + ParseTimestampWithNegativeOffset) { + TimestampDuration timestamp; + google::protobuf::Timestamp* ts = timestamp.mutable_ts(); + ts->set_seconds(1448249855); + ts->set_nanos(33155000); + + ow_->StartObject("") + ->RenderString("ts", "2015-11-22T19:47:35.033155-07:50") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + TimestampWithInvalidOffset1) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "2016-03-07T15:14:23+"))); + + ow_->StartObject("")->RenderString("ts", "2016-03-07T15:14:23+")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + TimestampWithInvalidOffset2) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "2016-03-07T15:14:23+08-10"))); + + ow_->StartObject("") + ->RenderString("ts", "2016-03-07T15:14:23+08-10") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + TimestampWithInvalidOffset3) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "2016-03-07T15:14:23+24:10"))); + + ow_->StartObject("") + ->RenderString("ts", "2016-03-07T15:14:23+24:10") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + TimestampWithInvalidOffset4) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "2016-03-07T15:14:23+04:60"))); + + ow_->StartObject("") + ->RenderString("ts", "2016-03-07T15:14:23+04:60") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError1) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: "))); + + ow_->StartObject("")->RenderString("ts", "")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError2) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: Z"))); + + ow_->StartObject("")->RenderString("ts", "Z")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError3) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "1970-01-01T00:00:00.ABZ"))); + + ow_->StartObject("") + ->RenderString("ts", "1970-01-01T00:00:00.ABZ") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError4) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "-8031-10-18T00:00:00.000Z"))); + + ow_->StartObject("") + ->RenderString("ts", "-8031-10-18T00:00:00.000Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError5) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "2015-11-23T03:37:35.033155 Z"))); + + ow_->StartObject("") + // Whitespace in the Timestamp nanos is not allowed. + ->RenderString("ts", "2015-11-23T03:37:35.033155 Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError6) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "2015-11-23T03:37:35.033155 1234Z"))); + + ow_->StartObject("") + // Whitespace in the Timestamp nanos is not allowed. + ->RenderString("ts", "2015-11-23T03:37:35.033155 1234Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError7) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "2015-11-23T03:37:35.033abc155Z"))); + + ow_->StartObject("") + // Non-numeric characters in the Timestamp nanos is not allowed. + ->RenderString("ts", "2015-11-23T03:37:35.033abc155Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError8) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Invalid time format: " + "0-12-31T23:59:59.000Z"))); + + ow_->StartObject("") + ->RenderString("ts", "0-12-31T23:59:59.000Z") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, ParseDuration) { + TimestampDuration duration; + google::protobuf::Duration* dur = duration.mutable_dur(); + dur->set_seconds(1448216930); + dur->set_nanos(132262000); + + ow_->StartObject("")->RenderString("dur", "1448216930.132262s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError1) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Illegal duration format; duration must " + "end with 's'"))); + + ow_->StartObject("")->RenderString("dur", "")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError2) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Invalid duration format, failed to parse " + "seconds"))); + + ow_->StartObject("")->RenderString("dur", "s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError3) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece("Field 'dur', Invalid duration format, failed to " + "parse nano seconds"))); + + ow_->StartObject("")->RenderString("dur", "123.DEFs")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError4) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece("Field 'dur', Duration value exceeds limits"))); + + ow_->StartObject("")->RenderString("dur", "315576000002s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError5) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece("Field 'dur', Duration value exceeds limits"))); + + ow_->StartObject("")->RenderString("dur", "0.1000000001s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + MismatchedTimestampTypeInput) { + TimestampDuration timestamp; + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Invalid data type for timestamp, value is 1"))) + .With(Args<0>(HasObjectLocation("ts"))); + ow_->StartObject("")->RenderInt32("ts", 1)->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + MismatchedDurationTypeInput) { + TimestampDuration duration; + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "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); +} + +class ProtoStreamObjectWriterStructTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterStructTest() { ResetProtoWriter(); } + + // Resets ProtoWriter with current set of options and other state. + void ResetProtoWriter() { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(StructType::descriptor()); + descriptors.push_back(google::protobuf::Struct::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterStructTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +// TODO(skarvaje): Write tests for failure cases. +TEST_P(ProtoStreamObjectWriterStructTest, StructRenderSuccess) { + StructType struct_type; + google::protobuf::Struct* s = struct_type.mutable_object(); + s->mutable_fields()->operator[]("k1").set_number_value(123); + s->mutable_fields()->operator[]("k2").set_bool_value(true); + + ow_->StartObject("") + ->StartObject("object") + ->RenderDouble("k1", 123) + ->RenderBool("k2", true) + ->EndObject() + ->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, StructNullInputSuccess) { + StructType struct_type; + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderNull("")->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, StructInvalidInputFailure) { + StructType struct_type; + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Struct"), + StringPiece("true"))) + .With(Args<0>(HasObjectLocation("object"))); + + ow_->StartObject("")->RenderBool("object", true)->EndObject(); + 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_, + InvalidName(_, StringPiece("gBike"), + StringPiece( + "Repeated map key: 'gBike' is already set."))); + ow_->StartObject("") + ->StartObject("object") + ->RenderString("gBike", "v1") + ->RenderString("gBike", "v2") + ->EndObject() + ->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterStructTest, RepeatedStructMapListKeyTest) { + EXPECT_CALL( + listener_, + InvalidName(_, StringPiece("k1"), + StringPiece("Repeated map key: 'k1' is already set."))); + ow_->StartObject("") + ->StartObject("object") + ->RenderString("k1", "v1") + ->StartList("k1") + ->RenderString("", "v2") + ->EndList() + ->EndObject() + ->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterStructTest, RepeatedStructMapObjectKeyTest) { + EXPECT_CALL( + listener_, + InvalidName(_, StringPiece("k1"), + StringPiece("Repeated map key: 'k1' is already set."))); + ow_->StartObject("") + ->StartObject("object") + ->StartObject("k1") + ->RenderString("sub_k1", "v1") + ->EndObject() + ->StartObject("k1") + ->RenderString("sub_k2", "v2") + ->EndObject() + ->EndObject() + ->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterStructTest, OptionStructIntAsStringsTest) { + StructType struct_type; + google::protobuf::Struct* s = struct_type.mutable_object(); + s->mutable_fields()->operator[]("k1").set_string_value("123"); + s->mutable_fields()->operator[]("k2").set_bool_value(true); + s->mutable_fields()->operator[]("k3").set_string_value("-222222222"); + s->mutable_fields()->operator[]("k4").set_string_value("33333333"); + + options_.struct_integers_as_strings = true; + ResetProtoWriter(); + + ow_->StartObject("") + ->StartObject("object") + ->RenderDouble("k1", 123) + ->RenderBool("k2", true) + ->RenderInt64("k3", -222222222) + ->RenderUint64("k4", 33333333) + ->EndObject() + ->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, Struct32BitIntsAndFloatsTest) { + StructType struct_type; + google::protobuf::Struct* s = struct_type.mutable_object(); + s->mutable_fields()->operator[]("k1").set_number_value(1.5); + s->mutable_fields()->operator[]("k2").set_number_value(100); + s->mutable_fields()->operator[]("k3").set_number_value(100); + ResetProtoWriter(); + + ow_->StartObject("") + ->StartObject("object") + ->RenderFloat("k1", 1.5) + ->RenderInt32("k2", 100) + ->RenderUint32("k3", 100) + ->EndObject() + ->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, + Struct32BitIntsAndFloatsAsStringsTest) { + StructType struct_type; + google::protobuf::Struct* s = struct_type.mutable_object(); + s->mutable_fields()->operator[]("k1").set_string_value("1.5"); + s->mutable_fields()->operator[]("k2").set_string_value("100"); + s->mutable_fields()->operator[]("k3").set_string_value("100"); + + options_.struct_integers_as_strings = true; + ResetProtoWriter(); + + ow_->StartObject("") + ->StartObject("object") + ->RenderFloat("k1", 1.5) + ->RenderInt32("k2", 100) + ->RenderUint32("k3", 100) + ->EndObject() + ->EndObject(); + 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() { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(MapIn::descriptor()); + descriptors.push_back(google::protobuf::DoubleValue::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterMapTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterMapTest, MapShouldNotAcceptList) { + MapIn mm; + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Map"), + StringPiece( + "Cannot bind a list to map for field 'map_input'."))); + ow_->StartObject("") + ->StartList("map_input") + ->RenderString("a", "b") + ->EndList() + ->EndObject(); + CheckOutput(mm); +} + +TEST_P(ProtoStreamObjectWriterMapTest, MapAcceptsNullValue) { + // Null should not be a valid map value. + // See http://go/proto3-json-spec#heading=h.r2ddatp7y4vi + // This test is added for backward compatibility. + MapIn mm; + (*mm.mutable_map_input())["a"] = "b"; + (*mm.mutable_map_input())["x"] = ""; + ow_->StartObject("") + ->StartObject("map_input") + ->RenderString("a", "b") + ->RenderNull("x") + ->EndObject() + ->EndObject(); + CheckOutput(mm); +} + +TEST_P(ProtoStreamObjectWriterMapTest, MapShouldIgnoreNullValueEntry) { + options_.ignore_null_value_map_entry = true; + ResetTypeInfo(MapIn::descriptor()); + MapIn mm; + (*mm.mutable_map_input())["a"] = "b"; + ow_->StartObject("") + ->StartObject("map_input") + ->RenderString("a", "b") + ->RenderNull("x") + ->EndObject() + ->EndObject(); + CheckOutput(mm); +} + +TEST_P(ProtoStreamObjectWriterMapTest, RepeatedMapKeyTest) { + EXPECT_CALL( + listener_, + InvalidName(_, StringPiece("k1"), + StringPiece("Repeated map key: 'k1' is already set."))); + ow_->StartObject("") + ->RenderString("other", "test") + ->StartObject("map_input") + ->RenderString("k1", "v1") + ->RenderString("k1", "v2") + ->EndObject() + ->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterMapTest, AnyInMap) { + MapIn mm; + google::protobuf::DoubleValue d; + d.set_value(40.2); + (*mm.mutable_map_any())["foo"].PackFrom(d); + ow_->StartObject("") + ->StartObject("map_any") + ->StartObject("foo") + ->RenderString("@type", "type.googleapis.com/google.protobuf.DoubleValue") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(mm); +} + +class ProtoStreamObjectWriterAnyTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterAnyTest() { + std::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::Value::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterAnyTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyRenderSuccess) { + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.DoubleValue"); + google::protobuf::DoubleValue d; + d.set_value(40.2); + any_type->set_value(d.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.DoubleValue") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, RecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url( + "type.googleapis.com/proto_util_converter.testing.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + nested_any.set_value(m.SerializeAsString()); + + any->set_value(nested_any.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(out, 112); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, DoubleRecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + 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.Any"); + + ::google::protobuf::Any second_nested_any; + second_nested_any.set_type_url( + "type.googleapis.com/proto_util_converter.testing.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + second_nested_any.set_value(m.SerializeAsString()); + + nested_any.set_value(second_nested_any.SerializeAsString()); + any->set_value(nested_any.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/proto_util_converter.testing.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(out, 156); +} + +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/proto_util_converter.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); + + std::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/proto_util_converter.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); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails1) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece("Missing @type for any field in " + "proto_util_converter.testing.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->StartObject("another") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails2) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece("Missing @type for any field in " + "proto_util_converter.testing.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->StartList("another") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails3) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece("Missing @type for any field in " + "proto_util_converter.testing.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("value", "somevalue") + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithInvalidTypeUrlFails) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("Any"), + StringPiece("Invalid type URL, type URLs must be of the form " + "'type.googleapis.com/<typename>', got: " + "type.other.com/some.Type"))); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.other.com/some.Type") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithUnknownTypeFails) { + AnyOut any; + + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Invalid type URL, unknown type: some.Type"))); + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/some.Type") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +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); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWellKnownTypeErrorTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("Any"), + StringPiece("Invalid time format: "))); + + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.Timestamp"); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Timestamp") + ->RenderString("value", "") + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Value", +// "value": "abc" +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithNestedPrimitiveValue) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + ::google::protobuf::Value value; + value.set_string_value("abc"); + any->PackFrom(value); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Value") + ->RenderString("value", "abc") + ->EndObject() + ->EndObject(); + CheckOutput(out); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Value", +// "value": { +// "foo": "abc" +// } +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithNestedObjectValue) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + ::google::protobuf::Value value; + (*value.mutable_struct_value()->mutable_fields())["foo"].set_string_value( + "abc"); + any->PackFrom(value); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Value") + ->StartObject("value") + ->RenderString("foo", "abc") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(out); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Value", +// "value": ["hello"], +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithNestedArrayValue) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + ::google::protobuf::Value value; + value.mutable_list_value()->add_values()->set_string_value("hello"); + any->PackFrom(value); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Value") + ->StartList("value") + ->RenderString("", "hello") + ->EndList() + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(out); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Value", +// "not_value": "" +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, + AnyWellKnownTypesNoValueFieldForPrimitive) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("Any"), + StringPiece("Expect a \"value\" field for well-known types."))); + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.Value"); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Value") + ->RenderString("not_value", "") + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Value", +// "not_value": {} +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWellKnownTypesNoValueFieldForObject) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("Any"), + StringPiece("Expect a \"value\" field for well-known types."))); + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.Value"); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Value") + ->StartObject("not_value") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Value", +// "not_value": [], +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWellKnownTypesNoValueFieldForArray) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("Any"), + StringPiece("Expect a \"value\" field for well-known types."))); + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.Value"); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Value") + ->StartList("not_value") + ->EndList() + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Struct", +// "value": "", +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWellKnownTypesExpectObjectForStruct) { + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece("Expect a JSON object."))); + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.Struct"); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Struct") + ->RenderString("value", "") + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +// Test the following case: +// +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.Any", +// "value": "", +// } +// } +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWellKnownTypesExpectObjectForAny) { + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece("Expect a JSON object."))); + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->RenderString("value", "") + ->EndObject() + ->EndObject(); + 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: + ProtoStreamObjectWriterFieldMaskTest() { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(FieldMaskTest::descriptor()); + descriptors.push_back(google::protobuf::FieldMask::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterFieldMaskTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, SimpleFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + expected.mutable_single_mask()->add_paths("path1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "path1"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MultipleMasksInCompactForm) { + FieldMaskTest expected; + expected.set_id("1"); + expected.mutable_single_mask()->add_paths("camel_case1"); + expected.mutable_single_mask()->add_paths("camel_case2"); + expected.mutable_single_mask()->add_paths("camel_case3"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "camelCase1,camelCase2,camelCase3"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, RepeatedFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + google::protobuf::FieldMask* mask = expected.add_repeated_mask(); + mask->add_paths("field1"); + mask->add_paths("field2"); + expected.add_repeated_mask()->add_paths("field3"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->StartList("repeated_mask"); + ow_->RenderString("", "field1,field2"); + ow_->RenderString("", "field3"); + ow_->EndList(); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, EmptyFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", ""); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MaskUsingApiaryStyleShouldWork) { + FieldMaskTest expected; + expected.set_id("1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + // Case1 + ow_->RenderString("single_mask", + "outerField(camelCase1,camelCase2,camelCase3)"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case1"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case2"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case3"); + + ow_->StartList("repeated_mask"); + + ow_->RenderString("", "a(field1,field2)"); + google::protobuf::FieldMask* mask = expected.add_repeated_mask(); + mask->add_paths("a.field1"); + mask->add_paths("a.field2"); + + ow_->RenderString("", "a(field3)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field3"); + + ow_->RenderString("", "a()"); + expected.add_repeated_mask(); + + ow_->RenderString("", "a(,)"); + expected.add_repeated_mask(); + + ow_->RenderString("", "a(field1(field2(field3)))"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field1.field2.field3"); + + ow_->RenderString("", "a(field1(field2(field3,field4),field5),field6)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field1.field2.field3"); + mask->add_paths("a.field1.field2.field4"); + mask->add_paths("a.field1.field5"); + mask->add_paths("a.field6"); + + ow_->RenderString("", "a(id,field1(id,field2(field3,field4),field5),field6)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.id"); + mask->add_paths("a.field1.id"); + mask->add_paths("a.field1.field2.field3"); + mask->add_paths("a.field1.field2.field4"); + mask->add_paths("a.field1.field5"); + mask->add_paths("a.field6"); + + ow_->RenderString("", "a(((field3,field4)))"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field3"); + mask->add_paths("a.field4"); + + ow_->EndList(); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MoreCloseThanOpenParentheses) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece("Field 'single_mask', Invalid FieldMask 'a(b,c))'. " + "Cannot find matching '(' for all ')'."))); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "a(b,c))"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MoreOpenThanCloseParentheses) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask 'a(((b,c)'. Cannot " + "find matching ')' for all '('."))); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "a(((b,c)"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, PathWithMapKeyShouldWork) { + FieldMaskTest expected; + expected.mutable_single_mask()->add_paths("path.to.map[\"key1\"]"); + expected.mutable_single_mask()->add_paths( + "path.to.map[\"e\\\"[]][scape\\\"\"]"); + expected.mutable_single_mask()->add_paths("path.to.map[\"key2\"]"); + + ow_->StartObject(""); + ow_->RenderString("single_mask", + "path.to.map[\"key1\"],path.to.map[\"e\\\"[]][scape\\\"\"]," + "path.to.map[\"key2\"]"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, + MapKeyMustBeAtTheEndOfAPathSegment) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"key1\"]a,path.to.map[\"key2\"]'. " + "Map keys should be at the end of a path segment."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", + "path.to.map[\"key1\"]a,path.to.map[\"key2\"]"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyMustEnd) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece("Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"key1\"'. Map keys should be " + "represented as [\"some_key\"]."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", "path.to.map[\"key1\""); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyMustBeEscapedCorrectly) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece("Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"ke\"y1\"]'. Map keys should be " + "represented as [\"some_key\"]."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", "path.to.map[\"ke\"y1\"]"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyCanContainAnyChars) { + FieldMaskTest expected; + expected.mutable_single_mask()->add_paths( + // \xE5\xAD\x99 is the UTF-8 byte sequence for chinese character 孙. + // We cannot embed non-ASCII characters in the code directly because + // some windows compilers will try to interpret them using the system's + // current encoding and end up with invalid UTF-8 byte sequence. + "path.to.map[\"(),[],\\\"'!@#$%^&*123_|War\xE5\xAD\x99,./?><\\\\\"]"); + expected.mutable_single_mask()->add_paths("path.to.map[\"key2\"]"); + + ow_->StartObject(""); + ow_->RenderString( + "single_mask", + "path.to.map[\"(),[],\\\"'!@#$%^&*123_|War\xE5\xAD\x99,./?><\\\\\"]," + "path.to.map[\"key2\"]"); + ow_->EndObject(); + + 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() { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(Int32Wrapper::descriptor()); + descriptors.push_back(google::protobuf::Int32Value::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +INSTANTIATE_TEST_SUITE_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: + ProtoStreamObjectWriterOneOfsTest() { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(OneOfsRequest::descriptor()); + descriptors.push_back(google::protobuf::Struct::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +INSTANTIATE_TEST_SUITE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterOneOfsTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForPrimitiveTypesTest) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. Cannot set 'intData'"))); + + ow_->StartObject(""); + ow_->RenderString("strData", "blah"); + ow_->RenderString("intData", "123"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForMessageTypesPrimitiveFirstTest) { + // Test for setting primitive oneof field first and then message field. + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'messageData'"))); + + // JSON: { "strData": "blah", "messageData": { "dataValue": 123 } } + ow_->StartObject(""); + ow_->RenderString("strData", "blah"); + ow_->StartObject("messageData"); + ow_->RenderInt32("dataValue", 123); + ow_->EndObject(); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForMessageTypesMessageFirstTest) { + // Test for setting message oneof field first and then primitive field. + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'strData'"))); + + // JSON: { "messageData": { "dataValue": 123 }, "strData": "blah" } + ow_->StartObject(""); + ow_->StartObject("messageData"); + ow_->RenderInt32("dataValue", 123); + ow_->EndObject(); + ow_->RenderString("strData", "blah"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForStructTypesPrimitiveFirstTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'structData'"))); + + // JSON: { "strData": "blah", "structData": { "a": "b" } } + ow_->StartObject(""); + ow_->RenderString("strData", "blah"); + ow_->StartObject("structData"); + ow_->RenderString("a", "b"); + ow_->EndObject(); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForStructTypesStructFirstTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'strData'"))); + + // JSON: { "structData": { "a": "b" }, "strData": "blah" } + ow_->StartObject(""); + ow_->StartObject("structData"); + ow_->RenderString("a", "b"); + ow_->EndObject(); + ow_->RenderString("strData", "blah"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForStructValueTypesTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'valueData'"))); + + // JSON: { "messageData": { "dataValue": 123 }, "valueData": { "a": "b" } } + ow_->StartObject(""); + ow_->StartObject("messageData"); + ow_->RenderInt32("dataValue", 123); + ow_->EndObject(); + ow_->StartObject("valueData"); + ow_->RenderString("a", "b"); + ow_->EndObject(); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForWellKnownTypesPrimitiveFirstTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'tsData'"))); + + // JSON: { "intData": 123, "tsData": "1970-01-02T01:00:00.000Z" } + ow_->StartObject(""); + ow_->RenderInt32("intData", 123); + ow_->RenderString("tsData", "1970-01-02T01:00:00.000Z"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForWellKnownTypesWktFirstTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'intData'"))); + + // JSON: { "tsData": "1970-01-02T01:00:00.000Z", "intData": 123 } + ow_->StartObject(""); + ow_->RenderString("tsData", "1970-01-02T01:00:00.000Z"); + ow_->RenderInt32("intData", 123); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForWellKnownTypesAndMessageTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'messageData'"))); + + // JSON: { "tsData": "1970-01-02T01:00:00.000Z", + // "messageData": { "dataValue": 123 } } + ow_->StartObject(""); + ow_->RenderString("tsData", "1970-01-02T01:00:00.000Z"); + ow_->StartObject("messageData"); + ow_->RenderInt32("dataValue", 123); + ow_->EndObject(); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterOneOfsTest, + MultipleOneofsFailForOneofWithinAnyTest) { + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("oneof"), + StringPiece( + "oneof field 'data' is already set. " + "Cannot set 'intData'"))); + + // JSON: + // { "anyData": + // { "@type": + // "type.googleapis.com/proto_util_converter.testing.oneofs.OneOfsRequest", + // "strData": "blah", + // "intData": 123 + // } + // } + ow_->StartObject(""); + ow_->StartObject("anyData"); + ow_->RenderString( + "@type", + "type.googleapis.com/proto_util_converter.testing.oneofs.OneOfsRequest"); + ow_->RenderString("strData", "blah"); + ow_->RenderInt32("intData", 123); + ow_->EndObject(); + ow_->EndObject(); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/structured_objectwriter.h b/NorthstarDedicatedTest/include/protobuf/util/internal/structured_objectwriter.h new file mode 100644 index 00000000..88fa187e --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/structured_objectwriter.h @@ -0,0 +1,120 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_STRUCTURED_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_STRUCTURED_OBJECTWRITER_H__ + +#include <memory> + +#include <stubs/casts.h> +#include <stubs/common.h> +#include <util/internal/object_writer.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// An StructuredObjectWriter is an ObjectWriter for writing +// tree-structured data in a stream of events representing objects +// and collections. Implementation of this interface can be used to +// write an object stream to an in-memory structure, protobufs, +// JSON, XML, or any other output format desired. The ObjectSource +// interface is typically used as the source of an object stream. +// +// See JsonObjectWriter for a sample implementation of +// StructuredObjectWriter and its use. +// +// Derived classes could be thread-unsafe. +class PROTOBUF_EXPORT StructuredObjectWriter : public ObjectWriter { + public: + virtual ~StructuredObjectWriter() {} + + protected: + // A base element class for subclasses to extend, makes tracking state easier. + // + // StructuredObjectWriter behaves as a visitor. BaseElement represents a node + // in the input tree. Implementation of StructuredObjectWriter should also + // extend BaseElement to keep track of the location in the input tree. + class PROTOBUF_EXPORT BaseElement { + public: + // Takes ownership of the parent Element. + explicit BaseElement(BaseElement* parent) + : parent_(parent), + level_(parent == nullptr ? 0 : parent->level() + 1) {} + virtual ~BaseElement() {} + + // Releases ownership of the parent and returns a pointer to it. + template <typename ElementType> + ElementType* pop() { + return down_cast<ElementType*>(parent_.release()); + } + + // Returns true if this element is the root. + bool is_root() const { return parent_ == nullptr; } + + // Returns the number of hops from this element to the root element. + int level() const { return level_; } + + protected: + // Returns pointer to parent element without releasing ownership. + virtual BaseElement* parent() const { return parent_.get(); } + + private: + // Pointer to the parent Element. + std::unique_ptr<BaseElement> parent_; + + // Number of hops to the root Element. + // The root Element has nullptr parent_ and a level_ of 0. + const int level_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(BaseElement); + }; + + StructuredObjectWriter() {} + + // Returns the current element. Used for indentation and name overrides. + virtual BaseElement* element() = 0; + + private: + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(StructuredObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_STRUCTURED_OBJECTWRITER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/anys.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/anys.proto new file mode 100644 index 00000000..b6fc3f6c --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/anys.proto @@ -0,0 +1,118 @@ +// 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 proto_util_converter.testing; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.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; + AnyWrapper repeated_any = 16; + AnyWrapper empty_any_with_null_type_url = 17; + AnyWrapper any_with_empty = 18; + AnyWrapper any_with_default_timestamp = 19; + + google.protobuf.Any top_level_any = 50; + google.protobuf.Any top_level_any_with_type_field_at_end = 51; + google.protobuf.Any top_level_any_with_pivot_one = 52; + google.protobuf.Any top_level_any_with_pivot_two = 53; + google.protobuf.Any top_level_any_unordered = 54; +} + +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; + google.protobuf.Empty empty = 6; + 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; + repeated google.protobuf.Any repeated_any = 10; +} + +service AnyTestService { + rpc Call(AnyTestCases) returns (AnyTestCases); + rpc Call1(Imports) returns (Imports); +} + +message AnyIn { + string something = 1; + google.protobuf.Any any = 2; +} + +message AnyOut { + google.protobuf.Any any = 1; +} + +message AnyM { + string foo = 1; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/books.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/books.proto new file mode 100644 index 00000000..328c5ce0 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/books.proto @@ -0,0 +1,251 @@ +// 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. + +// 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 +// LINT: ALLOW_GROUPS + +syntax = "proto2"; + +package proto_util_converter.testing; + +import "google/protobuf/util/internal/testdata/anys.proto"; + +// A book +message Book { + optional string title = 1; + optional Author author = 2; + optional uint32 length = 3; + optional int64 published = 4; + optional bytes content = 5; + + optional group Data = 6 { + optional uint32 year = 7; + optional string copyright = 8; + } + + message Label { + optional string key = 1; + optional string value = 2; + } + + optional Publisher publisher = 9; + repeated Label labels = 10; + + enum Type { + FICTION = 1; + KIDS = 2; + ACTION_AND_ADVENTURE = 3; + arts_and_photography = 4; + I18N_Tech = 5; + } + optional Type type = 11; + + // Useful for testing JSON snake/camel-case conversions. + optional string snake_field = 12; + + // Used to test type not found in type info. Messages defined in other files + // are not added when adding Book to type info. + optional AnyWrapper type_not_found = 13; + + // Used to test invalid value inside of primitive repeated fields. + repeated int32 primitive_repeated = 14; + + extensions 200 to 499; +} + +// A publisher of a book, tests required fields. +message Publisher { + required string name = 1; +} + +// An author of a book +message Author { + optional uint64 id = 1 [json_name = "@id"]; + optional string name = 2; + repeated string pseudonym = 3; + optional bool alive = 4; + repeated Author friend = 5; +} + +// For testing resiliency of our protostream parser. +// Field numbers of Author are reused for something else. +message BadAuthor { + optional string id = 1; // non-length-delimited to length-delimited. + repeated uint64 name = 2; // string to repeated (both length-delimited). + optional string pseudonym = 3; // Repeated to optional. + repeated bool alive = 4 [packed = true]; // Optional to repeated. +} + +// All primitive types +message Primitive { + // 32 bit numbers: + optional fixed32 fix32 = 1; + optional uint32 u32 = 2; + optional int32 i32 = 3; + optional sfixed32 sf32 = 4; + optional sint32 s32 = 5; + + // 64 bit numbers: + optional fixed64 fix64 = 6; + optional uint64 u64 = 7; + optional int64 i64 = 8; + optional sfixed64 sf64 = 9; + optional sint64 s64 = 10; + + // The other stuff. + optional string str = 11; + optional bytes bytes = 12; + optional float float = 13; + optional double double = 14; + optional bool bool = 15; + + // repeated 32 bit numbers: + repeated fixed32 rep_fix32 = 16; + repeated uint32 rep_u32 = 17; + repeated int32 rep_i32 = 18; + repeated sfixed32 rep_sf32 = 19; + repeated sint32 rep_s32 = 20; + + // repeated 64 bit numbers: + repeated fixed64 rep_fix64 = 21; + repeated uint64 rep_u64 = 22; + repeated int64 rep_i64 = 23; + repeated sfixed64 rep_sf64 = 24; + repeated sint64 rep_s64 = 25; + + // repeated other stuff: + repeated string rep_str = 26; + repeated bytes rep_bytes = 27; + repeated float rep_float = 28; + repeated double rep_double = 29; + repeated bool rep_bool = 30; +} + +// Test packed versions of all repeated primitives. +// The field numbers should match their non-packed version in Primitive message. +message PackedPrimitive { + // repeated 32 bit numbers: + repeated fixed32 rep_fix32 = 16 [packed = true]; + repeated uint32 rep_u32 = 17 [packed = true]; + repeated int32 rep_i32 = 18 [packed = true]; + repeated sfixed32 rep_sf32 = 19 [packed = true]; + repeated sint32 rep_s32 = 20 [packed = true]; + + // repeated 64 bit numbers: + repeated fixed64 rep_fix64 = 21 [packed = true]; + repeated uint64 rep_u64 = 22 [packed = true]; + repeated int64 rep_i64 = 23 [packed = true]; + repeated sfixed64 rep_sf64 = 24 [packed = true]; + repeated sint64 rep_s64 = 25 [packed = true]; + + // repeated other stuff: + repeated float rep_float = 28 [packed = true]; + repeated double rep_double = 29 [packed = true]; + repeated bool rep_bool = 30 [packed = true]; +} + +// Test extensions. +extend Book { + repeated Author more_author = 201; +} + +// Test nested extensions. +message NestedBook { + extend Book { + optional NestedBook another_book = 301; + } + // Recurse + optional Book book = 1; +} + +// For testing resiliency of our protostream parser. +// Field number of NestedBook is reused for something else. +message BadNestedBook { + repeated uint32 book = 1 [packed = true]; // Packed to optional message. +} + +// A recursively defined message. +message Cyclic { + optional int32 m_int = 1; + optional string m_str = 2; + optional Book m_book = 3; + repeated Author m_author = 5; + optional Cyclic m_cyclic = 4; +} + +// Test that two messages can have different fields mapped to the same JSON +// name. See: https://github.com/protocolbuffers/protobuf/issues/1415 +message TestJsonName1 { + optional int32 one_value = 1 [json_name = "value"]; +} +message TestJsonName2 { + optional int32 another_value = 1 [json_name = "value"]; +} + +message TestPrimitiveFieldsWithSameJsonName { + optional string val_str1 = 1; + optional string val_str_1 = 2; + + optional int32 val_int321 = 3; + optional int32 val_int32_1 = 4; + + optional uint32 val_uint321 = 5; + optional uint32 val_uint32_1 = 6; + + optional int64 val_int641 = 7; + optional int64 val_int64_1 = 8; + + optional uint64 val_uint641 = 9; + optional uint64 val_uint64_1 = 10; + + optional bool val_bool1 = 11; + optional bool val_bool_1 = 12; + + optional double val_double1 = 13; + optional double val_double_1 = 14; + + optional float val_float1 = 15; + optional float val_float_1 = 16; +} + +message TestRepeatedFieldsWithSameJsonName { + repeated string rep_str1 = 1; + repeated string rep_str_1 = 2; +} + +message TestMessageFieldsWithSameJsonName { + optional Primitive prim1 = 1; + optional Primitive prim_1 = 2; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/default_value.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/default_value.proto new file mode 100644 index 00000000..79ce1443 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/default_value.proto @@ -0,0 +1,170 @@ +// 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 proto_util_converter.testing; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +message DefaultValueTestCases { + DoubleMessage empty_double = 1; + DoubleMessage double_with_default_value = 2; + DoubleMessage double_with_nondefault_value = 3; + DoubleMessage repeated_double = 4; + DoubleMessage nested_message = 5; + DoubleMessage repeated_nested_message = 6; + DoubleMessage double_message_with_oneof = 7; + StructMessage empty_struct = 201; + StructMessage empty_struct2 = 202; + StructMessage struct_with_null_value = 203; + StructMessage struct_with_values = 204; + StructMessage struct_with_nested_struct = 205; + StructMessage struct_with_nested_list = 206; + StructMessage struct_with_list_of_nulls = 207; + StructMessage struct_with_list_of_lists = 208; + StructMessage struct_with_list_of_structs = 209; + google.protobuf.Struct top_level_struct = 210; + ValueMessage value_wrapper_simple = 212; + ValueMessage value_wrapper_with_struct = 213; + ValueMessage value_wrapper_with_list = 214; + ListValueMessage list_value_wrapper = 215; + google.protobuf.Value top_level_value_simple = 216; + google.protobuf.Value top_level_value_with_struct = 217; + google.protobuf.Value top_level_value_with_list = 218; + google.protobuf.ListValue top_level_listvalue = 219; + AnyMessage empty_any = 301; + AnyMessage type_only_any = 302; + AnyMessage recursive_any = 303; + AnyMessage any_with_message_value = 304; + AnyMessage any_with_nested_message = 305; + AnyMessage any_with_message_containing_map = 306; + AnyMessage any_with_message_containing_struct = 307; + google.protobuf.Any top_level_any = 308; + StringtoIntMap empty_map = 401; + StringtoIntMap string_to_int = 402; + IntToStringMap int_to_string = 403; + MixedMap mixed1 = 404; + MixedMap2 mixed2 = 405; + MixedMap2 empty_mixed2 = 406; + MessageMap map_of_objects = 407; + MixedMap mixed_empty = 408; + MessageMap message_map_empty = 409; + DoubleValueMessage double_value = 501; + DoubleValueMessage double_value_default = 502; +} + +message DoubleMessage { + double double_value = 1; + repeated double repeated_double = 2; + DoubleMessage nested_message = 3; + repeated DoubleMessage repeated_nested_message = 4; + google.protobuf.DoubleValue double_wrapper = 100; + oneof value { + string str_value = 112; + int64 num_value = 113; + } +} + +message StructMessage { + google.protobuf.Struct struct = 1; +} + +message ValueMessage { + google.protobuf.Value value = 1; +} + +message ListValueMessage { + google.protobuf.ListValue shopping_list = 1; +} +message RequestMessage { + string content = 1; +} + +// A test service. +service DefaultValueTestService { + // A test method. + rpc Call(RequestMessage) returns (DefaultValueTestCases); +} + +message AnyMessage { + google.protobuf.Any any = 1; + AnyData data = 2; +} + +message AnyData { + int32 attr = 1; + string str = 2; + repeated string msgs = 3; + AnyData nested_data = 4; + map<string, string> map_data = 7; + google.protobuf.Struct struct_data = 8; + repeated AnyData repeated_data = 9; +} + +message StringtoIntMap { + map<string, int32> map = 1; +} + +message IntToStringMap { + map<int32, string> map = 1; +} + +message MixedMap { + string msg = 1; + map<string, float> map = 2; + int32 int_value = 3; +} + +message MixedMap2 { + enum E { + E0 = 0; + E1 = 1; + E2 = 2; + E3 = 3; + } + map<int32, bool> map = 1; + E ee = 2; + string msg = 4; +} + +message MessageMap { + message M { + int32 inner_int = 1; + string inner_text = 2; + } + map<string, M> map = 1; +} + +message DoubleValueMessage { + google.protobuf.DoubleValue double = 1; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/default_value_test.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/default_value_test.proto new file mode 100644 index 00000000..af755d3d --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/default_value_test.proto @@ -0,0 +1,53 @@ +// 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 proto_util_converter.testing; + +message DefaultValueTest { + double double_value = 1; + repeated double repeated_double = 2; + float float_value = 3; + int64 int64_value = 5; + uint64 uint64_value = 7; + int32 int32_value = 9; + uint32 uint32_value = 11; + bool bool_value = 13; + string string_value = 15; + bytes bytes_value = 17 [ctype = CORD]; + + enum EnumDefault { + ENUM_FIRST = 0; + ENUM_SECOND = 1; + ENUM_THIRD = 2; + } + EnumDefault enum_value = 18; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/field_mask.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/field_mask.proto new file mode 100644 index 00000000..9d2bc400 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/field_mask.proto @@ -0,0 +1,71 @@ +// 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 proto_util_converter.testing; + +import "google/protobuf/field_mask.proto"; + +message NestedFieldMask { + string data = 1; + google.protobuf.FieldMask single_mask = 2; + repeated google.protobuf.FieldMask repeated_mask = 3; +} + +message FieldMaskTest { + string id = 1; + google.protobuf.FieldMask single_mask = 2; + repeated google.protobuf.FieldMask repeated_mask = 3; + repeated NestedFieldMask nested_mask = 4; +} + +message FieldMaskTestCases { + FieldMaskWrapper single_mask = 1; + FieldMaskWrapper multiple_mask = 2; + FieldMaskWrapper snake_camel = 3; + FieldMaskWrapper empty_field = 4; + FieldMaskWrapper apiary_format1 = 5; + FieldMaskWrapper apiary_format2 = 6; + FieldMaskWrapper apiary_format3 = 7; + FieldMaskWrapper map_key1 = 8; + FieldMaskWrapper map_key2 = 9; + FieldMaskWrapper map_key3 = 10; + FieldMaskWrapper map_key4 = 11; + FieldMaskWrapper map_key5 = 12; +} + +message FieldMaskWrapper { + google.protobuf.FieldMask mask = 1; +} + +service FieldMaskTestService { + rpc Call(FieldMaskTestCases) returns (FieldMaskTestCases); +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/maps.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/maps.proto new file mode 100644 index 00000000..a9fdbe63 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/maps.proto @@ -0,0 +1,148 @@ +// 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 proto_util_converter.testing; + +import "google/protobuf/any.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 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; + repeated string things = 2; + map<string, string> map_input = 3; + map<string, google.protobuf.Any> map_any = 4; +} + +message MapOut { + map<string, MapM> map1 = 1; + map<string, MapOut> map2 = 2; + map<int32, string> map3 = 3; + map<bool, string> map4 = 5; + string bar = 4; +} + +// A message with exactly the same wire representation as MapOut, but using +// repeated message fields instead of map fields. We use this message to test +// the wire-format compatibility of the JSON transcoder (e.g., whether it +// handles missing keys correctly). +message MapOutWireFormat { + message Map1Entry { + string key = 1; + MapM value = 2; + } + repeated Map1Entry map1 = 1; + message Map2Entry { + string key = 1; + MapOut value = 2; + } + repeated Map2Entry map2 = 2; + message Map3Entry { + int32 key = 1; + string value = 2; + } + repeated Map3Entry map3 = 3; + message Map4Entry { + bool key = 1; + string value = 2; + } + repeated Map4Entry map4 = 5; + string bar = 4; +} + +message MapM { + string foo = 1; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/oneofs.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/oneofs.proto new file mode 100644 index 00000000..7706af0b --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/oneofs.proto @@ -0,0 +1,77 @@ +// 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. + +// Proto to test proto3 oneofs. +syntax = "proto3"; + +package proto_util_converter.testing.oneofs; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +message OneOfsRequest { + string value = 1; + oneof data { + string str_data = 2; + int32 int_data = 3; + // Simple message + Data message_data = 4; + MoreData more_data = 5; + // Well known types + 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 MoreData { + string str_value = 1; +} + +message Response { + string value = 1; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/proto3.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/proto3.proto new file mode 100644 index 00000000..01434b02 --- /dev/null +++ b/NorthstarDedicatedTest/include/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 proto_util_converter.testing; + +message Proto3Message { + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + } + NestedEnum enum_value = 1; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/struct.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/struct.proto new file mode 100644 index 00000000..a50ea87f --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/struct.proto @@ -0,0 +1,117 @@ +// 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 proto_util_converter.testing; + +import "google/protobuf/struct.proto"; + +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; +} + +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/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/timestamp_duration.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/timestamp_duration.proto new file mode 100644 index 00000000..8e87bdd2 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/timestamp_duration.proto @@ -0,0 +1,80 @@ +// 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 proto_util_converter.testing; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.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; + repeated google.protobuf.Timestamp rep_ts = 3; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/wrappers.proto b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/wrappers.proto new file mode 100644 index 00000000..e7a0541d --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/testdata/wrappers.proto @@ -0,0 +1,100 @@ +// 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 proto_util_converter.testing; + +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 WrappersTestCases { + DoubleWrapper double_wrapper = 1; + FloatWrapper float_wrapper = 2; + Int64Wrapper int64_wrapper = 3; + UInt64Wrapper uint64_wrapper = 4; + Int32Wrapper int32_wrapper = 5; + UInt32Wrapper uint32_wrapper = 6; + BoolWrapper bool_wrapper = 7; + StringWrapper string_wrapper = 8; + BytesWrapper bytes_wrapper = 9; + + DoubleWrapper double_wrapper_default = 10; + FloatWrapper float_wrapper_default = 11; + Int64Wrapper int64_wrapper_default = 12; + UInt64Wrapper uint64_wrapper_default = 13; + Int32Wrapper int32_wrapper_default = 14; + UInt32Wrapper uint32_wrapper_default = 15; + BoolWrapper bool_wrapper_default = 16; + StringWrapper string_wrapper_default = 17; + BytesWrapper bytes_wrapper_default = 18; +} + +message DoubleWrapper { + google.protobuf.DoubleValue double = 1; +} + +message FloatWrapper { + google.protobuf.FloatValue float = 1; +} + +message Int64Wrapper { + google.protobuf.Int64Value int64 = 1; +} + +message UInt64Wrapper { + google.protobuf.UInt64Value uint64 = 1; +} + +message Int32Wrapper { + google.protobuf.Int32Value int32 = 1; +} + +message UInt32Wrapper { + google.protobuf.UInt32Value uint32 = 1; +} + +message BoolWrapper { + google.protobuf.BoolValue bool = 1; +} + +message StringWrapper { + google.protobuf.StringValue string = 1; +} + +message BytesWrapper { + google.protobuf.BytesValue bytes = 1; +} + +service WrappersTestService { + rpc Call(WrappersTestCases) returns (WrappersTestCases); +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/type_info.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info.cc new file mode 100644 index 00000000..0cf509ed --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info.cc @@ -0,0 +1,182 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/type_info.h> + +#include <map> +#include <set> + +#include <stubs/common.h> +#include <type.pb.h> +#include <util/internal/utility.h> +#include <stubs/status.h> +#include <stubs/statusor.h> +#include <stubs/strutil.h> +#include <stubs/map_util.h> +#include <stubs/status.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +namespace { +// A TypeInfo that looks up information provided by a TypeResolver. +class TypeInfoForTypeResolver : public TypeInfo { + public: + explicit TypeInfoForTypeResolver(TypeResolver* type_resolver) + : type_resolver_(type_resolver) {} + + virtual ~TypeInfoForTypeResolver() { + DeleteCachedTypes(&cached_types_); + DeleteCachedTypes(&cached_enums_); + } + + util::StatusOr<const google::protobuf::Type*> ResolveTypeUrl( + StringPiece type_url) const override { + std::map<StringPiece, StatusOrType>::iterator it = + cached_types_.find(type_url); + if (it != cached_types_.end()) { + return it->second; + } + // Stores the string value so it can be referenced using StringPiece in the + // cached_types_ map. + const std::string& string_type_url = + *string_storage_.insert(std::string(type_url)).first; + std::unique_ptr<google::protobuf::Type> type(new google::protobuf::Type()); + util::Status status = + type_resolver_->ResolveMessageType(string_type_url, type.get()); + StatusOrType result = + status.ok() ? StatusOrType(type.release()) : StatusOrType(status); + cached_types_[string_type_url] = result; + return result; + } + + const google::protobuf::Type* GetTypeByTypeUrl( + StringPiece type_url) const override { + StatusOrType result = ResolveTypeUrl(type_url); + return result.ok() ? result.value() : NULL; + } + + const google::protobuf::Enum* GetEnumByTypeUrl( + StringPiece type_url) const override { + std::map<StringPiece, StatusOrEnum>::iterator it = + cached_enums_.find(type_url); + if (it != cached_enums_.end()) { + return it->second.ok() ? it->second.value() : NULL; + } + // Stores the string value so it can be referenced using StringPiece in the + // cached_enums_ map. + const std::string& string_type_url = + *string_storage_.insert(std::string(type_url)).first; + std::unique_ptr<google::protobuf::Enum> enum_type( + new google::protobuf::Enum()); + util::Status status = + type_resolver_->ResolveEnumType(string_type_url, enum_type.get()); + StatusOrEnum result = + status.ok() ? StatusOrEnum(enum_type.release()) : StatusOrEnum(status); + cached_enums_[string_type_url] = result; + return result.ok() ? result.value() : NULL; + } + + const google::protobuf::Field* FindField( + const google::protobuf::Type* type, + StringPiece camel_case_name) const override { + std::map<const google::protobuf::Type*, CamelCaseNameTable>::const_iterator + it = indexed_types_.find(type); + const CamelCaseNameTable& camel_case_name_table = + (it == indexed_types_.end()) + ? PopulateNameLookupTable(type, &indexed_types_[type]) + : it->second; + StringPiece name = FindWithDefault( + camel_case_name_table, camel_case_name, StringPiece()); + if (name.empty()) { + // Didn't find a mapping. Use whatever provided. + name = camel_case_name; + } + return FindFieldInTypeOrNull(type, name); + } + + private: + typedef util::StatusOr<const google::protobuf::Type*> StatusOrType; + typedef util::StatusOr<const google::protobuf::Enum*> StatusOrEnum; + typedef std::map<StringPiece, StringPiece> CamelCaseNameTable; + + template <typename T> + static void DeleteCachedTypes(std::map<StringPiece, T>* cached_types) { + for (typename std::map<StringPiece, T>::iterator it = + cached_types->begin(); + it != cached_types->end(); ++it) { + if (it->second.ok()) { + delete it->second.value(); + } + } + } + + const CamelCaseNameTable& PopulateNameLookupTable( + const google::protobuf::Type* type, + CamelCaseNameTable* camel_case_name_table) const { + for (int i = 0; i < type->fields_size(); ++i) { + const google::protobuf::Field& field = type->fields(i); + StringPiece name = field.name(); + StringPiece camel_case_name = field.json_name(); + const StringPiece* existing = InsertOrReturnExisting( + camel_case_name_table, camel_case_name, name); + if (existing && *existing != name) { + GOOGLE_LOG(WARNING) << "Field '" << name << "' and '" << *existing + << "' map to the same camel case name '" << camel_case_name + << "'."; + } + } + return *camel_case_name_table; + } + + TypeResolver* type_resolver_; + + // Stores string values that will be referenced by StringPieces in + // cached_types_, cached_enums_. + mutable std::set<std::string> string_storage_; + + mutable std::map<StringPiece, StatusOrType> cached_types_; + mutable std::map<StringPiece, StatusOrEnum> cached_enums_; + + mutable std::map<const google::protobuf::Type*, CamelCaseNameTable> + indexed_types_; +}; +} // namespace + +TypeInfo* TypeInfo::NewTypeInfo(TypeResolver* type_resolver) { + return new TypeInfoForTypeResolver(type_resolver); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/type_info.h b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info.h new file mode 100644 index 00000000..80c73ea1 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info.h @@ -0,0 +1,97 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_H__ + +#include <stubs/common.h> +#include <type.pb.h> +#include <util/type_resolver.h> +#include <stubs/statusor.h> +#include <stubs/strutil.h> +#include <stubs/status.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +// Internal helper class for type resolving. Note that this class is not +// thread-safe and should only be accessed in one thread. +class PROTOBUF_EXPORT TypeInfo { + public: + TypeInfo() {} + virtual ~TypeInfo() {} + + // Resolves a type url into a Type. If the type url is invalid, returns + // INVALID_ARGUMENT error status. If the type url is valid but the + // corresponding type cannot be found, returns a NOT_FOUND error status. + // + // This TypeInfo class retains the ownership of the returned pointer. + virtual util::StatusOr<const google::protobuf::Type*> ResolveTypeUrl( + StringPiece type_url) const = 0; + + // Resolves a type url into a Type. Like ResolveTypeUrl() but returns + // NULL if the type url is invalid or the type cannot be found. + // + // This TypeInfo class retains the ownership of the returned pointer. + virtual const google::protobuf::Type* GetTypeByTypeUrl( + StringPiece type_url) const = 0; + + // Resolves a type url for an enum. Returns NULL if the type url is + // invalid or the type cannot be found. + // + // This TypeInfo class retains the ownership of the returned pointer. + virtual const google::protobuf::Enum* GetEnumByTypeUrl( + StringPiece type_url) const = 0; + + // Looks up a field in the specified type given a CamelCase name. + virtual const google::protobuf::Field* FindField( + const google::protobuf::Type* type, + StringPiece camel_case_name) const = 0; + + // Creates a TypeInfo object that looks up type information from a + // TypeResolver. Caller takes ownership of the returned pointer. + static TypeInfo* NewTypeInfo(TypeResolver* type_resolver); + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(TypeInfo); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/type_info_test_helper.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info_test_helper.cc new file mode 100644 index 00000000..69ab08a8 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info_test_helper.cc @@ -0,0 +1,132 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/type_info_test_helper.h> + +#include <memory> +#include <vector> + +#include <stubs/logging.h> +#include <stubs/common.h> +#include <descriptor.h> +#include <util/internal/default_value_objectwriter.h> +#include <util/internal/type_info.h> +#include <util/internal/constants.h> +#include <util/internal/protostream_objectsource.h> +#include <util/internal/protostream_objectwriter.h> +#include <util/type_resolver.h> +#include <util/type_resolver_util.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +namespace testing { + + +void TypeInfoTestHelper::ResetTypeInfo( + const std::vector<const Descriptor*>& descriptors) { + switch (type_) { + case USE_TYPE_RESOLVER: { + const DescriptorPool* pool = descriptors[0]->file()->pool(); + for (int i = 1; i < descriptors.size(); ++i) { + GOOGLE_CHECK(pool == descriptors[i]->file()->pool()) + << "Descriptors from different pools are not supported."; + } + type_resolver_.reset( + NewTypeResolverForDescriptorPool(kTypeServiceBaseUrl, pool)); + typeinfo_.reset(TypeInfo::NewTypeInfo(type_resolver_.get())); + return; + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; +} + +void TypeInfoTestHelper::ResetTypeInfo(const Descriptor* descriptor) { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(descriptor); + ResetTypeInfo(descriptors); +} + +void TypeInfoTestHelper::ResetTypeInfo(const Descriptor* descriptor1, + const Descriptor* descriptor2) { + std::vector<const Descriptor*> descriptors; + descriptors.push_back(descriptor1); + descriptors.push_back(descriptor2); + ResetTypeInfo(descriptors); +} + +TypeInfo* TypeInfoTestHelper::GetTypeInfo() { return typeinfo_.get(); } + +ProtoStreamObjectSource* TypeInfoTestHelper::NewProtoSource( + io::CodedInputStream* coded_input, const std::string& type_url, + ProtoStreamObjectSource::RenderOptions render_options) { + const google::protobuf::Type* type = typeinfo_->GetTypeByTypeUrl(type_url); + switch (type_) { + case USE_TYPE_RESOLVER: { + return new ProtoStreamObjectSource(coded_input, type_resolver_.get(), + *type, render_options); + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; + return nullptr; +} + +ProtoStreamObjectWriter* TypeInfoTestHelper::NewProtoWriter( + const std::string& type_url, strings::ByteSink* output, + ErrorListener* listener, const ProtoStreamObjectWriter::Options& options) { + const google::protobuf::Type* type = typeinfo_->GetTypeByTypeUrl(type_url); + switch (type_) { + case USE_TYPE_RESOLVER: { + return new ProtoStreamObjectWriter(type_resolver_.get(), *type, output, + listener, options); + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; + return nullptr; +} + +DefaultValueObjectWriter* TypeInfoTestHelper::NewDefaultValueWriter( + const std::string& type_url, ObjectWriter* writer) { + const google::protobuf::Type* type = typeinfo_->GetTypeByTypeUrl(type_url); + switch (type_) { + case USE_TYPE_RESOLVER: { + return new DefaultValueObjectWriter(type_resolver_.get(), *type, writer); + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; + return nullptr; +} + +} // namespace testing +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/type_info_test_helper.h b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info_test_helper.h new file mode 100644 index 00000000..8697bc15 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/type_info_test_helper.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_TEST_HELPER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_TEST_HELPER_H__ + +#include <memory> +#include <vector> + +#include <io/coded_stream.h> +#include <descriptor.h> +#include <util/internal/default_value_objectwriter.h> +#include <util/internal/type_info.h> +#include <util/internal/protostream_objectsource.h> +#include <util/internal/protostream_objectwriter.h> +#include <util/type_resolver.h> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +namespace testing { + +enum TypeInfoSource { + USE_TYPE_RESOLVER, +}; + +// In the unit-tests we want to test two scenarios: one with type info from +// ServiceTypeInfo, the other with type info from TypeResolver. This class +// wraps the detail of where the type info is from and provides the same +// interface so the same unit-test code can test both scenarios. +class TypeInfoTestHelper { + public: + explicit TypeInfoTestHelper(TypeInfoSource type) : type_(type) {} + + // Creates a TypeInfo object for the given set of descriptors. + void ResetTypeInfo(const std::vector<const Descriptor*>& descriptors); + + // Convenient overloads. + void ResetTypeInfo(const Descriptor* descriptor); + void ResetTypeInfo(const Descriptor* descriptor1, + const Descriptor* descriptor2); + + // Returns the TypeInfo created after ResetTypeInfo. + TypeInfo* GetTypeInfo(); + + ProtoStreamObjectSource* NewProtoSource( + io::CodedInputStream* coded_input, const std::string& type_url, + ProtoStreamObjectSource::RenderOptions render_options = {}); + + ProtoStreamObjectWriter* NewProtoWriter( + const std::string& type_url, strings::ByteSink* output, + ErrorListener* listener, const ProtoStreamObjectWriter::Options& options); + + DefaultValueObjectWriter* NewDefaultValueWriter(const std::string& type_url, + ObjectWriter* writer); + + private: + TypeInfoSource type_; + std::unique_ptr<TypeInfo> typeinfo_; + std::unique_ptr<TypeResolver> type_resolver_; +}; +} // namespace testing +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_TEST_HELPER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/utility.cc b/NorthstarDedicatedTest/include/protobuf/util/internal/utility.cc new file mode 100644 index 00000000..b90e7ba5 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/utility.cc @@ -0,0 +1,416 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/internal/utility.h> + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <limits> + +#include <stubs/callback.h> +#include <stubs/common.h> +#include <stubs/logging.h> +#include <wrappers.pb.h> +#include <descriptor.pb.h> +#include <descriptor.h> +#include <util/internal/constants.h> +#include <stubs/strutil.h> +#include <stubs/map_util.h> + +// clang-format off +#include <port_def.inc> +// clang-format on + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +bool GetBoolOptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, bool default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == nullptr) { + return default_value; + } + return GetBoolFromAny(opt->value()); +} + +int64_t GetInt64OptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, int64_t default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == nullptr) { + return default_value; + } + return GetInt64FromAny(opt->value()); +} + +double GetDoubleOptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, double default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == nullptr) { + return default_value; + } + return GetDoubleFromAny(opt->value()); +} + +std::string GetStringOptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, StringPiece default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == nullptr) { + return std::string(default_value); + } + return GetStringFromAny(opt->value()); +} + +template <typename T> +void ParseFromAny(const std::string& data, T* result) { + result->ParseFromString(data); +} + +// Returns a boolean value contained in Any type. +// TODO(skarvaje): Add type checking & error messages here. +bool GetBoolFromAny(const google::protobuf::Any& any) { + google::protobuf::BoolValue b; + ParseFromAny(any.value(), &b); + return b.value(); +} + +int64_t GetInt64FromAny(const google::protobuf::Any& any) { + google::protobuf::Int64Value i; + ParseFromAny(any.value(), &i); + return i.value(); +} + +double GetDoubleFromAny(const google::protobuf::Any& any) { + google::protobuf::DoubleValue i; + ParseFromAny(any.value(), &i); + return i.value(); +} + +std::string GetStringFromAny(const google::protobuf::Any& any) { + google::protobuf::StringValue s; + ParseFromAny(any.value(), &s); + return s.value(); +} + +const StringPiece GetTypeWithoutUrl(StringPiece type_url) { + if (type_url.size() > kTypeUrlSize && type_url[kTypeUrlSize] == '/') { + return type_url.substr(kTypeUrlSize + 1); + } else { + size_t idx = type_url.rfind('/'); + if (idx != type_url.npos) { + type_url.remove_prefix(idx + 1); + } + return type_url; + } +} + +const std::string GetFullTypeWithUrl(StringPiece simple_type) { + return StrCat(kTypeServiceBaseUrl, "/", simple_type); +} + +const google::protobuf::Option* FindOptionOrNull( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name) { + for (int i = 0; i < options.size(); ++i) { + const google::protobuf::Option& opt = options.Get(i); + if (opt.name() == option_name) { + return &opt; + } + } + return nullptr; +} + +const google::protobuf::Field* FindFieldInTypeOrNull( + const google::protobuf::Type* type, StringPiece field_name) { + if (type != nullptr) { + for (int i = 0; i < type->fields_size(); ++i) { + const google::protobuf::Field& field = type->fields(i); + if (field.name() == field_name) { + return &field; + } + } + } + return nullptr; +} + +const google::protobuf::Field* FindJsonFieldInTypeOrNull( + const google::protobuf::Type* type, StringPiece json_name) { + if (type != nullptr) { + for (int i = 0; i < type->fields_size(); ++i) { + const google::protobuf::Field& field = type->fields(i); + if (field.json_name() == json_name) { + return &field; + } + } + } + return nullptr; +} + +const google::protobuf::Field* FindFieldInTypeByNumberOrNull( + const google::protobuf::Type* type, int32_t number) { + if (type != nullptr) { + for (int i = 0; i < type->fields_size(); ++i) { + const google::protobuf::Field& field = type->fields(i); + if (field.number() == number) { + return &field; + } + } + } + return nullptr; +} + +const google::protobuf::EnumValue* FindEnumValueByNameOrNull( + const google::protobuf::Enum* enum_type, StringPiece enum_name) { + if (enum_type != nullptr) { + for (int i = 0; i < enum_type->enumvalue_size(); ++i) { + const google::protobuf::EnumValue& enum_value = enum_type->enumvalue(i); + if (enum_value.name() == enum_name) { + return &enum_value; + } + } + } + return nullptr; +} + +const google::protobuf::EnumValue* FindEnumValueByNumberOrNull( + const google::protobuf::Enum* enum_type, int32_t value) { + if (enum_type != nullptr) { + for (int i = 0; i < enum_type->enumvalue_size(); ++i) { + const google::protobuf::EnumValue& enum_value = enum_type->enumvalue(i); + if (enum_value.number() == value) { + return &enum_value; + } + } + } + return nullptr; +} + +const google::protobuf::EnumValue* FindEnumValueByNameWithoutUnderscoreOrNull( + const google::protobuf::Enum* enum_type, StringPiece enum_name) { + if (enum_type != nullptr) { + for (int i = 0; i < enum_type->enumvalue_size(); ++i) { + const google::protobuf::EnumValue& enum_value = enum_type->enumvalue(i); + std::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 (std::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 nullptr; +} + +std::string EnumValueNameToLowerCamelCase(StringPiece input) { + std::string input_string(input); + std::transform(input_string.begin(), input_string.end(), input_string.begin(), + ::tolower); + return ToCamelCase(input_string); +} + +std::string ToCamelCase(StringPiece input) { + bool capitalize_next = false; + bool was_cap = true; + bool is_cap = false; + bool first_word = true; + std::string result; + result.reserve(input.size()); + + for (size_t i = 0; i < input.size(); ++i, was_cap = is_cap) { + is_cap = ascii_isupper(input[i]); + if (input[i] == '_') { + capitalize_next = true; + if (!result.empty()) first_word = false; + continue; + } else if (first_word) { + // Consider when the current character B is capitalized, + // first word ends when: + // 1) following a lowercase: "...aB..." + // 2) followed by a lowercase: "...ABc..." + if (!result.empty() && is_cap && + (!was_cap || + (i + 1 < input.size() && ascii_islower(input[i + 1])))) { + first_word = false; + result.push_back(input[i]); + } else { + result.push_back(ascii_tolower(input[i])); + continue; + } + } else if (capitalize_next) { + capitalize_next = false; + if (ascii_islower(input[i])) { + result.push_back(ascii_toupper(input[i])); + continue; + } else { + result.push_back(input[i]); + continue; + } + } else { + result.push_back(ascii_tolower(input[i])); + } + } + return result; +} + +std::string ToSnakeCase(StringPiece input) { + bool was_not_underscore = false; // Initialize to false for case 1 (below) + bool was_not_cap = false; + std::string result; + result.reserve(input.size() << 1); + + for (size_t i = 0; i < input.size(); ++i) { + if (ascii_isupper(input[i])) { + // Consider when the current character B is capitalized: + // 1) At beginning of input: "B..." => "b..." + // (e.g. "Biscuit" => "biscuit") + // 2) Following a lowercase: "...aB..." => "...a_b..." + // (e.g. "gBike" => "g_bike") + // 3) At the end of input: "...AB" => "...ab" + // (e.g. "GoogleLAB" => "google_lab") + // 4) Followed by a lowercase: "...ABc..." => "...a_bc..." + // (e.g. "GBike" => "g_bike") + if (was_not_underscore && // case 1 out + (was_not_cap || // case 2 in, case 3 out + (i + 1 < input.size() && // case 3 out + ascii_islower(input[i + 1])))) { // case 4 in + // We add an underscore for case 2 and case 4. + result.push_back('_'); + } + result.push_back(ascii_tolower(input[i])); + was_not_underscore = true; + was_not_cap = false; + } else { + result.push_back(input[i]); + was_not_underscore = input[i] != '_'; + was_not_cap = true; + } + } + return result; +} + +std::set<std::string>* well_known_types_ = nullptr; +PROTOBUF_NAMESPACE_ID::internal::once_flag well_known_types_init_; +const char* well_known_types_name_array_[] = { + "google.protobuf.Timestamp", "google.protobuf.Duration", + "google.protobuf.DoubleValue", "google.protobuf.FloatValue", + "google.protobuf.Int64Value", "google.protobuf.UInt64Value", + "google.protobuf.Int32Value", "google.protobuf.UInt32Value", + "google.protobuf.BoolValue", "google.protobuf.StringValue", + "google.protobuf.BytesValue", "google.protobuf.FieldMask"}; + +void DeleteWellKnownTypes() { delete well_known_types_; } + +void InitWellKnownTypes() { + well_known_types_ = new std::set<std::string>; + for (int i = 0; i < GOOGLE_ARRAYSIZE(well_known_types_name_array_); ++i) { + well_known_types_->insert(well_known_types_name_array_[i]); + } + google::protobuf::internal::OnShutdown(&DeleteWellKnownTypes); +} + +bool IsWellKnownType(const std::string& type_name) { + PROTOBUF_NAMESPACE_ID::internal::call_once(well_known_types_init_, + InitWellKnownTypes); + return ContainsKey(*well_known_types_, type_name); +} + +bool IsValidBoolString(StringPiece bool_string) { + return bool_string == "true" || bool_string == "false" || + bool_string == "1" || bool_string == "0"; +} + +bool IsMap(const google::protobuf::Field& field, + const google::protobuf::Type& type) { + return field.cardinality() == google::protobuf::Field::CARDINALITY_REPEATED && + (GetBoolOptionOrDefault(type.options(), "map_entry", false) || + GetBoolOptionOrDefault(type.options(), + "google.protobuf.MessageOptions.map_entry", + false)); +} + +bool IsMessageSetWireFormat(const google::protobuf::Type& type) { + return GetBoolOptionOrDefault(type.options(), "message_set_wire_format", + false) || + GetBoolOptionOrDefault( + type.options(), + "google.protobuf.MessageOptions.message_set_wire_format", false); +} + +std::string DoubleAsString(double value) { + if (value == std::numeric_limits<double>::infinity()) return "Infinity"; + if (value == -std::numeric_limits<double>::infinity()) return "-Infinity"; + if (std::isnan(value)) return "NaN"; + + return SimpleDtoa(value); +} + +std::string FloatAsString(float value) { + if (std::isfinite(value)) return SimpleFtoa(value); + return DoubleAsString(value); +} + +bool SafeStrToFloat(StringPiece str, float* value) { + double double_value; + if (!safe_strtod(str, &double_value)) { + return false; + } + + if (std::isinf(double_value) || std::isnan(double_value)) return false; + + // Fail if the value is not representable in float. + if (double_value > std::numeric_limits<float>::max() || + double_value < -std::numeric_limits<float>::max()) { + return false; + } + + *value = static_cast<float>(double_value); + return true; +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/internal/utility.h b/NorthstarDedicatedTest/include/protobuf/util/internal/utility.h new file mode 100644 index 00000000..69325dec --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/internal/utility.h @@ -0,0 +1,204 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_UTILITY_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_UTILITY_H__ + +#include <cstdint> +#include <memory> +#include <string> +#include <utility> + +#include <stubs/common.h> +#include <stubs/logging.h> +#include <any.pb.h> +#include <type.pb.h> +#include <repeated_field.h> +#include <stubs/strutil.h> +#include <stubs/statusor.h> +#include <stubs/status.h> + +// Must be included last. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// Size of "type.googleapis.com" +static const int64_t kTypeUrlSize = 19; + +// Finds the tech option identified by option_name. Parses the boolean value and +// returns it. +// When the option with the given name is not found, default_value is returned. +PROTOBUF_EXPORT bool GetBoolOptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, bool default_value); + +// Returns int64 option value. If the option isn't found, returns the +// default_value. +PROTOBUF_EXPORT int64_t GetInt64OptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, int64_t default_value); + +// Returns double option value. If the option isn't found, returns the +// default_value. +PROTOBUF_EXPORT double GetDoubleOptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, double default_value); + +// Returns string option value. If the option isn't found, returns the +// default_value. +PROTOBUF_EXPORT std::string GetStringOptionOrDefault( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name, StringPiece default_value); + +// Returns a boolean value contained in Any type. +// TODO(skarvaje): Make these utilities dealing with Any types more generic, +// add more error checking and move to a more public/shareable location so +// others can use. +PROTOBUF_EXPORT bool GetBoolFromAny(const google::protobuf::Any& any); + +// Returns int64 value contained in Any type. +PROTOBUF_EXPORT int64_t GetInt64FromAny(const google::protobuf::Any& any); + +// Returns double value contained in Any type. +PROTOBUF_EXPORT double GetDoubleFromAny(const google::protobuf::Any& any); + +// Returns string value contained in Any type. +PROTOBUF_EXPORT std::string GetStringFromAny(const google::protobuf::Any& any); + +// Returns the type string without the url prefix. e.g.: If the passed type is +// 'type.googleapis.com/tech.type.Bool', the returned value is 'tech.type.Bool'. +PROTOBUF_EXPORT const StringPiece GetTypeWithoutUrl( + StringPiece type_url); + +// Returns the simple_type with the base type url (kTypeServiceBaseUrl) +// prefixed. +// +// E.g: +// GetFullTypeWithUrl("google.protobuf.Timestamp") returns the string +// "type.googleapis.com/google.protobuf.Timestamp". +PROTOBUF_EXPORT const std::string GetFullTypeWithUrl( + StringPiece simple_type); + +// Finds and returns option identified by name and option_name within the +// provided map. Returns nullptr if none found. +const google::protobuf::Option* FindOptionOrNull( + const RepeatedPtrField<google::protobuf::Option>& options, + StringPiece option_name); + +// Finds and returns the field identified by field_name in the passed tech Type +// object. Returns nullptr if none found. +const google::protobuf::Field* FindFieldInTypeOrNull( + const google::protobuf::Type* type, StringPiece field_name); + +// Similar to FindFieldInTypeOrNull, but this looks up fields with given +// json_name. +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_t number); + +// Finds and returns the EnumValue identified by enum_name in the passed tech +// Enum object. Returns nullptr if none found. +const google::protobuf::EnumValue* FindEnumValueByNameOrNull( + const google::protobuf::Enum* enum_type, StringPiece enum_name); + +// Finds and returns the EnumValue identified by value in the passed tech +// Enum object. Returns nullptr if none found. +const google::protobuf::EnumValue* FindEnumValueByNumberOrNull( + const google::protobuf::Enum* enum_type, int32_t value); + +// Finds and returns the EnumValue identified by enum_name without underscore in +// the passed tech Enum object. Returns nullptr 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. +PROTOBUF_EXPORT std::string ToCamelCase(const StringPiece input); + +// Converts enum name string to camel-case and returns it. +std::string EnumValueNameToLowerCamelCase(const StringPiece input); + +// Converts input to snake_case and returns it. +PROTOBUF_EXPORT std::string ToSnakeCase(StringPiece input); + +// Returns true if type_name represents a well-known type. +PROTOBUF_EXPORT bool IsWellKnownType(const std::string& type_name); + +// Returns true if 'bool_string' represents a valid boolean value. Only "true", +// "false", "0" and "1" are allowed. +PROTOBUF_EXPORT bool IsValidBoolString(StringPiece bool_string); + +// Returns true if "field" is a protobuf map field based on its type. +PROTOBUF_EXPORT bool IsMap(const google::protobuf::Field& field, + const google::protobuf::Type& type); + +// Returns true if the given type has special MessageSet wire format. +bool IsMessageSetWireFormat(const google::protobuf::Type& type); + +// Infinity/NaN-aware conversion to string. +PROTOBUF_EXPORT std::string DoubleAsString(double value); +PROTOBUF_EXPORT std::string FloatAsString(float value); + +// Convert from int32, int64, uint32, uint64, double or float to string. +template <typename T> +std::string ValueAsString(T value) { + return StrCat(value); +} + +template <> +inline std::string ValueAsString(float value) { + return FloatAsString(value); +} + +template <> +inline std::string ValueAsString(double value) { + return DoubleAsString(value); +} + +// Converts a string to float. Unlike safe_strtof, conversion will fail if the +// value fits into double but not float (e.g., DBL_MAX). +PROTOBUF_EXPORT bool SafeStrToFloat(StringPiece str, float* value); + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_UTILITY_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/json_format.proto b/NorthstarDedicatedTest/include/protobuf/util/json_format.proto new file mode 100644 index 00000000..7b7100d7 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/json_format.proto @@ -0,0 +1,140 @@ +// 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. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// A proto file we will use for unit testing. + +syntax = "proto2"; + +package protobuf_unittest; + +message TestFlagsAndStrings { + required int32 A = 1; + repeated group RepeatedGroup = 2 { + required string f = 3; + } +} + +message TestBase64ByteArrays { + required bytes a = 1; +} + +message TestJavaScriptJSON { + optional int32 a = 1; + optional float final = 2; + optional string in = 3; + optional string Var = 4; +} + +message TestJavaScriptOrderJSON1 { + optional int32 d = 1; + optional int32 c = 2; + optional bool x = 3; + optional int32 b = 4; + optional int32 a = 5; +} + +message TestJavaScriptOrderJSON2 { + optional int32 d = 1; + optional int32 c = 2; + optional bool x = 3; + optional int32 b = 4; + optional int32 a = 5; + repeated TestJavaScriptOrderJSON1 z = 6; +} + +message TestLargeInt { + required int64 a = 1; + required uint64 b = 2; +} + +message TestNumbers { + enum MyType { + OK = 0; + WARNING = 1; + ERROR = 2; + } + optional MyType a = 1; + optional int32 b = 2; + optional float c = 3; + optional bool d = 4; + optional double e = 5; + optional uint32 f = 6; +} + + +message TestCamelCase { + optional string normal_field = 1; + optional int32 CAPITAL_FIELD = 2; + optional int32 CamelCaseField = 3; +} + +message TestBoolMap { + map<bool, int32> bool_map = 1; +} + +message TestRecursion { + optional int32 value = 1; + optional TestRecursion child = 2; +} + +message TestStringMap { + map<string, string> string_map = 1; +} + +message TestStringSerializer { + optional string scalar_string = 1; + repeated string repeated_string = 2; + map<string, string> string_map = 3; +} + +message TestMessageWithExtension { + extensions 100 to max; +} + +message TestExtension { + extend TestMessageWithExtension { + optional TestExtension ext = 100; + } + optional string value = 1; +} + +enum EnumValue { + PROTOCOL = 0; + BUFFER = 1; + DEFAULT = 2; +} + +message TestDefaultEnumValue { + optional EnumValue enum_value = 1 [default = DEFAULT]; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/json_format_proto3.proto b/NorthstarDedicatedTest/include/protobuf/util/json_format_proto3.proto new file mode 100644 index 00000000..0eec9da3 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/json_format_proto3.proto @@ -0,0 +1,194 @@ +// 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 proto3; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/unittest.proto"; + +option java_package = "com.google.protobuf.util"; +option java_outer_classname = "JsonFormatProto3"; + +enum EnumType { + FOO = 0; + BAR = 1; +} + +message MessageType { + int32 value = 1; +} + +message TestMessage { + bool bool_value = 1; + int32 int32_value = 2; + int64 int64_value = 3; + uint32 uint32_value = 4; + uint64 uint64_value = 5; + float float_value = 6; + double double_value = 7; + string string_value = 8; + bytes bytes_value = 9; + EnumType enum_value = 10; + MessageType message_value = 11; + + repeated bool repeated_bool_value = 21; + repeated int32 repeated_int32_value = 22; + repeated int64 repeated_int64_value = 23; + repeated uint32 repeated_uint32_value = 24; + repeated uint64 repeated_uint64_value = 25; + repeated float repeated_float_value = 26; + repeated double repeated_double_value = 27; + repeated string repeated_string_value = 28; + repeated bytes repeated_bytes_value = 29; + repeated EnumType repeated_enum_value = 30; + repeated MessageType repeated_message_value = 31; +} + +message TestOneof { + // In JSON format oneof fields behave mostly the same as optional + // fields except that: + // 1. Oneof fields have field presence information and will be + // printed if it's set no matter whether it's the default value. + // 2. Multiple oneof fields in the same oneof cannot appear at the + // same time in the input. + oneof oneof_value { + int32 oneof_int32_value = 1; + string oneof_string_value = 2; + bytes oneof_bytes_value = 3; + EnumType oneof_enum_value = 4; + MessageType oneof_message_value = 5; + google.protobuf.NullValue oneof_null_value = 6; + } +} + +message TestMap { + map<bool, int32> bool_map = 1; + map<int32, int32> int32_map = 2; + map<int64, int32> int64_map = 3; + map<uint32, int32> uint32_map = 4; + map<uint64, int32> uint64_map = 5; + map<string, int32> string_map = 6; +} + +message TestNestedMap { + map<bool, int32> bool_map = 1; + map<int32, int32> int32_map = 2; + map<int64, int32> int64_map = 3; + map<uint32, int32> uint32_map = 4; + map<uint64, int32> uint64_map = 5; + map<string, int32> string_map = 6; + map<string, TestNestedMap> map_map = 7; +} + +message TestStringMap { + map<string, string> string_map = 1; +} + +message TestWrapper { + google.protobuf.BoolValue bool_value = 1; + google.protobuf.Int32Value int32_value = 2; + google.protobuf.Int64Value int64_value = 3; + google.protobuf.UInt32Value uint32_value = 4; + google.protobuf.UInt64Value uint64_value = 5; + google.protobuf.FloatValue float_value = 6; + google.protobuf.DoubleValue double_value = 7; + google.protobuf.StringValue string_value = 8; + google.protobuf.BytesValue bytes_value = 9; + + repeated google.protobuf.BoolValue repeated_bool_value = 11; + repeated google.protobuf.Int32Value repeated_int32_value = 12; + repeated google.protobuf.Int64Value repeated_int64_value = 13; + repeated google.protobuf.UInt32Value repeated_uint32_value = 14; + repeated google.protobuf.UInt64Value repeated_uint64_value = 15; + repeated google.protobuf.FloatValue repeated_float_value = 16; + repeated google.protobuf.DoubleValue repeated_double_value = 17; + repeated google.protobuf.StringValue repeated_string_value = 18; + repeated google.protobuf.BytesValue repeated_bytes_value = 19; +} + +message TestTimestamp { + google.protobuf.Timestamp value = 1; + repeated google.protobuf.Timestamp repeated_value = 2; +} + +message TestDuration { + google.protobuf.Duration value = 1; + repeated google.protobuf.Duration repeated_value = 2; +} + +message TestFieldMask { + google.protobuf.FieldMask value = 1; +} + +message TestStruct { + google.protobuf.Struct value = 1; + repeated google.protobuf.Struct repeated_value = 2; +} + +message TestAny { + google.protobuf.Any value = 1; + repeated google.protobuf.Any repeated_value = 2; +} + +message TestValue { + google.protobuf.Value value = 1; + repeated google.protobuf.Value repeated_value = 2; +} + +message TestListValue { + google.protobuf.ListValue value = 1; + repeated google.protobuf.ListValue repeated_value = 2; +} + +message TestBoolValue { + bool bool_value = 1; + map<bool, int32> bool_map = 2; +} + +message TestCustomJsonName { + int32 value = 1 [json_name = "@value"]; +} + +message TestExtensions { + .protobuf_unittest.TestAllExtensions extensions = 1; +} + +message TestEnumValue { + EnumType enum_value1 = 1; + EnumType enum_value2 = 2; + EnumType enum_value3 = 3; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/json_util.cc b/NorthstarDedicatedTest/include/protobuf/util/json_util.cc new file mode 100644 index 00000000..6daa2542 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/json_util.cc @@ -0,0 +1,282 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/json_util.h> + +#include <stubs/common.h> +#include <io/coded_stream.h> +#include <io/zero_copy_stream.h> +#include <stubs/once.h> +#include <util/internal/default_value_objectwriter.h> +#include <util/internal/error_listener.h> +#include <util/internal/json_objectwriter.h> +#include <util/internal/json_stream_parser.h> +#include <util/internal/protostream_objectsource.h> +#include <util/internal/protostream_objectwriter.h> +#include <util/type_resolver.h> +#include <util/type_resolver_util.h> +#include <stubs/bytestream.h> +#include <stubs/status.h> +#include <stubs/strutil.h> +#include <stubs/status_macros.h> + +// clang-format off +#include <port_def.inc> +// clang-format on + +namespace google { +namespace protobuf { +namespace util { + +namespace internal { +ZeroCopyStreamByteSink::~ZeroCopyStreamByteSink() { + if (buffer_size_ > 0) { + stream_->BackUp(buffer_size_); + } +} + +void ZeroCopyStreamByteSink::Append(const char* bytes, size_t len) { + while (true) { + if (len <= buffer_size_) { + memcpy(buffer_, bytes, len); + buffer_ = static_cast<char*>(buffer_) + len; + buffer_size_ -= len; + return; + } + if (buffer_size_ > 0) { + memcpy(buffer_, bytes, buffer_size_); + bytes += buffer_size_; + len -= buffer_size_; + } + if (!stream_->Next(&buffer_, &buffer_size_)) { + // There isn't a way for ByteSink to report errors. + buffer_size_ = 0; + return; + } + } +} +} // namespace internal + +util::Status BinaryToJsonStream(TypeResolver* resolver, + const std::string& type_url, + io::ZeroCopyInputStream* binary_input, + io::ZeroCopyOutputStream* json_output, + const JsonPrintOptions& options) { + io::CodedInputStream in_stream(binary_input); + google::protobuf::Type type; + RETURN_IF_ERROR(resolver->ResolveMessageType(type_url, &type)); + converter::ProtoStreamObjectSource::RenderOptions render_options; + render_options.use_ints_for_enums = options.always_print_enums_as_ints; + render_options.preserve_proto_field_names = + options.preserve_proto_field_names; + converter::ProtoStreamObjectSource proto_source(&in_stream, resolver, type, + render_options); + io::CodedOutputStream out_stream(json_output); + converter::JsonObjectWriter json_writer(options.add_whitespace ? " " : "", + &out_stream); + if (options.always_print_primitive_fields) { + converter::DefaultValueObjectWriter default_value_writer(resolver, type, + &json_writer); + default_value_writer.set_preserve_proto_field_names( + options.preserve_proto_field_names); + default_value_writer.set_print_enums_as_ints( + options.always_print_enums_as_ints); + return proto_source.WriteTo(&default_value_writer); + } else { + return proto_source.WriteTo(&json_writer); + } +} + +util::Status BinaryToJsonString(TypeResolver* resolver, + const std::string& type_url, + const std::string& binary_input, + std::string* json_output, + const JsonPrintOptions& options) { + io::ArrayInputStream input_stream(binary_input.data(), binary_input.size()); + io::StringOutputStream output_stream(json_output); + return BinaryToJsonStream(resolver, type_url, &input_stream, &output_stream, + options); +} + +namespace { +class StatusErrorListener : public converter::ErrorListener { + public: + StatusErrorListener() {} + ~StatusErrorListener() override {} + + util::Status GetStatus() { return status_; } + + void InvalidName(const converter::LocationTrackerInterface& loc, + StringPiece unknown_name, + StringPiece message) override { + std::string loc_string = GetLocString(loc); + if (!loc_string.empty()) { + loc_string.append(" "); + } + status_ = util::InvalidArgumentError( + StrCat(loc_string, unknown_name, ": ", message)); + } + + void InvalidValue(const converter::LocationTrackerInterface& loc, + StringPiece type_name, + StringPiece value) override { + status_ = util::InvalidArgumentError( + StrCat(GetLocString(loc), ": invalid value ", std::string(value), + " for type ", std::string(type_name))); + } + + void MissingField(const converter::LocationTrackerInterface& loc, + StringPiece missing_name) override { + status_ = util::InvalidArgumentError(StrCat( + GetLocString(loc), ": missing field ", std::string(missing_name))); + } + + private: + util::Status status_; + + std::string GetLocString(const converter::LocationTrackerInterface& loc) { + std::string loc_string = loc.ToString(); + StripWhitespace(&loc_string); + if (!loc_string.empty()) { + loc_string = StrCat("(", loc_string, ")"); + } + return loc_string; + } + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(StatusErrorListener); +}; +} // namespace + +util::Status JsonToBinaryStream(TypeResolver* resolver, + const std::string& type_url, + io::ZeroCopyInputStream* json_input, + io::ZeroCopyOutputStream* binary_output, + const JsonParseOptions& options) { + google::protobuf::Type type; + RETURN_IF_ERROR(resolver->ResolveMessageType(type_url, &type)); + internal::ZeroCopyStreamByteSink sink(binary_output); + StatusErrorListener listener; + converter::ProtoStreamObjectWriter::Options proto_writer_options; + proto_writer_options.ignore_unknown_fields = options.ignore_unknown_fields; + proto_writer_options.ignore_unknown_enum_values = + options.ignore_unknown_fields; + proto_writer_options.case_insensitive_enum_parsing = + options.case_insensitive_enum_parsing; + converter::ProtoStreamObjectWriter proto_writer( + resolver, type, &sink, &listener, proto_writer_options); + + converter::JsonStreamParser parser(&proto_writer); + const void* buffer; + int length; + while (json_input->Next(&buffer, &length)) { + if (length == 0) continue; + RETURN_IF_ERROR(parser.Parse( + StringPiece(static_cast<const char*>(buffer), length))); + } + RETURN_IF_ERROR(parser.FinishParse()); + + return listener.GetStatus(); +} + +util::Status JsonToBinaryString(TypeResolver* resolver, + const std::string& type_url, + StringPiece json_input, + std::string* binary_output, + const JsonParseOptions& options) { + io::ArrayInputStream input_stream(json_input.data(), json_input.size()); + io::StringOutputStream output_stream(binary_output); + return JsonToBinaryStream(resolver, type_url, &input_stream, &output_stream, + options); +} + +namespace { +const char* kTypeUrlPrefix = "type.googleapis.com"; +TypeResolver* generated_type_resolver_ = NULL; +PROTOBUF_NAMESPACE_ID::internal::once_flag generated_type_resolver_init_; + +std::string GetTypeUrl(const Message& message) { + return std::string(kTypeUrlPrefix) + "/" + + message.GetDescriptor()->full_name(); +} + +void DeleteGeneratedTypeResolver() { delete generated_type_resolver_; } + +void InitGeneratedTypeResolver() { + generated_type_resolver_ = NewTypeResolverForDescriptorPool( + kTypeUrlPrefix, DescriptorPool::generated_pool()); + ::google::protobuf::internal::OnShutdown(&DeleteGeneratedTypeResolver); +} + +TypeResolver* GetGeneratedTypeResolver() { + PROTOBUF_NAMESPACE_ID::internal::call_once(generated_type_resolver_init_, + InitGeneratedTypeResolver); + return generated_type_resolver_; +} +} // namespace + +util::Status MessageToJsonString(const Message& message, std::string* output, + const JsonOptions& options) { + const DescriptorPool* pool = message.GetDescriptor()->file()->pool(); + TypeResolver* resolver = + pool == DescriptorPool::generated_pool() + ? GetGeneratedTypeResolver() + : NewTypeResolverForDescriptorPool(kTypeUrlPrefix, pool); + util::Status result = + BinaryToJsonString(resolver, GetTypeUrl(message), + message.SerializeAsString(), output, options); + if (pool != DescriptorPool::generated_pool()) { + delete resolver; + } + return result; +} + +util::Status JsonStringToMessage(StringPiece input, Message* message, + const JsonParseOptions& options) { + const DescriptorPool* pool = message->GetDescriptor()->file()->pool(); + TypeResolver* resolver = + pool == DescriptorPool::generated_pool() + ? GetGeneratedTypeResolver() + : NewTypeResolverForDescriptorPool(kTypeUrlPrefix, pool); + std::string binary; + util::Status result = JsonToBinaryString(resolver, GetTypeUrl(*message), + input, &binary, options); + if (result.ok() && !message->ParseFromString(binary)) { + result = util::InvalidArgumentError( + "JSON transcoder produced invalid protobuf output."); + } + if (pool != DescriptorPool::generated_pool()) { + delete resolver; + } + return result; +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/json_util.h b/NorthstarDedicatedTest/include/protobuf/util/json_util.h new file mode 100644 index 00000000..02903dbf --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/json_util.h @@ -0,0 +1,204 @@ +// 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. + +// Utility functions to convert between protobuf binary format and proto3 JSON +// format. +#ifndef GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__ + +#include <message.h> +#include <util/type_resolver.h> +#include <stubs/bytestream.h> +#include <stubs/status.h> +#include <stubs/strutil.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace io { +class ZeroCopyInputStream; +class ZeroCopyOutputStream; +} // namespace io +namespace util { + +struct JsonParseOptions { + // Whether to ignore unknown JSON fields during parsing + bool ignore_unknown_fields; + + // If true, when a lowercase enum value fails to parse, try convert it to + // UPPER_CASE and see if it matches a valid enum. + // WARNING: This option exists only to preserve legacy behavior. Avoid using + // this option. If your enum needs to support different casing, consider using + // allow_alias instead. + bool case_insensitive_enum_parsing; + + JsonParseOptions() + : ignore_unknown_fields(false), + case_insensitive_enum_parsing(false) {} +}; + +struct JsonPrintOptions { + // Whether to add spaces, line breaks and indentation to make the JSON output + // easy to read. + bool add_whitespace; + // Whether to always print primitive fields. By default proto3 primitive + // fields with default values will be omitted in JSON output. For example, an + // int32 field set to 0 will be omitted. Set this flag to true will override + // the default behavior and print primitive fields regardless of their values. + bool always_print_primitive_fields; + // Whether to always print enums as ints. By default they are rendered as + // strings. + bool always_print_enums_as_ints; + // Whether to preserve proto field names + bool preserve_proto_field_names; + + JsonPrintOptions() + : add_whitespace(false), + always_print_primitive_fields(false), + always_print_enums_as_ints(false), + preserve_proto_field_names(false) {} +}; + +// DEPRECATED. Use JsonPrintOptions instead. +typedef JsonPrintOptions JsonOptions; + +// Converts from protobuf message to JSON and appends it to |output|. This is a +// simple wrapper of BinaryToJsonString(). It will use the DescriptorPool of the +// passed-in message to resolve Any types. +PROTOBUF_EXPORT util::Status MessageToJsonString(const Message& message, + std::string* output, + const JsonOptions& options); + +inline util::Status MessageToJsonString(const Message& message, + std::string* output) { + return MessageToJsonString(message, output, JsonOptions()); +} + +// Converts from JSON to protobuf message. This is a simple wrapper of +// JsonStringToBinary(). It will use the DescriptorPool of the passed-in +// message to resolve Any types. +PROTOBUF_EXPORT util::Status JsonStringToMessage( + StringPiece input, Message* message, const JsonParseOptions& options); + +inline util::Status JsonStringToMessage(StringPiece input, + Message* message) { + return JsonStringToMessage(input, message, JsonParseOptions()); +} + +// Converts protobuf binary data to JSON. +// The conversion will fail if: +// 1. TypeResolver fails to resolve a type. +// 2. input is not valid protobuf wire format, or conflicts with the type +// information returned by TypeResolver. +// Note that unknown fields will be discarded silently. +PROTOBUF_EXPORT util::Status BinaryToJsonStream( + TypeResolver* resolver, const std::string& type_url, + io::ZeroCopyInputStream* binary_input, + io::ZeroCopyOutputStream* json_output, const JsonPrintOptions& options); + +inline util::Status BinaryToJsonStream(TypeResolver* resolver, + const std::string& type_url, + io::ZeroCopyInputStream* binary_input, + io::ZeroCopyOutputStream* json_output) { + return BinaryToJsonStream(resolver, type_url, binary_input, json_output, + JsonPrintOptions()); +} + +PROTOBUF_EXPORT util::Status BinaryToJsonString( + TypeResolver* resolver, const std::string& type_url, + const std::string& binary_input, std::string* json_output, + const JsonPrintOptions& options); + +inline util::Status BinaryToJsonString(TypeResolver* resolver, + const std::string& type_url, + const std::string& binary_input, + std::string* json_output) { + return BinaryToJsonString(resolver, type_url, binary_input, json_output, + JsonPrintOptions()); +} + +// Converts JSON data to protobuf binary format. +// The conversion will fail if: +// 1. TypeResolver fails to resolve a type. +// 2. input is not valid JSON format, or conflicts with the type +// information returned by TypeResolver. +PROTOBUF_EXPORT util::Status JsonToBinaryStream( + TypeResolver* resolver, const std::string& type_url, + io::ZeroCopyInputStream* json_input, + io::ZeroCopyOutputStream* binary_output, const JsonParseOptions& options); + +inline util::Status JsonToBinaryStream( + TypeResolver* resolver, const std::string& type_url, + io::ZeroCopyInputStream* json_input, + io::ZeroCopyOutputStream* binary_output) { + return JsonToBinaryStream(resolver, type_url, json_input, binary_output, + JsonParseOptions()); +} + +PROTOBUF_EXPORT util::Status JsonToBinaryString( + TypeResolver* resolver, const std::string& type_url, + StringPiece json_input, std::string* binary_output, + const JsonParseOptions& options); + +inline util::Status JsonToBinaryString(TypeResolver* resolver, + const std::string& type_url, + StringPiece json_input, + std::string* binary_output) { + return JsonToBinaryString(resolver, type_url, json_input, binary_output, + JsonParseOptions()); +} + +namespace internal { +// Internal helper class. Put in the header so we can write unit-tests for it. +class PROTOBUF_EXPORT ZeroCopyStreamByteSink : public strings::ByteSink { + public: + explicit ZeroCopyStreamByteSink(io::ZeroCopyOutputStream* stream) + : stream_(stream), buffer_(NULL), buffer_size_(0) {} + ~ZeroCopyStreamByteSink(); + + void Append(const char* bytes, size_t len) override; + + private: + io::ZeroCopyOutputStream* stream_; + void* buffer_; + int buffer_size_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ZeroCopyStreamByteSink); +}; +} // namespace internal + +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/json_util_test.cc b/NorthstarDedicatedTest/include/protobuf/util/json_util_test.cc new file mode 100644 index 00000000..f29b079c --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/json_util_test.cc @@ -0,0 +1,668 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/json_util.h> + +#include <cstdint> +#include <list> +#include <string> + +#include <io/zero_copy_stream.h> +#include <io/zero_copy_stream_impl.h> +#include <descriptor_database.h> +#include <dynamic_message.h> +#include <util/internal/testdata/maps.pb.h> +#include <util/json_format.pb.h> +#include <util/json_format_proto3.pb.h> +#include <util/type_resolver.h> +#include <util/type_resolver_util.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { +namespace { + +using proto3::BAR; +using proto3::FOO; +using proto3::TestAny; +using proto3::TestEnumValue; +using proto3::TestMap; +using proto3::TestMessage; +using proto3::TestOneof; +using proto_util_converter::testing::MapIn; + +// As functions defined in json_util.h are just thin wrappers around the +// JSON conversion code in //net/proto2/util/converter, in this test we +// only cover some very basic cases to make sure the wrappers have forwarded +// parameters to the underlying implementation correctly. More detailed +// tests are contained in the //net/proto2/util/converter directory. +class JsonUtilTest : public ::testing::Test { + protected: + JsonUtilTest() {} + + std::string ToJson(const Message& message, const JsonPrintOptions& options) { + std::string result; + GOOGLE_CHECK_OK(MessageToJsonString(message, &result, options)); + return result; + } + + bool FromJson(const std::string& json, Message* message, + const JsonParseOptions& options) { + return JsonStringToMessage(json, message, options).ok(); + } + + bool FromJson(const std::string& json, Message* message) { + return FromJson(json, message, JsonParseOptions()); + } + + std::unique_ptr<TypeResolver> resolver_; +}; + +TEST_F(JsonUtilTest, TestWhitespaces) { + TestMessage m; + m.mutable_message_value(); + + JsonPrintOptions options; + EXPECT_EQ("{\"messageValue\":{}}", ToJson(m, options)); + options.add_whitespace = true; + EXPECT_EQ( + "{\n" + " \"messageValue\": {}\n" + "}\n", + ToJson(m, options)); +} + +TEST_F(JsonUtilTest, TestDefaultValues) { + TestMessage m; + JsonPrintOptions options; + EXPECT_EQ("{}", ToJson(m, options)); + options.always_print_primitive_fields = true; + EXPECT_EQ( + "{\"boolValue\":false," + "\"int32Value\":0," + "\"int64Value\":\"0\"," + "\"uint32Value\":0," + "\"uint64Value\":\"0\"," + "\"floatValue\":0," + "\"doubleValue\":0," + "\"stringValue\":\"\"," + "\"bytesValue\":\"\"," + "\"enumValue\":\"FOO\"," + "\"repeatedBoolValue\":[]," + "\"repeatedInt32Value\":[]," + "\"repeatedInt64Value\":[]," + "\"repeatedUint32Value\":[]," + "\"repeatedUint64Value\":[]," + "\"repeatedFloatValue\":[]," + "\"repeatedDoubleValue\":[]," + "\"repeatedStringValue\":[]," + "\"repeatedBytesValue\":[]," + "\"repeatedEnumValue\":[]," + "\"repeatedMessageValue\":[]" + "}", + ToJson(m, options)); + + options.always_print_primitive_fields = true; + m.set_string_value("i am a test string value"); + m.set_bytes_value("i am a test bytes value"); + EXPECT_EQ( + "{\"boolValue\":false," + "\"int32Value\":0," + "\"int64Value\":\"0\"," + "\"uint32Value\":0," + "\"uint64Value\":\"0\"," + "\"floatValue\":0," + "\"doubleValue\":0," + "\"stringValue\":\"i am a test string value\"," + "\"bytesValue\":\"aSBhbSBhIHRlc3QgYnl0ZXMgdmFsdWU=\"," + "\"enumValue\":\"FOO\"," + "\"repeatedBoolValue\":[]," + "\"repeatedInt32Value\":[]," + "\"repeatedInt64Value\":[]," + "\"repeatedUint32Value\":[]," + "\"repeatedUint64Value\":[]," + "\"repeatedFloatValue\":[]," + "\"repeatedDoubleValue\":[]," + "\"repeatedStringValue\":[]," + "\"repeatedBytesValue\":[]," + "\"repeatedEnumValue\":[]," + "\"repeatedMessageValue\":[]" + "}", + ToJson(m, options)); + + options.preserve_proto_field_names = true; + m.set_string_value("i am a test string value"); + m.set_bytes_value("i am a test bytes value"); + EXPECT_EQ( + "{\"bool_value\":false," + "\"int32_value\":0," + "\"int64_value\":\"0\"," + "\"uint32_value\":0," + "\"uint64_value\":\"0\"," + "\"float_value\":0," + "\"double_value\":0," + "\"string_value\":\"i am a test string value\"," + "\"bytes_value\":\"aSBhbSBhIHRlc3QgYnl0ZXMgdmFsdWU=\"," + "\"enum_value\":\"FOO\"," + "\"repeated_bool_value\":[]," + "\"repeated_int32_value\":[]," + "\"repeated_int64_value\":[]," + "\"repeated_uint32_value\":[]," + "\"repeated_uint64_value\":[]," + "\"repeated_float_value\":[]," + "\"repeated_double_value\":[]," + "\"repeated_string_value\":[]," + "\"repeated_bytes_value\":[]," + "\"repeated_enum_value\":[]," + "\"repeated_message_value\":[]" + "}", + ToJson(m, options)); +} + +TEST_F(JsonUtilTest, TestPreserveProtoFieldNames) { + TestMessage m; + m.mutable_message_value(); + + JsonPrintOptions options; + options.preserve_proto_field_names = true; + EXPECT_EQ("{\"message_value\":{}}", ToJson(m, options)); +} + +TEST_F(JsonUtilTest, TestAlwaysPrintEnumsAsInts) { + TestMessage orig; + orig.set_enum_value(proto3::BAR); + orig.add_repeated_enum_value(proto3::FOO); + orig.add_repeated_enum_value(proto3::BAR); + + JsonPrintOptions print_options; + print_options.always_print_enums_as_ints = true; + + std::string expected_json = "{\"enumValue\":1,\"repeatedEnumValue\":[0,1]}"; + EXPECT_EQ(expected_json, ToJson(orig, print_options)); + + TestMessage parsed; + JsonParseOptions parse_options; + ASSERT_TRUE(FromJson(expected_json, &parsed, parse_options)); + + EXPECT_EQ(proto3::BAR, parsed.enum_value()); + EXPECT_EQ(2, parsed.repeated_enum_value_size()); + EXPECT_EQ(proto3::FOO, parsed.repeated_enum_value(0)); + EXPECT_EQ(proto3::BAR, parsed.repeated_enum_value(1)); +} + +TEST_F(JsonUtilTest, TestPrintEnumsAsIntsWithDefaultValue) { + TestEnumValue orig; + // orig.set_enum_value1(proto3::FOO) + orig.set_enum_value2(proto3::FOO); + orig.set_enum_value3(proto3::BAR); + + JsonPrintOptions print_options; + print_options.always_print_enums_as_ints = true; + print_options.always_print_primitive_fields = true; + + std::string expected_json = + "{\"enumValue1\":0,\"enumValue2\":0,\"enumValue3\":1}"; + EXPECT_EQ(expected_json, ToJson(orig, print_options)); + + TestEnumValue parsed; + JsonParseOptions parse_options; + ASSERT_TRUE(FromJson(expected_json, &parsed, parse_options)); + + EXPECT_EQ(proto3::FOO, parsed.enum_value1()); + EXPECT_EQ(proto3::FOO, parsed.enum_value2()); + EXPECT_EQ(proto3::BAR, parsed.enum_value3()); +} + +TEST_F(JsonUtilTest, TestPrintProto2EnumAsIntWithDefaultValue) { + protobuf_unittest::TestDefaultEnumValue orig; + + JsonPrintOptions print_options; + // use enum as int + print_options.always_print_enums_as_ints = true; + print_options.always_print_primitive_fields = true; + + // result should be int rather than string + std::string expected_json = "{\"enumValue\":2}"; + EXPECT_EQ(expected_json, ToJson(orig, print_options)); + + protobuf_unittest::TestDefaultEnumValue parsed; + JsonParseOptions parse_options; + ASSERT_TRUE(FromJson(expected_json, &parsed, parse_options)); + + EXPECT_EQ(protobuf_unittest::DEFAULT, parsed.enum_value()); +} + +TEST_F(JsonUtilTest, ParseMessage) { + // Some random message but good enough to verify that the parsing wrapper + // functions are working properly. + std::string input = + "{\n" + " \"int32Value\": 1024,\n" + " \"repeatedInt32Value\": [1, 2],\n" + " \"messageValue\": {\n" + " \"value\": 2048\n" + " },\n" + " \"repeatedMessageValue\": [\n" + " {\"value\": 40}, {\"value\": 96}\n" + " ]\n" + "}\n"; + JsonParseOptions options; + TestMessage m; + ASSERT_TRUE(FromJson(input, &m, options)); + EXPECT_EQ(1024, m.int32_value()); + ASSERT_EQ(2, m.repeated_int32_value_size()); + EXPECT_EQ(1, m.repeated_int32_value(0)); + EXPECT_EQ(2, m.repeated_int32_value(1)); + EXPECT_EQ(2048, m.message_value().value()); + ASSERT_EQ(2, m.repeated_message_value_size()); + EXPECT_EQ(40, m.repeated_message_value(0).value()); + EXPECT_EQ(96, m.repeated_message_value(1).value()); +} + +TEST_F(JsonUtilTest, ParseMap) { + TestMap message; + (*message.mutable_string_map())["hello"] = 1234; + JsonPrintOptions print_options; + JsonParseOptions parse_options; + EXPECT_EQ("{\"stringMap\":{\"hello\":1234}}", ToJson(message, print_options)); + TestMap other; + ASSERT_TRUE(FromJson(ToJson(message, print_options), &other, parse_options)); + EXPECT_EQ(message.DebugString(), other.DebugString()); +} + +TEST_F(JsonUtilTest, ParsePrimitiveMapIn) { + MapIn message; + JsonPrintOptions print_options; + print_options.always_print_primitive_fields = true; + JsonParseOptions parse_options; + EXPECT_EQ("{\"other\":\"\",\"things\":[],\"mapInput\":{},\"mapAny\":{}}", + ToJson(message, print_options)); + MapIn other; + ASSERT_TRUE(FromJson(ToJson(message, print_options), &other, parse_options)); + EXPECT_EQ(message.DebugString(), other.DebugString()); +} + +TEST_F(JsonUtilTest, PrintPrimitiveOneof) { + TestOneof message; + JsonPrintOptions options; + options.always_print_primitive_fields = true; + message.mutable_oneof_message_value(); + EXPECT_EQ("{\"oneofMessageValue\":{\"value\":0}}", ToJson(message, options)); + + message.set_oneof_int32_value(1); + EXPECT_EQ("{\"oneofInt32Value\":1}", ToJson(message, options)); +} + +TEST_F(JsonUtilTest, TestParseIgnoreUnknownFields) { + TestMessage m; + JsonParseOptions options; + options.ignore_unknown_fields = true; + EXPECT_TRUE(FromJson("{\"unknownName\":0}", &m, options)); +} + +TEST_F(JsonUtilTest, TestParseErrors) { + TestMessage m; + JsonParseOptions options; + // Parsing should fail if the field name can not be recognized. + EXPECT_FALSE(FromJson("{\"unknownName\":0}", &m, options)); + // Parsing should fail if the value is invalid. + EXPECT_FALSE(FromJson("{\"int32Value\":2147483648}", &m, options)); +} + +TEST_F(JsonUtilTest, TestDynamicMessage) { + // Some random message but good enough to test the wrapper functions. + std::string input = + "{\n" + " \"int32Value\": 1024,\n" + " \"repeatedInt32Value\": [1, 2],\n" + " \"messageValue\": {\n" + " \"value\": 2048\n" + " },\n" + " \"repeatedMessageValue\": [\n" + " {\"value\": 40}, {\"value\": 96}\n" + " ]\n" + "}\n"; + + // Create a new DescriptorPool with the same protos as the generated one. + DescriptorPoolDatabase database(*DescriptorPool::generated_pool()); + DescriptorPool pool(&database); + // A dynamic version of the test proto. + DynamicMessageFactory factory; + std::unique_ptr<Message> message( + factory.GetPrototype(pool.FindMessageTypeByName("proto3.TestMessage")) + ->New()); + EXPECT_TRUE(FromJson(input, message.get())); + + // Convert to generated message for easy inspection. + TestMessage generated; + EXPECT_TRUE(generated.ParseFromString(message->SerializeAsString())); + EXPECT_EQ(1024, generated.int32_value()); + ASSERT_EQ(2, generated.repeated_int32_value_size()); + EXPECT_EQ(1, generated.repeated_int32_value(0)); + EXPECT_EQ(2, generated.repeated_int32_value(1)); + EXPECT_EQ(2048, generated.message_value().value()); + ASSERT_EQ(2, generated.repeated_message_value_size()); + EXPECT_EQ(40, generated.repeated_message_value(0).value()); + EXPECT_EQ(96, generated.repeated_message_value(1).value()); + + JsonOptions options; + EXPECT_EQ(ToJson(generated, options), ToJson(*message, options)); +} + +TEST_F(JsonUtilTest, TestParsingUnknownAnyFields) { + std::string input = + "{\n" + " \"value\": {\n" + " \"@type\": \"type.googleapis.com/proto3.TestMessage\",\n" + " \"unknown_field\": \"UNKNOWN_VALUE\",\n" + " \"string_value\": \"expected_value\"\n" + " }\n" + "}"; + + TestAny m; + JsonParseOptions options; + EXPECT_FALSE(FromJson(input, &m, options)); + + options.ignore_unknown_fields = true; + EXPECT_TRUE(FromJson(input, &m, options)); + + TestMessage t; + EXPECT_TRUE(m.value().UnpackTo(&t)); + EXPECT_EQ("expected_value", t.string_value()); +} + +TEST_F(JsonUtilTest, TestParsingUnknownEnumsProto2) { + std::string input = + "{\n" + " \"a\": \"UNKNOWN_VALUE\"\n" + "}"; + protobuf_unittest::TestNumbers m; + JsonParseOptions options; + EXPECT_FALSE(FromJson(input, &m, options)); + + options.ignore_unknown_fields = true; + EXPECT_TRUE(FromJson(input, &m, options)); + EXPECT_FALSE(m.has_a()); +} + +TEST_F(JsonUtilTest, TestParsingUnknownEnumsProto3) { + TestMessage m; + { + JsonParseOptions options; + ASSERT_FALSE(options.ignore_unknown_fields); + std::string input = + "{\n" + " \"enum_value\":\"UNKNOWN_VALUE\"\n" + "}"; + m.set_enum_value(proto3::BAR); + EXPECT_FALSE(FromJson(input, &m, options)); + ASSERT_EQ(proto3::BAR, m.enum_value()); // Keep previous value + + options.ignore_unknown_fields = true; + EXPECT_TRUE(FromJson(input, &m, options)); + EXPECT_EQ(0, m.enum_value()); // Unknown enum value must be decoded as 0 + } + // Integer values are read as usual + { + JsonParseOptions options; + std::string input = + "{\n" + " \"enum_value\":12345\n" + "}"; + m.set_enum_value(proto3::BAR); + EXPECT_TRUE(FromJson(input, &m, options)); + ASSERT_EQ(12345, m.enum_value()); + + options.ignore_unknown_fields = true; + EXPECT_TRUE(FromJson(input, &m, options)); + EXPECT_EQ(12345, m.enum_value()); + } + + // Trying to pass an object as an enum field value is always treated as an + // error + { + JsonParseOptions options; + std::string input = + "{\n" + " \"enum_value\":{}\n" + "}"; + options.ignore_unknown_fields = true; + EXPECT_FALSE(FromJson(input, &m, options)); + options.ignore_unknown_fields = false; + EXPECT_FALSE(FromJson(input, &m, options)); + } + // Trying to pass an array as an enum field value is always treated as an + // error + { + JsonParseOptions options; + std::string input = + "{\n" + " \"enum_value\":[]\n" + "}"; + EXPECT_FALSE(FromJson(input, &m, options)); + options.ignore_unknown_fields = true; + EXPECT_FALSE(FromJson(input, &m, options)); + } +} + +TEST_F(JsonUtilTest, TestParsingEnumIgnoreCase) { + TestMessage m; + { + JsonParseOptions options; + std::string input = + "{\n" + " \"enum_value\":\"bar\"\n" + "}"; + m.set_enum_value(proto3::FOO); + EXPECT_FALSE(FromJson(input, &m, options)); + // Default behavior is case-sensitive, so keep previous value. + ASSERT_EQ(proto3::FOO, m.enum_value()); + } + { + JsonParseOptions options; + options.case_insensitive_enum_parsing = false; + std::string input = + "{\n" + " \"enum_value\":\"bar\"\n" + "}"; + m.set_enum_value(proto3::FOO); + EXPECT_FALSE(FromJson(input, &m, options)); + ASSERT_EQ(proto3::FOO, m.enum_value()); // Keep previous value + } + { + JsonParseOptions options; + options.case_insensitive_enum_parsing = true; + std::string input = + "{\n" + " \"enum_value\":\"bar\"\n" + "}"; + m.set_enum_value(proto3::FOO); + EXPECT_TRUE(FromJson(input, &m, options)); + ASSERT_EQ(proto3::BAR, m.enum_value()); + } +} + +typedef std::pair<char*, int> Segment; +// A ZeroCopyOutputStream that writes to multiple buffers. +class SegmentedZeroCopyOutputStream : public io::ZeroCopyOutputStream { + public: + explicit SegmentedZeroCopyOutputStream(std::list<Segment> segments) + : segments_(segments), + last_segment_(static_cast<char*>(NULL), 0), + byte_count_(0) {} + + bool Next(void** buffer, int* length) override { + if (segments_.empty()) { + return false; + } + last_segment_ = segments_.front(); + segments_.pop_front(); + *buffer = last_segment_.first; + *length = last_segment_.second; + byte_count_ += *length; + return true; + } + + void BackUp(int length) override { + GOOGLE_CHECK(length <= last_segment_.second); + segments_.push_front( + Segment(last_segment_.first + last_segment_.second - length, length)); + last_segment_ = Segment(last_segment_.first, last_segment_.second - length); + byte_count_ -= length; + } + + int64_t ByteCount() const override { return byte_count_; } + + private: + std::list<Segment> segments_; + Segment last_segment_; + int64_t byte_count_; +}; + +// This test splits the output buffer and also the input data into multiple +// segments and checks that the implementation of ZeroCopyStreamByteSink +// handles all possible cases correctly. +TEST(ZeroCopyStreamByteSinkTest, TestAllInputOutputPatterns) { + static const int kOutputBufferLength = 10; + // An exhaustive test takes too long, skip some combinations to make the test + // run faster. + static const int kSkippedPatternCount = 7; + + char buffer[kOutputBufferLength]; + for (int split_pattern = 0; split_pattern < (1 << (kOutputBufferLength - 1)); + split_pattern += kSkippedPatternCount) { + // Split the buffer into small segments according to the split_pattern. + std::list<Segment> segments; + int segment_start = 0; + for (int i = 0; i < kOutputBufferLength - 1; ++i) { + if (split_pattern & (1 << i)) { + segments.push_back( + Segment(buffer + segment_start, i - segment_start + 1)); + segment_start = i + 1; + } + } + segments.push_back( + Segment(buffer + segment_start, kOutputBufferLength - segment_start)); + + // Write exactly 10 bytes through the ByteSink. + std::string input_data = "0123456789"; + for (int input_pattern = 0; input_pattern < (1 << (input_data.size() - 1)); + input_pattern += kSkippedPatternCount) { + memset(buffer, 0, sizeof(buffer)); + { + SegmentedZeroCopyOutputStream output_stream(segments); + internal::ZeroCopyStreamByteSink byte_sink(&output_stream); + int start = 0; + for (int j = 0; j < input_data.length() - 1; ++j) { + if (input_pattern & (1 << j)) { + byte_sink.Append(&input_data[start], j - start + 1); + start = j + 1; + } + } + byte_sink.Append(&input_data[start], input_data.length() - start); + } + EXPECT_EQ(input_data, std::string(buffer, input_data.length())); + } + + // Write only 9 bytes through the ByteSink. + input_data = "012345678"; + for (int input_pattern = 0; input_pattern < (1 << (input_data.size() - 1)); + input_pattern += kSkippedPatternCount) { + memset(buffer, 0, sizeof(buffer)); + { + SegmentedZeroCopyOutputStream output_stream(segments); + internal::ZeroCopyStreamByteSink byte_sink(&output_stream); + int start = 0; + for (int j = 0; j < input_data.length() - 1; ++j) { + if (input_pattern & (1 << j)) { + byte_sink.Append(&input_data[start], j - start + 1); + start = j + 1; + } + } + byte_sink.Append(&input_data[start], input_data.length() - start); + } + EXPECT_EQ(input_data, std::string(buffer, input_data.length())); + EXPECT_EQ(0, buffer[input_data.length()]); + } + + // Write 11 bytes through the ByteSink. The extra byte will just + // be ignored. + input_data = "0123456789A"; + for (int input_pattern = 0; input_pattern < (1 << (input_data.size() - 1)); + input_pattern += kSkippedPatternCount) { + memset(buffer, 0, sizeof(buffer)); + { + SegmentedZeroCopyOutputStream output_stream(segments); + internal::ZeroCopyStreamByteSink byte_sink(&output_stream); + int start = 0; + for (int j = 0; j < input_data.length() - 1; ++j) { + if (input_pattern & (1 << j)) { + byte_sink.Append(&input_data[start], j - start + 1); + start = j + 1; + } + } + byte_sink.Append(&input_data[start], input_data.length() - start); + } + EXPECT_EQ(input_data.substr(0, kOutputBufferLength), + std::string(buffer, kOutputBufferLength)); + } + } +} + +TEST_F(JsonUtilTest, TestWrongJsonInput) { + const char json[] = "{\"unknown_field\":\"some_value\"}"; + io::ArrayInputStream input_stream(json, strlen(json)); + char proto_buffer[10000]; + io::ArrayOutputStream output_stream(proto_buffer, sizeof(proto_buffer)); + std::string message_type = "type.googleapis.com/proto3.TestMessage"; + TypeResolver* resolver = NewTypeResolverForDescriptorPool( + "type.googleapis.com", DescriptorPool::generated_pool()); + + auto result_status = util::JsonToBinaryStream(resolver, message_type, + &input_stream, &output_stream); + + delete resolver; + + EXPECT_FALSE(result_status.ok()); + EXPECT_TRUE(util::IsInvalidArgument(result_status)); +} + +TEST_F(JsonUtilTest, HtmlEscape) { + TestMessage m; + m.set_string_value("</script>"); + JsonPrintOptions options; + EXPECT_EQ("{\"stringValue\":\"\\u003c/script\\u003e\"}", ToJson(m, options)); +} + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/message_differencer.cc b/NorthstarDedicatedTest/include/protobuf/util/message_differencer.cc new file mode 100644 index 00000000..61019a5e --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/message_differencer.cc @@ -0,0 +1,2222 @@ +// 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. + +// Author: jschorr@google.com (Joseph Schorr) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. + +#include <util/message_differencer.h> + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <functional> +#include <limits> +#include <memory> +#include <utility> + +#include <stubs/logging.h> +#include <stubs/common.h> +#include <stubs/stringprintf.h> +#include <io/printer.h> +#include <io/zero_copy_stream.h> +#include <io/zero_copy_stream_impl.h> +#include <descriptor.pb.h> +#include <descriptor.h> +#include <dynamic_message.h> +#include <generated_enum_reflection.h> +#include <map_field.h> +#include <message.h> +#include <text_format.h> +#include <util/field_comparator.h> +#include <stubs/strutil.h> + +// Always include as last one, otherwise it can break compilation +#include <port_def.inc> + +namespace google { +namespace protobuf { + +namespace util { + +// A reporter to report the total number of diffs. +// TODO(ykzhu): we can improve this to take into account the value differencers. +class NumDiffsReporter : public google::protobuf::util::MessageDifferencer::Reporter { + public: + NumDiffsReporter() : num_diffs_(0) {} + + // Returns the total number of diffs. + int32_t GetNumDiffs() const { return num_diffs_; } + void Reset() { num_diffs_ = 0; } + + // Report that a field has been added into Message2. + void ReportAdded( + const google::protobuf::Message& /* message1 */, + const google::protobuf::Message& /* message2 */, + const std::vector<google::protobuf::util::MessageDifferencer::SpecificField>& + /*field_path*/) override { + ++num_diffs_; + } + + // Report that a field has been deleted from Message1. + void ReportDeleted( + const google::protobuf::Message& /* message1 */, + const google::protobuf::Message& /* message2 */, + const std::vector<google::protobuf::util::MessageDifferencer::SpecificField>& + /*field_path*/) override { + ++num_diffs_; + } + + // Report that the value of a field has been modified. + void ReportModified( + const google::protobuf::Message& /* message1 */, + const google::protobuf::Message& /* message2 */, + const std::vector<google::protobuf::util::MessageDifferencer::SpecificField>& + /*field_path*/) override { + ++num_diffs_; + } + + private: + int32_t num_diffs_; +}; + +// When comparing a repeated field as map, MultipleFieldMapKeyComparator can +// be used to specify multiple fields as key for key comparison. +// Two elements of a repeated field will be regarded as having the same key +// iff they have the same value for every specified key field. +// Note that you can also specify only one field as key. +class MessageDifferencer::MultipleFieldsMapKeyComparator + : public MessageDifferencer::MapKeyComparator { + public: + MultipleFieldsMapKeyComparator( + MessageDifferencer* message_differencer, + const std::vector<std::vector<const FieldDescriptor*> >& key_field_paths) + : message_differencer_(message_differencer), + key_field_paths_(key_field_paths) { + GOOGLE_CHECK(!key_field_paths_.empty()); + for (const auto& path : key_field_paths_) { + GOOGLE_CHECK(!path.empty()); + } + } + MultipleFieldsMapKeyComparator(MessageDifferencer* message_differencer, + const FieldDescriptor* key) + : message_differencer_(message_differencer) { + std::vector<const FieldDescriptor*> key_field_path; + key_field_path.push_back(key); + key_field_paths_.push_back(key_field_path); + } + bool IsMatch(const Message& message1, const Message& message2, + const std::vector<SpecificField>& parent_fields) const override { + for (const auto& path : key_field_paths_) { + if (!IsMatchInternal(message1, message2, parent_fields, path, 0)) { + return false; + } + } + return true; + } + + private: + bool IsMatchInternal( + const Message& message1, const Message& message2, + const std::vector<SpecificField>& parent_fields, + const std::vector<const FieldDescriptor*>& key_field_path, + int path_index) const { + const FieldDescriptor* field = key_field_path[path_index]; + std::vector<SpecificField> current_parent_fields(parent_fields); + if (path_index == static_cast<int64_t>(key_field_path.size() - 1)) { + if (field->is_map()) { + return message_differencer_->CompareMapField(message1, message2, field, + ¤t_parent_fields); + } else if (field->is_repeated()) { + return message_differencer_->CompareRepeatedField( + message1, message2, field, ¤t_parent_fields); + } else { + return message_differencer_->CompareFieldValueUsingParentFields( + message1, message2, field, -1, -1, ¤t_parent_fields); + } + } else { + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + bool has_field1 = reflection1->HasField(message1, field); + bool has_field2 = reflection2->HasField(message2, field); + if (!has_field1 && !has_field2) { + return true; + } + if (has_field1 != has_field2) { + return false; + } + SpecificField specific_field; + specific_field.field = field; + current_parent_fields.push_back(specific_field); + return IsMatchInternal(reflection1->GetMessage(message1, field), + reflection2->GetMessage(message2, field), + current_parent_fields, key_field_path, + path_index + 1); + } + } + MessageDifferencer* message_differencer_; + std::vector<std::vector<const FieldDescriptor*> > key_field_paths_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MultipleFieldsMapKeyComparator); +}; + +// Preserve the order when treating repeated field as SMART_LIST. The current +// implementation is to find the longest matching sequence from the first +// element. The optimal solution requires to use //util/diff/lcs.h, which is +// not open sourced yet. Overwrite this method if you want to have that. +// TODO(ykzhu): change to use LCS once it is open sourced. +void MatchIndicesPostProcessorForSmartList(std::vector<int>* match_list1, + std::vector<int>* match_list2) { + int last_matched_index = -1; + for (size_t i = 0; i < match_list1->size(); ++i) { + if (match_list1->at(i) < 0) { + continue; + } + if (last_matched_index < 0 || match_list1->at(i) > last_matched_index) { + last_matched_index = match_list1->at(i); + } else { + match_list2->at(match_list1->at(i)) = -1; + match_list1->at(i) = -1; + } + } +} + +void AddSpecificIndex( + google::protobuf::util::MessageDifferencer::SpecificField* specific_field, + const Message& message, const FieldDescriptor* field, int index) { + if (field->is_map()) { + const Reflection* reflection = message.GetReflection(); + specific_field->map_entry1 = + &reflection->GetRepeatedMessage(message, field, index); + } + specific_field->index = index; +} + +void AddSpecificNewIndex( + google::protobuf::util::MessageDifferencer::SpecificField* specific_field, + const Message& message, const FieldDescriptor* field, int index) { + if (field->is_map()) { + const Reflection* reflection = message.GetReflection(); + specific_field->map_entry2 = + &reflection->GetRepeatedMessage(message, field, index); + } + specific_field->new_index = index; +} + +MessageDifferencer::MapEntryKeyComparator::MapEntryKeyComparator( + MessageDifferencer* message_differencer) + : message_differencer_(message_differencer) {} + +bool MessageDifferencer::MapEntryKeyComparator::IsMatch( + const Message& message1, const Message& message2, + const std::vector<SpecificField>& parent_fields) const { + // Map entry has its key in the field with tag 1. See the comment for + // map_entry in MessageOptions. + const FieldDescriptor* key = message1.GetDescriptor()->FindFieldByNumber(1); + // If key is not present in message1 and we're doing partial comparison or if + // map key is explicitly ignored treat the field as set instead, + const bool treat_as_set = + (message_differencer_->scope() == PARTIAL && + !message1.GetReflection()->HasField(message1, key)) || + message_differencer_->IsIgnored(message1, message2, key, parent_fields); + + std::vector<SpecificField> current_parent_fields(parent_fields); + if (treat_as_set) { + return message_differencer_->Compare(message1, message2, + ¤t_parent_fields); + } + return message_differencer_->CompareFieldValueUsingParentFields( + message1, message2, key, -1, -1, ¤t_parent_fields); +} + +bool MessageDifferencer::Equals(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + + return differencer.Compare(message1, message2); +} + +bool MessageDifferencer::Equivalent(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + differencer.set_message_field_comparison(MessageDifferencer::EQUIVALENT); + + return differencer.Compare(message1, message2); +} + +bool MessageDifferencer::ApproximatelyEquals(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + differencer.set_float_comparison(MessageDifferencer::APPROXIMATE); + + return differencer.Compare(message1, message2); +} + +bool MessageDifferencer::ApproximatelyEquivalent(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + differencer.set_message_field_comparison(MessageDifferencer::EQUIVALENT); + differencer.set_float_comparison(MessageDifferencer::APPROXIMATE); + + return differencer.Compare(message1, message2); +} + +// =========================================================================== + +MessageDifferencer::MessageDifferencer() + : reporter_(NULL), + message_field_comparison_(EQUAL), + scope_(FULL), + repeated_field_comparison_(AS_LIST), + map_entry_key_comparator_(this), + report_matches_(false), + report_moves_(true), + report_ignores_(true), + output_string_(nullptr), + match_indices_for_smart_list_callback_( + MatchIndicesPostProcessorForSmartList) {} + +MessageDifferencer::~MessageDifferencer() { + for (MapKeyComparator* comparator : owned_key_comparators_) { + delete comparator; + } + for (IgnoreCriteria* criteria : ignore_criteria_) { + delete criteria; + } +} + +void MessageDifferencer::set_field_comparator(FieldComparator* comparator) { + GOOGLE_CHECK(comparator) << "Field comparator can't be NULL."; + field_comparator_kind_ = kFCBase; + field_comparator_.base = comparator; +} + +#ifdef PROTOBUF_FUTURE_BREAKING_CHANGES +void MessageDifferencer::set_field_comparator( + DefaultFieldComparator* comparator) { + GOOGLE_CHECK(comparator) << "Field comparator can't be NULL."; + field_comparator_kind_ = kFCDefault; + field_comparator_.default_impl = comparator; +} +#endif // PROTOBUF_FUTURE_BREAKING_CHANGES + +void MessageDifferencer::set_message_field_comparison( + MessageFieldComparison comparison) { + message_field_comparison_ = comparison; +} + +void MessageDifferencer::set_scope(Scope scope) { scope_ = scope; } + +MessageDifferencer::Scope MessageDifferencer::scope() { return scope_; } + +void MessageDifferencer::set_float_comparison(FloatComparison comparison) { + default_field_comparator_.set_float_comparison( + comparison == EXACT ? DefaultFieldComparator::EXACT + : DefaultFieldComparator::APPROXIMATE); +} + +void MessageDifferencer::set_repeated_field_comparison( + RepeatedFieldComparison comparison) { + repeated_field_comparison_ = comparison; +} + +MessageDifferencer::RepeatedFieldComparison +MessageDifferencer::repeated_field_comparison() { + return repeated_field_comparison_; +} + +void MessageDifferencer::CheckRepeatedFieldComparisons( + const FieldDescriptor* field, + const RepeatedFieldComparison& new_comparison) { + GOOGLE_CHECK(field->is_repeated()) + << "Field must be repeated: " << field->full_name(); + const MapKeyComparator* key_comparator = GetMapKeyComparator(field); + GOOGLE_CHECK(key_comparator == NULL) + << "Cannot treat this repeated field as both MAP and " << new_comparison + << " for comparison. Field name is: " << field->full_name(); +} + +void MessageDifferencer::TreatAsSet(const FieldDescriptor* field) { + CheckRepeatedFieldComparisons(field, AS_SET); + repeated_field_comparisons_[field] = AS_SET; +} + +void MessageDifferencer::TreatAsSmartSet(const FieldDescriptor* field) { + CheckRepeatedFieldComparisons(field, AS_SMART_SET); + repeated_field_comparisons_[field] = AS_SMART_SET; +} + +void MessageDifferencer::SetMatchIndicesForSmartListCallback( + std::function<void(std::vector<int>*, std::vector<int>*)> callback) { + match_indices_for_smart_list_callback_ = callback; +} + +void MessageDifferencer::TreatAsList(const FieldDescriptor* field) { + CheckRepeatedFieldComparisons(field, AS_LIST); + repeated_field_comparisons_[field] = AS_LIST; +} + +void MessageDifferencer::TreatAsSmartList(const FieldDescriptor* field) { + CheckRepeatedFieldComparisons(field, AS_SMART_LIST); + repeated_field_comparisons_[field] = AS_SMART_LIST; +} + +void MessageDifferencer::TreatAsMap(const FieldDescriptor* field, + const FieldDescriptor* key) { + GOOGLE_CHECK_EQ(FieldDescriptor::CPPTYPE_MESSAGE, field->cpp_type()) + << "Field has to be message type. Field name is: " << field->full_name(); + GOOGLE_CHECK(key->containing_type() == field->message_type()) + << key->full_name() + << " must be a direct subfield within the repeated field " + << field->full_name() << ", not " << key->containing_type()->full_name(); + GOOGLE_CHECK(repeated_field_comparisons_.find(field) == + repeated_field_comparisons_.end()) + << "Cannot treat the same field as both " + << repeated_field_comparisons_[field] + << " and MAP. Field name is: " << field->full_name(); + MapKeyComparator* key_comparator = + new MultipleFieldsMapKeyComparator(this, key); + owned_key_comparators_.push_back(key_comparator); + map_field_key_comparator_[field] = key_comparator; +} + +void MessageDifferencer::TreatAsMapWithMultipleFieldsAsKey( + const FieldDescriptor* field, + const std::vector<const FieldDescriptor*>& key_fields) { + std::vector<std::vector<const FieldDescriptor*> > key_field_paths; + for (const FieldDescriptor* key_filed : key_fields) { + std::vector<const FieldDescriptor*> key_field_path; + key_field_path.push_back(key_filed); + key_field_paths.push_back(key_field_path); + } + TreatAsMapWithMultipleFieldPathsAsKey(field, key_field_paths); +} + +void MessageDifferencer::TreatAsMapWithMultipleFieldPathsAsKey( + const FieldDescriptor* field, + const std::vector<std::vector<const FieldDescriptor*> >& key_field_paths) { + GOOGLE_CHECK(field->is_repeated()) + << "Field must be repeated: " << field->full_name(); + GOOGLE_CHECK_EQ(FieldDescriptor::CPPTYPE_MESSAGE, field->cpp_type()) + << "Field has to be message type. Field name is: " << field->full_name(); + for (const auto& key_field_path : key_field_paths) { + for (size_t j = 0; j < key_field_path.size(); ++j) { + const FieldDescriptor* parent_field = + j == 0 ? field : key_field_path[j - 1]; + const FieldDescriptor* child_field = key_field_path[j]; + GOOGLE_CHECK(child_field->containing_type() == parent_field->message_type()) + << child_field->full_name() + << " must be a direct subfield within the field: " + << parent_field->full_name(); + if (j != 0) { + GOOGLE_CHECK_EQ(FieldDescriptor::CPPTYPE_MESSAGE, parent_field->cpp_type()) + << parent_field->full_name() << " has to be of type message."; + GOOGLE_CHECK(!parent_field->is_repeated()) + << parent_field->full_name() << " cannot be a repeated field."; + } + } + } + GOOGLE_CHECK(repeated_field_comparisons_.find(field) == + repeated_field_comparisons_.end()) + << "Cannot treat the same field as both " + << repeated_field_comparisons_[field] + << " and MAP. Field name is: " << field->full_name(); + MapKeyComparator* key_comparator = + new MultipleFieldsMapKeyComparator(this, key_field_paths); + owned_key_comparators_.push_back(key_comparator); + map_field_key_comparator_[field] = key_comparator; +} + +void MessageDifferencer::TreatAsMapUsingKeyComparator( + const FieldDescriptor* field, const MapKeyComparator* key_comparator) { + GOOGLE_CHECK(field->is_repeated()) + << "Field must be repeated: " << field->full_name(); + GOOGLE_CHECK(repeated_field_comparisons_.find(field) == + repeated_field_comparisons_.end()) + << "Cannot treat the same field as both " + << repeated_field_comparisons_[field] + << " and MAP. Field name is: " << field->full_name(); + map_field_key_comparator_[field] = key_comparator; +} + +void MessageDifferencer::AddIgnoreCriteria(IgnoreCriteria* ignore_criteria) { + ignore_criteria_.push_back(ignore_criteria); +} + +void MessageDifferencer::IgnoreField(const FieldDescriptor* field) { + ignored_fields_.insert(field); +} + +void MessageDifferencer::SetFractionAndMargin(const FieldDescriptor* field, + double fraction, double margin) { + default_field_comparator_.SetFractionAndMargin(field, fraction, margin); +} + +void MessageDifferencer::ReportDifferencesToString(std::string* output) { + GOOGLE_DCHECK(output) << "Specified output string was NULL"; + + output_string_ = output; + output_string_->clear(); +} + +void MessageDifferencer::ReportDifferencesTo(Reporter* reporter) { + // If an output string is set, clear it to prevent + // it superseding the specified reporter. + if (output_string_) { + output_string_ = NULL; + } + + reporter_ = reporter; +} + +bool MessageDifferencer::FieldBefore(const FieldDescriptor* field1, + const FieldDescriptor* field2) { + // Handle sentinel values (i.e. make sure NULLs are always ordered + // at the end of the list). + if (field1 == NULL) { + return false; + } + + if (field2 == NULL) { + return true; + } + + // Always order fields by their tag number + return (field1->number() < field2->number()); +} + +bool MessageDifferencer::Compare(const Message& message1, + const Message& message2) { + std::vector<SpecificField> parent_fields; + + bool result = false; + // Setup the internal reporter if need be. + if (output_string_) { + io::StringOutputStream output_stream(output_string_); + StreamReporter reporter(&output_stream); + reporter.SetMessages(message1, message2); + reporter_ = &reporter; + result = Compare(message1, message2, &parent_fields); + reporter_ = NULL; + } else { + result = Compare(message1, message2, &parent_fields); + } + return result; +} + +bool MessageDifferencer::CompareWithFields( + const Message& message1, const Message& message2, + const std::vector<const FieldDescriptor*>& message1_fields_arg, + const std::vector<const FieldDescriptor*>& message2_fields_arg) { + if (message1.GetDescriptor() != message2.GetDescriptor()) { + GOOGLE_LOG(DFATAL) << "Comparison between two messages with different " + << "descriptors."; + return false; + } + + std::vector<SpecificField> parent_fields; + + bool result = false; + + FieldDescriptorArray message1_fields(message1_fields_arg.size() + 1); + FieldDescriptorArray message2_fields(message2_fields_arg.size() + 1); + + std::copy(message1_fields_arg.cbegin(), message1_fields_arg.cend(), + message1_fields.begin()); + std::copy(message2_fields_arg.cbegin(), message2_fields_arg.cend(), + message2_fields.begin()); + + // Append sentinel values. + message1_fields[message1_fields_arg.size()] = nullptr; + message2_fields[message2_fields_arg.size()] = nullptr; + + std::sort(message1_fields.begin(), message1_fields.end(), FieldBefore); + std::sort(message2_fields.begin(), message2_fields.end(), FieldBefore); + + // Setup the internal reporter if need be. + if (output_string_) { + io::StringOutputStream output_stream(output_string_); + StreamReporter reporter(&output_stream); + reporter_ = &reporter; + result = CompareRequestedFieldsUsingSettings( + message1, message2, message1_fields, message2_fields, &parent_fields); + reporter_ = NULL; + } else { + result = CompareRequestedFieldsUsingSettings( + message1, message2, message1_fields, message2_fields, &parent_fields); + } + + return result; +} + +bool MessageDifferencer::Compare(const Message& message1, + const Message& message2, + std::vector<SpecificField>* parent_fields) { + const Descriptor* descriptor1 = message1.GetDescriptor(); + const Descriptor* descriptor2 = message2.GetDescriptor(); + if (descriptor1 != descriptor2) { + GOOGLE_LOG(DFATAL) << "Comparison between two messages with different " + << "descriptors. " << descriptor1->full_name() << " vs " + << descriptor2->full_name(); + return false; + } + + // Expand google.protobuf.Any payload if possible. + if (descriptor1->full_name() == internal::kAnyFullTypeName) { + std::unique_ptr<Message> data1; + std::unique_ptr<Message> data2; + if (unpack_any_field_.UnpackAny(message1, &data1) && + unpack_any_field_.UnpackAny(message2, &data2)) { + // Avoid DFATAL for different descriptors in google.protobuf.Any payloads. + if (data1->GetDescriptor() != data2->GetDescriptor()) { + return false; + } + return Compare(*data1, *data2, parent_fields); + } + } + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + + bool unknown_compare_result = true; + // Ignore unknown fields in EQUIVALENT mode + if (message_field_comparison_ != EQUIVALENT) { + const UnknownFieldSet& unknown_field_set1 = + reflection1->GetUnknownFields(message1); + const UnknownFieldSet& unknown_field_set2 = + reflection2->GetUnknownFields(message2); + if (!CompareUnknownFields(message1, message2, unknown_field_set1, + unknown_field_set2, parent_fields)) { + if (reporter_ == NULL) { + return false; + } + unknown_compare_result = false; + } + } + + FieldDescriptorArray message1_fields = RetrieveFields(message1, true); + FieldDescriptorArray message2_fields = RetrieveFields(message2, false); + + return CompareRequestedFieldsUsingSettings(message1, message2, + message1_fields, message2_fields, + parent_fields) && + unknown_compare_result; +} + +FieldDescriptorArray MessageDifferencer::RetrieveFields(const Message& message, + bool base_message) { + const Descriptor* descriptor = message.GetDescriptor(); + + tmp_message_fields_.clear(); + tmp_message_fields_.reserve(descriptor->field_count() + 1); + + const Reflection* reflection = message.GetReflection(); + if (descriptor->options().map_entry()) { + if (this->scope_ == PARTIAL && base_message) { + reflection->ListFields(message, &tmp_message_fields_); + } else { + // Map entry fields are always considered present. + for (int i = 0; i < descriptor->field_count(); i++) { + tmp_message_fields_.push_back(descriptor->field(i)); + } + } + } else { + reflection->ListFields(message, &tmp_message_fields_); + } + // Add sentinel values to deal with the + // case where the number of the fields in + // each list are different. + tmp_message_fields_.push_back(nullptr); + + FieldDescriptorArray message_fields(tmp_message_fields_.begin(), + tmp_message_fields_.end()); + + return message_fields; +} + +bool MessageDifferencer::CompareRequestedFieldsUsingSettings( + const Message& message1, const Message& message2, + const FieldDescriptorArray& message1_fields, + const FieldDescriptorArray& message2_fields, + std::vector<SpecificField>* parent_fields) { + if (scope_ == FULL) { + if (message_field_comparison_ == EQUIVALENT) { + // We need to merge the field lists of both messages (i.e. + // we are merely checking for a difference in field values, + // rather than the addition or deletion of fields). + FieldDescriptorArray fields_union = + CombineFields(message1_fields, FULL, message2_fields, FULL); + return CompareWithFieldsInternal(message1, message2, fields_union, + fields_union, parent_fields); + } else { + // Simple equality comparison, use the unaltered field lists. + return CompareWithFieldsInternal(message1, message2, message1_fields, + message2_fields, parent_fields); + } + } else { + if (message_field_comparison_ == EQUIVALENT) { + // We use the list of fields for message1 for both messages when + // comparing. This way, extra fields in message2 are ignored, + // and missing fields in message2 use their default value. + return CompareWithFieldsInternal(message1, message2, message1_fields, + message1_fields, parent_fields); + } else { + // We need to consider the full list of fields for message1 + // but only the intersection for message2. This way, any fields + // only present in message2 will be ignored, but any fields only + // present in message1 will be marked as a difference. + FieldDescriptorArray fields_intersection = + CombineFields(message1_fields, PARTIAL, message2_fields, PARTIAL); + return CompareWithFieldsInternal(message1, message2, message1_fields, + fields_intersection, parent_fields); + } + } +} + +FieldDescriptorArray MessageDifferencer::CombineFields( + const FieldDescriptorArray& fields1, Scope fields1_scope, + const FieldDescriptorArray& fields2, Scope fields2_scope) { + size_t index1 = 0; + size_t index2 = 0; + + tmp_message_fields_.clear(); + + while (index1 < fields1.size() && index2 < fields2.size()) { + const FieldDescriptor* field1 = fields1[index1]; + const FieldDescriptor* field2 = fields2[index2]; + + if (FieldBefore(field1, field2)) { + if (fields1_scope == FULL) { + tmp_message_fields_.push_back(fields1[index1]); + } + ++index1; + } else if (FieldBefore(field2, field1)) { + if (fields2_scope == FULL) { + tmp_message_fields_.push_back(fields2[index2]); + } + ++index2; + } else { + tmp_message_fields_.push_back(fields1[index1]); + ++index1; + ++index2; + } + } + + tmp_message_fields_.push_back(nullptr); + + FieldDescriptorArray combined_fields(tmp_message_fields_.begin(), + tmp_message_fields_.end()); + + return combined_fields; +} + +bool MessageDifferencer::CompareWithFieldsInternal( + const Message& message1, const Message& message2, + const FieldDescriptorArray& message1_fields, + const FieldDescriptorArray& message2_fields, + std::vector<SpecificField>* parent_fields) { + bool isDifferent = false; + int field_index1 = 0; + int field_index2 = 0; + + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + + while (true) { + const FieldDescriptor* field1 = message1_fields[field_index1]; + const FieldDescriptor* field2 = message2_fields[field_index2]; + + // Once we have reached sentinel values, we are done the comparison. + if (field1 == NULL && field2 == NULL) { + break; + } + + // Check for differences in the field itself. + if (FieldBefore(field1, field2)) { + // Field 1 is not in the field list for message 2. + if (IsIgnored(message1, message2, field1, *parent_fields)) { + // We are ignoring field1. Report the ignore and move on to + // the next field in message1_fields. + if (reporter_ != NULL) { + SpecificField specific_field; + specific_field.field = field1; + parent_fields->push_back(specific_field); + if (report_ignores_) { + reporter_->ReportIgnored(message1, message2, *parent_fields); + } + parent_fields->pop_back(); + } + ++field_index1; + continue; + } + + if (reporter_ != NULL) { + assert(field1 != NULL); + int count = field1->is_repeated() + ? reflection1->FieldSize(message1, field1) + : 1; + + for (int i = 0; i < count; ++i) { + SpecificField specific_field; + specific_field.field = field1; + if (field1->is_repeated()) { + AddSpecificIndex(&specific_field, message1, field1, i); + } else { + specific_field.index = -1; + } + + parent_fields->push_back(specific_field); + reporter_->ReportDeleted(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + + isDifferent = true; + } else { + return false; + } + + ++field_index1; + continue; + } else if (FieldBefore(field2, field1)) { + // Field 2 is not in the field list for message 1. + if (IsIgnored(message1, message2, field2, *parent_fields)) { + // We are ignoring field2. Report the ignore and move on to + // the next field in message2_fields. + if (reporter_ != NULL) { + SpecificField specific_field; + specific_field.field = field2; + parent_fields->push_back(specific_field); + if (report_ignores_) { + reporter_->ReportIgnored(message1, message2, *parent_fields); + } + parent_fields->pop_back(); + } + ++field_index2; + continue; + } + + if (reporter_ != NULL) { + int count = field2->is_repeated() + ? reflection2->FieldSize(message2, field2) + : 1; + + for (int i = 0; i < count; ++i) { + SpecificField specific_field; + specific_field.field = field2; + if (field2->is_repeated()) { + specific_field.index = i; + AddSpecificNewIndex(&specific_field, message2, field2, i); + } else { + specific_field.index = -1; + specific_field.new_index = -1; + } + + parent_fields->push_back(specific_field); + reporter_->ReportAdded(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + + isDifferent = true; + } else { + return false; + } + + ++field_index2; + continue; + } + + // By this point, field1 and field2 are guaranteed to point to the same + // field, so we can now compare the values. + if (IsIgnored(message1, message2, field1, *parent_fields)) { + // Ignore this field. Report and move on. + if (reporter_ != NULL) { + SpecificField specific_field; + specific_field.field = field1; + parent_fields->push_back(specific_field); + if (report_ignores_) { + reporter_->ReportIgnored(message1, message2, *parent_fields); + } + parent_fields->pop_back(); + } + + ++field_index1; + ++field_index2; + continue; + } + + bool fieldDifferent = false; + assert(field1 != NULL); + if (field1->is_map()) { + fieldDifferent = + !CompareMapField(message1, message2, field1, parent_fields); + } else if (field1->is_repeated()) { + fieldDifferent = + !CompareRepeatedField(message1, message2, field1, parent_fields); + } else { + fieldDifferent = !CompareFieldValueUsingParentFields( + message1, message2, field1, -1, -1, parent_fields); + + if (reporter_ != nullptr) { + SpecificField specific_field; + specific_field.field = field1; + parent_fields->push_back(specific_field); + if (fieldDifferent) { + reporter_->ReportModified(message1, message2, *parent_fields); + isDifferent = true; + } else if (report_matches_) { + reporter_->ReportMatched(message1, message2, *parent_fields); + } + parent_fields->pop_back(); + } + } + if (fieldDifferent) { + if (reporter_ == nullptr) return false; + isDifferent = true; + } + // Increment the field indices. + ++field_index1; + ++field_index2; + } + + return !isDifferent; +} + +bool MessageDifferencer::IsMatch( + const FieldDescriptor* repeated_field, + const MapKeyComparator* key_comparator, const Message* message1, + const Message* message2, const std::vector<SpecificField>& parent_fields, + Reporter* reporter, int index1, int index2) { + std::vector<SpecificField> current_parent_fields(parent_fields); + if (repeated_field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { + return CompareFieldValueUsingParentFields(*message1, *message2, + repeated_field, index1, index2, + ¤t_parent_fields); + } + // Back up the Reporter and output_string_. They will be reset in the + // following code. + Reporter* backup_reporter = reporter_; + std::string* output_string = output_string_; + reporter_ = reporter; + output_string_ = NULL; + bool match; + + if (key_comparator == NULL) { + match = CompareFieldValueUsingParentFields(*message1, *message2, + repeated_field, index1, index2, + ¤t_parent_fields); + } else { + const Reflection* reflection1 = message1->GetReflection(); + const Reflection* reflection2 = message2->GetReflection(); + const Message& m1 = + reflection1->GetRepeatedMessage(*message1, repeated_field, index1); + const Message& m2 = + reflection2->GetRepeatedMessage(*message2, repeated_field, index2); + SpecificField specific_field; + specific_field.field = repeated_field; + if (repeated_field->is_map()) { + specific_field.map_entry1 = &m1; + specific_field.map_entry2 = &m2; + } + specific_field.index = index1; + specific_field.new_index = index2; + current_parent_fields.push_back(specific_field); + match = key_comparator->IsMatch(m1, m2, current_parent_fields); + } + + reporter_ = backup_reporter; + output_string_ = output_string; + return match; +} + +bool MessageDifferencer::CompareMapFieldByMapReflection( + const Message& message1, const Message& message2, + const FieldDescriptor* map_field, std::vector<SpecificField>* parent_fields, + DefaultFieldComparator* comparator) { + GOOGLE_DCHECK_EQ(nullptr, reporter_); + GOOGLE_DCHECK(map_field->is_map()); + GOOGLE_DCHECK(map_field_key_comparator_.find(map_field) == + map_field_key_comparator_.end()); + GOOGLE_DCHECK_EQ(repeated_field_comparison_, AS_LIST); + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + const int count1 = reflection1->MapSize(message1, map_field); + const int count2 = reflection2->MapSize(message2, map_field); + const bool treated_as_subset = IsTreatedAsSubset(map_field); + if (count1 != count2 && !treated_as_subset) { + return false; + } + if (count1 > count2) { + return false; + } + + // First pass: check whether the same keys are present. + for (MapIterator it = reflection1->MapBegin(const_cast<Message*>(&message1), + map_field), + it_end = reflection1->MapEnd(const_cast<Message*>(&message1), + map_field); + it != it_end; ++it) { + if (!reflection2->ContainsMapKey(message2, map_field, it.GetKey())) { + return false; + } + } + + // Second pass: compare values for matching keys. + const FieldDescriptor* val_des = map_field->message_type()->map_value(); + switch (val_des->cpp_type()) { +#define HANDLE_TYPE(CPPTYPE, METHOD, COMPAREMETHOD) \ + case FieldDescriptor::CPPTYPE_##CPPTYPE: { \ + for (MapIterator it = reflection1->MapBegin( \ + const_cast<Message*>(&message1), map_field), \ + it_end = reflection1->MapEnd( \ + const_cast<Message*>(&message1), map_field); \ + it != it_end; ++it) { \ + MapValueConstRef value2; \ + reflection2->LookupMapValue(message2, map_field, it.GetKey(), &value2); \ + if (!comparator->Compare##COMPAREMETHOD(*val_des, \ + it.GetValueRef().Get##METHOD(), \ + value2.Get##METHOD())) { \ + return false; \ + } \ + } \ + break; \ + } + HANDLE_TYPE(INT32, Int32Value, Int32); + HANDLE_TYPE(INT64, Int64Value, Int64); + HANDLE_TYPE(UINT32, UInt32Value, UInt32); + HANDLE_TYPE(UINT64, UInt64Value, UInt64); + HANDLE_TYPE(DOUBLE, DoubleValue, Double); + HANDLE_TYPE(FLOAT, FloatValue, Float); + HANDLE_TYPE(BOOL, BoolValue, Bool); + HANDLE_TYPE(STRING, StringValue, String); + HANDLE_TYPE(ENUM, EnumValue, Int32); +#undef HANDLE_TYPE + case FieldDescriptor::CPPTYPE_MESSAGE: { + for (MapIterator it = reflection1->MapBegin( + const_cast<Message*>(&message1), map_field); + it != + reflection1->MapEnd(const_cast<Message*>(&message1), map_field); + ++it) { + if (!reflection2->ContainsMapKey(message2, map_field, it.GetKey())) { + return false; + } + bool compare_result; + MapValueConstRef value2; + reflection2->LookupMapValue(message2, map_field, it.GetKey(), &value2); + // Append currently compared field to the end of parent_fields. + SpecificField specific_value_field; + specific_value_field.field = val_des; + parent_fields->push_back(specific_value_field); + compare_result = Compare(it.GetValueRef().GetMessageValue(), + value2.GetMessageValue(), parent_fields); + parent_fields->pop_back(); + if (!compare_result) { + return false; + } + } + break; + } + } + return true; +} + +bool MessageDifferencer::CompareMapField( + const Message& message1, const Message& message2, + const FieldDescriptor* repeated_field, + std::vector<SpecificField>* parent_fields) { + GOOGLE_DCHECK(repeated_field->is_map()); + + // the input FieldDescriptor is guaranteed to be repeated field. + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + + // When both map fields are on map, do not sync to repeated field. + if (reflection1->GetMapData(message1, repeated_field)->IsMapValid() && + reflection2->GetMapData(message2, repeated_field)->IsMapValid() && + // TODO(jieluo): Add support for reporter + reporter_ == nullptr && + // Users didn't set custom map field key comparator + map_field_key_comparator_.find(repeated_field) == + map_field_key_comparator_.end() && + // Users didn't set repeated field comparison + repeated_field_comparison_ == AS_LIST && + // Users didn't set their own FieldComparator implementation + field_comparator_kind_ == kFCDefault) { + const FieldDescriptor* key_des = repeated_field->message_type()->map_key(); + const FieldDescriptor* val_des = + repeated_field->message_type()->map_value(); + std::vector<SpecificField> current_parent_fields(*parent_fields); + SpecificField specific_field; + specific_field.field = repeated_field; + current_parent_fields.push_back(specific_field); + if (!IsIgnored(message1, message2, key_des, current_parent_fields) && + !IsIgnored(message1, message2, val_des, current_parent_fields)) { + return CompareMapFieldByMapReflection(message1, message2, repeated_field, + ¤t_parent_fields, + field_comparator_.default_impl); + } + } + + return CompareRepeatedRep(message1, message2, repeated_field, parent_fields); +} + +bool MessageDifferencer::CompareRepeatedField( + const Message& message1, const Message& message2, + const FieldDescriptor* repeated_field, + std::vector<SpecificField>* parent_fields) { + GOOGLE_DCHECK(!repeated_field->is_map()); + return CompareRepeatedRep(message1, message2, repeated_field, parent_fields); +} + +bool MessageDifferencer::CompareRepeatedRep( + const Message& message1, const Message& message2, + const FieldDescriptor* repeated_field, + std::vector<SpecificField>* parent_fields) { + // the input FieldDescriptor is guaranteed to be repeated field. + GOOGLE_DCHECK(repeated_field->is_repeated()); + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + + const int count1 = reflection1->FieldSize(message1, repeated_field); + const int count2 = reflection2->FieldSize(message2, repeated_field); + const bool treated_as_subset = IsTreatedAsSubset(repeated_field); + + // If the field is not treated as subset and no detailed reports is needed, + // we do a quick check on the number of the elements to avoid unnecessary + // comparison. + if (count1 != count2 && reporter_ == NULL && !treated_as_subset) { + return false; + } + // A match can never be found if message1 has more items than message2. + if (count1 > count2 && reporter_ == NULL) { + return false; + } + + // These two list are used for store the index of the correspondent + // element in peer repeated field. + std::vector<int> match_list1; + std::vector<int> match_list2; + + const MapKeyComparator* key_comparator = GetMapKeyComparator(repeated_field); + bool smart_list = IsTreatedAsSmartList(repeated_field); + bool simple_list = key_comparator == nullptr && + !IsTreatedAsSet(repeated_field) && + !IsTreatedAsSmartSet(repeated_field) && !smart_list; + + // For simple lists, we avoid matching repeated field indices, saving the + // memory allocations that would otherwise be needed for match_list1 and + // match_list2. + if (!simple_list) { + // Try to match indices of the repeated fields. Return false if match fails. + if (!MatchRepeatedFieldIndices(message1, message2, repeated_field, + key_comparator, *parent_fields, &match_list1, + &match_list2) && + reporter_ == nullptr) { + return false; + } + } + + bool fieldDifferent = false; + SpecificField specific_field; + specific_field.field = repeated_field; + + // At this point, we have already matched pairs of fields (with the reporting + // to be done later). Now to check if the paired elements are different. + int next_unmatched_index = 0; + for (int i = 0; i < count1; i++) { + if (simple_list && i >= count2) { + break; + } + if (!simple_list && match_list1[i] == -1) { + if (smart_list) { + if (reporter_ == nullptr) return false; + AddSpecificIndex(&specific_field, message1, repeated_field, i); + parent_fields->push_back(specific_field); + reporter_->ReportDeleted(message1, message2, *parent_fields); + parent_fields->pop_back(); + fieldDifferent = true; + // Use -2 to mark this element has been reported. + match_list1[i] = -2; + } + continue; + } + if (smart_list) { + for (int j = next_unmatched_index; j < match_list1[i]; ++j) { + GOOGLE_CHECK_LE(0, j); + if (reporter_ == nullptr) return false; + specific_field.index = j; + AddSpecificNewIndex(&specific_field, message2, repeated_field, j); + parent_fields->push_back(specific_field); + reporter_->ReportAdded(message1, message2, *parent_fields); + parent_fields->pop_back(); + fieldDifferent = true; + // Use -2 to mark this element has been reported. + match_list2[j] = -2; + } + } + AddSpecificIndex(&specific_field, message1, repeated_field, i); + if (simple_list) { + AddSpecificNewIndex(&specific_field, message2, repeated_field, i); + } else { + AddSpecificNewIndex(&specific_field, message2, repeated_field, + match_list1[i]); + next_unmatched_index = match_list1[i] + 1; + } + + const bool result = CompareFieldValueUsingParentFields( + message1, message2, repeated_field, i, specific_field.new_index, + parent_fields); + + // If we have found differences, either report them or terminate if + // no reporter is present. Note that ReportModified, ReportMoved, and + // ReportMatched are all mutually exclusive. + if (!result) { + if (reporter_ == NULL) return false; + parent_fields->push_back(specific_field); + reporter_->ReportModified(message1, message2, *parent_fields); + parent_fields->pop_back(); + fieldDifferent = true; + } else if (reporter_ != NULL && + specific_field.index != specific_field.new_index && + !specific_field.field->is_map() && report_moves_) { + parent_fields->push_back(specific_field); + reporter_->ReportMoved(message1, message2, *parent_fields); + parent_fields->pop_back(); + } else if (report_matches_ && reporter_ != NULL) { + parent_fields->push_back(specific_field); + reporter_->ReportMatched(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + } + + // Report any remaining additions or deletions. + for (int i = 0; i < count2; ++i) { + if (!simple_list && match_list2[i] != -1) continue; + if (simple_list && i < count1) continue; + if (!treated_as_subset) { + fieldDifferent = true; + } + + if (reporter_ == NULL) continue; + specific_field.index = i; + AddSpecificNewIndex(&specific_field, message2, repeated_field, i); + parent_fields->push_back(specific_field); + reporter_->ReportAdded(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + + for (int i = 0; i < count1; ++i) { + if (!simple_list && match_list1[i] != -1) continue; + if (simple_list && i < count2) continue; + assert(reporter_ != NULL); + AddSpecificIndex(&specific_field, message1, repeated_field, i); + parent_fields->push_back(specific_field); + reporter_->ReportDeleted(message1, message2, *parent_fields); + parent_fields->pop_back(); + fieldDifferent = true; + } + return !fieldDifferent; +} + +bool MessageDifferencer::CompareFieldValue(const Message& message1, + const Message& message2, + const FieldDescriptor* field, + int index1, int index2) { + return CompareFieldValueUsingParentFields(message1, message2, field, index1, + index2, NULL); +} + +bool MessageDifferencer::CompareFieldValueUsingParentFields( + const Message& message1, const Message& message2, + const FieldDescriptor* field, int index1, int index2, + std::vector<SpecificField>* parent_fields) { + FieldContext field_context(parent_fields); + FieldComparator::ComparisonResult result = GetFieldComparisonResult( + message1, message2, field, index1, index2, &field_context); + + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE && + result == FieldComparator::RECURSE) { + // Get the nested messages and compare them using one of the Compare + // methods. + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + const Message& m1 = + field->is_repeated() + ? reflection1->GetRepeatedMessage(message1, field, index1) + : reflection1->GetMessage(message1, field); + const Message& m2 = + field->is_repeated() + ? reflection2->GetRepeatedMessage(message2, field, index2) + : reflection2->GetMessage(message2, field); + + // parent_fields is used in calls to Reporter methods. + if (parent_fields != NULL) { + // Append currently compared field to the end of parent_fields. + SpecificField specific_field; + specific_field.field = field; + AddSpecificIndex(&specific_field, message1, field, index1); + AddSpecificNewIndex(&specific_field, message2, field, index2); + parent_fields->push_back(specific_field); + const bool compare_result = Compare(m1, m2, parent_fields); + parent_fields->pop_back(); + return compare_result; + } else { + // Recreates parent_fields as if m1 and m2 had no parents. + return Compare(m1, m2); + } + } else { + return (result == FieldComparator::SAME); + } +} + +bool MessageDifferencer::CheckPathChanged( + const std::vector<SpecificField>& field_path) { + for (const SpecificField& specific_field : field_path) { + // Don't check indexes for map entries -- maps are unordered. + if (specific_field.field != nullptr && specific_field.field->is_map()) + continue; + if (specific_field.index != specific_field.new_index) return true; + } + return false; +} + +bool MessageDifferencer::IsTreatedAsSet(const FieldDescriptor* field) { + if (!field->is_repeated()) return false; + if (repeated_field_comparisons_.find(field) != + repeated_field_comparisons_.end()) { + return repeated_field_comparisons_[field] == AS_SET; + } + return GetMapKeyComparator(field) == nullptr && + repeated_field_comparison_ == AS_SET; +} + +bool MessageDifferencer::IsTreatedAsSmartSet(const FieldDescriptor* field) { + if (!field->is_repeated()) return false; + if (repeated_field_comparisons_.find(field) != + repeated_field_comparisons_.end()) { + return repeated_field_comparisons_[field] == AS_SMART_SET; + } + return GetMapKeyComparator(field) == nullptr && + repeated_field_comparison_ == AS_SMART_SET; +} + +bool MessageDifferencer::IsTreatedAsSmartList(const FieldDescriptor* field) { + if (!field->is_repeated()) return false; + if (repeated_field_comparisons_.find(field) != + repeated_field_comparisons_.end()) { + return repeated_field_comparisons_[field] == AS_SMART_LIST; + } + return GetMapKeyComparator(field) == nullptr && + repeated_field_comparison_ == AS_SMART_LIST; +} + +bool MessageDifferencer::IsTreatedAsSubset(const FieldDescriptor* field) { + return scope_ == PARTIAL && + (IsTreatedAsSet(field) || GetMapKeyComparator(field) != NULL); +} + +bool MessageDifferencer::IsIgnored( + const Message& message1, const Message& message2, + const FieldDescriptor* field, + const std::vector<SpecificField>& parent_fields) { + if (ignored_fields_.find(field) != ignored_fields_.end()) { + return true; + } + for (IgnoreCriteria* criteria : ignore_criteria_) { + if (criteria->IsIgnored(message1, message2, field, parent_fields)) { + return true; + } + } + return false; +} + +bool MessageDifferencer::IsUnknownFieldIgnored( + const Message& message1, const Message& message2, + const SpecificField& field, + const std::vector<SpecificField>& parent_fields) { + for (IgnoreCriteria* criteria : ignore_criteria_) { + if (criteria->IsUnknownFieldIgnored(message1, message2, field, + parent_fields)) { + return true; + } + } + return false; +} + +const MessageDifferencer::MapKeyComparator* +MessageDifferencer ::GetMapKeyComparator(const FieldDescriptor* field) const { + if (!field->is_repeated()) return NULL; + FieldKeyComparatorMap::const_iterator it = + map_field_key_comparator_.find(field); + if (it != map_field_key_comparator_.end()) { + return it->second; + } + if (field->is_map()) { + // field cannot already be treated as list or set since TreatAsList() and + // TreatAsSet() call GetMapKeyComparator() and fail if it returns non-NULL. + return &map_entry_key_comparator_; + } + return NULL; +} + +namespace { + +typedef std::pair<int, const UnknownField*> IndexUnknownFieldPair; + +struct UnknownFieldOrdering { + inline bool operator()(const IndexUnknownFieldPair& a, + const IndexUnknownFieldPair& b) const { + if (a.second->number() < b.second->number()) return true; + if (a.second->number() > b.second->number()) return false; + return a.second->type() < b.second->type(); + } +}; + +} // namespace + +bool MessageDifferencer::UnpackAnyField::UnpackAny( + const Message& any, std::unique_ptr<Message>* data) { + const Reflection* reflection = any.GetReflection(); + const FieldDescriptor* type_url_field; + const FieldDescriptor* value_field; + if (!internal::GetAnyFieldDescriptors(any, &type_url_field, &value_field)) { + return false; + } + const std::string& type_url = reflection->GetString(any, type_url_field); + std::string full_type_name; + if (!internal::ParseAnyTypeUrl(type_url, &full_type_name)) { + return false; + } + + const Descriptor* desc = + any.GetDescriptor()->file()->pool()->FindMessageTypeByName( + full_type_name); + if (desc == NULL) { + GOOGLE_LOG(INFO) << "Proto type '" << full_type_name << "' not found"; + return false; + } + + if (dynamic_message_factory_ == NULL) { + dynamic_message_factory_.reset(new DynamicMessageFactory()); + } + data->reset(dynamic_message_factory_->GetPrototype(desc)->New()); + std::string serialized_value = reflection->GetString(any, value_field); + if (!(*data)->ParsePartialFromString(serialized_value)) { + GOOGLE_DLOG(ERROR) << "Failed to parse value for " << full_type_name; + return false; + } + return true; +} + +bool MessageDifferencer::CompareUnknownFields( + const Message& message1, const Message& message2, + const UnknownFieldSet& unknown_field_set1, + const UnknownFieldSet& unknown_field_set2, + std::vector<SpecificField>* parent_field) { + // Ignore unknown fields in EQUIVALENT mode. + if (message_field_comparison_ == EQUIVALENT) return true; + + if (unknown_field_set1.empty() && unknown_field_set2.empty()) { + return true; + } + + bool is_different = false; + + // We first sort the unknown fields by field number and type (in other words, + // in tag order), making sure to preserve ordering of values with the same + // tag. This allows us to report only meaningful differences between the + // two sets -- that is, differing values for the same tag. We use + // IndexUnknownFieldPairs to keep track of the field's original index for + // reporting purposes. + std::vector<IndexUnknownFieldPair> fields1; // unknown_field_set1, sorted + std::vector<IndexUnknownFieldPair> fields2; // unknown_field_set2, sorted + fields1.reserve(unknown_field_set1.field_count()); + fields2.reserve(unknown_field_set2.field_count()); + + for (int i = 0; i < unknown_field_set1.field_count(); i++) { + fields1.push_back(std::make_pair(i, &unknown_field_set1.field(i))); + } + for (int i = 0; i < unknown_field_set2.field_count(); i++) { + fields2.push_back(std::make_pair(i, &unknown_field_set2.field(i))); + } + + UnknownFieldOrdering is_before; + std::stable_sort(fields1.begin(), fields1.end(), is_before); + std::stable_sort(fields2.begin(), fields2.end(), is_before); + + // In order to fill in SpecificField::index, we have to keep track of how + // many values we've seen with the same field number and type. + // current_repeated points at the first field in this range, and + // current_repeated_start{1,2} are the indexes of the first field in the + // range within fields1 and fields2. + const UnknownField* current_repeated = NULL; + int current_repeated_start1 = 0; + int current_repeated_start2 = 0; + + // Now that we have two sorted lists, we can detect fields which appear only + // in one list or the other by traversing them simultaneously. + size_t index1 = 0; + size_t index2 = 0; + while (index1 < fields1.size() || index2 < fields2.size()) { + enum { + ADDITION, + DELETION, + MODIFICATION, + COMPARE_GROUPS, + NO_CHANGE + } change_type; + + // focus_field is the field we're currently reporting on. (In the case + // of a modification, it's the field on the left side.) + const UnknownField* focus_field; + bool match = false; + + if (index2 == fields2.size() || + (index1 < fields1.size() && + is_before(fields1[index1], fields2[index2]))) { + // fields1[index1] is not present in fields2. + change_type = DELETION; + focus_field = fields1[index1].second; + } else if (index1 == fields1.size() || + is_before(fields2[index2], fields1[index1])) { + // fields2[index2] is not present in fields1. + if (scope_ == PARTIAL) { + // Ignore. + ++index2; + continue; + } + change_type = ADDITION; + focus_field = fields2[index2].second; + } else { + // Field type and number are the same. See if the values differ. + change_type = MODIFICATION; + focus_field = fields1[index1].second; + + switch (focus_field->type()) { + case UnknownField::TYPE_VARINT: + match = fields1[index1].second->varint() == + fields2[index2].second->varint(); + break; + case UnknownField::TYPE_FIXED32: + match = fields1[index1].second->fixed32() == + fields2[index2].second->fixed32(); + break; + case UnknownField::TYPE_FIXED64: + match = fields1[index1].second->fixed64() == + fields2[index2].second->fixed64(); + break; + case UnknownField::TYPE_LENGTH_DELIMITED: + match = fields1[index1].second->length_delimited() == + fields2[index2].second->length_delimited(); + break; + case UnknownField::TYPE_GROUP: + // We must deal with this later, after building the SpecificField. + change_type = COMPARE_GROUPS; + break; + } + if (match && change_type != COMPARE_GROUPS) { + change_type = NO_CHANGE; + } + } + + if (current_repeated == NULL || + focus_field->number() != current_repeated->number() || + focus_field->type() != current_repeated->type()) { + // We've started a new repeated field. + current_repeated = focus_field; + current_repeated_start1 = index1; + current_repeated_start2 = index2; + } + + if (change_type == NO_CHANGE && reporter_ == NULL) { + // Fields were already compared and matched and we have no reporter. + ++index1; + ++index2; + continue; + } + + // Build the SpecificField. This is slightly complicated. + SpecificField specific_field; + specific_field.unknown_field_number = focus_field->number(); + specific_field.unknown_field_type = focus_field->type(); + + specific_field.unknown_field_set1 = &unknown_field_set1; + specific_field.unknown_field_set2 = &unknown_field_set2; + + if (change_type != ADDITION) { + specific_field.unknown_field_index1 = fields1[index1].first; + } + if (change_type != DELETION) { + specific_field.unknown_field_index2 = fields2[index2].first; + } + + // Calculate the field index. + if (change_type == ADDITION) { + specific_field.index = index2 - current_repeated_start2; + specific_field.new_index = index2 - current_repeated_start2; + } else { + specific_field.index = index1 - current_repeated_start1; + specific_field.new_index = index2 - current_repeated_start2; + } + + if (IsUnknownFieldIgnored(message1, message2, specific_field, + *parent_field)) { + if (report_ignores_ && reporter_ != NULL) { + parent_field->push_back(specific_field); + reporter_->ReportUnknownFieldIgnored(message1, message2, *parent_field); + parent_field->pop_back(); + } + if (change_type != ADDITION) ++index1; + if (change_type != DELETION) ++index2; + continue; + } + + if (change_type == ADDITION || change_type == DELETION || + change_type == MODIFICATION) { + if (reporter_ == NULL) { + // We found a difference and we have no reporter. + return false; + } + is_different = true; + } + + parent_field->push_back(specific_field); + + switch (change_type) { + case ADDITION: + reporter_->ReportAdded(message1, message2, *parent_field); + ++index2; + break; + case DELETION: + reporter_->ReportDeleted(message1, message2, *parent_field); + ++index1; + break; + case MODIFICATION: + reporter_->ReportModified(message1, message2, *parent_field); + ++index1; + ++index2; + break; + case COMPARE_GROUPS: + if (!CompareUnknownFields( + message1, message2, fields1[index1].second->group(), + fields2[index2].second->group(), parent_field)) { + if (reporter_ == NULL) return false; + is_different = true; + reporter_->ReportModified(message1, message2, *parent_field); + } + ++index1; + ++index2; + break; + case NO_CHANGE: + ++index1; + ++index2; + if (report_matches_) { + reporter_->ReportMatched(message1, message2, *parent_field); + } + } + + parent_field->pop_back(); + } + + return !is_different; +} + +namespace { + +// Find maximum bipartite matching using the argumenting path algorithm. +class MaximumMatcher { + public: + typedef std::function<bool(int, int)> NodeMatchCallback; + // MaximumMatcher takes ownership of the passed in callback and uses it to + // determine whether a node on the left side of the bipartial graph matches + // a node on the right side. count1 is the number of nodes on the left side + // of the graph and count2 to is the number of nodes on the right side. + // Every node is referred to using 0-based indices. + // If a maximum match is found, the result will be stored in match_list1 and + // match_list2. match_list1[i] == j means the i-th node on the left side is + // matched to the j-th node on the right side and match_list2[x] == y means + // the x-th node on the right side is matched to y-th node on the left side. + // match_list1[i] == -1 means the node is not matched. Same with match_list2. + MaximumMatcher(int count1, int count2, NodeMatchCallback callback, + std::vector<int>* match_list1, std::vector<int>* match_list2); + // Find a maximum match and return the number of matched node pairs. + // If early_return is true, this method will return 0 immediately when it + // finds that not all nodes on the left side can be matched. + int FindMaximumMatch(bool early_return); + + private: + // Determines whether the node on the left side of the bipartial graph + // matches the one on the right side. + bool Match(int left, int right); + // Find an argumenting path starting from the node v on the left side. If a + // path can be found, update match_list2_ to reflect the path and return + // true. + bool FindArgumentPathDFS(int v, std::vector<bool>* visited); + + int count1_; + int count2_; + NodeMatchCallback match_callback_; + std::map<std::pair<int, int>, bool> cached_match_results_; + std::vector<int>* match_list1_; + std::vector<int>* match_list2_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MaximumMatcher); +}; + +MaximumMatcher::MaximumMatcher(int count1, int count2, + NodeMatchCallback callback, + std::vector<int>* match_list1, + std::vector<int>* match_list2) + : count1_(count1), + count2_(count2), + match_callback_(std::move(callback)), + match_list1_(match_list1), + match_list2_(match_list2) { + match_list1_->assign(count1, -1); + match_list2_->assign(count2, -1); +} + +int MaximumMatcher::FindMaximumMatch(bool early_return) { + int result = 0; + for (int i = 0; i < count1_; ++i) { + std::vector<bool> visited(count1_); + if (FindArgumentPathDFS(i, &visited)) { + ++result; + } else if (early_return) { + return 0; + } + } + // Backfill match_list1_ as we only filled match_list2_ when finding + // argumenting paths. + for (int i = 0; i < count2_; ++i) { + if ((*match_list2_)[i] != -1) { + (*match_list1_)[(*match_list2_)[i]] = i; + } + } + return result; +} + +bool MaximumMatcher::Match(int left, int right) { + std::pair<int, int> p(left, right); + std::map<std::pair<int, int>, bool>::iterator it = + cached_match_results_.find(p); + if (it != cached_match_results_.end()) { + return it->second; + } + cached_match_results_[p] = match_callback_(left, right); + return cached_match_results_[p]; +} + +bool MaximumMatcher::FindArgumentPathDFS(int v, std::vector<bool>* visited) { + (*visited)[v] = true; + // We try to match those un-matched nodes on the right side first. This is + // the step that the naive greedy matching algorithm uses. In the best cases + // where the greedy algorithm can find a maximum matching, we will always + // find a match in this step and the performance will be identical to the + // greedy algorithm. + for (int i = 0; i < count2_; ++i) { + int matched = (*match_list2_)[i]; + if (matched == -1 && Match(v, i)) { + (*match_list2_)[i] = v; + return true; + } + } + // Then we try those already matched nodes and see if we can find an + // alternative match for the node matched to them. + // The greedy algorithm will stop before this and fail to produce the + // correct result. + for (int i = 0; i < count2_; ++i) { + int matched = (*match_list2_)[i]; + if (matched != -1 && Match(v, i)) { + if (!(*visited)[matched] && FindArgumentPathDFS(matched, visited)) { + (*match_list2_)[i] = v; + return true; + } + } + } + return false; +} + +} // namespace + +bool MessageDifferencer::MatchRepeatedFieldIndices( + const Message& message1, const Message& message2, + const FieldDescriptor* repeated_field, + const MapKeyComparator* key_comparator, + const std::vector<SpecificField>& parent_fields, + std::vector<int>* match_list1, std::vector<int>* match_list2) { + const int count1 = + message1.GetReflection()->FieldSize(message1, repeated_field); + const int count2 = + message2.GetReflection()->FieldSize(message2, repeated_field); + const bool is_treated_as_smart_set = IsTreatedAsSmartSet(repeated_field); + + match_list1->assign(count1, -1); + match_list2->assign(count2, -1); + // Ensure that we don't report differences during the matching process. Since + // field comparators could potentially use this message differencer object to + // perform further comparisons, turn off reporting here and re-enable it + // before returning. + Reporter* reporter = reporter_; + reporter_ = NULL; + NumDiffsReporter num_diffs_reporter; + std::vector<int32_t> num_diffs_list1; + if (is_treated_as_smart_set) { + num_diffs_list1.assign(count1, std::numeric_limits<int32_t>::max()); + } + + bool success = true; + // Find potential match if this is a special repeated field. + if (scope_ == PARTIAL) { + // When partial matching is enabled, Compare(a, b) && Compare(a, c) + // doesn't necessarily imply Compare(b, c). Therefore a naive greedy + // algorithm will fail to find a maximum matching. + // Here we use the augmenting path algorithm. + auto callback = [&](int i1, int i2) { + return IsMatch(repeated_field, key_comparator, &message1, &message2, + parent_fields, nullptr, i1, i2); + }; + MaximumMatcher matcher(count1, count2, std::move(callback), match_list1, + match_list2); + // If diff info is not needed, we should end the matching process as + // soon as possible if not all items can be matched. + bool early_return = (reporter == nullptr); + int match_count = matcher.FindMaximumMatch(early_return); + if (match_count != count1 && early_return) return false; + success = success && (match_count == count1); + } else { + int start_offset = 0; + // If the two repeated fields are treated as sets, optimize for the case + // where both start with same items stored in the same order. + if (IsTreatedAsSet(repeated_field) || is_treated_as_smart_set || + IsTreatedAsSmartList(repeated_field)) { + start_offset = std::min(count1, count2); + for (int i = 0; i < count1 && i < count2; i++) { + if (IsMatch(repeated_field, key_comparator, &message1, &message2, + parent_fields, nullptr, i, i)) { + match_list1->at(i) = i; + match_list2->at(i) = i; + } else { + start_offset = i; + break; + } + } + } + for (int i = start_offset; i < count1; ++i) { + // Indicates any matched elements for this repeated field. + bool match = false; + int matched_j = -1; + + for (int j = start_offset; j < count2; j++) { + if (match_list2->at(j) != -1) { + if (!is_treated_as_smart_set || num_diffs_list1[i] == 0 || + num_diffs_list1[match_list2->at(j)] == 0) { + continue; + } + } + + if (is_treated_as_smart_set) { + num_diffs_reporter.Reset(); + match = IsMatch(repeated_field, key_comparator, &message1, &message2, + parent_fields, &num_diffs_reporter, i, j); + } else { + match = IsMatch(repeated_field, key_comparator, &message1, &message2, + parent_fields, nullptr, i, j); + } + + if (is_treated_as_smart_set) { + if (match) { + num_diffs_list1[i] = 0; + } else if (repeated_field->cpp_type() == + FieldDescriptor::CPPTYPE_MESSAGE) { + // Replace with the one with fewer diffs. + const int32_t num_diffs = num_diffs_reporter.GetNumDiffs(); + if (num_diffs < num_diffs_list1[i]) { + // If j has been already matched to some element, ensure the + // current num_diffs is smaller. + if (match_list2->at(j) == -1 || + num_diffs < num_diffs_list1[match_list2->at(j)]) { + num_diffs_list1[i] = num_diffs; + match = true; + } + } + } + } + + if (match) { + matched_j = j; + if (!is_treated_as_smart_set || num_diffs_list1[i] == 0) { + break; + } + } + } + + match = (matched_j != -1); + if (match) { + if (is_treated_as_smart_set && match_list2->at(matched_j) != -1) { + // This is to revert the previously matched index in list2. + match_list1->at(match_list2->at(matched_j)) = -1; + match = false; + } + match_list1->at(i) = matched_j; + match_list2->at(matched_j) = i; + } + if (!match && reporter == nullptr) return false; + success = success && match; + } + } + + if (IsTreatedAsSmartList(repeated_field)) { + match_indices_for_smart_list_callback_(match_list1, match_list2); + } + + reporter_ = reporter; + + return success; +} + +FieldComparator::ComparisonResult MessageDifferencer::GetFieldComparisonResult( + const Message& message1, const Message& message2, + const FieldDescriptor* field, int index1, int index2, + const FieldContext* field_context) { + FieldComparator* comparator = field_comparator_kind_ == kFCBase + ? field_comparator_.base + : field_comparator_.default_impl; + return comparator->Compare(message1, message2, field, index1, index2, + field_context); +} + +// =========================================================================== + +MessageDifferencer::Reporter::Reporter() {} +MessageDifferencer::Reporter::~Reporter() {} + +// =========================================================================== + +MessageDifferencer::MapKeyComparator::MapKeyComparator() {} +MessageDifferencer::MapKeyComparator::~MapKeyComparator() {} + +// =========================================================================== + +MessageDifferencer::IgnoreCriteria::IgnoreCriteria() {} +MessageDifferencer::IgnoreCriteria::~IgnoreCriteria() {} + +// =========================================================================== + +// Note that the printer's delimiter is not used, because if we are given a +// printer, we don't know its delimiter. +MessageDifferencer::StreamReporter::StreamReporter( + io::ZeroCopyOutputStream* output) + : printer_(new io::Printer(output, '$')), + delete_printer_(true), + report_modified_aggregates_(false), + message1_(nullptr), + message2_(nullptr) {} + +MessageDifferencer::StreamReporter::StreamReporter(io::Printer* printer) + : printer_(printer), + delete_printer_(false), + report_modified_aggregates_(false), + message1_(nullptr), + message2_(nullptr) {} + +MessageDifferencer::StreamReporter::~StreamReporter() { + if (delete_printer_) delete printer_; +} + +void MessageDifferencer::StreamReporter::PrintPath( + const std::vector<SpecificField>& field_path, bool left_side) { + for (size_t i = 0; i < field_path.size(); ++i) { + SpecificField specific_field = field_path[i]; + + if (specific_field.field != nullptr && + specific_field.field->name() == "value") { + // check to see if this the value label of a map value. If so, skip it + // because it isn't meaningful + if (i > 0 && field_path[i - 1].field->is_map()) { + continue; + } + } + if (i > 0) { + printer_->Print("."); + } + if (specific_field.field != NULL) { + if (specific_field.field->is_extension()) { + printer_->Print("($name$)", "name", specific_field.field->full_name()); + } else { + printer_->PrintRaw(specific_field.field->name()); + } + + if (specific_field.field->is_map()) { + PrintMapKey(left_side, specific_field); + continue; + } + } else { + printer_->PrintRaw(StrCat(specific_field.unknown_field_number)); + } + if (left_side && specific_field.index >= 0) { + printer_->Print("[$name$]", "name", StrCat(specific_field.index)); + } + if (!left_side && specific_field.new_index >= 0) { + printer_->Print("[$name$]", "name", + StrCat(specific_field.new_index)); + } + } +} + + +void MessageDifferencer::StreamReporter::PrintValue( + const Message& message, const std::vector<SpecificField>& field_path, + bool left_side) { + const SpecificField& specific_field = field_path.back(); + const FieldDescriptor* field = specific_field.field; + if (field != NULL) { + std::string output; + int index = left_side ? specific_field.index : specific_field.new_index; + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + const Reflection* reflection = message.GetReflection(); + const Message& field_message = + field->is_repeated() + ? reflection->GetRepeatedMessage(message, field, index) + : reflection->GetMessage(message, field); + const FieldDescriptor* fd = nullptr; + + if (field->is_map() && message1_ != nullptr && message2_ != nullptr) { + fd = field_message.GetDescriptor()->field(1); + if (fd->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + output = field_message.GetReflection() + ->GetMessage(field_message, fd) + .ShortDebugString(); + } else { + TextFormat::PrintFieldValueToString(field_message, fd, -1, &output); + } + } else { + output = field_message.ShortDebugString(); + } + if (output.empty()) { + printer_->Print("{ }"); + } else { + if ((fd != nullptr) && + (fd->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE)) { + printer_->PrintRaw(output); + } else { + printer_->Print("{ $name$ }", "name", output); + } + } + } else { + TextFormat::PrintFieldValueToString(message, field, index, &output); + printer_->PrintRaw(output); + } + } else { + const UnknownFieldSet* unknown_fields = + (left_side ? specific_field.unknown_field_set1 + : specific_field.unknown_field_set2); + const UnknownField* unknown_field = + &unknown_fields->field(left_side ? specific_field.unknown_field_index1 + : specific_field.unknown_field_index2); + PrintUnknownFieldValue(unknown_field); + } +} + +void MessageDifferencer::StreamReporter::PrintUnknownFieldValue( + const UnknownField* unknown_field) { + GOOGLE_CHECK(unknown_field != NULL) << " Cannot print NULL unknown_field."; + + std::string output; + switch (unknown_field->type()) { + case UnknownField::TYPE_VARINT: + output = StrCat(unknown_field->varint()); + break; + case UnknownField::TYPE_FIXED32: + output = StrCat( + "0x", strings::Hex(unknown_field->fixed32(), strings::ZERO_PAD_8)); + break; + case UnknownField::TYPE_FIXED64: + output = StrCat( + "0x", strings::Hex(unknown_field->fixed64(), strings::ZERO_PAD_16)); + break; + case UnknownField::TYPE_LENGTH_DELIMITED: + output = StringPrintf( + "\"%s\"", CEscape(unknown_field->length_delimited()).c_str()); + break; + case UnknownField::TYPE_GROUP: + // TODO(kenton): Print the contents of the group like we do for + // messages. Requires an equivalent of ShortDebugString() for + // UnknownFieldSet. + output = "{ ... }"; + break; + } + printer_->PrintRaw(output); +} + +void MessageDifferencer::StreamReporter::Print(const std::string& str) { + printer_->Print(str.c_str()); +} + +void MessageDifferencer::StreamReporter::PrintMapKey( + bool left_side, const SpecificField& specific_field) { + if (message1_ == nullptr || message2_ == nullptr) { + GOOGLE_LOG(INFO) << "PrintPath cannot log map keys; " + "use SetMessages to provide the messages " + "being compared prior to any processing."; + return; + } + + const Message* found_message = + left_side ? specific_field.map_entry1 : specific_field.map_entry2; + std::string key_string = ""; + if (found_message != nullptr) { + // NB: the map key is always the first field + const FieldDescriptor* fd = found_message->GetDescriptor()->field(0); + if (fd->cpp_type() == FieldDescriptor::CPPTYPE_STRING) { + // Not using PrintFieldValueToString for strings to avoid extra + // characters + key_string = found_message->GetReflection()->GetString( + *found_message, found_message->GetDescriptor()->field(0)); + } else { + TextFormat::PrintFieldValueToString(*found_message, fd, -1, &key_string); + } + if (key_string.empty()) { + key_string = "''"; + } + printer_->PrintRaw(StrCat("[", key_string, "]")); + } +} + +void MessageDifferencer::StreamReporter::ReportAdded( + const Message& /*message1*/, const Message& message2, + const std::vector<SpecificField>& field_path) { + printer_->Print("added: "); + PrintPath(field_path, false); + printer_->Print(": "); + PrintValue(message2, field_path, false); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportDeleted( + const Message& message1, const Message& /*message2*/, + const std::vector<SpecificField>& field_path) { + printer_->Print("deleted: "); + PrintPath(field_path, true); + printer_->Print(": "); + PrintValue(message1, field_path, true); + printer_->Print("\n"); // Print for newlines +} + +void MessageDifferencer::StreamReporter::ReportModified( + const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) { + if (!report_modified_aggregates_ && field_path.back().field == NULL) { + if (field_path.back().unknown_field_type == UnknownField::TYPE_GROUP) { + // Any changes to the subfields have already been printed. + return; + } + } else if (!report_modified_aggregates_) { + if (field_path.back().field->cpp_type() == + FieldDescriptor::CPPTYPE_MESSAGE) { + // Any changes to the subfields have already been printed. + return; + } + } + + printer_->Print("modified: "); + PrintPath(field_path, true); + if (CheckPathChanged(field_path)) { + printer_->Print(" -> "); + PrintPath(field_path, false); + } + printer_->Print(": "); + PrintValue(message1, field_path, true); + printer_->Print(" -> "); + PrintValue(message2, field_path, false); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportMoved( + const Message& message1, const Message& /*message2*/, + const std::vector<SpecificField>& field_path) { + printer_->Print("moved: "); + PrintPath(field_path, true); + printer_->Print(" -> "); + PrintPath(field_path, false); + printer_->Print(" : "); + PrintValue(message1, field_path, true); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportMatched( + const Message& message1, const Message& /*message2*/, + const std::vector<SpecificField>& field_path) { + printer_->Print("matched: "); + PrintPath(field_path, true); + if (CheckPathChanged(field_path)) { + printer_->Print(" -> "); + PrintPath(field_path, false); + } + printer_->Print(" : "); + PrintValue(message1, field_path, true); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportIgnored( + const Message& /*message1*/, const Message& /*message2*/, + const std::vector<SpecificField>& field_path) { + printer_->Print("ignored: "); + PrintPath(field_path, true); + if (CheckPathChanged(field_path)) { + printer_->Print(" -> "); + PrintPath(field_path, false); + } + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::SetMessages(const Message& message1, + const Message& message2) { + message1_ = &message1; + message2_ = &message2; +} + +void MessageDifferencer::StreamReporter::ReportUnknownFieldIgnored( + const Message& /*message1*/, const Message& /*message2*/, + const std::vector<SpecificField>& field_path) { + printer_->Print("ignored: "); + PrintPath(field_path, true); + if (CheckPathChanged(field_path)) { + printer_->Print(" -> "); + PrintPath(field_path, false); + } + printer_->Print("\n"); // Print for newlines. +} + +MessageDifferencer::MapKeyComparator* +MessageDifferencer::CreateMultipleFieldsMapKeyComparator( + const std::vector<std::vector<const FieldDescriptor*> >& key_field_paths) { + return new MultipleFieldsMapKeyComparator(this, key_field_paths); +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/message_differencer.h b/NorthstarDedicatedTest/include/protobuf/util/message_differencer.h new file mode 100644 index 00000000..c2f79051 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/message_differencer.h @@ -0,0 +1,976 @@ +// 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. + +// Author: jschorr@google.com (Joseph Schorr) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// This file defines static methods and classes for comparing Protocol +// Messages. +// +// Aug. 2008: Added Unknown Fields Comparison for messages. +// Aug. 2009: Added different options to compare repeated fields. +// Apr. 2010: Moved field comparison to FieldComparator +// Sep. 2020: Added option to output map keys in path + +#ifndef GOOGLE_PROTOBUF_UTIL_MESSAGE_DIFFERENCER_H__ +#define GOOGLE_PROTOBUF_UTIL_MESSAGE_DIFFERENCER_H__ + +#include <functional> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include <descriptor.h> // FieldDescriptor +#include <message.h> // Message +#include <unknown_field_set.h> +#include <util/field_comparator.h> + +// Always include as last one, otherwise it can break compilation +#include <port_def.inc> + +namespace google { +namespace protobuf { + +class DynamicMessageFactory; +class FieldDescriptor; + +namespace io { +class ZeroCopyOutputStream; +class Printer; +} // namespace io + +namespace util { + +class DefaultFieldComparator; +class FieldContext; // declared below MessageDifferencer + +// Defines a collection of field descriptors. +// In case of internal google codebase we are using absl::FixedArray instead +// of vector. It significantly speeds up proto comparison (by ~30%) by +// reducing the number of malloc/free operations +typedef std::vector<const FieldDescriptor*> FieldDescriptorArray; + +// A basic differencer that can be used to determine +// the differences between two specified Protocol Messages. If any differences +// are found, the Compare method will return false, and any differencer reporter +// specified via ReportDifferencesTo will have its reporting methods called (see +// below for implementation of the report). Based off of the original +// ProtocolDifferencer implementation in //net/proto/protocol-differencer.h +// (Thanks Todd!). +// +// MessageDifferencer REQUIRES that compared messages be the same type, defined +// as messages that share the same descriptor. If not, the behavior of this +// class is undefined. +// +// People disagree on what MessageDifferencer should do when asked to compare +// messages with different descriptors. Some people think it should always +// return false. Others expect it to try to look for similar fields and +// compare them anyway -- especially if the descriptors happen to be identical. +// If we chose either of these behaviors, some set of people would find it +// surprising, and could end up writing code expecting the other behavior +// without realizing their error. Therefore, we forbid that usage. +// +// This class is implemented based on the proto2 reflection. The performance +// should be good enough for normal usages. However, for places where the +// performance is extremely sensitive, there are several alternatives: +// - Comparing serialized string +// Downside: false negatives (there are messages that are the same but their +// serialized strings are different). +// - Equals code generator by compiler plugin (net/proto2/contrib/equals_plugin) +// Downside: more generated code; maintenance overhead for the additional rule +// (must be in sync with the original proto_library). +// +// Note on handling of google.protobuf.Any: MessageDifferencer automatically +// unpacks Any::value into a Message and compares its individual fields. +// Messages encoded in a repeated Any cannot be compared using TreatAsMap. +// +// Note on thread-safety: MessageDifferencer is *not* thread-safe. You need to +// guard it with a lock to use the same MessageDifferencer instance from +// multiple threads. Note that it's fine to call static comparison methods +// (like MessageDifferencer::Equals) concurrently, but it's not recommended for +// performance critical code as it leads to extra allocations. +class PROTOBUF_EXPORT MessageDifferencer { + public: + // Determines whether the supplied messages are equal. Equality is defined as + // all fields within the two messages being set to the same value. Primitive + // fields and strings are compared by value while embedded messages/groups + // are compared as if via a recursive call. Use Compare() with IgnoreField() + // if some fields should be ignored in the comparison. Use Compare() with + // TreatAsSet() if there are repeated fields where ordering does not matter. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool Equals(const Message& message1, const Message& message2); + + // Determines whether the supplied messages are equivalent. Equivalency is + // defined as all fields within the two messages having the same value. This + // differs from the Equals method above in that fields with default values + // are considered set to said value automatically. For details on how default + // values are defined for each field type, see: + // https://developers.google.com/protocol-buffers/docs/proto?csw=1#optional. + // Also, Equivalent() ignores unknown fields. Use IgnoreField() and Compare() + // if some fields should be ignored in the comparison. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool Equivalent(const Message& message1, const Message& message2); + + // Determines whether the supplied messages are approximately equal. + // Approximate equality is defined as all fields within the two messages + // being approximately equal. Primitive (non-float) fields and strings are + // compared by value, floats are compared using MathUtil::AlmostEquals() and + // embedded messages/groups are compared as if via a recursive call. Use + // IgnoreField() and Compare() if some fields should be ignored in the + // comparison. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool ApproximatelyEquals(const Message& message1, + const Message& message2); + + // Determines whether the supplied messages are approximately equivalent. + // Approximate equivalency is defined as all fields within the two messages + // being approximately equivalent. As in + // MessageDifferencer::ApproximatelyEquals, primitive (non-float) fields and + // strings are compared by value, floats are compared using + // MathUtil::AlmostEquals() and embedded messages/groups are compared as if + // via a recursive call. However, fields with default values are considered + // set to said value, as per MessageDiffencer::Equivalent. Use IgnoreField() + // and Compare() if some fields should be ignored in the comparison. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool ApproximatelyEquivalent(const Message& message1, + const Message& message2); + + // Identifies an individual field in a message instance. Used for field_path, + // below. + struct SpecificField { + // For known fields, "field" is filled in and "unknown_field_number" is -1. + // For unknown fields, "field" is NULL, "unknown_field_number" is the field + // number, and "unknown_field_type" is its type. + const FieldDescriptor* field = nullptr; + int unknown_field_number = -1; + UnknownField::Type unknown_field_type = UnknownField::Type::TYPE_VARINT; + + // If this a repeated field, "index" is the index within it. For unknown + // fields, this is the index of the field among all unknown fields of the + // same field number and type. + int index = -1; + + // If "field" is a repeated field which is being treated as a map or + // a set (see TreatAsMap() and TreatAsSet(), below), new_index indicates + // the index the position to which the element has moved. If the element + // has not moved, "new_index" will have the same value as "index". + int new_index = -1; + + // If "field" is a map field, point to the map entry. + const Message* map_entry1 = nullptr; + const Message* map_entry2 = nullptr; + + // For unknown fields, these are the pointers to the UnknownFieldSet + // containing the unknown fields. In certain cases (e.g. proto1's + // MessageSet, or nested groups of unknown fields), these may differ from + // the messages' internal UnknownFieldSets. + const UnknownFieldSet* unknown_field_set1 = nullptr; + const UnknownFieldSet* unknown_field_set2 = nullptr; + + // For unknown fields, these are the index of the field within the + // UnknownFieldSets. One or the other will be -1 when + // reporting an addition or deletion. + int unknown_field_index1 = -1; + int unknown_field_index2 = -1; + }; + + // Abstract base class from which all MessageDifferencer + // reporters derive. The five Report* methods below will be called when + // a field has been added, deleted, modified, moved, or matched. The third + // argument is a vector of FieldDescriptor pointers which describes the chain + // of fields that was taken to find the current field. For example, for a + // field found in an embedded message, the vector will contain two + // FieldDescriptors. The first will be the field of the embedded message + // itself and the second will be the actual field in the embedded message + // that was added/deleted/modified. + // Fields will be reported in PostTraversalOrder. + // For example, given following proto, if both baz and quux are changed. + // foo { + // bar { + // baz: 1 + // quux: 2 + // } + // } + // ReportModified will be invoked with following order: + // 1. foo.bar.baz or foo.bar.quux + // 2. foo.bar.quux or foo.bar.baz + // 2. foo.bar + // 3. foo + class PROTOBUF_EXPORT Reporter { + public: + Reporter(); + virtual ~Reporter(); + + // Reports that a field has been added into Message2. + virtual void ReportAdded(const Message& message1, const Message& message2, + 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 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 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 + // note that for any given field, ReportModified and ReportMoved are + // mutually exclusive. If a field has been both moved and modified, then + // only ReportModified will be called. + virtual void ReportMoved( + const Message& /* message1 */, const Message& /* message2 */, + 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. + // Note that you must call set_report_matches(true) before calling Compare + // to make use of this function. + virtual void ReportMatched( + const Message& /* message1 */, const Message& /* message2 */, + 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 + // 'ignored' using IgnoreField(). This function is mutually + // exclusive with all the other Report() functions. + // + // The contract of ReportIgnored is slightly different than the + // other Report() functions, in that |field_path.back().index| is + // always equal to -1, even if the last field is repeated. This is + // because while the other Report() functions indicate where in a + // repeated field the action (Addition, Deletion, etc...) + // happened, when a repeated field is 'ignored', the differencer + // simply calls ReportIgnored on the repeated field as a whole and + // moves on without looking at its individual elements. + // + // Furthermore, ReportIgnored() does not indicate whether the + // fields were in fact equal or not, as Compare() does not inspect + // these fields at all. It is up to the Reporter to decide whether + // the fields are equal or not (perhaps with a second call to + // Compare()), if it cares. + virtual void ReportIgnored( + const Message& /* message1 */, const Message& /* message2 */, + 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 std::vector<SpecificField>& /* field_path */) {} + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Reporter); + }; + + // MapKeyComparator is used to determine if two elements have the same key + // when comparing elements of a repeated field as a map. + class PROTOBUF_EXPORT MapKeyComparator { + public: + MapKeyComparator(); + virtual ~MapKeyComparator(); + + 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; + } + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MapKeyComparator); + }; + + // Abstract base class from which all IgnoreCriteria derive. + // By adding IgnoreCriteria more complex ignore logic can be implemented. + // IgnoreCriteria are registered with AddIgnoreCriteria. For each compared + // field IsIgnored is called on each added IgnoreCriteria until one returns + // true or all return false. + // IsIgnored is called for fields where at least one side has a value. + class PROTOBUF_EXPORT IgnoreCriteria { + public: + IgnoreCriteria(); + virtual ~IgnoreCriteria(); + + // Returns true if the field should be ignored. + virtual bool IsIgnored( + const Message& /* message1 */, const Message& /* message2 */, + const FieldDescriptor* /* field */, + 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 + // field.field will be null. + virtual bool IsUnknownFieldIgnored( + const Message& /* message1 */, const Message& /* message2 */, + const SpecificField& /* field */, + const std::vector<SpecificField>& /* parent_fields */) { + return false; + } + }; + + // To add a Reporter, construct default here, then use ReportDifferencesTo or + // ReportDifferencesToString. + explicit MessageDifferencer(); + + ~MessageDifferencer(); + + enum MessageFieldComparison { + EQUAL, // Fields must be present in both messages + // for the messages to be considered the same. + EQUIVALENT, // Fields with default values are considered set + // for comparison purposes even if not explicitly + // set in the messages themselves. Unknown fields + // are ignored. + }; + + enum Scope { + FULL, // All fields of both messages are considered in the comparison. + PARTIAL // Only fields present in the first message are considered; fields + // set only in the second message will be skipped during + // comparison. + }; + + // DEPRECATED. Use FieldComparator::FloatComparison instead. + enum FloatComparison { + EXACT, // Floats and doubles are compared exactly. + APPROXIMATE // Floats and doubles are compared using the + // MathUtil::AlmostEquals method. + }; + + enum RepeatedFieldComparison { + AS_LIST, // Repeated fields are compared in order. Differing values at + // the same index are reported using ReportModified(). If the + // repeated fields have different numbers of elements, the + // unpaired elements are reported using ReportAdded() or + // ReportDeleted(). + AS_SET, // Treat all the repeated fields as sets. + // See TreatAsSet(), as below. + AS_SMART_LIST, // Similar to AS_SET, but preserve the order and find the + // longest matching sequence from the first matching + // element. To use an optimal solution, call + // SetMatchIndicesForSmartListCallback() to pass it in. + AS_SMART_SET, // Similar to AS_SET, but match elements with fewest diffs. + }; + + // The elements of the given repeated field will be treated as a set for + // diffing purposes, so different orderings of the same elements will be + // considered equal. Elements which are present on both sides of the + // comparison but which have changed position will be reported with + // ReportMoved(). Elements which only exist on one side or the other are + // reported with ReportAdded() and ReportDeleted() regardless of their + // positions. ReportModified() is never used for this repeated field. If + // the only differences between the compared messages is that some fields + // have been moved, then the comparison returns true. + // + // Note that despite the name of this method, this is really + // comparison as multisets: if one side of the comparison has a duplicate + // in the repeated field but the other side doesn't, this will count as + // a mismatch. + // + // If the scope of comparison is set to PARTIAL, then in addition to what's + // above, extra values added to repeated fields of the second message will + // not cause the comparison to fail. + // + // Note that set comparison is currently O(k * n^2) (where n is the total + // number of elements, and k is the average size of each element). In theory + // it could be made O(n * k) with a more complex hashing implementation. Feel + // free to contribute one if the current implementation is too slow for you. + // If partial matching is also enabled, the time complexity will be O(k * n^2 + // + n^3) in which n^3 is the time complexity of the maximum matching + // algorithm. + // + // REQUIRES: field->is_repeated() and field not registered with TreatAsMap* + void TreatAsSet(const FieldDescriptor* field); + void TreatAsSmartSet(const FieldDescriptor* field); + + // The elements of the given repeated field will be treated as a list for + // diffing purposes, so different orderings of the same elements will NOT be + // considered equal. + // + // REQUIRES: field->is_repeated() and field not registered with TreatAsMap* + void TreatAsList(const FieldDescriptor* field); + // Note that the complexity is similar to treating as SET. + void TreatAsSmartList(const FieldDescriptor* field); + + // The elements of the given repeated field will be treated as a map for + // diffing purposes, with |key| being the map key. Thus, elements with the + // same key will be compared even if they do not appear at the same index. + // Differences are reported similarly to TreatAsSet(), except that + // ReportModified() is used to report elements with the same key but + // different values. Note that if an element is both moved and modified, + // only ReportModified() will be called. As with TreatAsSet, if the only + // differences between the compared messages is that some fields have been + // moved, then the comparison returns true. See TreatAsSet for notes on + // performance. + // + // REQUIRES: field->is_repeated() + // REQUIRES: field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE + // REQUIRES: key->containing_type() == field->message_type() + void TreatAsMap(const FieldDescriptor* field, const FieldDescriptor* key); + // Same as TreatAsMap except that this method will use multiple fields as + // the key in comparison. All specified fields in 'key_fields' should be + // present in the compared elements. Two elements will be treated as having + // the same key iff they have the same value for every specified field. There + // are two steps in the comparison process. The first one is key matching. + // Every element from one message will be compared to every element from + // the other message. Only fields in 'key_fields' are compared in this step + // to decide if two elements have the same key. The second step is value + // comparison. Those pairs of elements with the same key (with equal value + // for every field in 'key_fields') will be compared in this step. + // Time complexity of the first step is O(s * m * n ^ 2) where s is the + // average size of the fields specified in 'key_fields', m is the number of + // fields in 'key_fields' and n is the number of elements. If partial + // matching is enabled, an extra O(n^3) will be incured by the maximum + // matching algorithm. The second step is O(k * n) where k is the average + // size of each element. + void TreatAsMapWithMultipleFieldsAsKey( + const FieldDescriptor* field, + 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 + // successive subfield to reach the key field. + // + // REQUIRES: + // for key_field_path in key_field_paths: + // key_field_path[0]->containing_type() == field->message_type() + // for i in [0, key_field_path.size() - 1): + // key_field_path[i+1]->containing_type() == + // key_field_path[i]->message_type() + // key_field_path[i]->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE + // !key_field_path[i]->is_repeated() + void TreatAsMapWithMultipleFieldPathsAsKey( + const FieldDescriptor* field, + 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. + // The caller is responsible to delete the key_comparator. + // This method varies from TreatAsMapWithMultipleFieldsAsKey only in the + // first key matching step. Rather than comparing some specified fields, it + // will invoke the IsMatch method of the given 'key_comparator' to decide if + // two elements have the same key. + void TreatAsMapUsingKeyComparator(const FieldDescriptor* field, + const MapKeyComparator* key_comparator); + + // Initiates and returns a new instance of MultipleFieldsMapKeyComparator. + MapKeyComparator* CreateMultipleFieldsMapKeyComparator( + const std::vector<std::vector<const FieldDescriptor*> >& key_field_paths); + + // Add a custom ignore criteria that is evaluated in addition to the + // ignored fields added with IgnoreField. + // Takes ownership of ignore_criteria. + void AddIgnoreCriteria(IgnoreCriteria* ignore_criteria); + + // Indicates that any field with the given descriptor should be + // ignored for the purposes of comparing two messages. This applies + // to fields nested in the message structure as well as top level + // ones. When the MessageDifferencer encounters an ignored field, + // ReportIgnored is called on the reporter, if one is specified. + // + // The only place where the field's 'ignored' status is not applied is when + // it is being used as a key in a field passed to TreatAsMap or is one of + // the fields passed to TreatAsMapWithMultipleFieldsAsKey. + // In this case it is compared in key matching but after that it's ignored + // in value comparison. + void IgnoreField(const FieldDescriptor* field); + + // Sets the field comparator used to determine differences between protocol + // buffer fields. By default it's set to a DefaultFieldComparator instance. + // MessageDifferencer doesn't take ownership over the passed object. + // Note that this method must be called before Compare for the comparator to + // be used. + void set_field_comparator(FieldComparator* comparator); +#ifdef PROTOBUF_FUTURE_BREAKING_CHANGES + void set_field_comparator(DefaultFieldComparator* comparator); +#endif // PROTOBUF_FUTURE_BREAKING_CHANGES + + // DEPRECATED. Pass a DefaultFieldComparator instance instead. + // Sets the fraction and margin for the float comparison of a given field. + // Uses MathUtil::WithinFractionOrMargin to compare the values. + // NOTE: this method does nothing if differencer's field comparator has been + // set to a custom object. + // + // REQUIRES: field->cpp_type == FieldDescriptor::CPPTYPE_DOUBLE or + // field->cpp_type == FieldDescriptor::CPPTYPE_FLOAT + // REQUIRES: float_comparison_ == APPROXIMATE + void SetFractionAndMargin(const FieldDescriptor* field, double fraction, + double margin); + + // Sets the type of comparison (as defined in the MessageFieldComparison + // enumeration above) that is used by this differencer when determining how + // to compare fields in messages. + void set_message_field_comparison(MessageFieldComparison comparison); + + // Tells the differencer whether or not to report matches. This method must + // be called before Compare. The default for a new differencer is false. + void set_report_matches(bool report_matches) { + report_matches_ = report_matches; + } + + // Tells the differencer whether or not to report moves (in a set or map + // repeated field). This method must be called before Compare. The default for + // a new differencer is true. + void set_report_moves(bool report_moves) { report_moves_ = report_moves; } + + // Tells the differencer whether or not to report ignored values. This method + // must be called before Compare. The default for a new differencer is true. + void set_report_ignores(bool report_ignores) { + report_ignores_ = report_ignores; + } + + // Sets the scope of the comparison (as defined in the Scope enumeration + // above) that is used by this differencer when determining which fields to + // compare between the messages. + void set_scope(Scope scope); + + // Returns the current scope used by this differencer. + Scope scope(); + + // DEPRECATED. Pass a DefaultFieldComparator instance instead. + // Sets the type of comparison (as defined in the FloatComparison enumeration + // above) that is used by this differencer when comparing float (and double) + // fields in messages. + // NOTE: this method does nothing if differencer's field comparator has been + // set to a custom object. + void set_float_comparison(FloatComparison comparison); + + // Sets the type of comparison for repeated field (as defined in the + // RepeatedFieldComparison enumeration above) that is used by this + // differencer when compare repeated fields in messages. + void set_repeated_field_comparison(RepeatedFieldComparison comparison); + + // Returns the current repeated field comparison used by this differencer. + RepeatedFieldComparison repeated_field_comparison(); + + // Compares the two specified messages, returning true if they are the same, + // false otherwise. If this method returns false, any changes between the + // two messages will be reported if a Reporter was specified via + // ReportDifferencesTo (see also ReportDifferencesToString). + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + bool Compare(const Message& message1, const Message& message2); + + // 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 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 + // method must be called before Compare. + void ReportDifferencesToString(std::string* output); + + // Tells the MessageDifferencer to report differences via the specified + // reporter. Note that this method must be called before Compare for + // the reporter to be used. It is the responsibility of the caller to delete + // this object. + // If the provided pointer equals NULL, the MessageDifferencer stops reporting + // differences to any previously set reporters or output strings. + void ReportDifferencesTo(Reporter* reporter); + + private: + // Class for processing Any deserialization. This logic is used by both the + // MessageDifferencer and StreamReporter classes. + class UnpackAnyField { + private: + std::unique_ptr<DynamicMessageFactory> dynamic_message_factory_; + + public: + UnpackAnyField() = default; + ~UnpackAnyField() = default; + // If "any" is of type google.protobuf.Any, extract its payload using + // DynamicMessageFactory and store in "data". + bool UnpackAny(const Message& any, std::unique_ptr<Message>* data); + }; + + public: + // An implementation of the MessageDifferencer Reporter that outputs + // any differences found in human-readable form to the supplied + // ZeroCopyOutputStream or Printer. If a printer is used, the delimiter + // *must* be '$'. + // + // WARNING: this reporter does not necessarily flush its output until it is + // destroyed. As a result, it is not safe to assume the output is valid or + // complete until after you destroy the reporter. For example, if you use a + // StreamReporter to write to a StringOutputStream, the target string may + // contain uninitialized data until the reporter is destroyed. + class PROTOBUF_EXPORT StreamReporter : public Reporter { + public: + explicit StreamReporter(io::ZeroCopyOutputStream* output); + explicit StreamReporter(io::Printer* printer); // delimiter '$' + ~StreamReporter() override; + + // When set to true, the stream reporter will also output aggregates nodes + // (i.e. messages and groups) whose subfields have been modified. When + // false, will only report the individual subfields. Defaults to false. + void set_report_modified_aggregates(bool report) { + report_modified_aggregates_ = report; + } + + // The following are implementations of the methods described above. + + void ReportAdded(const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) override; + + void ReportDeleted(const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) override; + + void ReportModified(const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) override; + + void ReportMoved(const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) override; + + void ReportMatched(const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) override; + + void ReportIgnored(const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) override; + + void ReportUnknownFieldIgnored( + const Message& message1, const Message& message2, + const std::vector<SpecificField>& field_path) override; + + // Messages that are being compared must be provided to StreamReporter prior + // to processing + void SetMessages(const Message& message1, const Message& message2); + + protected: + // Prints the specified path of fields to the buffer. + 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 + // given message is from the left side of the comparison, false if it + // was the right. This is relevant only to decide whether to follow + // unknown_field_index1 or unknown_field_index2 when an unknown field + // is encountered in field_path. + virtual void PrintValue(const Message& message, + const std::vector<SpecificField>& field_path, + bool left_side); + + // Prints the specified path of unknown fields to the buffer. + virtual void PrintUnknownFieldValue(const UnknownField* unknown_field); + + // Just print a string + void Print(const std::string& str); + + private: + // helper function for PrintPath that contains logic for printing maps + void PrintMapKey(bool left_side, const SpecificField& specific_field); + + io::Printer* printer_; + bool delete_printer_; + bool report_modified_aggregates_; + const Message* message1_; + const Message* message2_; + MessageDifferencer::UnpackAnyField unpack_any_field_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(StreamReporter); + }; + + private: + friend class SimpleFieldComparator; + + // A MapKeyComparator to be used in TreatAsMapUsingKeyComparator. + // Implementation of this class needs to do field value comparison which + // relies on some private methods of MessageDifferencer. That's why this + // class is declared as a nested class of MessageDifferencer. + class MultipleFieldsMapKeyComparator; + + // A MapKeyComparator for use with map_entries. + class PROTOBUF_EXPORT MapEntryKeyComparator : public MapKeyComparator { + public: + explicit MapEntryKeyComparator(MessageDifferencer* message_differencer); + bool IsMatch( + const Message& message1, const Message& message2, + const std::vector<SpecificField>& parent_fields) const override; + + private: + MessageDifferencer* message_differencer_; + }; + + // Returns true if field1's number() is less than field2's. + static bool FieldBefore(const FieldDescriptor* field1, + const FieldDescriptor* field2); + + // Retrieve all the set fields, including extensions. + FieldDescriptorArray RetrieveFields(const Message& message, + bool base_message); + + // Combine the two lists of fields into the combined_fields output vector. + // 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. + FieldDescriptorArray CombineFields(const FieldDescriptorArray& fields1, + Scope fields1_scope, + const FieldDescriptorArray& fields2, + Scope fields2_scope); + + // Internal version of the Compare method which performs the actual + // comparison. The parent_fields vector is a vector containing field + // descriptors of all fields accessed to get to this comparison operation + // (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, + std::vector<SpecificField>* parent_fields); + + // Compares all the unknown fields in two messages. + bool CompareUnknownFields(const Message& message1, const Message& message2, + const UnknownFieldSet&, const UnknownFieldSet&, + 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 FieldDescriptorArray& message1_fields, + const FieldDescriptorArray& 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 FieldDescriptorArray& message1_fields, + const FieldDescriptorArray& 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, + std::vector<SpecificField>* parent_fields); + + // Compares map fields, and report the error. + bool CompareMapField(const Message& message1, const Message& message2, + const FieldDescriptor* field, + std::vector<SpecificField>* parent_fields); + + // Helper for CompareRepeatedField and CompareMapField: compares and reports + // differences element-wise. This is the implementation for non-map fields, + // and can also compare map fields by using the underlying representation. + bool CompareRepeatedRep(const Message& message1, const Message& message2, + const FieldDescriptor* field, + std::vector<SpecificField>* parent_fields); + + // Helper for CompareMapField: compare the map fields using map reflection + // instead of sync to repeated. + bool CompareMapFieldByMapReflection(const Message& message1, + const Message& message2, + const FieldDescriptor* field, + std::vector<SpecificField>* parent_fields, + DefaultFieldComparator* comparator); + + // Shorthand for CompareFieldValueUsingParentFields with NULL parent_fields. + bool CompareFieldValue(const Message& message1, const Message& message2, + const FieldDescriptor* field, int index1, int index2); + + // Compares the specified field on the two messages, returning + // true if they are the same, false otherwise. For repeated fields, + // this method only compares the value in the specified index. This method + // uses Compare functions to recurse into submessages. + // The parent_fields vector is used in calls to a Reporter instance calls. + // It can be NULL, in which case the MessageDifferencer will create new + // 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, + std::vector<SpecificField>* parent_fields); + + // Compares the specified field on the two messages, returning comparison + // result, as returned by appropriate FieldComparator. + FieldComparator::ComparisonResult GetFieldComparisonResult( + const Message& message1, const Message& message2, + const FieldDescriptor* field, int index1, int index2, + const FieldContext* field_context); + + // Check if the two elements in the repeated field are match to each other. + // if the key_comprator is NULL, this function returns true when the two + // elements are equal. + bool IsMatch(const FieldDescriptor* repeated_field, + const MapKeyComparator* key_comparator, const Message* message1, + const Message* message2, + const std::vector<SpecificField>& parent_fields, + Reporter* reporter, int index1, int index2); + + // Returns true when this repeated field has been configured to be treated + // as a Set / SmartSet / SmartList. + bool IsTreatedAsSet(const FieldDescriptor* field); + bool IsTreatedAsSmartSet(const FieldDescriptor* field); + + bool IsTreatedAsSmartList(const FieldDescriptor* field); + // When treating as SMART_LIST, it uses MatchIndicesPostProcessorForSmartList + // by default to find the longest matching sequence from the first matching + // element. The callback takes two vectors showing the matching indices from + // the other vector, where -1 means an unmatch. + void SetMatchIndicesForSmartListCallback( + std::function<void(std::vector<int>*, std::vector<int>*)> callback); + + // Returns true when this repeated field is to be compared as a subset, ie. + // has been configured to be treated as a set or map and scope is set to + // PARTIAL. + bool IsTreatedAsSubset(const FieldDescriptor* field); + + // Returns true if this field is to be ignored when this + // MessageDifferencer compares messages. + bool IsIgnored(const Message& message1, const Message& message2, + const FieldDescriptor* field, + 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 std::vector<SpecificField>& parent_fields); + + // Returns MapKeyComparator* when this field has been configured to be treated + // as a map or its is_map() return true. If not, returns NULL. + const MapKeyComparator* GetMapKeyComparator( + const FieldDescriptor* field) const; + + // Attempts to match indices of a repeated field, so that the contained values + // match. Clears output vectors and sets their values to indices of paired + // messages, ie. if message1[0] matches message2[1], then match_list1[0] == 1 + // and match_list2[1] == 0. The unmatched indices are indicated by -1. + // Assumes the repeated field is not treated as a simple list. + // 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 MapKeyComparator* key_comparator, + const std::vector<SpecificField>& parent_fields, + std::vector<int>* match_list1, std::vector<int>* match_list2); + + // Checks if index is equal to new_index in all the specific fields. + static bool CheckPathChanged(const std::vector<SpecificField>& parent_fields); + + // CHECKs that the given repeated field can be compared according to + // new_comparison. + void CheckRepeatedFieldComparisons( + const FieldDescriptor* field, + const RepeatedFieldComparison& new_comparison); + + // Defines a map between field descriptors and their MapKeyComparators. + // Used for repeated fields when they are configured as TreatAsMap. + 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 std::set<const FieldDescriptor*> FieldSet; + typedef std::map<const FieldDescriptor*, RepeatedFieldComparison> FieldMap; + + Reporter* reporter_; + DefaultFieldComparator default_field_comparator_; + MessageFieldComparison message_field_comparison_; + Scope scope_; + RepeatedFieldComparison repeated_field_comparison_; + + FieldMap repeated_field_comparisons_; + // Keeps track of MapKeyComparators that are created within + // MessageDifferencer. These MapKeyComparators should be deleted + // before MessageDifferencer is destroyed. + // When TreatAsMap or TreatAsMapWithMultipleFieldsAsKey is called, we don't + // store the supplied FieldDescriptors directly. Instead, a new + // MapKeyComparator is created for comparison purpose. + std::vector<MapKeyComparator*> owned_key_comparators_; + FieldKeyComparatorMap map_field_key_comparator_; + MapEntryKeyComparator map_entry_key_comparator_; + std::vector<IgnoreCriteria*> ignore_criteria_; + // Reused multiple times in RetrieveFields to avoid extra allocations + std::vector<const FieldDescriptor*> tmp_message_fields_; + + FieldSet ignored_fields_; + + union { + DefaultFieldComparator* default_impl; + FieldComparator* base; + } field_comparator_ = {&default_field_comparator_}; + enum { kFCDefault, kFCBase } field_comparator_kind_ = kFCDefault; + + bool report_matches_; + bool report_moves_; + bool report_ignores_; + + std::string* output_string_; + + // Callback to post-process the matched indices to support SMART_LIST. + std::function<void(std::vector<int>*, std::vector<int>*)> + match_indices_for_smart_list_callback_; + + MessageDifferencer::UnpackAnyField unpack_any_field_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MessageDifferencer); +}; + +// This class provides extra information to the FieldComparator::Compare +// function. +class PROTOBUF_EXPORT FieldContext { + public: + explicit FieldContext( + std::vector<MessageDifferencer::SpecificField>* parent_fields) + : parent_fields_(parent_fields) {} + + std::vector<MessageDifferencer::SpecificField>* parent_fields() const { + return parent_fields_; + } + + private: + std::vector<MessageDifferencer::SpecificField>* parent_fields_; +}; + +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_MESSAGE_DIFFERENCER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/message_differencer_unittest.cc b/NorthstarDedicatedTest/include/protobuf/util/message_differencer_unittest.cc new file mode 100644 index 00000000..dd138b21 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/message_differencer_unittest.cc @@ -0,0 +1,3812 @@ +// 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. + +// Author: jschorr@google.com (Joseph Schorr) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// TODO(ksroka): Move some of these tests to field_comparator_test.cc. + +#include <algorithm> +#include <random> +#include <string> +#include <vector> + +#include <stubs/common.h> + +#include <stubs/strutil.h> + +#include <stubs/logging.h> +#include <any_test.pb.h> +#include <map_test_util.h> +#include <map_unittest.pb.h> +#include <test_util.h> +#include <unittest.pb.h> +#include <io/coded_stream.h> +#include <io/zero_copy_stream_impl.h> +#include <text_format.h> +#include <wire_format.h> +#include <util/message_differencer_unittest.pb.h> +#include <util/field_comparator.h> +#include <util/message_differencer.h> +#include <testing/googletest.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { + +namespace { + + +const FieldDescriptor* GetFieldDescriptor(const Message& message, + const std::string& field_name) { + std::vector<std::string> field_path = + Split(field_name, ".", true); + const Descriptor* descriptor = message.GetDescriptor(); + const FieldDescriptor* field = NULL; + for (int i = 0; i < field_path.size(); i++) { + field = descriptor->FindFieldByName(field_path[i]); + descriptor = field->message_type(); + } + return field; +} + +void ExpectEqualsWithDifferencer(util::MessageDifferencer* differencer, + const Message& msg1, const Message& msg2) { + differencer->set_scope(util::MessageDifferencer::FULL); + EXPECT_TRUE(differencer->Compare(msg1, msg2)); + + differencer->set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer->Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.add_repeated_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetOptimizationTest) { + util::MessageDifferencer differencer; + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item1 = msg1.add_item(); + protobuf_unittest::TestDiffMessage::Item* item2 = msg2.add_item(); + differencer.TreatAsSet(item1->GetDescriptor()->FindFieldByName("ra")); + differencer.TreatAsSet(item2->GetDescriptor()->FindFieldByName("ra")); + for (int i = 0; i < 1000; i++) { + item1->add_ra(i); + item2->add_ra(i); + } + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + item2->add_ra(1001); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + item1->add_ra(1001); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + item1->add_ra(1002); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, MapFieldEqualityTest) { + // Create the testing protos + unittest::TestMap msg1; + unittest::TestMap msg2; + + MapReflectionTester tester(unittest::TestMap::descriptor()); + tester.SetMapFieldsViaReflection(&msg1); + tester.SetMapFieldsViaReflection(&msg2); + tester.SwapMapsViaReflection(&msg1); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); + + // Get map entries by index will sync map to repeated field + MapTestUtil::GetMapEntries(msg1, 0); + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); + + // Compare values not match + (*msg1.mutable_map_int32_int32())[1] = 2; + (*msg2.mutable_map_int32_int32())[1] = 3; + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + + // Compare keys not match + msg1.Clear(); + msg2.Clear(); + (*msg1.mutable_map_string_string())["1"] = ""; + (*msg2.mutable_map_string_string())["2"] = ""; + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + + // Compare message values not match + msg1.Clear(); + msg2.Clear(); + (*msg1.mutable_map_int32_foreign_message())[1].set_c(1); + (*msg2.mutable_map_int32_foreign_message())[1].set_c(2); + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEqualityTestExtraField) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.clear_optional_int32(); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEqualityTestSkipRequiredField) { + // Create the testing protos + unittest::TestRequired msg1; + unittest::TestRequired msg2; + + msg1.set_a(401); + msg2.set_a(401); + msg2.set_b(402); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialInequalityMissingFieldTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg2.clear_optional_int32(); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldPartialInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.add_repeated_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, EquivalencyNotEqualTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.clear_optional_int32(); + msg2.set_optional_int32(0); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicInequivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicEquivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicInequivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + msg1.set_optional_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEquivalencyNotEqualTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(0); + msg2.clear_optional_int32(); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEquivalencyTestExtraField) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.clear_optional_int32(); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEquivalencyTestSkipRequiredField) { + // Create the testing protos + unittest::TestRequired msg1; + unittest::TestRequired msg2; + + msg1.set_a(401); + msg2.set_a(401); + msg2.set_b(402); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialInequivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialEquivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialInequivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + msg1.set_optional_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateModifiedEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + const float v1 = 2.300005f; + const float v2 = 2.300006f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + + // Compare + ASSERT_NE(v1, v2) << "Should not be the same: " << v1 << ", " << v2; + ASSERT_FLOAT_EQ(v1, v2) << "Should be approx. equal: " << v1 << ", " << v2; + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateModifiedEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Modify the approximateness requirement + const float v1 = 2.300005f; + const float v2 = 2.300006f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + + // Compare + ASSERT_NE(v1, v2) << "Should not be the same: " << v1 << ", " << v2; + ASSERT_FLOAT_EQ(v1, v2) << "Should be approx. equal: " << v1 << ", " << v2; + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, msg2)); + + // Modify the equivalency requirement too + msg1.clear_optional_int32(); + msg2.set_optional_int32(0); + + // Compare. Now should only pass on ApproximatelyEquivalent + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_FALSE(util::MessageDifferencer::Equivalent(msg1, msg2)); + EXPECT_FALSE(util::MessageDifferencer::ApproximatelyEquals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateInequivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on equivalency + msg1.set_optional_int32(-1); + EXPECT_FALSE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, msg2)); + + // Make these fields the same again. + msg1.set_optional_int32(0); + msg2.set_optional_int32(0); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, msg2)); + + // Should fail on approximate equality check + const float v1 = 2.3f; + const float v2 = 9.3f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + EXPECT_FALSE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, WithinFractionOrMarginFloatTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on approximate equality check + const float v1 = 100.0f; + const float v2 = 109.9f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + + // Compare + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + const FieldDescriptor* fd = + msg1.GetDescriptor()->FindFieldByName("optional_float"); + + // Set float comparison to exact, margin and fraction value should not matter. + differencer.set_float_comparison(util::MessageDifferencer::EXACT); + // Set margin for float comparison. + differencer.SetFractionAndMargin(fd, 0.0, 10.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Margin and fraction float comparison is activated when float comparison is + // set to approximate. + differencer.set_float_comparison(util::MessageDifferencer::APPROXIMATE); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Test out float comparison with fraction. + differencer.SetFractionAndMargin(fd, 0.2, 0.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Should fail since the fraction is smaller than error. + differencer.SetFractionAndMargin(fd, 0.01, 0.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Should pass if either fraction or margin are satisfied. + differencer.SetFractionAndMargin(fd, 0.01, 10.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Make sure that the margin and fraction only affects the field that it was + // set for. + msg1.set_default_float(v1); + msg2.set_default_float(v2); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + msg1.set_default_float(v1); + msg2.set_default_float(v1); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, WithinFractionOrMarginDoubleTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on approximate equality check + const double v1 = 100.0; + const double v2 = 109.9; + msg1.set_optional_double(v1); + msg2.set_optional_double(v2); + + // Compare + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Set comparison to exact, margin and fraction value should not matter. + differencer.set_float_comparison(util::MessageDifferencer::EXACT); + // Set margin for float comparison. + const FieldDescriptor* fd = + msg1.GetDescriptor()->FindFieldByName("optional_double"); + differencer.SetFractionAndMargin(fd, 0.0, 10.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Margin and fraction comparison is activated when float comparison is + // set to approximate. + differencer.set_float_comparison(util::MessageDifferencer::APPROXIMATE); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Test out comparison with fraction. + differencer.SetFractionAndMargin(fd, 0.2, 0.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Should fail since the fraction is smaller than error. + differencer.SetFractionAndMargin(fd, 0.01, 0.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Should pass if either fraction or margin are satisfied. + differencer.SetFractionAndMargin(fd, 0.01, 10.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Make sure that the margin and fraction only affects the field that it was + // set for. + msg1.set_default_double(v1); + msg2.set_default_double(v2); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + msg1.set_default_double(v1); + msg2.set_default_double(v1); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, WithinDefaultFractionOrMarginDoubleTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on approximate equality check + const double v1 = 100.0; + const double v2 = 109.9; + msg1.set_optional_double(v1); + msg2.set_optional_double(v2); + + util::MessageDifferencer differencer; + + // Compare + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Set up a custom field comparator, with a default fraction and margin for + // float and double comparison. + util::DefaultFieldComparator field_comparator; + field_comparator.SetDefaultFractionAndMargin(0.0, 10.0); + differencer.set_field_comparator(&field_comparator); + + // Set comparison to exact, margin and fraction value should not matter. + field_comparator.set_float_comparison(util::DefaultFieldComparator::EXACT); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Margin and fraction comparison is activated when float comparison is + // set to approximate. + field_comparator.set_float_comparison( + util::DefaultFieldComparator::APPROXIMATE); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Test out comparison with fraction. + field_comparator.SetDefaultFractionAndMargin(0.2, 0.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Should fail since the fraction is smaller than error. + field_comparator.SetDefaultFractionAndMargin(0.01, 0.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Should pass if either fraction or margin are satisfied. + field_comparator.SetDefaultFractionAndMargin(0.01, 10.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Make sure that the default margin and fraction affects all fields + msg1.set_default_double(v1); + msg2.set_default_double(v2); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicFieldOrderingsTest) { + // Create the testing protos + unittest::TestFieldOrderings msg1; + unittest::TestFieldOrderings msg2; + + TestUtil::SetAllFieldsAndExtensions(&msg1); + TestUtil::SetAllFieldsAndExtensions(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicFieldOrderingInequalityTest) { + // Create the testing protos + unittest::TestFieldOrderings msg1; + unittest::TestFieldOrderings msg2; + + TestUtil::SetAllFieldsAndExtensions(&msg1); + TestUtil::SetAllFieldsAndExtensions(&msg2); + + msg1.set_my_float(15.00); + msg2.set_my_float(16.00); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicExtensionTest) { + // Create the testing protos + unittest::TestAllExtensions msg1; + unittest::TestAllExtensions msg2; + + TestUtil::SetAllExtensions(&msg1); + TestUtil::SetAllExtensions(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicExtensionInequalityTest) { + // Create the testing protos + unittest::TestAllExtensions msg1; + unittest::TestAllExtensions msg2; + + TestUtil::SetAllExtensions(&msg1); + TestUtil::SetAllExtensions(&msg2); + + msg1.SetExtension(unittest::optional_int32_extension, 101); + msg2.SetExtension(unittest::optional_int32_extension, 102); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, OneofTest) { + // Create the testing protos + unittest::TestOneof2 msg1; + unittest::TestOneof2 msg2; + + TestUtil::SetOneof1(&msg1); + TestUtil::SetOneof1(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, OneofInequalityTest) { + // Create the testing protos + unittest::TestOneof2 msg1; + unittest::TestOneof2 msg2; + + TestUtil::SetOneof1(&msg1); + TestUtil::SetOneof2(&msg2); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, UnknownFieldPartialEqualTest) { + unittest::TestEmptyMessage empty1; + unittest::TestEmptyMessage empty2; + + UnknownFieldSet* unknown1 = empty1.mutable_unknown_fields(); + UnknownFieldSet* unknown2 = empty2.mutable_unknown_fields(); + + unknown1->AddVarint(243, 122); + unknown1->AddLengthDelimited(245, "abc"); + unknown1->AddGroup(246)->AddFixed32(248, 1); + unknown1->mutable_field(2)->mutable_group()->AddFixed32(248, 2); + + unknown2->AddVarint(243, 122); + unknown2->AddLengthDelimited(245, "abc"); + unknown2->AddGroup(246)->AddFixed32(248, 1); + unknown2->mutable_field(2)->mutable_group()->AddFixed32(248, 2); + + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(empty1, empty2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEqualityAllTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + std::vector<const FieldDescriptor*> fields1; + std::vector<const FieldDescriptor*> fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsInequalityAllTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + + std::vector<const FieldDescriptor*> fields1; + std::vector<const FieldDescriptor*> fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEmptyListAlwaysSucceeds) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + + std::vector<const FieldDescriptor*> empty_fields; + + util::MessageDifferencer differencer; + EXPECT_TRUE( + differencer.CompareWithFields(msg1, msg2, empty_fields, empty_fields)); + + TestUtil::SetAllFields(&msg2); + EXPECT_TRUE( + differencer.CompareWithFields(msg1, msg2, empty_fields, empty_fields)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsCompareWithSelf) { + unittest::TestAllTypes msg1; + TestUtil::SetAllFields(&msg1); + + std::vector<const FieldDescriptor*> fields; + msg1.GetReflection()->ListFields(msg1, &fields); + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg1, fields, fields)); + + { + // Compare with a subset of fields. + std::vector<const FieldDescriptor*> compare_fields; + for (int i = 0; i < fields.size(); ++i) { + if (i % 2 == 0) { + compare_fields.push_back(fields[i]); + } + } + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg1, compare_fields, + compare_fields)); + } + { + // Specify a different set of fields to compare, even though we're using the + // same message. This should fail, since we are explicitly saying that the + // set of fields are different. + std::vector<const FieldDescriptor*> compare_fields1; + std::vector<const FieldDescriptor*> compare_fields2; + for (int i = 0; i < fields.size(); ++i) { + if (i % 2 == 0) { + compare_fields1.push_back(fields[i]); + } else { + compare_fields2.push_back(fields[i]); + } + } + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg1, compare_fields1, + compare_fields2)); + } +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEqualityAllShuffledTest) { + // This is a public function, so make sure there are no assumptions about the + // list of fields. Randomly shuffle them to make sure that they are properly + // ordered for comparison. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + std::vector<const FieldDescriptor*> fields1; + std::vector<const FieldDescriptor*> fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + std::default_random_engine rng; + std::shuffle(fields1.begin(), fields1.end(), rng); + std::shuffle(fields2.begin(), fields2.end(), rng); + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsSubsetEqualityTest) { + // Specify a set of fields to compare. All the fields are equal. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + std::vector<const FieldDescriptor*> fields1; + msg1.GetReflection()->ListFields(msg1, &fields1); + + std::vector<const FieldDescriptor*> compare_fields; + // Only compare the field descriptors with even indices. + for (int i = 0; i < fields1.size(); ++i) { + if (i % 2 == 0) { + compare_fields.push_back(fields1[i]); + } + } + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, compare_fields, + compare_fields)); +} + +TEST(MessageDifferencerTest, + SpecifiedFieldsSubsetIgnoresOtherFieldDifferencesTest) { + // Specify a set of fields to compare, but clear all the other fields in one + // of the messages. This should fail a regular compare, but CompareWithFields + // should succeed. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + std::vector<const FieldDescriptor*> fields1; + const Reflection* reflection = msg1.GetReflection(); + reflection->ListFields(msg1, &fields1); + + std::vector<const FieldDescriptor*> compare_fields; + // Only compare the field descriptors with even indices. + for (int i = 0; i < fields1.size(); ++i) { + if (i % 2 == 0) { + compare_fields.push_back(fields1[i]); + } else { + reflection->ClearField(&msg2, fields1[i]); + } + } + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, compare_fields, + compare_fields)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsDetectsDifferencesTest) { + // Change all of the repeated fields in one of the messages, and use only + // those fields for comparison. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + TestUtil::ModifyRepeatedFields(&msg2); + + std::vector<const FieldDescriptor*> fields1; + msg1.GetReflection()->ListFields(msg1, &fields1); + + std::vector<const FieldDescriptor*> compare_fields; + // Only compare the repeated field descriptors. + for (int i = 0; i < fields1.size(); ++i) { + if (fields1[i]->is_repeated()) { + compare_fields.push_back(fields1[i]); + } + } + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg2, compare_fields, + compare_fields)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEquivalenceAllTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + std::vector<const FieldDescriptor*> fields1; + std::vector<const FieldDescriptor*> fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, + SpecifiedFieldsEquivalenceIgnoresOtherFieldDifferencesTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + const Descriptor* desc = msg1.GetDescriptor(); + + const FieldDescriptor* optional_int32_desc = + desc->FindFieldByName("optional_int32"); + const FieldDescriptor* optional_int64_desc = + desc->FindFieldByName("optional_int64"); + const FieldDescriptor* default_int64_desc = + desc->FindFieldByName("default_int64"); + ASSERT_TRUE(optional_int32_desc != NULL); + ASSERT_TRUE(optional_int64_desc != NULL); + ASSERT_TRUE(default_int64_desc != NULL); + msg1.set_optional_int32(0); + msg2.set_optional_int64(0); + msg1.set_default_int64(default_int64_desc->default_value_int64()); + + // Set a field to a non-default value so we know that field selection is + // actually doing something. + msg2.set_optional_uint64(23); + + std::vector<const FieldDescriptor*> fields1; + std::vector<const FieldDescriptor*> fields2; + fields1.push_back(optional_int32_desc); + fields1.push_back(default_int64_desc); + + fields2.push_back(optional_int64_desc); + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldTreatmentChangeListToSet) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + msg1.add_rv(1); + msg1.add_rv(2); + msg2.add_rv(2); + msg2.add_rv(1); + + util::MessageDifferencer differencer; + differencer.TreatAsList( + protobuf_unittest::TestDiffMessage::descriptor()->FindFieldByName("rv")); + differencer.TreatAsSet( + protobuf_unittest::TestDiffMessage::descriptor()->FindFieldByName("rv")); + + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldTreatmentChangeSetToList) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + msg1.add_rv(1); + msg1.add_rv(2); + msg2.add_rv(2); + msg2.add_rv(1); + + util::MessageDifferencer differencer; + differencer.TreatAsSet( + protobuf_unittest::TestDiffMessage::descriptor()->FindFieldByName("rv")); + differencer.TreatAsList( + protobuf_unittest::TestDiffMessage::descriptor()->FindFieldByName("rv")); + + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSmartListTest) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + msg1.add_rv(1); + msg1.add_rv(2); + msg1.add_rv(3); + msg1.add_rv(9); + msg1.add_rv(4); + msg1.add_rv(5); + msg1.add_rv(7); + msg1.add_rv(2); + msg2.add_rv(9); + msg2.add_rv(0); + msg2.add_rv(2); + msg2.add_rv(7); + msg2.add_rv(3); + msg2.add_rv(4); + msg2.add_rv(5); + msg2.add_rv(6); + msg2.add_rv(2); + // Compare + // a: 1, 2, 3, 9, 4, 5, 7, 2 + // b: 9, 0, 2, 7, 3, 4, 5, 6, 2 + std::string diff_report; + util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&diff_report); + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_LIST); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "deleted: rv[0]: 1\n" + "added: rv[0]: 9\n" + "added: rv[1]: 0\n" + "moved: rv[1] -> rv[2] : 2\n" + "added: rv[3]: 7\n" + "moved: rv[2] -> rv[4] : 3\n" + "deleted: rv[3]: 9\n" + "moved: rv[4] -> rv[5] : 4\n" + "moved: rv[5] -> rv[6] : 5\n" + "deleted: rv[6]: 7\n" + "added: rv[7]: 6\n" + "moved: rv[7] -> rv[8] : 2\n", + diff_report); + + // Compare two sub messages + // a: 1, 2, 3, 4, 5 + // b: 2, 6, 4, + msg1.Clear(); + msg2.Clear(); + msg1.add_rm()->set_a(1); + msg1.add_rm()->set_a(2); + msg1.add_rm()->set_a(3); + msg1.add_rm()->set_a(4); + msg1.add_rm()->set_a(5); + msg2.add_rm()->set_a(2); + msg2.add_rm()->set_a(6); + msg2.add_rm()->set_a(4); + differencer.ReportDifferencesToString(&diff_report); + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_LIST); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "deleted: rm[0]: { a: 1 }\n" + "moved: rm[1] -> rm[0] : { a: 2 }\n" + "deleted: rm[2]: { a: 3 }\n" + "added: rm[1]: { a: 6 }\n" + "moved: rm[3] -> rm[2] : { a: 4 }\n" + "deleted: rm[4]: { a: 5 }\n", + diff_report); +} + +TEST(MessageDifferencerTest, RepeatedFieldSmartSetTest) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestField elem1_1, elem2_1, elem3_1; + protobuf_unittest::TestField elem1_2, elem2_2, elem3_2; + + // Only one field is different for each pair of elememts + elem1_1.set_a(1); + elem1_2.set_a(0); + elem1_1.set_b(1); + elem1_2.set_b(1); + elem1_1.set_c(1); + elem1_2.set_c(1); + elem2_1.set_a(2); + elem2_2.set_a(2); + elem2_1.set_b(2); + elem2_2.set_b(0); + elem2_1.set_c(2); + elem2_2.set_c(2); + elem3_1.set_a(3); + elem3_2.set_a(3); + elem3_1.set_b(3); + elem3_2.set_b(0); + elem3_1.set_c(3); + elem3_2.set_c(3); + + *msg1.add_rm() = elem1_1; + *msg1.add_rm() = elem2_1; + *msg1.add_rm() = elem3_1; + // Change the order of those elements for the second message. + *msg2.add_rm() = elem3_2; + *msg2.add_rm() = elem1_2; + *msg2.add_rm() = elem2_2; + + std::string diff_report; + util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&diff_report); + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_SET); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: rm[0].a -> rm[1].a: 1 -> 0\n" + "modified: rm[1].b -> rm[2].b: 2 -> 0\n" + "modified: rm[2].b -> rm[0].b: 3 -> 0\n", + diff_report); +} + +TEST(MessageDifferencerTest, RepeatedFieldSmartSetTest_IdenticalElements) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestField elem; + + elem.set_a(1); + elem.set_b(1); + elem.set_c(1); + + *msg1.add_rm() = elem; + *msg1.add_rm() = elem; + *msg2.add_rm() = elem; + *msg2.add_rm() = elem; + + util::MessageDifferencer differencer; + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_SET); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSmartSetTest_PreviouslyMatch) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestField elem1_1, elem1_2; + protobuf_unittest::TestField elem2_1, elem2_2; + + elem1_1.set_a(1); + elem1_1.set_b(1); + elem1_1.set_c(1); + elem1_2.set_a(1); + elem1_2.set_b(1); + elem1_2.set_c(0); + + elem2_1.set_a(1); + elem2_1.set_b(1); + elem2_1.set_c(1); + elem2_2.set_a(1); + elem2_2.set_b(0); + elem2_2.set_c(1); + + *msg1.add_rm() = elem1_1; + *msg1.add_rm() = elem2_1; + *msg2.add_rm() = elem1_2; + *msg2.add_rm() = elem2_2; + + std::string diff_report; + util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&diff_report); + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_SET); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: rm[0].c: 1 -> 0\n" + "modified: rm[1].b: 1 -> 0\n", + diff_report); +} + +TEST(MessageDifferencerTest, RepeatedFieldSmartSet_MultipleMatches) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestField elem1_1, elem2_1, elem3_1; + protobuf_unittest::TestField elem2_2, elem3_2; + + // Only one field is different for each pair of elememts + elem1_1.set_a(1); + elem1_1.set_b(1); + elem1_1.set_c(1); + elem2_1.set_a(2); + elem2_2.set_a(2); + elem2_1.set_b(2); + elem2_2.set_b(0); + elem2_1.set_c(2); + elem2_2.set_c(2); + elem3_1.set_a(3); + elem3_2.set_a(3); + elem3_1.set_b(3); + elem3_2.set_b(0); + elem3_1.set_c(3); + elem3_2.set_c(3); + + // In this testcase, elem1_1 will match with elem2_2 first and then get + // reverted because elem2_1 matches with elem2_2 later. + *msg1.add_rm() = elem1_1; + *msg1.add_rm() = elem2_1; + *msg1.add_rm() = elem3_1; + *msg2.add_rm() = elem2_2; + *msg2.add_rm() = elem3_2; + + std::string diff_report; + util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&diff_report); + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_SET); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: rm[1].b -> rm[0].b: 2 -> 0\n" + "modified: rm[2].b -> rm[1].b: 3 -> 0\n" + "deleted: rm[0]: { c: 1 a: 1 b: 1 }\n", + diff_report); +} + +TEST(MessageDifferencerTest, RepeatedFieldSmartSet_MultipleMatchesNoReporter) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestField elem1, elem2, elem3, elem4; + elem1.set_a(1); + elem2.set_a(2); + elem3.set_a(3); + elem4.set_a(4); + + *msg1.add_rm() = elem1; + *msg1.add_rm() = elem2; + *msg1.add_rm() = elem3; + *msg2.add_rm() = elem2; + *msg2.add_rm() = elem3; + *msg2.add_rm() = elem4; + + util::MessageDifferencer differencer; + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_SET); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSmartSet_NonMessageTypeTest) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + // Create 3 elements, but in different order. + msg1.add_rw("b"); + msg2.add_rw("a"); + msg1.add_rw("x"); + msg2.add_rw("x"); + msg1.add_rw("a"); + msg2.add_rw("b"); + + std::string diff_report; + util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&diff_report); + differencer.set_repeated_field_comparison( + util::MessageDifferencer::AS_SMART_SET); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "moved: rw[0] -> rw[2] : \"b\"\n" + "moved: rw[2] -> rw[0] : \"a\"\n", + diff_report); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_SetOfSet) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->add_ra(1); + item->add_ra(2); + item->add_ra(3); + item = msg1.add_item(); + item->add_ra(5); + item->add_ra(6); + item = msg1.add_item(); + item->add_ra(1); + item->add_ra(3); + item = msg1.add_item(); + item->add_ra(6); + item->add_ra(7); + item->add_ra(8); + + item = msg2.add_item(); + item->add_ra(6); + item->add_ra(5); + item = msg2.add_item(); + item->add_ra(6); + item->add_ra(8); + item->add_ra(7); + item = msg2.add_item(); + item->add_ra(1); + item->add_ra(3); + item = msg2.add_item(); + item->add_ra(3); + item->add_ra(2); + item->add_ra(1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Combination) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = "a" + // Treat "item.ra" also as Set + // Treat "rv" as Set + // Treat "rw" as List + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(3); + item->add_ra(1); + item->add_ra(2); + item->add_ra(3); + item = msg1.add_item(); + item->set_a(4); + item->add_ra(5); + item->add_ra(6); + item = msg1.add_item(); + item->set_a(1); + item->add_ra(1); + item->add_ra(3); + item = msg1.add_item(); + item->set_a(2); + item->add_ra(6); + item->add_ra(7); + item->add_ra(8); + + item = msg2.add_item(); + item->set_a(4); + item->add_ra(6); + item->add_ra(5); + item = msg2.add_item(); + item->set_a(2); + item->add_ra(6); + item->add_ra(8); + item->add_ra(7); + item = msg2.add_item(); + item->set_a(1); + item->add_ra(1); + item->add_ra(3); + item = msg2.add_item(); + item->set_a(3); + item->add_ra(3); + item->add_ra(2); + item->add_ra(1); + + msg1.add_rv(3); + msg1.add_rv(4); + msg1.add_rv(7); + msg1.add_rv(0); + msg2.add_rv(4); + msg2.add_rv(3); + msg2.add_rv(0); + msg2.add_rv(7); + + msg1.add_rw("nothing"); + msg2.add_rw("nothing"); + msg1.add_rw("should"); + msg2.add_rw("should"); + msg1.add_rw("change"); + msg2.add_rw("change"); + + // Compare + util::MessageDifferencer differencer1; + differencer1.TreatAsMap(msg1.GetDescriptor()->FindFieldByName("item"), + item->GetDescriptor()->FindFieldByName("a")); + differencer1.TreatAsSet(msg1.GetDescriptor()->FindFieldByName("rv")); + differencer1.TreatAsSet(item->GetDescriptor()->FindFieldByName("ra")); + EXPECT_TRUE(differencer1.Compare(msg1, msg2)); + + util::MessageDifferencer differencer2; + differencer2.TreatAsMap(msg1.GetDescriptor()->FindFieldByName("item"), + item->GetDescriptor()->FindFieldByName("a")); + differencer2.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + differencer2.TreatAsList(msg1.GetDescriptor()->FindFieldByName("rw")); + EXPECT_TRUE(differencer2.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_Partial) { + protobuf_unittest::TestDiffMessage msg1; + // message msg1 { + // item { a: 1; b: "11" } + // } + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(1); + item->set_b("11"); + + protobuf_unittest::TestDiffMessage msg2; + // message msg2 { + // item { a: 2; b: "22" } + // item { a: 1; b: "11" } + // } + item = msg2.add_item(); + item->set_a(2); + item->set_b("22"); + item = msg2.add_item(); + item->set_a(1); + item->set_b("11"); + + // Compare + util::MessageDifferencer differencer; + differencer.TreatAsMap(GetFieldDescriptor(msg1, "item"), + GetFieldDescriptor(msg1, "item.a")); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Duplicates) { + protobuf_unittest::TestDiffMessage a, b, c; + // message a: { + // rv: 0 + // rv: 1 + // rv: 0 + // } + a.add_rv(0); + a.add_rv(1); + a.add_rv(0); + // message b: { + // rv: 0 + // rv: 0 + // rv: 1 + // } + b.add_rv(0); + b.add_rv(0); + b.add_rv(1); + // message c: { + // rv: 0 + // rv: 1 + // } + c.add_rv(0); + c.add_rv(1); + util::MessageDifferencer differencer; + differencer.TreatAsSet(GetFieldDescriptor(a, "rv")); + EXPECT_TRUE(differencer.Compare(b, a)); + EXPECT_FALSE(differencer.Compare(c, a)); + + util::MessageDifferencer differencer1; + differencer1.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + EXPECT_TRUE(differencer1.Compare(b, a)); + EXPECT_FALSE(differencer1.Compare(c, a)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_PartialSimple) { + protobuf_unittest::TestDiffMessage a, b, c; + // message a: { + // rm { c: 1 } + // rm { c: 0 } + // } + a.add_rm()->set_c(1); + a.add_rm()->set_c(0); + // message b: { + // rm { c: 1 } + // rm {} + // } + b.add_rm()->set_c(1); + b.add_rm(); + // message c: { + // rm {} + // rm { c: 1 } + // } + c.add_rm(); + c.add_rm()->set_c(1); + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.TreatAsSet(GetFieldDescriptor(a, "rm")); + EXPECT_TRUE(differencer.Compare(b, a)); + EXPECT_TRUE(differencer.Compare(c, a)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Partial) { + protobuf_unittest::TestDiffMessage msg1, msg2; + // message msg1: { + // rm { a: 1 } + // rm { b: 2 } + // rm { c: 3 } + // } + msg1.add_rm()->set_a(1); + msg1.add_rm()->set_b(2); + msg1.add_rm()->set_c(3); + // message msg2: { + // rm { a: 1; c: 3 } + // rm { b: 2; c: 3 } + // rm { b: 2 } + // } + protobuf_unittest::TestField* field = msg2.add_rm(); + field->set_a(1); + field->set_c(3); + field = msg2.add_rm(); + field->set_b(2); + field->set_c(3); + field = msg2.add_rm(); + field->set_b(2); + + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.TreatAsSet(GetFieldDescriptor(msg1, "rm")); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_MultipleFieldsAsKey) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = ("a", "ra") + // Treat "item.ra" as Set + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + // key => value: (1, {2, 3}) => "a" + item->set_a(1); + item->add_ra(2); + item->add_ra(3); + item->set_b("a"); + item = msg1.add_item(); + // key => value: (2, {1, 3}) => "b" + item->set_a(2); + item->add_ra(1); + item->add_ra(3); + item->set_b("b"); + item = msg1.add_item(); + // key => value: (1, {1, 3}) => "c" + item->set_a(1); + item->add_ra(1); + item->add_ra(3); + item->set_b("c"); + + item = msg2.add_item(); + // key => value: (1, {1, 3}) => "c" + item->set_a(1); + item->add_ra(3); + item->add_ra(1); + item->set_b("c"); + item = msg2.add_item(); + // key => value: (1, {2, 3}) => "a" + item->set_a(1); + item->add_ra(3); + item->add_ra(2); + item->set_b("a"); + item = msg2.add_item(); + // key => value: (2, {1, 3}) => "b" + item->set_a(2); + item->add_ra(3); + item->add_ra(1); + item->set_b("b"); + + // Compare + util::MessageDifferencer differencer; + differencer.TreatAsSet(GetFieldDescriptor(msg1, "item.ra")); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + std::vector<const FieldDescriptor*> key_fields; + key_fields.push_back(GetFieldDescriptor(msg1, "item.a")); + key_fields.push_back(GetFieldDescriptor(msg1, "item.ra")); + differencer.TreatAsMapWithMultipleFieldsAsKey( + GetFieldDescriptor(msg1, "item"), key_fields); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Introduce some differences. + msg1.clear_item(); + msg2.clear_item(); + item = msg1.add_item(); + item->set_a(4); + item->add_ra(5); + item->add_ra(6); + item->set_b("hello"); + item = msg2.add_item(); + item->set_a(4); + item->add_ra(6); + item->add_ra(5); + item->set_b("world"); + std::string output; + differencer.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "moved: item[0].ra[0] -> item[0].ra[1] : 5\n" + "moved: item[0].ra[1] -> item[0].ra[0] : 6\n" + "modified: item[0].b: \"hello\" -> \"world\"\n", + output); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_MultipleFieldPathsAsKey) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = ("m.a", "m.rc") + // Treat "item.m.rc" as Set + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + // key => value: (1, {2, 3}) => "a" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(2); + item->mutable_m()->add_rc(3); + item->set_b("a"); + item = msg1.add_item(); + // key => value: (2, {1, 3}) => "b" + item->mutable_m()->set_a(2); + item->mutable_m()->add_rc(1); + item->mutable_m()->add_rc(3); + item->set_b("b"); + item = msg1.add_item(); + // key => value: (1, {1, 3}) => "c" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(1); + item->mutable_m()->add_rc(3); + item->set_b("c"); + + item = msg2.add_item(); + // key => value: (1, {1, 3}) => "c" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(3); + item->mutable_m()->add_rc(1); + item->set_b("c"); + item = msg2.add_item(); + // key => value: (1, {2, 3}) => "a" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(3); + item->mutable_m()->add_rc(2); + item->set_b("a"); + item = msg2.add_item(); + // key => value: (2, {1, 3}) => "b" + item->mutable_m()->set_a(2); + item->mutable_m()->add_rc(3); + item->mutable_m()->add_rc(1); + item->set_b("b"); + + // Compare + util::MessageDifferencer differencer; + differencer.TreatAsSet(GetFieldDescriptor(msg1, "item.m.rc")); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + std::vector<std::vector<const FieldDescriptor*> > key_field_paths; + std::vector<const FieldDescriptor*> key_field_path1; + key_field_path1.push_back(GetFieldDescriptor(msg1, "item.m")); + key_field_path1.push_back(GetFieldDescriptor(msg1, "item.m.a")); + std::vector<const FieldDescriptor*> key_field_path2; + key_field_path2.push_back(GetFieldDescriptor(msg1, "item.m")); + key_field_path2.push_back(GetFieldDescriptor(msg1, "item.m.rc")); + key_field_paths.push_back(key_field_path1); + key_field_paths.push_back(key_field_path2); + differencer.TreatAsMapWithMultipleFieldPathsAsKey( + GetFieldDescriptor(msg1, "item"), key_field_paths); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Introduce some differences. + msg1.clear_item(); + msg2.clear_item(); + item = msg1.add_item(); + item->mutable_m()->set_a(4); + item->mutable_m()->add_rc(5); + item->mutable_m()->add_rc(6); + item->set_b("hello"); + item = msg2.add_item(); + item->mutable_m()->set_a(4); + item->mutable_m()->add_rc(6); + item->mutable_m()->add_rc(5); + item->set_b("world"); + std::string output; + differencer.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: item[0].b: \"hello\" -> \"world\"\n" + "moved: item[0].m.rc[0] -> item[0].m.rc[1] : 5\n" + "moved: item[0].m.rc[1] -> item[0].m.rc[0] : 6\n", + output); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_IgnoredKeyFields) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = ("a", "ra") + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(1); + item->add_ra(2); + item->set_b("hello"); + item = msg2.add_item(); + item->set_a(1); + item->add_ra(3); + item->set_b("world"); + // Compare + util::MessageDifferencer differencer; + std::vector<const FieldDescriptor*> key_fields; + key_fields.push_back(GetFieldDescriptor(msg1, "item.a")); + key_fields.push_back(GetFieldDescriptor(msg1, "item.ra")); + differencer.TreatAsMapWithMultipleFieldsAsKey( + GetFieldDescriptor(msg1, "item"), key_fields); + std::string output; + differencer.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "added: item[0]: { a: 1 ra: 3 b: \"world\" }\n" + "deleted: item[0]: { a: 1 ra: 2 b: \"hello\" }\n", + output); + // Ignored fields that are listed as parts of the key are still used + // in key comparison, but they're not used in value comparison. + differencer.IgnoreField(GetFieldDescriptor(msg1, "item.ra")); + output.clear(); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "added: item[0]: { a: 1 ra: 3 b: \"world\" }\n" + "deleted: item[0]: { a: 1 ra: 2 b: \"hello\" }\n", + output); + // Ignoring a field in the key is different from treating the left fields + // as key. That is: + // (key = ("a", "ra") && ignore "ra") != (key = ("a") && ignore "ra") + util::MessageDifferencer differencer2; + differencer2.TreatAsMap(GetFieldDescriptor(msg1, "item"), + GetFieldDescriptor(msg1, "item.a")); + differencer2.IgnoreField(GetFieldDescriptor(msg1, "item.ra")); + output.clear(); + differencer2.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer2.Compare(msg1, msg2)); + EXPECT_EQ( + "ignored: item[0].ra\n" + "modified: item[0].b: \"hello\" -> \"world\"\n", + output); +} + +TEST(MessageDifferencerTest, PrintMapKeysTest) { + // Note that because map is unordered, the comparison + // output string for test evaluation cannot assume order of + // output of fields (IOW if two fields are deleted + // one cannot assume which deleted field log will be printed first). + // Test currently just has a single record per operation to address this. + // This should only be a limitation for EXPECT_EQ evaluation. + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->mutable_mp()->insert({{"key_a", 1}, {"key_b", 2}, {"key_c", 3}}); + item = msg2.add_item(); + item->mutable_mp()->insert({{"key_a", 1}, {"key_b", 3}, {"key_d", 4}}); + + util::MessageDifferencer differencer; + std::string diff; + differencer.ReportDifferencesToString(&diff); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: item[0].mp[key_b]: 2 -> 3\n" + "added: item[0].mp[key_d]: 4\n" + "deleted: item[0].mp[key_c]: 3\n", + diff); + + google::protobuf::Any any1, any2; + any1.PackFrom(msg1); + any2.PackFrom(msg2); + std::string diff_with_any; + differencer.ReportDifferencesToString(&diff_with_any); + EXPECT_FALSE(differencer.Compare(any1, any2)); + EXPECT_EQ( + "modified: item[0].mp[key_b]: 2 -> 3\n" + "added: item[0].mp[key_d]: 4\n" + "deleted: item[0].mp[key_c]: 3\n", + diff_with_any); +} + +static const char* const kIgnoredFields[] = {"rm.b", "rm.m.b"}; + +class TestIgnorer : public util::MessageDifferencer::IgnoreCriteria { + public: + virtual bool IsIgnored( + const Message& message1, const Message& message2, + const FieldDescriptor* field, + const std::vector<util::MessageDifferencer::SpecificField>& + parent_fields) { + std::string name = ""; + for (int i = 0; i < parent_fields.size(); ++i) { + name += parent_fields[i].field->name() + "."; + } + name += field->name(); + for (int i = 0; i < GOOGLE_ARRAYSIZE(kIgnoredFields); ++i) { + if (name.compare(kIgnoredFields[i]) == 0) { + return true; + } + } + return false; + } +}; + +TEST(MessageDifferencerTest, TreatRepeatedFieldAsSetWithIgnoredFields) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + TextFormat::MergeFromString("rm { a: 11\n b: 12 }", &msg1); + TextFormat::MergeFromString("rm { a: 11\n b: 13 }", &msg2); + util::MessageDifferencer differ; + differ.TreatAsSet(GetFieldDescriptor(msg1, "rm")); + differ.AddIgnoreCriteria(new TestIgnorer); + EXPECT_TRUE(differ.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, TreatRepeatedFieldAsMapWithIgnoredKeyFields) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + TextFormat::MergeFromString("rm { a: 11\n m { a: 12\n b: 13\n } }", &msg1); + TextFormat::MergeFromString("rm { a: 11\n m { a: 12\n b: 14\n } }", &msg2); + util::MessageDifferencer differ; + differ.TreatAsMap(GetFieldDescriptor(msg1, "rm"), + GetFieldDescriptor(msg1, "rm.m")); + differ.AddIgnoreCriteria(new TestIgnorer); + EXPECT_TRUE(differ.Compare(msg1, msg2)); +} + +// Takes the product of all elements of item.ra as the key for key comparison. +class ValueProductMapKeyComparator + : public util::MessageDifferencer::MapKeyComparator { + public: + typedef util::MessageDifferencer::SpecificField SpecificField; + virtual bool IsMatch(const Message& message1, const Message& message2, + const std::vector<SpecificField>& parent_fields) const { + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + // FieldDescriptor for item.ra + const FieldDescriptor* ra_field = + message1.GetDescriptor()->FindFieldByName("ra"); + // Get the product of all elements in item.ra + int result1 = 1, result2 = 1; + for (int i = 0; i < reflection1->FieldSize(message1, ra_field); ++i) { + result1 *= reflection1->GetRepeatedInt32(message1, ra_field, i); + } + for (int i = 0; i < reflection2->FieldSize(message2, ra_field); ++i) { + result2 *= reflection2->GetRepeatedInt32(message2, ra_field, i); + } + return result1 == result2; + } +}; + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_CustomMapKeyComparator) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, using custom key comparator to determine if two + // elements have the same key. + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->add_ra(6); + item->add_ra(35); + item->set_b("hello"); + item = msg2.add_item(); + item->add_ra(10); + item->add_ra(21); + item->set_b("hello"); + util::MessageDifferencer differencer; + ValueProductMapKeyComparator key_comparator; + differencer.TreatAsMapUsingKeyComparator(GetFieldDescriptor(msg1, "item"), + &key_comparator); + std::string output; + differencer.ReportDifferencesToString(&output); + // Though the above two messages have different values for item.ra, they + // are regarded as having the same key because 6 * 35 == 10 * 21. That's + // how the key comparator determines if the two have the same key. + // However, in value comparison, all fields of the message are taken into + // consideration, so they are different because their item.ra fields have + // different values using normal value comparison. + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: item[0].ra[0]: 6 -> 10\n" + "modified: item[0].ra[1]: 35 -> 21\n", + output); + differencer.IgnoreField(GetFieldDescriptor(msg1, "item.ra")); + output.clear(); + // item.ra is ignored in value comparison, so the two messages equal. + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ("ignored: item[0].ra\n", output); +} + +// Compares fields by their index offset by one, so index 0 matches with 1, etc. +class OffsetByOneMapKeyComparator + : public util::MessageDifferencer::MapKeyComparator { + public: + typedef util::MessageDifferencer::SpecificField SpecificField; + virtual bool IsMatch(const Message& message1, const Message& message2, + const std::vector<SpecificField>& parent_fields) const { + return parent_fields.back().index + 1 == parent_fields.back().new_index; + } +}; + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_CustomIndexMapKeyComparator) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, using custom key comparator to determine if two + // elements have the same key. + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_b("one"); + item = msg2.add_item(); + item->set_b("zero"); + item = msg2.add_item(); + item->set_b("one"); + util::MessageDifferencer differencer; + OffsetByOneMapKeyComparator key_comparator; + differencer.TreatAsMapUsingKeyComparator(GetFieldDescriptor(msg1, "item"), + &key_comparator); + std::string output; + differencer.ReportDifferencesToString(&output); + // With the offset by one comparator msg1.item[0] should be compared to + // msg2.item[1] and thus be moved, msg2.item[0] should be marked as added. + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "moved: item[0] -> item[1] : { b: \"one\" }\n" + "added: item[0]: { b: \"zero\" }\n", + output); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Subset) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + msg1.add_rv(3); + msg1.add_rv(8); + msg1.add_rv(2); + msg2.add_rv(2); + msg2.add_rv(3); + msg2.add_rv(5); + msg2.add_rv(8); + + util::MessageDifferencer differencer; + + // Fail with only partial scope set. + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_LIST); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Fail with only set-like comparison set. + differencer.set_scope(util::MessageDifferencer::FULL); + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Succeed with scope and repeated field comparison set properly. + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, IgnoreField_Single) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + + msg2.set_c(5); + msg2.add_rc(1); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "c")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Repeated) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + msg1.add_rc(2); + + msg2.set_c(3); + msg2.add_rc(1); + msg2.add_rc(3); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "rc")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Message) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestField* field; + + field = msg1.add_rm(); + field->set_c(3); + + field = msg2.add_rm(); + field->set_c(4); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "rm")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Group) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(3); + + item = msg2.add_item(); + item->set_a(4); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "item")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Missing) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + + msg2.add_rc(1); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "c")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); + ExpectEqualsWithDifferencer(&differencer, msg2, msg1); +} + +TEST(MessageDifferencerTest, IgnoreField_Multiple) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + msg1.add_rc(2); + + msg2.set_c(5); + msg2.add_rc(1); + msg2.add_rc(3); + + const FieldDescriptor* c = GetFieldDescriptor(msg1, "c"); + const FieldDescriptor* rc = GetFieldDescriptor(msg1, "rc"); + + { // Ignore c + util::MessageDifferencer differencer; + differencer.IgnoreField(c); + + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + } + { // Ignore rc + util::MessageDifferencer differencer; + differencer.IgnoreField(rc); + + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + } + { // Ignore both + util::MessageDifferencer differencer; + differencer.IgnoreField(c); + differencer.IgnoreField(rc); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); + } +} + +TEST(MessageDifferencerTest, IgnoreField_NestedMessage) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestField* field; + + field = msg1.add_rm(); + field->set_c(3); + field->add_rc(1); + + field = msg2.add_rm(); + field->set_c(4); + field->add_rc(1); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "rm.c")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_NestedGroup) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(3); + item->set_b("foo"); + + item = msg2.add_item(); + item->set_a(4); + item->set_b("foo"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "item.a")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_InsideSet) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(1); + item->set_b("foo"); + item->add_ra(1); + + item = msg1.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(1); + item->set_b("baz"); + item->add_ra(1); + + const FieldDescriptor* item_desc = GetFieldDescriptor(msg1, "item"); + const FieldDescriptor* b = GetFieldDescriptor(msg1, "item.b"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(b); + differencer.TreatAsSet(item_desc); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_InsideMap) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(1); + item->set_b("foo"); + item->add_ra(1); + + item = msg1.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(1); + item->set_b("baz"); + item->add_ra(1); + + const FieldDescriptor* item_desc = GetFieldDescriptor(msg1, "item"); + const FieldDescriptor* a = GetFieldDescriptor(msg1, "item.a"); + const FieldDescriptor* b = GetFieldDescriptor(msg1, "item.b"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(b); + differencer.TreatAsMap(item_desc, a); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_DoesNotIgnoreKey) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(1); + item->set_b("foo"); + item->add_ra(1); + + item = msg2.add_item(); + item->set_a(2); + item->set_b("foo"); + item->add_ra(1); + + const FieldDescriptor* item_desc = GetFieldDescriptor(msg1, "item"); + const FieldDescriptor* a = GetFieldDescriptor(msg1, "item.a"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(a); + differencer.TreatAsMap(item_desc, a); + + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, IgnoreField_TrumpsCompareWithFields) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + msg1.add_rc(2); + + msg2.set_c(3); + msg2.add_rc(1); + msg2.add_rc(3); + + const FieldDescriptor* c = GetFieldDescriptor(msg1, "c"); + const FieldDescriptor* rc = GetFieldDescriptor(msg1, "rc"); + + std::vector<const FieldDescriptor*> fields; + fields.push_back(c); + fields.push_back(rc); + + util::MessageDifferencer differencer; + differencer.IgnoreField(rc); + + differencer.set_scope(util::MessageDifferencer::FULL); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields, fields)); + + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields, fields)); +} + +TEST(MessageDifferencerTest, IgnoreField_SetReportIgnoresFalse) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_a(1); + msg1.set_b(2); + msg1.set_c(3); + msg1.add_rc(1); + msg1.add_rc(2); + + msg2.set_a(1); + msg2.set_b(1); + + const FieldDescriptor* a = GetFieldDescriptor(msg1, "a"); + const FieldDescriptor* b = GetFieldDescriptor(msg1, "b"); + const FieldDescriptor* c = GetFieldDescriptor(msg1, "c"); + const FieldDescriptor* rc = GetFieldDescriptor(msg1, "rc"); + + std::vector<const FieldDescriptor*> fields; + fields.push_back(a); + fields.push_back(b); + fields.push_back(c); + fields.push_back(rc); + + std::string diff_report; + util::MessageDifferencer differencer; + differencer.set_report_ignores(false); + differencer.set_report_matches(true); + differencer.ReportDifferencesToString(&diff_report); + differencer.IgnoreField(c); + differencer.IgnoreField(rc); + differencer.set_scope(util::MessageDifferencer::FULL); + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg2, fields, fields)); + + EXPECT_EQ(diff_report, + "matched: a : 1\n" + "modified: b: 2 -> 1\n"); +} + + +// Test class to save a copy of the last field_context.parent_fields() vector +// passed to the comparison function. +class ParentSavingFieldComparator : public util::FieldComparator { + public: + ParentSavingFieldComparator() {} + + virtual ComparisonResult Compare(const Message& message_1, + const Message& message_2, + const FieldDescriptor* field, int index_1, + int index_2, + const util::FieldContext* field_context) { + if (field_context) parent_fields_ = *(field_context->parent_fields()); + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + return RECURSE; + } else { + return SAME; + } + } + + std::vector<util::MessageDifferencer::SpecificField> parent_fields() { + return parent_fields_; + } + + private: + std::vector<util::MessageDifferencer::SpecificField> parent_fields_; +}; + +// Tests if MessageDifferencer sends the parent fields in the FieldContext +// parameter. +TEST(MessageDifferencerTest, FieldContextParentFieldsTest) { + protobuf_unittest::TestDiffMessage msg1; + msg1.add_rm()->set_c(1); + protobuf_unittest::TestDiffMessage msg2; + msg2.add_rm()->set_c(1); + + ParentSavingFieldComparator field_comparator; + util::MessageDifferencer differencer; + differencer.set_field_comparator(&field_comparator); + differencer.Compare(msg1, msg2); + + // We want only one parent with the name "rm" + ASSERT_EQ(1, field_comparator.parent_fields().size()); + EXPECT_EQ("rm", field_comparator.parent_fields()[0].field->name()); +} + + +class ComparisonTest : public testing::Test { + protected: + ComparisonTest() : use_equivalency_(false), repeated_field_as_set_(false) { + // Setup the test. + TestUtil::SetAllFields(&proto1_); + TestUtil::SetAllFields(&proto2_); + + TestUtil::SetAllExtensions(&proto1ex_); + TestUtil::SetAllExtensions(&proto2ex_); + + TestUtil::SetAllFieldsAndExtensions(&orderings_proto1_); + TestUtil::SetAllFieldsAndExtensions(&orderings_proto2_); + + unknown1_ = empty1_.mutable_unknown_fields(); + unknown2_ = empty2_.mutable_unknown_fields(); + } + + ~ComparisonTest() {} + + void SetSpecialFieldOption(const Message& message, + util::MessageDifferencer* d) { + if (!ignored_field_.empty()) { + d->IgnoreField(GetFieldDescriptor(message, ignored_field_)); + } + + if (repeated_field_as_set_) { + d->set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + } + + if (!set_field_.empty()) { + d->TreatAsSet(GetFieldDescriptor(message, set_field_)); + } + + if (!map_field_.empty() && !map_key_.empty()) { + d->TreatAsMap(GetFieldDescriptor(message, map_field_), + GetFieldDescriptor(message, map_field_ + "." + map_key_)); + } + } + + std::string Run(const Message& msg1, const Message& msg2) { + std::string output; + + // Setup the comparison. + util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&output); + + if (use_equivalency_) { + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + } + + SetSpecialFieldOption(msg1, &differencer); + + // Conduct the comparison. + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + return output; + } + + std::string Run() { return Run(proto1_, proto2_); } + + std::string RunOrder() { return Run(orderings_proto1_, orderings_proto2_); } + + std::string RunEx() { return Run(proto1ex_, proto2ex_); } + + std::string RunDiff() { return Run(proto1diff_, proto2diff_); } + + std::string RunUn() { return Run(empty1_, empty2_); } + + void use_equivalency() { use_equivalency_ = true; } + + void repeated_field_as_set() { repeated_field_as_set_ = true; } + + void field_as_set(const std::string& field) { set_field_ = field; } + + void field_as_map(const std::string& field, const std::string& key) { + map_field_ = field; + map_key_ = key; + } + + void ignore_field(const std::string& field) { ignored_field_ = field; } + + unittest::TestAllTypes proto1_; + unittest::TestAllTypes proto2_; + + unittest::TestFieldOrderings orderings_proto1_; + unittest::TestFieldOrderings orderings_proto2_; + + unittest::TestAllExtensions proto1ex_; + unittest::TestAllExtensions proto2ex_; + + unittest::TestDiffMessage proto1diff_; + unittest::TestDiffMessage proto2diff_; + + unittest::TestEmptyMessage empty1_; + unittest::TestEmptyMessage empty2_; + + unittest::TestMap map_proto1_; + unittest::TestMap map_proto2_; + + UnknownFieldSet* unknown1_; + UnknownFieldSet* unknown2_; + + bool use_equivalency_; + bool repeated_field_as_set_; + + std::string set_field_; + std::string map_field_; + std::string map_key_; + std::string ignored_field_; +}; + +// Basic tests. +TEST_F(ComparisonTest, AdditionTest) { + proto1_.clear_optional_int32(); + + EXPECT_EQ("added: optional_int32: 101\n", Run()); +} + +TEST_F(ComparisonTest, Addition_OrderTest) { + orderings_proto1_.clear_my_int(); + + EXPECT_EQ("added: my_int: 1\n", RunOrder()); +} + +TEST_F(ComparisonTest, DeletionTest) { + proto2_.clear_optional_int32(); + + EXPECT_EQ("deleted: optional_int32: 101\n", Run()); +} + +TEST_F(ComparisonTest, Deletion_OrderTest) { + orderings_proto2_.clear_my_string(); + + EXPECT_EQ("deleted: my_string: \"foo\"\n", RunOrder()); +} + +TEST_F(ComparisonTest, RepeatedDeletionTest) { + proto2_.clear_repeated_int32(); + + EXPECT_EQ( + "deleted: repeated_int32[0]: 201\n" + "deleted: repeated_int32[1]: 301\n", + Run()); +} + +TEST_F(ComparisonTest, ModificationTest) { + proto1_.set_optional_int32(-1); + + EXPECT_EQ("modified: optional_int32: -1 -> 101\n", Run()); +} + +// Basic equivalency tests. +TEST_F(ComparisonTest, EquivalencyAdditionTest) { + use_equivalency(); + + proto1_.clear_optional_int32(); + + EXPECT_EQ("modified: optional_int32: 0 -> 101\n", Run()); +} + +TEST_F(ComparisonTest, EquivalencyDeletionTest) { + use_equivalency(); + + proto2_.clear_optional_int32(); + + EXPECT_EQ("modified: optional_int32: 101 -> 0\n", Run()); +} + +// Group tests. +TEST_F(ComparisonTest, GroupAdditionTest) { + proto1_.mutable_optionalgroup()->clear_a(); + + EXPECT_EQ("added: optionalgroup.a: 117\n", Run()); +} + +TEST_F(ComparisonTest, GroupDeletionTest) { + proto2_.mutable_optionalgroup()->clear_a(); + + EXPECT_EQ("deleted: optionalgroup.a: 117\n", Run()); +} + +TEST_F(ComparisonTest, GroupModificationTest) { + proto1_.mutable_optionalgroup()->set_a(2); + + EXPECT_EQ("modified: optionalgroup.a: 2 -> 117\n", Run()); +} + +TEST_F(ComparisonTest, GroupFullAdditionTest) { + proto1_.clear_optionalgroup(); + + // Note the difference in the output between this and GroupAdditionTest. + EXPECT_EQ("added: optionalgroup: { a: 117 }\n", Run()); +} + +TEST_F(ComparisonTest, GroupFullDeletionTest) { + proto2_.clear_optionalgroup(); + + EXPECT_EQ("deleted: optionalgroup: { a: 117 }\n", Run()); +} + +TEST_F(ComparisonTest, RepeatedSetOptionTest) { + repeated_field_as_set(); + + proto2_.clear_repeatedgroup(); + proto1_.clear_repeatedgroup(); + proto1_.add_repeatedgroup()->set_a(317); + proto2_.add_repeatedgroup()->set_a(909); + proto2_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(904); + proto1_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(909); + + EXPECT_EQ( + "moved: repeatedgroup[2] -> repeatedgroup[1] : { a: 907 }\n" + "moved: repeatedgroup[3] -> repeatedgroup[0] : { a: 909 }\n" + "deleted: repeatedgroup[0]: { a: 317 }\n" + "deleted: repeatedgroup[1]: { a: 904 }\n", + Run()); +} + +TEST_F(ComparisonTest, RepeatedSetOptionTest_Ex) { + repeated_field_as_set(); + + proto1ex_.ClearExtension(protobuf_unittest::repeated_nested_message_extension); + proto2ex_.ClearExtension(protobuf_unittest::repeated_nested_message_extension); + proto2ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(909); + proto2ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(907); + proto1ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(904); + proto1ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(907); + proto1ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(909); + + EXPECT_EQ( + "moved: (protobuf_unittest.repeated_nested_message_extension)[2] ->" + " (protobuf_unittest.repeated_nested_message_extension)[0] :" + " { bb: 909 }\n" + "deleted: (protobuf_unittest.repeated_nested_message_extension)[0]:" + " { bb: 904 }\n", + RunEx()); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_Group) { + field_as_map("repeatedgroup", "a"); + proto1_.clear_repeatedgroup(); + proto2_.clear_repeatedgroup(); + + proto1_.add_repeatedgroup()->set_a(317); // deleted + proto1_.add_repeatedgroup()->set_a(904); // deleted + proto1_.add_repeatedgroup()->set_a(907); // moved from + proto1_.add_repeatedgroup()->set_a(909); // moved from + + proto2_.add_repeatedgroup()->set_a(909); // moved to + proto2_.add_repeatedgroup()->set_a(318); // added + proto2_.add_repeatedgroup()->set_a(907); // moved to + + EXPECT_EQ( + "moved: repeatedgroup[3] -> repeatedgroup[0] : { a: 909 }\n" + "added: repeatedgroup[1]: { a: 318 }\n" + "deleted: repeatedgroup[0]: { a: 317 }\n" + "deleted: repeatedgroup[1]: { a: 904 }\n", + Run()); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_MessageKey) { + // Use m as key, but use b as value. + field_as_map("item", "m"); + + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + + // The following code creates one deletion, one addition and two moved fields + // on the messages. + item->mutable_m()->set_c(0); + item->set_b("first"); + item = msg1.add_item(); + item->mutable_m()->set_c(2); + item->set_b("second"); + item = msg1.add_item(); + item->set_b("null"); // empty key moved + item = msg1.add_item(); + item->mutable_m()->set_c(3); + item->set_b("third"); // deletion + item = msg1.add_item(); + item->mutable_m()->set_c(2); + item->set_b("second"); // duplicated key ( deletion ) + item = msg2.add_item(); + item->mutable_m()->set_c(2); + item->set_b("second"); // modification + item = msg2.add_item(); + item->mutable_m()->set_c(4); + item->set_b("fourth"); // addition + item = msg2.add_item(); + item->mutable_m()->set_c(0); + item->set_b("fist"); // move with change + item = msg2.add_item(); + item->set_b("null"); + + EXPECT_EQ( + "modified: item[0].b -> item[2].b: \"first\" -> \"fist\"\n" + "moved: item[1] -> item[0] : { b: \"second\" m { c: 2 } }\n" + "moved: item[2] -> item[3] : { b: \"null\" }\n" + "added: item[1]: { b: \"fourth\" m { c: 4 } }\n" + "deleted: item[3]: { b: \"third\" m { c: 3 } }\n" + "deleted: item[4]: { b: \"second\" m { c: 2 } }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedFieldSetTest_SetOfSet) { + repeated_field_as_set(); + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->add_ra(1); + item->add_ra(2); + item->add_ra(3); + item = msg1.add_item(); + item->add_ra(5); + item->add_ra(6); + item = msg1.add_item(); + item->add_ra(1); + item->add_ra(3); + item = msg1.add_item(); + item->add_ra(6); + item->add_ra(7); + item->add_ra(8); + + item = msg2.add_item(); + item->add_ra(6); + item->add_ra(5); + item = msg2.add_item(); + item->add_ra(6); + item->add_ra(8); + item = msg2.add_item(); + item->add_ra(1); + item->add_ra(3); + item = msg2.add_item(); + item->add_ra(3); + item->add_ra(2); + item->add_ra(1); + + // Compare + EXPECT_EQ( + "moved: item[0].ra[0] -> item[3].ra[2] : 1\n" + "moved: item[0].ra[2] -> item[3].ra[0] : 3\n" + "moved: item[0] -> item[3] : { ra: 1 ra: 2 ra: 3 }\n" + "moved: item[1].ra[0] -> item[0].ra[1] : 5\n" + "moved: item[1].ra[1] -> item[0].ra[0] : 6\n" + "moved: item[1] -> item[0] : { ra: 5 ra: 6 }\n" + "added: item[1]: { ra: 6 ra: 8 }\n" + "deleted: item[3]: { ra: 6 ra: 7 ra: 8 }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_RepeatedKey) { + // used rb as a key, but b is the value. + repeated_field_as_set(); + field_as_map("item", "rb"); + + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->add_rb("a"); + item->add_rb("b"); + item->set_b("first"); + + item = msg2.add_item(); + item->add_rb("c"); + item->set_b("second"); + + item = msg2.add_item(); + item->add_rb("b"); + item->add_rb("a"); + item->set_b("fist"); + + EXPECT_EQ( + "modified: item[0].b -> item[1].b: \"first\" -> \"fist\"\n" + "moved: item[0].rb[0] -> item[1].rb[1] : \"a\"\n" + "moved: item[0].rb[1] -> item[1].rb[0] : \"b\"\n" + "added: item[0]: { b: \"second\" rb: \"c\" }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_RepeatedMessageKey) { + field_as_map("item", "rm"); + + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + protobuf_unittest::TestField* key = item->add_rm(); + key->set_c(2); + key->add_rc(10); + key->add_rc(10); + item = msg1.add_item(); + key = item->add_rm(); + key->set_c(0); + key->add_rc(1); + key->add_rc(2); + key = item->add_rm(); + key->set_c(0); + item->add_rb("first"); + + item = msg2.add_item(); + item->CopyFrom(msg1.item(1)); + item->add_rb("second"); + + EXPECT_EQ( + "added: item[0].rb[1]: \"second\"\n" + "deleted: item[0]: { rm { c: 2 rc: 10 rc: 10 } }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedSetOptionTest_Unknown) { + // Currently, as_set option doesn't have affects on unknown field. + // If needed, this feature will be added by request. + repeated_field_as_set(); + unknown1_->AddGroup(245)->AddFixed32(248, 1); + unknown2_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddGroup(245)->AddFixed32(248, 1); + + // We expect it behaves the same as normal comparison. + EXPECT_EQ( + "modified: 245[0].248[0]: 0x00000001 -> 0x00000003\n" + "added: 245[1]: { ... }\n", + RunUn()); +} + +TEST_F(ComparisonTest, Matching_Unknown) { + unknown1_->AddGroup(245)->AddFixed32(248, 1); + unknown2_->AddGroup(245)->AddFixed32(248, 1); + unknown1_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddLengthDelimited(242, "cat"); + unknown2_->AddGroup(246)->AddFixed32(248, 4); + + // report_match is false so only added/modified fields are expected. + EXPECT_EQ( + "added: 242[0]: \"cat\"\n" + "added: 246[0]: { ... }\n", + RunUn()); +} + +TEST_F(ComparisonTest, RepeatedSetFieldTest) { + field_as_set("repeatedgroup"); + + proto1_.clear_repeatedgroup(); + proto2_.clear_repeatedgroup(); + proto2_.add_repeatedgroup()->set_a(909); + proto2_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(317); + proto1_.add_repeatedgroup()->set_a(904); + proto1_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(909); + + EXPECT_EQ( + "moved: repeatedgroup[2] -> repeatedgroup[1] : { a: 907 }\n" + "moved: repeatedgroup[3] -> repeatedgroup[0] : { a: 909 }\n" + "deleted: repeatedgroup[0]: { a: 317 }\n" + "deleted: repeatedgroup[1]: { a: 904 }\n", + Run()); +} + +// Embedded message tests. +TEST_F(ComparisonTest, EmbeddedAdditionTest) { + proto1_.mutable_optional_nested_message()->clear_bb(); + + EXPECT_EQ("added: optional_nested_message.bb: 118\n", Run()); +} + +TEST_F(ComparisonTest, EmbeddedDeletionTest) { + proto2_.mutable_optional_nested_message()->clear_bb(); + + EXPECT_EQ("deleted: optional_nested_message.bb: 118\n", Run()); +} + +TEST_F(ComparisonTest, EmbeddedModificationTest) { + proto1_.mutable_optional_nested_message()->set_bb(2); + + EXPECT_EQ("modified: optional_nested_message.bb: 2 -> 118\n", Run()); +} + +TEST_F(ComparisonTest, EmbeddedFullAdditionTest) { + proto1_.clear_optional_nested_message(); + + EXPECT_EQ("added: optional_nested_message: { bb: 118 }\n", Run()); +} + +TEST_F(ComparisonTest, EmbeddedPartialAdditionTest) { + proto1_.clear_optional_nested_message(); + proto2_.mutable_optional_nested_message()->clear_bb(); + + EXPECT_EQ("added: optional_nested_message: { }\n", Run()); +} + +TEST_F(ComparisonTest, EmbeddedFullDeletionTest) { + proto2_.clear_optional_nested_message(); + + EXPECT_EQ("deleted: optional_nested_message: { bb: 118 }\n", Run()); +} + +// Repeated element tests. +TEST_F(ComparisonTest, BasicRepeatedTest) { + proto1_.clear_repeated_int32(); + proto2_.clear_repeated_int32(); + + proto1_.add_repeated_int32(500); + proto1_.add_repeated_int32(501); + proto1_.add_repeated_int32(502); + proto1_.add_repeated_int32(503); + proto1_.add_repeated_int32(500); + + proto2_.add_repeated_int32(500); + proto2_.add_repeated_int32(509); + proto2_.add_repeated_int32(502); + proto2_.add_repeated_int32(504); + + EXPECT_EQ( + "modified: repeated_int32[1]: 501 -> 509\n" + "modified: repeated_int32[3]: 503 -> 504\n" + "deleted: repeated_int32[4]: 500\n", + Run()); +} + +TEST_F(ComparisonTest, BasicRepeatedTest_SetOption) { + repeated_field_as_set(); + proto1_.clear_repeated_int32(); + proto2_.clear_repeated_int32(); + + proto1_.add_repeated_int32(501); + proto1_.add_repeated_int32(502); + proto1_.add_repeated_int32(503); + proto1_.add_repeated_int32(500); + proto1_.add_repeated_int32(500); + + proto2_.add_repeated_int32(500); + proto2_.add_repeated_int32(509); + proto2_.add_repeated_int32(503); + proto2_.add_repeated_int32(502); + proto2_.add_repeated_int32(504); + + EXPECT_EQ( + "moved: repeated_int32[1] -> repeated_int32[3] : 502\n" + "moved: repeated_int32[3] -> repeated_int32[0] : 500\n" + "added: repeated_int32[1]: 509\n" + "added: repeated_int32[4]: 504\n" + "deleted: repeated_int32[0]: 501\n" + "deleted: repeated_int32[4]: 500\n", + Run()); +} + +TEST_F(ComparisonTest, BasicRepeatedTest_SetField) { + field_as_set("repeated_int32"); + proto1_.clear_repeated_int32(); + proto2_.clear_repeated_int32(); + + proto1_.add_repeated_int32(501); + proto1_.add_repeated_int32(502); + proto1_.add_repeated_int32(503); + proto1_.add_repeated_int32(500); + proto1_.add_repeated_int32(500); + + proto2_.add_repeated_int32(500); + proto2_.add_repeated_int32(509); + proto2_.add_repeated_int32(503); + proto2_.add_repeated_int32(502); + proto2_.add_repeated_int32(504); + + EXPECT_EQ( + "moved: repeated_int32[1] -> repeated_int32[3] : 502\n" + "moved: repeated_int32[3] -> repeated_int32[0] : 500\n" + "added: repeated_int32[1]: 509\n" + "added: repeated_int32[4]: 504\n" + "deleted: repeated_int32[0]: 501\n" + "deleted: repeated_int32[4]: 500\n", + Run()); +} + +// Multiple action tests. +TEST_F(ComparisonTest, AddDeleteTest) { + proto1_.clear_optional_int32(); + proto2_.clear_optional_int64(); + + EXPECT_EQ( + "added: optional_int32: 101\n" + "deleted: optional_int64: 102\n", + Run()); +} + +TEST_F(ComparisonTest, AddDelete_FieldOrderingTest) { + orderings_proto1_.ClearExtension(unittest::my_extension_string); + orderings_proto2_.clear_my_int(); + + EXPECT_EQ( + "deleted: my_int: 1\n" + "added: (protobuf_unittest.my_extension_string): \"bar\"\n", + RunOrder()); +} + +TEST_F(ComparisonTest, AllThreeTest) { + proto1_.clear_optional_int32(); + proto2_.clear_optional_float(); + proto2_.set_optional_string("hello world!"); + + EXPECT_EQ( + "added: optional_int32: 101\n" + "deleted: optional_float: 111\n" + "modified: optional_string: \"115\" -> \"hello world!\"\n", + Run()); +} + +TEST_F(ComparisonTest, SandwhichTest) { + proto1_.clear_optional_int64(); + proto1_.clear_optional_uint32(); + + proto2_.clear_optional_uint64(); + + EXPECT_EQ( + "added: optional_int64: 102\n" + "added: optional_uint32: 103\n" + "deleted: optional_uint64: 104\n", + Run()); +} + +TEST_F(ComparisonTest, IgnoredNoChangeTest) { + proto1diff_.set_v(3); + proto2diff_.set_v(3); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ( + "ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredAddTest) { + proto2diff_.set_v(3); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ( + "ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredDeleteTest) { + proto1diff_.set_v(3); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ( + "ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredModifyTest) { + proto1diff_.set_v(3); + proto2diff_.set_v(4); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ( + "ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedAddTest) { + proto1diff_.add_rv(3); + proto1diff_.add_rv(4); + + proto2diff_.add_rv(3); + proto2diff_.add_rv(4); + proto2diff_.add_rv(5); + + proto2diff_.set_w("foo"); + + ignore_field("rv"); + + EXPECT_EQ( + "ignored: rv\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedDeleteTest) { + proto1diff_.add_rv(3); + proto1diff_.add_rv(4); + proto1diff_.add_rv(5); + + proto2diff_.add_rv(3); + proto2diff_.add_rv(4); + + proto2diff_.set_w("foo"); + + ignore_field("rv"); + + EXPECT_EQ( + "ignored: rv\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedModifyTest) { + proto1diff_.add_rv(3); + proto1diff_.add_rv(4); + + proto2diff_.add_rv(3); + proto2diff_.add_rv(5); + + proto2diff_.set_w("foo"); + + ignore_field("rv"); + + EXPECT_EQ( + "ignored: rv\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredWholeNestedMessage) { + proto1diff_.mutable_m()->set_c(3); + proto2diff_.mutable_m()->set_c(4); + + proto2diff_.set_w("foo"); + + ignore_field("m"); + + EXPECT_EQ( + "added: w: \"foo\"\n" + "ignored: m\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredNestedField) { + proto1diff_.mutable_m()->set_c(3); + proto2diff_.mutable_m()->set_c(4); + + proto2diff_.set_w("foo"); + + ignore_field("m.c"); + + EXPECT_EQ( + "added: w: \"foo\"\n" + "ignored: m.c\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedNested) { + proto1diff_.add_rm()->set_c(0); + proto1diff_.add_rm()->set_c(1); + proto2diff_.add_rm()->set_c(2); + proto2diff_.add_rm()->set_c(3); + + proto2diff_.set_w("foo"); + + ignore_field("rm.c"); + + EXPECT_EQ( + "ignored: rm[0].c\n" + "ignored: rm[1].c\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredNestedRepeated) { + proto1diff_.mutable_m()->add_rc(23); + proto1diff_.mutable_m()->add_rc(24); + proto2diff_.mutable_m()->add_rc(25); + + proto2diff_.set_w("foo"); + + ignore_field("m.rc"); + + EXPECT_EQ( + "added: w: \"foo\"\n" + "ignored: m.rc\n", + RunDiff()); +} + +TEST_F(ComparisonTest, ExtensionTest) { + proto1ex_.SetExtension(unittest::optional_int32_extension, 401); + proto2ex_.SetExtension(unittest::optional_int32_extension, 402); + + proto1ex_.ClearExtension(unittest::optional_int64_extension); + proto2ex_.SetExtension(unittest::optional_int64_extension, 403); + + EXPECT_EQ( + "modified: (protobuf_unittest.optional_int32_extension): 401 -> 402\n" + "added: (protobuf_unittest.optional_int64_extension): 403\n", + RunEx()); +} + +TEST_F(ComparisonTest, MatchedUnknownFieldTagTest) { + unknown1_->AddVarint(240, 122); + unknown2_->AddVarint(240, 121); + unknown1_->AddFixed32(241, 1); + unknown2_->AddFixed64(241, 2); + unknown1_->AddLengthDelimited(242, "cat"); + unknown2_->AddLengthDelimited(242, "dog"); + + EXPECT_EQ( + "modified: 240[0]: 122 -> 121\n" + "deleted: 241[0]: 0x00000001\n" + "added: 241[0]: 0x0000000000000002\n" + "modified: 242[0]: \"cat\" -> \"dog\"\n", + RunUn()); +} + +TEST_F(ComparisonTest, UnmatchedUnknownFieldTagTest) { + unknown1_->AddFixed32(243, 1); + unknown2_->AddVarint(244, 2); + unknown2_->AddVarint(244, 4); + + EXPECT_EQ( + "deleted: 243[0]: 0x00000001\n" + "added: 244[0]: 2\n" + "added: 244[1]: 4\n", + RunUn()); +} + +TEST_F(ComparisonTest, DifferentSizedUnknownFieldTest) { + unknown1_->AddVarint(240, 1); + unknown1_->AddVarint(240, 3); + unknown1_->AddVarint(240, 4); + unknown2_->AddVarint(240, 2); + unknown2_->AddVarint(240, 3); + unknown2_->AddVarint(240, 2); + unknown2_->AddVarint(240, 5); + + EXPECT_EQ( + "modified: 240[0]: 1 -> 2\n" + "modified: 240[2]: 4 -> 2\n" + "added: 240[3]: 5\n", + RunUn()); +} + +TEST_F(ComparisonTest, UnknownFieldsAll) { + unknown1_->AddVarint(243, 122); + unknown1_->AddFixed64(244, 0x0172356); + unknown1_->AddFixed64(244, 0x098); + unknown1_->AddGroup(245)->AddFixed32(248, 1); + unknown1_->mutable_field(3)->mutable_group()->AddFixed32(248, 2); + unknown1_->AddGroup(249)->AddFixed64(250, 1); + + unknown2_->AddVarint(243, 121); + unknown2_->AddLengthDelimited(73882, "test 123"); + unknown2_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddGroup(247); + + EXPECT_EQ( + "modified: 243[0]: 122 -> 121\n" + "deleted: 244[0]: 0x0000000000172356\n" + "deleted: 244[1]: 0x0000000000000098\n" + "modified: 245[0].248[0]: 0x00000001 -> 0x00000003\n" + "deleted: 245[0].248[1]: 0x00000002\n" + "added: 247[0]: { ... }\n" + "deleted: 249[0]: { ... }\n" + "added: 73882[0]: \"test 123\"\n", + RunUn()); +} + +TEST_F(ComparisonTest, EquivalentIgnoresUnknown) { + unittest::ForeignMessage message1, message2; + + message1.set_c(5); + message1.mutable_unknown_fields()->AddVarint(123, 456); + message2.set_c(5); + message2.mutable_unknown_fields()->AddVarint(321, 654); + + EXPECT_FALSE(util::MessageDifferencer::Equals(message1, message2)); + EXPECT_TRUE(util::MessageDifferencer::Equivalent(message1, message2)); +} + +TEST_F(ComparisonTest, MapTest) { + Map<std::string, std::string>& map1 = + *map_proto1_.mutable_map_string_string(); + map1["key1"] = "1"; + map1["key2"] = "2"; + map1["key3"] = "3"; + Map<std::string, std::string>& map2 = + *map_proto2_.mutable_map_string_string(); + map2["key3"] = "0"; + map2["key2"] = "2"; + map2["key1"] = "1"; + + EXPECT_EQ("modified: map_string_string[key3]: \"3\" -> \"0\"\n", + Run(map_proto1_, map_proto2_)); +} + +TEST_F(ComparisonTest, MapIgnoreKeyTest) { + Map<std::string, std::string>& map1 = + *map_proto1_.mutable_map_string_string(); + map1["key1"] = "1"; + map1["key2"] = "2"; + map1["key3"] = "3"; + Map<std::string, std::string>& map2 = + *map_proto2_.mutable_map_string_string(); + map2["key4"] = "2"; + map2["key5"] = "3"; + map2["key6"] = "1"; + + util::MessageDifferencer differencer; + differencer.IgnoreField( + GetFieldDescriptor(map_proto1_, "map_string_string.key")); + EXPECT_TRUE(differencer.Compare(map_proto1_, map_proto2_)); +} + +TEST_F(ComparisonTest, MapRoundTripSyncTest) { + TextFormat::Parser parser; + unittest::TestMap map_reflection1; + + // By setting via reflection, data exists in repeated field. + ASSERT_TRUE(parser.ParseFromString("map_int32_foreign_message { key: 1 }", + &map_reflection1)); + + // During copy, data is synced from repeated field to map. + unittest::TestMap map_reflection2 = map_reflection1; + + // During comparison, data is synced from map to repeated field. + EXPECT_TRUE( + util::MessageDifferencer::Equals(map_reflection1, map_reflection2)); +} + +TEST_F(ComparisonTest, MapEntryPartialTest) { + TextFormat::Parser parser; + unittest::TestMap map1; + unittest::TestMap map2; + + std::string output; + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.ReportDifferencesToString(&output); + + ASSERT_TRUE(parser.ParseFromString( + "map_int32_foreign_message { key: 1 value { c: 1 } }", &map1)); + ASSERT_TRUE(parser.ParseFromString( + "map_int32_foreign_message { key: 1 value { c: 2 }}", &map2)); + EXPECT_FALSE(differencer.Compare(map1, map2)); + EXPECT_EQ("modified: map_int32_foreign_message[1].c: 1 -> 2\n", output); + + ASSERT_TRUE( + parser.ParseFromString("map_int32_foreign_message { key: 1 }", &map1)); + EXPECT_TRUE(differencer.Compare(map1, map2)); +} + +TEST_F(ComparisonTest, MapEntryPartialEmptyKeyTest) { + TextFormat::Parser parser; + unittest::TestMap map1; + unittest::TestMap map2; + ASSERT_TRUE(parser.ParseFromString("map_int32_foreign_message {}", &map1)); + ASSERT_TRUE( + parser.ParseFromString("map_int32_foreign_message { key: 1 }", &map2)); + + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + // TODO(jieluo): Remove the round trip + std::string serialized_value; + map1.SerializeToString(&serialized_value); + map1.ParseFromString(serialized_value); + EXPECT_FALSE(differencer.Compare(map1, map2)); +} + +TEST_F(ComparisonTest, MapEntryMissingEmptyFieldIsOkTest) { + TextFormat::Parser parser; + protobuf_unittest::TestMap msg1; + protobuf_unittest::TestMap msg2; + + ASSERT_TRUE(parser.ParseFromString( + "map_string_foreign_message { key: 'key1' value {}}", &msg1)); + ASSERT_TRUE(parser.ParseFromString( + "map_string_foreign_message { key: 'key1' }", &msg2)); + + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + + ASSERT_TRUE(differencer.Compare(msg1, msg2)); +} + +// Considers strings keys as equal if they have equal lengths. +class LengthMapKeyComparator + : public util::MessageDifferencer::MapKeyComparator { + public: + typedef util::MessageDifferencer::SpecificField SpecificField; + virtual bool IsMatch(const Message& message1, const Message& message2, + const std::vector<SpecificField>& parent_fields) const { + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + const FieldDescriptor* key_field = + message1.GetDescriptor()->FindFieldByName("key"); + return reflection1->GetString(message1, key_field).size() == + reflection2->GetString(message2, key_field).size(); + } +}; + +TEST_F(ComparisonTest, MapEntryCustomMapKeyComparator) { + TextFormat::Parser parser; + protobuf_unittest::TestMap msg1; + protobuf_unittest::TestMap msg2; + + ASSERT_TRUE(parser.ParseFromString( + "map_string_foreign_message { key: 'key1' value { c: 1 }}", &msg1)); + ASSERT_TRUE(parser.ParseFromString( + "map_string_foreign_message { key: 'key2' value { c: 1 }}", &msg2)); + + util::MessageDifferencer differencer; + LengthMapKeyComparator key_comparator; + differencer.TreatAsMapUsingKeyComparator( + GetFieldDescriptor(msg1, "map_string_foreign_message"), &key_comparator); + std::string output; + differencer.ReportDifferencesToString(&output); + // Though the above two messages have different keys for their map entries, + // they are considered the same by key_comparator because their lengths are + // equal. However, in value comparison, all fields of the message are taken + // into consideration, so they are reported as different. + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: map_string_foreign_message[key1].key: \"key1\" -> \"key2\"\n", + output); + differencer.IgnoreField( + GetFieldDescriptor(msg1, "map_string_foreign_message.key")); + output.clear(); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ("ignored: map_string_foreign_message[key1].key\n", output); +} + +class MatchingTest : public testing::Test { + public: + typedef util::MessageDifferencer MessageDifferencer; + + protected: + MatchingTest() {} + + ~MatchingTest() {} + + std::string RunWithResult(MessageDifferencer* differencer, + const Message& msg1, const Message& msg2, + bool result) { + std::string output; + { + // Before we return the "output" string, we must make sure the + // StreamReporter is destructored because its destructor will + // flush the stream. + io::StringOutputStream output_stream(&output); + MessageDifferencer::StreamReporter reporter(&output_stream); + reporter.set_report_modified_aggregates(true); + differencer->set_report_matches(true); + differencer->ReportDifferencesTo(&reporter); + if (result) { + EXPECT_TRUE(differencer->Compare(msg1, msg2)); + } else { + EXPECT_FALSE(differencer->Compare(msg1, msg2)); + } + } + return output; + } + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MatchingTest); +}; + +TEST_F(MatchingTest, StreamReporterMatching) { + protobuf_unittest::TestField msg1, msg2; + msg1.set_c(72); + msg2.set_c(72); + msg1.add_rc(13); + msg2.add_rc(13); + msg1.add_rc(17); + msg2.add_rc(17); + std::string output; + MessageDifferencer differencer; + differencer.set_report_matches(true); + differencer.ReportDifferencesToString(&output); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "matched: c : 72\n" + "matched: rc[0] : 13\n" + "matched: rc[1] : 17\n", + output); +} + +TEST_F(MatchingTest, DontReportMatchedWhenIgnoring) { + protobuf_unittest::TestField msg1, msg2; + msg1.set_c(72); + msg2.set_c(72); + msg1.add_rc(13); + msg2.add_rc(13); + msg1.add_rc(17); + msg2.add_rc(17); + std::string output; + MessageDifferencer differencer; + differencer.set_report_matches(true); + differencer.ReportDifferencesToString(&output); + + differencer.IgnoreField(msg1.GetDescriptor()->FindFieldByName("c")); + + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "ignored: c\n" + "matched: rc[0] : 13\n" + "matched: rc[1] : 17\n", + output); +} + +TEST_F(MatchingTest, ReportMatchedForMovedFields) { + protobuf_unittest::TestDiffMessage msg1, msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(53); + item->set_b("hello"); + item = msg2.add_item(); + item->set_a(27); + item = msg2.add_item(); + item->set_a(53); + item->set_b("hello"); + item = msg1.add_item(); + item->set_a(27); + MessageDifferencer differencer; + const FieldDescriptor* desc; + desc = msg1.GetDescriptor()->FindFieldByName("item"); + differencer.TreatAsSet(desc); + + EXPECT_EQ( + "matched: item[0].a -> item[1].a : 53\n" + "matched: item[0].b -> item[1].b : \"hello\"\n" + "moved: item[0] -> item[1] : { a: 53 b: \"hello\" }\n" + "matched: item[1].a -> item[0].a : 27\n" + "moved: item[1] -> item[0] : { a: 27 }\n", + RunWithResult(&differencer, msg1, msg2, true)); +} + + +TEST_F(MatchingTest, MatchesAppearInPostTraversalOrderForMovedFields) { + protobuf_unittest::TestDiffMessage msg1, msg2; + protobuf_unittest::TestDiffMessage::Item* item; + protobuf_unittest::TestField* field; + + const FieldDescriptor* desc; + const FieldDescriptor* nested_desc; + const FieldDescriptor* double_nested_desc; + desc = msg1.GetDescriptor()->FindFieldByName("item"); + nested_desc = desc->message_type()->FindFieldByName("rm"); + double_nested_desc = nested_desc->message_type()->FindFieldByName("rc"); + MessageDifferencer differencer; + differencer.TreatAsSet(desc); + differencer.TreatAsSet(nested_desc); + differencer.TreatAsSet(double_nested_desc); + + item = msg1.add_item(); + field = item->add_rm(); + field->set_c(1); + field->add_rc(2); + field->add_rc(3); + field = item->add_rm(); + field->set_c(4); + field->add_rc(5); + field->add_rc(6); + field->add_rc(7); + item = msg2.add_item(); + field = item->add_rm(); + field->set_c(4); + field->add_rc(7); + field->add_rc(6); + field->add_rc(5); + field = item->add_rm(); + field->set_c(1); + field->add_rc(3); + field->add_rc(2); + item = msg1.add_item(); + field = item->add_rm(); + field->set_c(8); + field->add_rc(10); + field->add_rc(11); + field->add_rc(9); + item = msg2.add_item(); + field = item->add_rm(); + field->set_c(8); + field->add_rc(9); + field->add_rc(10); + field->add_rc(11); + + EXPECT_EQ( + "matched: item[0].rm[0].c -> item[0].rm[1].c : 1\n" + "moved: item[0].rm[0].rc[0] -> item[0].rm[1].rc[1] : 2\n" + "moved: item[0].rm[0].rc[1] -> item[0].rm[1].rc[0] : 3\n" + "moved: item[0].rm[0] -> item[0].rm[1] : { c: 1 rc: 2 rc: 3 }\n" + "matched: item[0].rm[1].c -> item[0].rm[0].c : 4\n" + "moved: item[0].rm[1].rc[0] -> item[0].rm[0].rc[2] : 5\n" + "matched: item[0].rm[1].rc[1] -> item[0].rm[0].rc[1] : 6\n" + "moved: item[0].rm[1].rc[2] -> item[0].rm[0].rc[0] : 7\n" + "moved: item[0].rm[1] -> item[0].rm[0] : { c: 4 rc: 5 rc: 6 rc: 7 }\n" + "matched: item[0] : { rm { c: 1 rc: 2 rc: 3 }" + " rm { c: 4 rc: 5 rc: 6 rc: 7 } }\n" + "matched: item[1].rm[0].c : 8\n" + "moved: item[1].rm[0].rc[0] -> item[1].rm[0].rc[1] : 10\n" + "moved: item[1].rm[0].rc[1] -> item[1].rm[0].rc[2] : 11\n" + "moved: item[1].rm[0].rc[2] -> item[1].rm[0].rc[0] : 9\n" + "matched: item[1].rm[0] : { c: 8 rc: 10 rc: 11 rc: 9 }\n" + "matched: item[1] : { rm { c: 8 rc: 10 rc: 11 rc: 9 } }\n", + RunWithResult(&differencer, msg1, msg2, true)); +} + +TEST_F(MatchingTest, MatchAndModifiedInterleaveProperly) { + protobuf_unittest::TestDiffMessage msg1, msg2; + protobuf_unittest::TestDiffMessage::Item* item; + protobuf_unittest::TestField* field; + + const FieldDescriptor* desc; + const FieldDescriptor* nested_key; + const FieldDescriptor* nested_desc; + const FieldDescriptor* double_nested_key; + const FieldDescriptor* double_nested_desc; + desc = msg1.GetDescriptor()->FindFieldByName("item"); + nested_key = desc->message_type()->FindFieldByName("a"); + nested_desc = desc->message_type()->FindFieldByName("rm"); + double_nested_key = nested_desc->message_type()->FindFieldByName("c"); + double_nested_desc = nested_desc->message_type()->FindFieldByName("rc"); + + MessageDifferencer differencer; + differencer.TreatAsMap(desc, nested_key); + differencer.TreatAsMap(nested_desc, double_nested_key); + differencer.TreatAsSet(double_nested_desc); + + item = msg1.add_item(); + item->set_a(1); + field = item->add_rm(); + field->set_c(2); + field->add_rc(3); + field->add_rc(4); + field = item->add_rm(); + field->set_c(5); + field->add_rc(6); + field->add_rc(7); + field->add_rc(8); + item = msg1.add_item(); + item->set_a(9); + field = item->add_rm(); + field->set_c(10); + field->add_rc(11); + field->add_rc(12); + field = item->add_rm(); + field->set_c(13); + + item = msg2.add_item(); + item->set_a(1); + field = item->add_rm(); + field->set_c(5); + field->add_rc(8); + field->add_rc(8); + field->add_rc(6); + field = item->add_rm(); + field->set_c(3); + field->add_rc(2); + field->add_rc(4); + item = msg2.add_item(); + item->set_a(9); + field = item->add_rm(); + field->set_c(10); + field->add_rc(12); + field->add_rc(11); + field = item->add_rm(); + field->set_c(13); + + EXPECT_EQ( + "matched: item[0].a : 1\n" + "matched: item[0].rm[1].c -> item[0].rm[0].c : 5\n" + "moved: item[0].rm[1].rc[0] -> item[0].rm[0].rc[2] : 6\n" + "moved: item[0].rm[1].rc[2] -> item[0].rm[0].rc[0] : 8\n" + "added: item[0].rm[0].rc[1]: 8\n" + "deleted: item[0].rm[1].rc[1]: 7\n" + "modified: item[0].rm[1] -> item[0].rm[0]: { c: 5 rc: 6 rc: 7 rc: 8 } ->" + " { c: 5 rc: 8 rc: 8 rc: 6 }\n" + "added: item[0].rm[1]: { c: 3 rc: 2 rc: 4 }\n" + "deleted: item[0].rm[0]: { c: 2 rc: 3 rc: 4 }\n" + "modified: item[0]: { a: 1 rm { c: 2 rc: 3 rc: 4 }" + " rm { c: 5 rc: 6 rc: 7 rc: 8 } } ->" + " { a: 1 rm { c: 5 rc: 8 rc: 8 rc: 6 }" + " rm { c: 3 rc: 2 rc: 4 } }\n" + "matched: item[1].a : 9\n" + "matched: item[1].rm[0].c : 10\n" + "moved: item[1].rm[0].rc[0] -> item[1].rm[0].rc[1] : 11\n" + "moved: item[1].rm[0].rc[1] -> item[1].rm[0].rc[0] : 12\n" + "matched: item[1].rm[0] : { c: 10 rc: 11 rc: 12 }\n" + "matched: item[1].rm[1].c : 13\n" + "matched: item[1].rm[1] : { c: 13 }\n" + "matched: item[1] : { a: 9 rm { c: 10 rc: 11 rc: 12 } rm { c: 13 } }\n", + RunWithResult(&differencer, msg1, msg2, false)); +} + +TEST_F(MatchingTest, MatchingWorksWithExtensions) { + protobuf_unittest::TestAllExtensions msg1, msg2; + protobuf_unittest::TestAllTypes::NestedMessage* nested; + using protobuf_unittest::repeated_nested_message_extension; + + const FileDescriptor* descriptor; + const FieldDescriptor* desc; + const FieldDescriptor* nested_key; + descriptor = msg1.GetDescriptor()->file(); + desc = descriptor->FindExtensionByName("repeated_nested_message_extension"); + ASSERT_FALSE(desc == NULL); + nested_key = desc->message_type()->FindFieldByName("bb"); + + MessageDifferencer differencer; + differencer.TreatAsMap(desc, nested_key); + + nested = msg1.AddExtension(repeated_nested_message_extension); + nested->set_bb(7); + nested = msg1.AddExtension(repeated_nested_message_extension); + nested->set_bb(13); + nested = msg1.AddExtension(repeated_nested_message_extension); + nested->set_bb(11); + nested = msg2.AddExtension(repeated_nested_message_extension); + nested->set_bb(11); + nested = msg2.AddExtension(repeated_nested_message_extension); + nested->set_bb(13); + nested = msg2.AddExtension(repeated_nested_message_extension); + nested->set_bb(7); + + EXPECT_EQ( + "matched: (protobuf_unittest.repeated_nested_message_extension)[0].bb ->" + " (protobuf_unittest.repeated_nested_message_extension)[2].bb : 7\n" + "moved: (protobuf_unittest.repeated_nested_message_extension)[0] ->" + " (protobuf_unittest.repeated_nested_message_extension)[2] :" + " { bb: 7 }\n" + "matched: (protobuf_unittest.repeated_nested_message_extension)[1].bb :" + " 13\n" + "matched: (protobuf_unittest.repeated_nested_message_extension)[1] :" + " { bb: 13 }\n" + "matched: (protobuf_unittest.repeated_nested_message_extension)[2].bb ->" + " (protobuf_unittest.repeated_nested_message_extension)[0].bb :" + " 11\n" + "moved: (protobuf_unittest.repeated_nested_message_extension)[2] ->" + " (protobuf_unittest.repeated_nested_message_extension)[0] :" + " { bb: 11 }\n", + RunWithResult(&differencer, msg1, msg2, true)); +} + +TEST(AnyTest, Simple) { + protobuf_unittest::TestField value1, value2; + value1.set_a(20); + value2.set_a(21); + + protobuf_unittest::TestAny m1, m2; + m1.mutable_any_value()->PackFrom(value1); + m2.mutable_any_value()->PackFrom(value2); + util::MessageDifferencer message_differencer; + std::string difference_string; + message_differencer.ReportDifferencesToString(&difference_string); + EXPECT_FALSE(message_differencer.Compare(m1, m2)); + EXPECT_EQ("modified: any_value.a: 20 -> 21\n", difference_string); +} + +TEST(Anytest, TreatAsSet) { + protobuf_unittest::TestField value1, value2; + value1.set_a(20); + value1.set_b(30); + value2.set_a(20); + value2.set_b(31); + + protobuf_unittest::TestAny m1, m2; + m1.add_repeated_any_value()->PackFrom(value1); + m1.add_repeated_any_value()->PackFrom(value2); + m2.add_repeated_any_value()->PackFrom(value2); + m2.add_repeated_any_value()->PackFrom(value1); + + util::MessageDifferencer message_differencer; + message_differencer.TreatAsSet(GetFieldDescriptor(m1, "repeated_any_value")); + EXPECT_TRUE(message_differencer.Compare(m1, m2)); +} + +TEST(Anytest, TreatAsSet_DifferentType) { + protobuf_unittest::TestField value1; + value1.set_a(20); + value1.set_b(30); + protobuf_unittest::TestDiffMessage value2; + value2.add_rv(40); + + protobuf_unittest::TestAny m1, m2; + m1.add_repeated_any_value()->PackFrom(value1); + m1.add_repeated_any_value()->PackFrom(value2); + m2.add_repeated_any_value()->PackFrom(value2); + m2.add_repeated_any_value()->PackFrom(value1); + + util::MessageDifferencer message_differencer; + message_differencer.TreatAsSet(GetFieldDescriptor(m1, "repeated_any_value")); + EXPECT_TRUE(message_differencer.Compare(m1, m2)); +} + + +} // namespace +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/message_differencer_unittest.proto b/NorthstarDedicatedTest/include/protobuf/util/message_differencer_unittest.proto new file mode 100644 index 00000000..0ad67929 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/message_differencer_unittest.proto @@ -0,0 +1,79 @@ +// 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. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// This file contains messages for testing repeated field comparison +// LINT: ALLOW_GROUPS + +syntax = "proto2"; + +package protobuf_unittest; + +import "google/protobuf/any.proto"; + +option optimize_for = SPEED; + +message TestField { + optional int32 a = 3; + optional int32 b = 4; + optional int32 c = 1; + repeated int32 rc = 2; + optional TestField m = 5; + + extend TestDiffMessage { + optional TestField tf = 100; + } +} + +message TestDiffMessage { + repeated group Item = 1 { + optional int32 a = 2; // Test basic repeated field comparison. + optional string b = 4; // Test basic repeated field comparison. + repeated int32 ra = 3; // Test SetOfSet Comparison. + repeated string rb = 5; // Test TreatAsMap when key is repeated + optional TestField m = 6; // Test TreatAsMap when key is a message + repeated TestField rm = 7; // Test TreatAsMap when key is a repeated + // message + map<string, int32> mp = 8; // Test for map comparisons + } + + optional int32 v = 13 [deprecated = true]; + optional string w = 14; + optional TestField m = 15; + repeated int32 rv = 11; // Test for combinations + repeated string rw = 10; // Test for combinations + repeated TestField rm = 12 [deprecated = true]; // Test for combinations + repeated google.protobuf.Any rany = + 16; // Test for repeated Any type resolution + extensions 100 to 199; +} diff --git a/NorthstarDedicatedTest/include/protobuf/util/package_info.h b/NorthstarDedicatedTest/include/protobuf/util/package_info.h new file mode 100644 index 00000000..96019203 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/package_info.h @@ -0,0 +1,46 @@ +// 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. + +// This file exists solely to document the google::protobuf::util namespace. +// It is not compiled into anything, but it may be read by an automated +// documentation generator. + +namespace google { + +namespace protobuf { + +// Utility classes. +// +// This package contains various utilities for message comparison, JSON +// conversion, well known types, etc. +namespace util {} + +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/time_util.cc b/NorthstarDedicatedTest/include/protobuf/util/time_util.cc new file mode 100644 index 00000000..4da49009 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/time_util.cc @@ -0,0 +1,511 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/time_util.h> + +#include <cstdint> + +#include <stubs/stringprintf.h> +#include <stubs/strutil.h> +#include <duration.pb.h> +#include <timestamp.pb.h> +#include <stubs/int128.h> +#include <stubs/time.h> + +// Must go after other includes. +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { + +using google::protobuf::Duration; +using google::protobuf::Timestamp; + +namespace { +static const int kNanosPerSecond = 1000000000; +static const int kMicrosPerSecond = 1000000; +static const int kMillisPerSecond = 1000; +static const int kNanosPerMillisecond = 1000000; +static const int kNanosPerMicrosecond = 1000; +static const int kSecondsPerMinute = 60; // Note that we ignore leap seconds. +static const int kSecondsPerHour = 3600; + +template <typename T> +T CreateNormalized(int64_t seconds, int64_t nanos); + +template <> +Timestamp CreateNormalized(int64_t seconds, int64_t nanos) { + // Make sure nanos is in the range. + if (nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + seconds += nanos / kNanosPerSecond; + nanos = nanos % kNanosPerSecond; + } + // For Timestamp nanos should be in the range [0, 999999999] + if (nanos < 0) { + seconds -= 1; + nanos += kNanosPerSecond; + } + GOOGLE_DCHECK(seconds >= TimeUtil::kTimestampMinSeconds && + seconds <= TimeUtil::kTimestampMaxSeconds); + Timestamp result; + result.set_seconds(seconds); + result.set_nanos(static_cast<int32_t>(nanos)); + return result; +} + +template <> +Duration CreateNormalized(int64_t seconds, int64_t nanos) { + // Make sure nanos is in the range. + if (nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + seconds += nanos / kNanosPerSecond; + nanos = nanos % kNanosPerSecond; + } + // nanos should have the same sign as seconds. + if (seconds < 0 && nanos > 0) { + seconds += 1; + nanos -= kNanosPerSecond; + } else if (seconds > 0 && nanos < 0) { + seconds -= 1; + nanos += kNanosPerSecond; + } + GOOGLE_DCHECK(seconds >= TimeUtil::kDurationMinSeconds && + seconds <= TimeUtil::kDurationMaxSeconds); + Duration result; + result.set_seconds(seconds); + result.set_nanos(static_cast<int32_t>(nanos)); + return result; +} + +// Format nanoseconds with either 3, 6, or 9 digits depending on the required +// precision to represent the exact value. +std::string FormatNanos(int32_t nanos) { + if (nanos % kNanosPerMillisecond == 0) { + return StringPrintf("%03d", nanos / kNanosPerMillisecond); + } else if (nanos % kNanosPerMicrosecond == 0) { + return StringPrintf("%06d", nanos / kNanosPerMicrosecond); + } else { + return StringPrintf("%09d", nanos); + } +} + +std::string FormatTime(int64 seconds, int32 nanos) { + return ::google::protobuf::internal::FormatTime(seconds, nanos); +} + +bool ParseTime(const std::string& value, int64* seconds, int32* nanos) { + return ::google::protobuf::internal::ParseTime(value, seconds, nanos); +} + +void CurrentTime(int64* seconds, int32* nanos) { + return ::google::protobuf::internal::GetCurrentTime(seconds, nanos); +} + +// Truncates the remainder part after division. +int64_t RoundTowardZero(int64_t value, int64_t divider) { + int64_t result = value / divider; + int64_t remainder = value % divider; + // Before C++11, the sign of the remainder is implementation dependent if + // any of the operands is negative. Here we try to enforce C++11's "rounded + // toward zero" semantics. For example, for (-5) / 2 an implementation may + // give -3 as the result with the remainder being 1. This function ensures + // we always return -2 (closer to zero) regardless of the implementation. + if (result < 0 && remainder > 0) { + return result + 1; + } else { + return result; + } +} +} // namespace + +// Actually define these static const integers. Required by C++ standard (but +// some compilers don't like it). +#ifndef _MSC_VER +const int64_t TimeUtil::kTimestampMinSeconds; +const int64_t TimeUtil::kTimestampMaxSeconds; +const int64_t TimeUtil::kDurationMaxSeconds; +const int64_t TimeUtil::kDurationMinSeconds; +#endif // !_MSC_VER + +std::string TimeUtil::ToString(const Timestamp& timestamp) { + return FormatTime(timestamp.seconds(), timestamp.nanos()); +} + +bool TimeUtil::FromString(const std::string& value, Timestamp* timestamp) { + int64_t seconds; + int32_t nanos; + if (!ParseTime(value, &seconds, &nanos)) { + return false; + } + *timestamp = CreateNormalized<Timestamp>(seconds, nanos); + return true; +} + +Timestamp TimeUtil::GetCurrentTime() { + int64_t seconds; + int32_t nanos; + CurrentTime(&seconds, &nanos); + return CreateNormalized<Timestamp>(seconds, nanos); +} + +Timestamp TimeUtil::GetEpoch() { return Timestamp(); } + +std::string TimeUtil::ToString(const Duration& duration) { + std::string result; + int64_t seconds = duration.seconds(); + int32_t nanos = duration.nanos(); + if (seconds < 0 || nanos < 0) { + result += "-"; + seconds = -seconds; + nanos = -nanos; + } + result += StrCat(seconds); + if (nanos != 0) { + result += "." + FormatNanos(nanos); + } + result += "s"; + return result; +} + +static int64_t Pow(int64_t x, int y) { + int64_t result = 1; + for (int i = 0; i < y; ++i) { + result *= x; + } + return result; +} + +bool TimeUtil::FromString(const std::string& value, Duration* duration) { + if (value.length() <= 1 || value[value.length() - 1] != 's') { + return false; + } + bool negative = (value[0] == '-'); + int sign_length = (negative ? 1 : 0); + // Parse the duration value as two integers rather than a float value + // to avoid precision loss. + std::string seconds_part, nanos_part; + size_t pos = value.find_last_of('.'); + if (pos == std::string::npos) { + seconds_part = value.substr(sign_length, value.length() - 1 - sign_length); + nanos_part = "0"; + } else { + seconds_part = value.substr(sign_length, pos - sign_length); + nanos_part = value.substr(pos + 1, value.length() - pos - 2); + } + char* end; + int64_t seconds = strto64(seconds_part.c_str(), &end, 10); + if (end != seconds_part.c_str() + seconds_part.length()) { + return false; + } + int64_t nanos = strto64(nanos_part.c_str(), &end, 10); + if (end != nanos_part.c_str() + nanos_part.length()) { + return false; + } + nanos = nanos * Pow(10, 9 - nanos_part.length()); + if (negative) { + // If a Duration is negative, both seconds and nanos should be negative. + seconds = -seconds; + nanos = -nanos; + } + duration->set_seconds(seconds); + duration->set_nanos(static_cast<int32_t>(nanos)); + return true; +} + +Duration TimeUtil::NanosecondsToDuration(int64_t nanos) { + return CreateNormalized<Duration>(nanos / kNanosPerSecond, + nanos % kNanosPerSecond); +} + +Duration TimeUtil::MicrosecondsToDuration(int64_t micros) { + return CreateNormalized<Duration>( + micros / kMicrosPerSecond, + (micros % kMicrosPerSecond) * kNanosPerMicrosecond); +} + +Duration TimeUtil::MillisecondsToDuration(int64_t millis) { + return CreateNormalized<Duration>( + millis / kMillisPerSecond, + (millis % kMillisPerSecond) * kNanosPerMillisecond); +} + +Duration TimeUtil::SecondsToDuration(int64_t seconds) { + return CreateNormalized<Duration>(seconds, 0); +} + +Duration TimeUtil::MinutesToDuration(int64_t minutes) { + return CreateNormalized<Duration>(minutes * kSecondsPerMinute, 0); +} + +Duration TimeUtil::HoursToDuration(int64_t hours) { + return CreateNormalized<Duration>(hours * kSecondsPerHour, 0); +} + +int64_t TimeUtil::DurationToNanoseconds(const Duration& duration) { + return duration.seconds() * kNanosPerSecond + duration.nanos(); +} + +int64_t TimeUtil::DurationToMicroseconds(const Duration& duration) { + return duration.seconds() * kMicrosPerSecond + + RoundTowardZero(duration.nanos(), kNanosPerMicrosecond); +} + +int64_t TimeUtil::DurationToMilliseconds(const Duration& duration) { + return duration.seconds() * kMillisPerSecond + + RoundTowardZero(duration.nanos(), kNanosPerMillisecond); +} + +int64_t TimeUtil::DurationToSeconds(const Duration& duration) { + return duration.seconds(); +} + +int64_t TimeUtil::DurationToMinutes(const Duration& duration) { + return RoundTowardZero(duration.seconds(), kSecondsPerMinute); +} + +int64_t TimeUtil::DurationToHours(const Duration& duration) { + return RoundTowardZero(duration.seconds(), kSecondsPerHour); +} + +Timestamp TimeUtil::NanosecondsToTimestamp(int64_t nanos) { + return CreateNormalized<Timestamp>(nanos / kNanosPerSecond, + nanos % kNanosPerSecond); +} + +Timestamp TimeUtil::MicrosecondsToTimestamp(int64_t micros) { + return CreateNormalized<Timestamp>( + micros / kMicrosPerSecond, + micros % kMicrosPerSecond * kNanosPerMicrosecond); +} + +Timestamp TimeUtil::MillisecondsToTimestamp(int64_t millis) { + return CreateNormalized<Timestamp>( + millis / kMillisPerSecond, + millis % kMillisPerSecond * kNanosPerMillisecond); +} + +Timestamp TimeUtil::SecondsToTimestamp(int64_t seconds) { + return CreateNormalized<Timestamp>(seconds, 0); +} + +int64_t TimeUtil::TimestampToNanoseconds(const Timestamp& timestamp) { + return timestamp.seconds() * kNanosPerSecond + timestamp.nanos(); +} + +int64_t TimeUtil::TimestampToMicroseconds(const Timestamp& timestamp) { + return timestamp.seconds() * kMicrosPerSecond + + RoundTowardZero(timestamp.nanos(), kNanosPerMicrosecond); +} + +int64_t TimeUtil::TimestampToMilliseconds(const Timestamp& timestamp) { + return timestamp.seconds() * kMillisPerSecond + + RoundTowardZero(timestamp.nanos(), kNanosPerMillisecond); +} + +int64_t TimeUtil::TimestampToSeconds(const Timestamp& timestamp) { + return timestamp.seconds(); +} + +Timestamp TimeUtil::TimeTToTimestamp(time_t value) { + return CreateNormalized<Timestamp>(static_cast<int64_t>(value), 0); +} + +time_t TimeUtil::TimestampToTimeT(const Timestamp& value) { + return static_cast<time_t>(value.seconds()); +} + +Timestamp TimeUtil::TimevalToTimestamp(const timeval& value) { + return CreateNormalized<Timestamp>(value.tv_sec, + value.tv_usec * kNanosPerMicrosecond); +} + +timeval TimeUtil::TimestampToTimeval(const Timestamp& value) { + timeval result; + result.tv_sec = value.seconds(); + result.tv_usec = RoundTowardZero(value.nanos(), kNanosPerMicrosecond); + return result; +} + +Duration TimeUtil::TimevalToDuration(const timeval& value) { + return CreateNormalized<Duration>(value.tv_sec, + value.tv_usec * kNanosPerMicrosecond); +} + +timeval TimeUtil::DurationToTimeval(const Duration& value) { + timeval result; + result.tv_sec = value.seconds(); + result.tv_usec = RoundTowardZero(value.nanos(), kNanosPerMicrosecond); + // timeval.tv_usec's range is [0, 1000000) + if (result.tv_usec < 0) { + result.tv_sec -= 1; + result.tv_usec += kMicrosPerSecond; + } + return result; +} + +} // namespace util +} // namespace protobuf +} // namespace google + +namespace google { +namespace protobuf { +namespace { +using ::PROTOBUF_NAMESPACE_ID::util::CreateNormalized; +using ::PROTOBUF_NAMESPACE_ID::util::kNanosPerSecond; + +// Convert a Duration to uint128. +void ToUint128(const Duration& value, uint128* result, bool* negative) { + if (value.seconds() < 0 || value.nanos() < 0) { + *negative = true; + *result = static_cast<uint64_t>(-value.seconds()); + *result = *result * kNanosPerSecond + static_cast<uint32_t>(-value.nanos()); + } else { + *negative = false; + *result = static_cast<uint64_t>(value.seconds()); + *result = *result * kNanosPerSecond + static_cast<uint32_t>(value.nanos()); + } +} + +void ToDuration(const uint128& value, bool negative, Duration* duration) { + int64_t seconds = + static_cast<int64_t>(Uint128Low64(value / kNanosPerSecond)); + int32_t nanos = + static_cast<int32_t>(Uint128Low64(value % kNanosPerSecond)); + if (negative) { + seconds = -seconds; + nanos = -nanos; + } + duration->set_seconds(seconds); + duration->set_nanos(nanos); +} +} // namespace + +Duration& operator+=(Duration& d1, const Duration& d2) { + d1 = CreateNormalized<Duration>(d1.seconds() + d2.seconds(), + d1.nanos() + d2.nanos()); + return d1; +} + +Duration& operator-=(Duration& d1, const Duration& d2) { // NOLINT + d1 = CreateNormalized<Duration>(d1.seconds() - d2.seconds(), + d1.nanos() - d2.nanos()); + return d1; +} + +Duration& operator*=(Duration& d, int64_t r) { // NOLINT + bool negative; + uint128 value; + ToUint128(d, &value, &negative); + if (r > 0) { + value *= static_cast<uint64_t>(r); + } else { + negative = !negative; + value *= static_cast<uint64_t>(-r); + } + ToDuration(value, negative, &d); + return d; +} + +Duration& operator*=(Duration& d, double r) { // NOLINT + double result = (d.seconds() * 1.0 + 1.0 * d.nanos() / kNanosPerSecond) * r; + int64_t seconds = static_cast<int64_t>(result); + int32_t nanos = static_cast<int32_t>((result - seconds) * kNanosPerSecond); + // Note that we normalize here not just because nanos can have a different + // sign from seconds but also that nanos can be any arbitrary value when + // overflow happens (i.e., the result is a much larger value than what + // int64 can represent). + d = CreateNormalized<Duration>(seconds, nanos); + return d; +} + +Duration& operator/=(Duration& d, int64_t r) { // NOLINT + bool negative; + uint128 value; + ToUint128(d, &value, &negative); + if (r > 0) { + value /= static_cast<uint64_t>(r); + } else { + negative = !negative; + value /= static_cast<uint64_t>(-r); + } + ToDuration(value, negative, &d); + return d; +} + +Duration& operator/=(Duration& d, double r) { // NOLINT + return d *= 1.0 / r; +} + +Duration& operator%=(Duration& d1, const Duration& d2) { // NOLINT + bool negative1, negative2; + uint128 value1, value2; + ToUint128(d1, &value1, &negative1); + ToUint128(d2, &value2, &negative2); + uint128 result = value1 % value2; + // When negative values are involved in division, we round the division + // result towards zero. With this semantics, sign of the remainder is the + // same as the dividend. For example: + // -5 / 10 = 0, -5 % 10 = -5 + // -5 / (-10) = 0, -5 % (-10) = -5 + // 5 / (-10) = 0, 5 % (-10) = 5 + ToDuration(result, negative1, &d1); + return d1; +} + +int64_t operator/(const Duration& d1, const Duration& d2) { + bool negative1, negative2; + uint128 value1, value2; + ToUint128(d1, &value1, &negative1); + ToUint128(d2, &value2, &negative2); + int64_t result = Uint128Low64(value1 / value2); + if (negative1 != negative2) { + result = -result; + } + return result; +} + +Timestamp& operator+=(Timestamp& t, const Duration& d) { // NOLINT + t = CreateNormalized<Timestamp>(t.seconds() + d.seconds(), + t.nanos() + d.nanos()); + return t; +} + +Timestamp& operator-=(Timestamp& t, const Duration& d) { // NOLINT + t = CreateNormalized<Timestamp>(t.seconds() - d.seconds(), + t.nanos() - d.nanos()); + return t; +} + +Duration operator-(const Timestamp& t1, const Timestamp& t2) { + return CreateNormalized<Duration>(t1.seconds() - t2.seconds(), + t1.nanos() - t2.nanos()); +} +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/time_util.h b/NorthstarDedicatedTest/include/protobuf/util/time_util.h new file mode 100644 index 00000000..6442e827 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/time_util.h @@ -0,0 +1,313 @@ +// 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. + +// Defines utilities for the Timestamp and Duration well known types. + +#ifndef GOOGLE_PROTOBUF_UTIL_TIME_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_TIME_UTIL_H__ + +#include <cstdint> +#include <ctime> +#include <ostream> +#include <string> +#ifdef _MSC_VER +#ifdef _XBOX_ONE +struct timeval { + int64 tv_sec; /* seconds */ + int64 tv_usec; /* and microseconds */ +}; +#else +#include <winsock2.h> +#endif // _XBOX_ONE +#else +#include <sys/time.h> +#endif + +#include <duration.pb.h> +#include <timestamp.pb.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +namespace util { + +// Utility functions for Timestamp and Duration. +class PROTOBUF_EXPORT TimeUtil { + typedef google::protobuf::Timestamp Timestamp; + typedef google::protobuf::Duration Duration; + + public: + // The min/max Timestamp/Duration values we support. + // + // For "0001-01-01T00:00:00Z". + static const int64_t kTimestampMinSeconds = -62135596800LL; + // For "9999-12-31T23:59:59.999999999Z". + static const int64_t kTimestampMaxSeconds = 253402300799LL; + static const int64_t kDurationMinSeconds = -315576000000LL; + static const int64_t kDurationMaxSeconds = 315576000000LL; + + // Converts Timestamp to/from RFC 3339 date string format. + // Generated output will always be Z-normalized and uses 3, 6 or 9 + // fractional digits as required to represent the exact time. When + // parsing, any fractional digits (or none) and any offset are + // accepted as long as they fit into nano-seconds precision. + // Note that Timestamp can only represent time from + // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. Converting + // a Timestamp outside of this range is undefined behavior. + // See https://www.ietf.org/rfc/rfc3339.txt + // + // Example of generated format: + // "1972-01-01T10:00:20.021Z" + // + // Example of accepted format: + // "1972-01-01T10:00:20.021-05:00" + static std::string ToString(const Timestamp& timestamp); + static bool FromString(const std::string& value, Timestamp* timestamp); + + // Converts Duration to/from string format. The string format will contains + // 3, 6, or 9 fractional digits depending on the precision required to + // represent the exact Duration value. For example: + // "1s", "1.010s", "1.000000100s", "-3.100s" + // The range that can be represented by Duration is from -315,576,000,000 + // to +315,576,000,000 inclusive (in seconds). + static std::string ToString(const Duration& duration); + static bool FromString(const std::string& value, Duration* timestamp); + +#ifdef GetCurrentTime +#undef GetCurrentTime // Visual Studio has macro GetCurrentTime +#endif + // Gets the current UTC time. + static Timestamp GetCurrentTime(); + // Returns the Time representing "1970-01-01 00:00:00". + static Timestamp GetEpoch(); + + // Converts between Duration and integer types. The behavior is undefined if + // the input value is not in the valid range of Duration. + static Duration NanosecondsToDuration(int64_t nanos); + static Duration MicrosecondsToDuration(int64_t micros); + static Duration MillisecondsToDuration(int64_t millis); + static Duration SecondsToDuration(int64_t seconds); + static Duration MinutesToDuration(int64_t minutes); + static Duration HoursToDuration(int64_t hours); + // Result will be truncated towards zero. For example, "-1.5s" will be + // truncated to "-1s", and "1.5s" to "1s" when converting to seconds. + // It's undefined behavior if the input duration is not valid or the result + // exceeds the range of int64. A duration is not valid if it's not in the + // valid range of Duration, or have an invalid nanos value (i.e., larger + // than 999999999, less than -999999999, or have a different sign from the + // seconds part). + static int64_t DurationToNanoseconds(const Duration& duration); + static int64_t DurationToMicroseconds(const Duration& duration); + static int64_t DurationToMilliseconds(const Duration& duration); + static int64_t DurationToSeconds(const Duration& duration); + static int64_t DurationToMinutes(const Duration& duration); + static int64_t DurationToHours(const Duration& duration); + // Creates Timestamp from integer types. The integer value indicates the + // time elapsed from Epoch time. The behavior is undefined if the input + // value is not in the valid range of Timestamp. + static Timestamp NanosecondsToTimestamp(int64_t nanos); + static Timestamp MicrosecondsToTimestamp(int64_t micros); + static Timestamp MillisecondsToTimestamp(int64_t millis); + static Timestamp SecondsToTimestamp(int64_t seconds); + // Result will be truncated down to the nearest integer value. For example, + // with "1969-12-31T23:59:59.9Z", TimestampToMilliseconds() returns -100 + // and TimestampToSeconds() returns -1. It's undefined behavior if the input + // Timestamp is not valid (i.e., its seconds part or nanos part does not fall + // in the valid range) or the return value doesn't fit into int64. + static int64_t TimestampToNanoseconds(const Timestamp& timestamp); + static int64_t TimestampToMicroseconds(const Timestamp& timestamp); + static int64_t TimestampToMilliseconds(const Timestamp& timestamp); + static int64_t TimestampToSeconds(const Timestamp& timestamp); + + // Conversion to/from other time/date types. Note that these types may + // have a different precision and time range from Timestamp/Duration. + // When converting to a lower precision type, the value will be truncated + // to the nearest value that can be represented. If the value is + // out of the range of the result type, the return value is undefined. + // + // Conversion to/from time_t + static Timestamp TimeTToTimestamp(time_t value); + static time_t TimestampToTimeT(const Timestamp& value); + + // Conversion to/from timeval + static Timestamp TimevalToTimestamp(const timeval& value); + static timeval TimestampToTimeval(const Timestamp& value); + static Duration TimevalToDuration(const timeval& value); + static timeval DurationToTimeval(const Duration& value); +}; + +} // namespace util +} // namespace protobuf +} // namespace google + +namespace google { +namespace protobuf { +// Overloaded operators for Duration. +// +// Assignment operators. +PROTOBUF_EXPORT Duration& operator+=(Duration& d1, + const Duration& d2); // NOLINT +PROTOBUF_EXPORT Duration& operator-=(Duration& d1, + const Duration& d2); // NOLINT +PROTOBUF_EXPORT Duration& operator*=(Duration& d, int64_t r); // NOLINT +PROTOBUF_EXPORT Duration& operator*=(Duration& d, double r); // NOLINT +PROTOBUF_EXPORT Duration& operator/=(Duration& d, int64_t r); // NOLINT +PROTOBUF_EXPORT Duration& operator/=(Duration& d, double r); // NOLINT +// Overload for other integer types. +template <typename T> +Duration& operator*=(Duration& d, T r) { // NOLINT + int64_t x = r; + return d *= x; +} +template <typename T> +Duration& operator/=(Duration& d, T r) { // NOLINT + int64_t x = r; + return d /= x; +} +PROTOBUF_EXPORT Duration& operator%=(Duration& d1, + const Duration& d2); // NOLINT +// Relational operators. +inline bool operator<(const Duration& d1, const Duration& d2) { + if (d1.seconds() == d2.seconds()) { + return d1.nanos() < d2.nanos(); + } + return d1.seconds() < d2.seconds(); +} +inline bool operator>(const Duration& d1, const Duration& d2) { + return d2 < d1; +} +inline bool operator>=(const Duration& d1, const Duration& d2) { + return !(d1 < d2); +} +inline bool operator<=(const Duration& d1, const Duration& d2) { + return !(d2 < d1); +} +inline bool operator==(const Duration& d1, const Duration& d2) { + return d1.seconds() == d2.seconds() && d1.nanos() == d2.nanos(); +} +inline bool operator!=(const Duration& d1, const Duration& d2) { + return !(d1 == d2); +} +// Additive operators +inline Duration operator-(const Duration& d) { + Duration result; + result.set_seconds(-d.seconds()); + result.set_nanos(-d.nanos()); + return result; +} +inline Duration operator+(const Duration& d1, const Duration& d2) { + Duration result = d1; + return result += d2; +} +inline Duration operator-(const Duration& d1, const Duration& d2) { + Duration result = d1; + return result -= d2; +} +// Multiplicative operators +template <typename T> +inline Duration operator*(Duration d, T r) { + return d *= r; +} +template <typename T> +inline Duration operator*(T r, Duration d) { + return d *= r; +} +template <typename T> +inline Duration operator/(Duration d, T r) { + return d /= r; +} +PROTOBUF_EXPORT int64_t operator/(const Duration& d1, const Duration& d2); + +inline Duration operator%(const Duration& d1, const Duration& d2) { + Duration result = d1; + return result %= d2; +} + +inline std::ostream& operator<<(std::ostream& out, const Duration& d) { + out << ::PROTOBUF_NAMESPACE_ID::util::TimeUtil::ToString(d); + return out; +} + +// Overloaded operators for Timestamp +// +// Assignment operators. +PROTOBUF_EXPORT Timestamp& operator+=(Timestamp& t, + const Duration& d); // NOLINT +PROTOBUF_EXPORT Timestamp& operator-=(Timestamp& t, + const Duration& d); // NOLINT +// Relational operators. +inline bool operator<(const Timestamp& t1, const Timestamp& t2) { + if (t1.seconds() == t2.seconds()) { + return t1.nanos() < t2.nanos(); + } + return t1.seconds() < t2.seconds(); +} +inline bool operator>(const Timestamp& t1, const Timestamp& t2) { + return t2 < t1; +} +inline bool operator>=(const Timestamp& t1, const Timestamp& t2) { + return !(t1 < t2); +} +inline bool operator<=(const Timestamp& t1, const Timestamp& t2) { + return !(t2 < t1); +} +inline bool operator==(const Timestamp& t1, const Timestamp& t2) { + return t1.seconds() == t2.seconds() && t1.nanos() == t2.nanos(); +} +inline bool operator!=(const Timestamp& t1, const Timestamp& t2) { + return !(t1 == t2); +} +// Additive operators. +inline Timestamp operator+(const Timestamp& t, const Duration& d) { + Timestamp result = t; + return result += d; +} +inline Timestamp operator+(const Duration& d, const Timestamp& t) { + Timestamp result = t; + return result += d; +} +inline Timestamp operator-(const Timestamp& t, const Duration& d) { + Timestamp result = t; + return result -= d; +} +PROTOBUF_EXPORT Duration operator-(const Timestamp& t1, const Timestamp& t2); + +inline std::ostream& operator<<(std::ostream& out, const Timestamp& t) { + out << ::PROTOBUF_NAMESPACE_ID::util::TimeUtil::ToString(t); + return out; +} + +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_TIME_UTIL_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/time_util_test.cc b/NorthstarDedicatedTest/include/protobuf/util/time_util_test.cc new file mode 100644 index 00000000..55a0db6f --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/time_util_test.cc @@ -0,0 +1,384 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/time_util.h> + +#include <cstdint> +#include <ctime> + +#include <duration.pb.h> +#include <timestamp.pb.h> +#include <testing/googletest.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { + +using google::protobuf::Duration; +using google::protobuf::Timestamp; + +namespace { + +TEST(TimeUtilTest, TimestampStringFormat) { + Timestamp begin, end; + EXPECT_TRUE(TimeUtil::FromString("0001-01-01T00:00:00Z", &begin)); + EXPECT_EQ(TimeUtil::kTimestampMinSeconds, begin.seconds()); + EXPECT_EQ(0, begin.nanos()); + EXPECT_TRUE(TimeUtil::FromString("9999-12-31T23:59:59.999999999Z", &end)); + EXPECT_EQ(TimeUtil::kTimestampMaxSeconds, end.seconds()); + EXPECT_EQ(999999999, end.nanos()); + EXPECT_EQ("0001-01-01T00:00:00Z", TimeUtil::ToString(begin)); + EXPECT_EQ("9999-12-31T23:59:59.999999999Z", TimeUtil::ToString(end)); + + // Test negative timestamps. + Timestamp time = TimeUtil::NanosecondsToTimestamp(-1); + EXPECT_EQ(-1, time.seconds()); + // Timestamp's nano part is always non-negative. + EXPECT_EQ(999999999, time.nanos()); + EXPECT_EQ("1969-12-31T23:59:59.999999999Z", TimeUtil::ToString(time)); + + // Generated output should contain 3, 6, or 9 fractional digits. + EXPECT_EQ("1970-01-01T00:00:00Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(0))); + EXPECT_EQ("1970-01-01T00:00:00.010Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(10000000))); + EXPECT_EQ("1970-01-01T00:00:00.000010Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(10000))); + EXPECT_EQ("1970-01-01T00:00:00.000000010Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(10))); + + // Parsing accepts an fractional digits as long as they fit into nano + // precision. + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00.1Z", &time)); + EXPECT_EQ(100000000, TimeUtil::TimestampToNanoseconds(time)); + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00.0001Z", &time)); + EXPECT_EQ(100000, TimeUtil::TimestampToNanoseconds(time)); + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00.0000001Z", &time)); + EXPECT_EQ(100, TimeUtil::TimestampToNanoseconds(time)); + + // Also accepts offsets. + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00-08:00", &time)); + EXPECT_EQ(8 * 3600, TimeUtil::TimestampToSeconds(time)); +} + +TEST(TimeUtilTest, DurationStringFormat) { + Timestamp begin, end; + EXPECT_TRUE(TimeUtil::FromString("0001-01-01T00:00:00Z", &begin)); + EXPECT_TRUE(TimeUtil::FromString("9999-12-31T23:59:59.999999999Z", &end)); + + EXPECT_EQ("315537897599.999999999s", TimeUtil::ToString(end - begin)); + EXPECT_EQ(999999999, (end - begin).nanos()); + EXPECT_EQ("-315537897599.999999999s", TimeUtil::ToString(begin - end)); + EXPECT_EQ(-999999999, (begin - end).nanos()); + + // Generated output should contain 3, 6, or 9 fractional digits. + EXPECT_EQ("1s", TimeUtil::ToString(TimeUtil::SecondsToDuration(1))); + EXPECT_EQ("0.010s", TimeUtil::ToString(TimeUtil::MillisecondsToDuration(10))); + EXPECT_EQ("0.000010s", + TimeUtil::ToString(TimeUtil::MicrosecondsToDuration(10))); + EXPECT_EQ("0.000000010s", + TimeUtil::ToString(TimeUtil::NanosecondsToDuration(10))); + + // Parsing accepts an fractional digits as long as they fit into nano + // precision. + Duration d; + EXPECT_TRUE(TimeUtil::FromString("0.1s", &d)); + EXPECT_EQ(100, TimeUtil::DurationToMilliseconds(d)); + EXPECT_TRUE(TimeUtil::FromString("0.0001s", &d)); + EXPECT_EQ(100, TimeUtil::DurationToMicroseconds(d)); + EXPECT_TRUE(TimeUtil::FromString("0.0000001s", &d)); + EXPECT_EQ(100, TimeUtil::DurationToNanoseconds(d)); + + // Duration must support range from -315,576,000,000s to +315576000000s + // which includes negative values. + EXPECT_TRUE(TimeUtil::FromString("315576000000.999999999s", &d)); + EXPECT_EQ(int64_t{315576000000}, d.seconds()); + EXPECT_EQ(999999999, d.nanos()); + EXPECT_TRUE(TimeUtil::FromString("-315576000000.999999999s", &d)); + EXPECT_EQ(int64_t{-315576000000}, d.seconds()); + EXPECT_EQ(-999999999, d.nanos()); +} + +TEST(TimeUtilTest, GetEpoch) { + EXPECT_EQ(0, TimeUtil::TimestampToNanoseconds(TimeUtil::GetEpoch())); +} + +TEST(TimeUtilTest, DurationIntegerConversion) { + EXPECT_EQ("0.000000001s", + TimeUtil::ToString(TimeUtil::NanosecondsToDuration(1))); + EXPECT_EQ("-0.000000001s", + TimeUtil::ToString(TimeUtil::NanosecondsToDuration(-1))); + EXPECT_EQ("0.000001s", + TimeUtil::ToString(TimeUtil::MicrosecondsToDuration(1))); + EXPECT_EQ("-0.000001s", + TimeUtil::ToString(TimeUtil::MicrosecondsToDuration(-1))); + EXPECT_EQ("0.001s", TimeUtil::ToString(TimeUtil::MillisecondsToDuration(1))); + EXPECT_EQ("-0.001s", + TimeUtil::ToString(TimeUtil::MillisecondsToDuration(-1))); + EXPECT_EQ("1s", TimeUtil::ToString(TimeUtil::SecondsToDuration(1))); + EXPECT_EQ("-1s", TimeUtil::ToString(TimeUtil::SecondsToDuration(-1))); + EXPECT_EQ("60s", TimeUtil::ToString(TimeUtil::MinutesToDuration(1))); + EXPECT_EQ("-60s", TimeUtil::ToString(TimeUtil::MinutesToDuration(-1))); + EXPECT_EQ("3600s", TimeUtil::ToString(TimeUtil::HoursToDuration(1))); + EXPECT_EQ("-3600s", TimeUtil::ToString(TimeUtil::HoursToDuration(-1))); + + EXPECT_EQ( + 1, TimeUtil::DurationToNanoseconds(TimeUtil::NanosecondsToDuration(1))); + EXPECT_EQ( + -1, TimeUtil::DurationToNanoseconds(TimeUtil::NanosecondsToDuration(-1))); + EXPECT_EQ( + 1, TimeUtil::DurationToMicroseconds(TimeUtil::MicrosecondsToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToMicroseconds( + TimeUtil::MicrosecondsToDuration(-1))); + EXPECT_EQ( + 1, TimeUtil::DurationToMilliseconds(TimeUtil::MillisecondsToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToMilliseconds( + TimeUtil::MillisecondsToDuration(-1))); + EXPECT_EQ(1, TimeUtil::DurationToSeconds(TimeUtil::SecondsToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToSeconds(TimeUtil::SecondsToDuration(-1))); + EXPECT_EQ(1, TimeUtil::DurationToMinutes(TimeUtil::MinutesToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToMinutes(TimeUtil::MinutesToDuration(-1))); + EXPECT_EQ(1, TimeUtil::DurationToHours(TimeUtil::HoursToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToHours(TimeUtil::HoursToDuration(-1))); + + // Test truncation behavior. + EXPECT_EQ(1, TimeUtil::DurationToMicroseconds( + TimeUtil::NanosecondsToDuration(1999))); + // For negative values, Duration will be rounded towards 0. + EXPECT_EQ(-1, TimeUtil::DurationToMicroseconds( + TimeUtil::NanosecondsToDuration(-1999))); +} + +TEST(TestUtilTest, TimestampIntegerConversion) { + EXPECT_EQ("1970-01-01T00:00:00.000000001Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59.999999999Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(-1))); + EXPECT_EQ("1970-01-01T00:00:00.000001Z", + TimeUtil::ToString(TimeUtil::MicrosecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59.999999Z", + TimeUtil::ToString(TimeUtil::MicrosecondsToTimestamp(-1))); + EXPECT_EQ("1970-01-01T00:00:00.001Z", + TimeUtil::ToString(TimeUtil::MillisecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59.999Z", + TimeUtil::ToString(TimeUtil::MillisecondsToTimestamp(-1))); + EXPECT_EQ("1970-01-01T00:00:01Z", + TimeUtil::ToString(TimeUtil::SecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59Z", + TimeUtil::ToString(TimeUtil::SecondsToTimestamp(-1))); + + EXPECT_EQ( + 1, TimeUtil::TimestampToNanoseconds(TimeUtil::NanosecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToNanoseconds( + TimeUtil::NanosecondsToTimestamp(-1))); + EXPECT_EQ(1, TimeUtil::TimestampToMicroseconds( + TimeUtil::MicrosecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToMicroseconds( + TimeUtil::MicrosecondsToTimestamp(-1))); + EXPECT_EQ(1, TimeUtil::TimestampToMilliseconds( + TimeUtil::MillisecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToMilliseconds( + TimeUtil::MillisecondsToTimestamp(-1))); + EXPECT_EQ(1, TimeUtil::TimestampToSeconds(TimeUtil::SecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToSeconds(TimeUtil::SecondsToTimestamp(-1))); + + // Test truncation behavior. + EXPECT_EQ(1, TimeUtil::TimestampToMicroseconds( + TimeUtil::NanosecondsToTimestamp(1999))); + // For negative values, Timestamp will be rounded down. + // For example, "1969-12-31T23:59:59.5Z" (i.e., -0.5s) rounded to seconds + // will be "1969-12-31T23:59:59Z" (i.e., -1s) rather than + // "1970-01-01T00:00:00Z" (i.e., 0s). + EXPECT_EQ(-2, TimeUtil::TimestampToMicroseconds( + TimeUtil::NanosecondsToTimestamp(-1999))); +} + +TEST(TimeUtilTest, TimeTConversion) { + time_t value = time(NULL); + EXPECT_EQ(value, + TimeUtil::TimestampToTimeT(TimeUtil::TimeTToTimestamp(value))); + EXPECT_EQ( + 1, TimeUtil::TimestampToTimeT(TimeUtil::MillisecondsToTimestamp(1999))); +} + +TEST(TimeUtilTest, TimevalConversion) { + timeval value = TimeUtil::TimestampToTimeval( + TimeUtil::NanosecondsToTimestamp(1999999999)); + EXPECT_EQ(1, value.tv_sec); + EXPECT_EQ(999999, value.tv_usec); + value = TimeUtil::TimestampToTimeval( + TimeUtil::NanosecondsToTimestamp(-1999999999)); + EXPECT_EQ(-2, value.tv_sec); + EXPECT_EQ(0, value.tv_usec); + + value = + TimeUtil::DurationToTimeval(TimeUtil::NanosecondsToDuration(1999999999)); + EXPECT_EQ(1, value.tv_sec); + EXPECT_EQ(999999, value.tv_usec); + value = + TimeUtil::DurationToTimeval(TimeUtil::NanosecondsToDuration(-1999999999)); + EXPECT_EQ(-2, value.tv_sec); + EXPECT_EQ(1, value.tv_usec); +} + +TEST(TimeUtilTest, DurationOperators) { + Duration one_second = TimeUtil::SecondsToDuration(1); + Duration one_nano = TimeUtil::NanosecondsToDuration(1); + + // Test +/- + Duration a = one_second; + a += one_second; + a -= one_nano; + EXPECT_EQ("1.999999999s", TimeUtil::ToString(a)); + Duration b = -a; + EXPECT_EQ("-1.999999999s", TimeUtil::ToString(b)); + EXPECT_EQ("3.999999998s", TimeUtil::ToString(a + a)); + EXPECT_EQ("0s", TimeUtil::ToString(a + b)); + EXPECT_EQ("0s", TimeUtil::ToString(b + a)); + EXPECT_EQ("-3.999999998s", TimeUtil::ToString(b + b)); + EXPECT_EQ("3.999999998s", TimeUtil::ToString(a - b)); + EXPECT_EQ("0s", TimeUtil::ToString(a - a)); + EXPECT_EQ("0s", TimeUtil::ToString(b - b)); + EXPECT_EQ("-3.999999998s", TimeUtil::ToString(b - a)); + + // Test * + EXPECT_EQ(a + a, a * 2); + EXPECT_EQ(b + b, a * (-2)); + EXPECT_EQ(b + b, b * 2); + EXPECT_EQ(a + a, b * (-2)); + EXPECT_EQ("0.999999999s", TimeUtil::ToString(a * 0.5)); + EXPECT_EQ("-0.999999999s", TimeUtil::ToString(b * 0.5)); + // Multiplication should not overflow if the result fits into the supported + // range of Duration (intermediate result may be larger than int64). + EXPECT_EQ("315575999684.424s", TimeUtil::ToString((one_second - one_nano) * + int64_t{315576000000})); + EXPECT_EQ("-315575999684.424s", TimeUtil::ToString((one_nano - one_second) * + int64_t{315576000000})); + EXPECT_EQ("-315575999684.424s", TimeUtil::ToString((one_second - one_nano) * + (int64_t{-315576000000}))); + + // Test / and % + EXPECT_EQ("0.999999999s", TimeUtil::ToString(a / 2)); + EXPECT_EQ("-0.999999999s", TimeUtil::ToString(b / 2)); + Duration large = + TimeUtil::SecondsToDuration(int64_t{315576000000}) - one_nano; + // We have to handle division with values beyond 64 bits. + EXPECT_EQ("0.999999999s", TimeUtil::ToString(large / int64_t{315576000000})); + EXPECT_EQ("-0.999999999s", + TimeUtil::ToString((-large) / int64_t{315576000000})); + EXPECT_EQ("-0.999999999s", + TimeUtil::ToString(large / (int64_t{-315576000000}))); + Duration large2 = large + one_nano; + EXPECT_EQ(large, large % large2); + EXPECT_EQ(-large, (-large) % large2); + EXPECT_EQ(large, large % (-large2)); + EXPECT_EQ(one_nano, large2 % large); + EXPECT_EQ(-one_nano, (-large2) % large); + EXPECT_EQ(one_nano, large2 % (-large)); + // Some corner cases about negative values. + // + // (-5) / 2 = -2, remainder = -1 + // (-5) / (-2) = 2, remainder = -1 + a = TimeUtil::NanosecondsToDuration(-5); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(-2), a / 2); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(2), a / (-2)); + b = TimeUtil::NanosecondsToDuration(2); + EXPECT_EQ(-2, a / b); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(-1), a % b); + EXPECT_EQ(2, a / (-b)); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(-1), a % (-b)); + + // Test relational operators. + EXPECT_TRUE(one_nano < one_second); + EXPECT_FALSE(one_second < one_second); + EXPECT_FALSE(one_second < one_nano); + EXPECT_FALSE(-one_nano < -one_second); + EXPECT_FALSE(-one_second < -one_second); + EXPECT_TRUE(-one_second < -one_nano); + EXPECT_TRUE(-one_nano < one_nano); + EXPECT_FALSE(one_nano < -one_nano); + + EXPECT_FALSE(one_nano > one_second); + EXPECT_FALSE(one_nano > one_nano); + EXPECT_TRUE(one_second > one_nano); + + EXPECT_FALSE(one_nano >= one_second); + EXPECT_TRUE(one_nano >= one_nano); + EXPECT_TRUE(one_second >= one_nano); + + EXPECT_TRUE(one_nano <= one_second); + EXPECT_TRUE(one_nano <= one_nano); + EXPECT_FALSE(one_second <= one_nano); + + EXPECT_TRUE(one_nano == one_nano); + EXPECT_FALSE(one_nano == one_second); + + EXPECT_FALSE(one_nano != one_nano); + EXPECT_TRUE(one_nano != one_second); +} + +TEST(TimeUtilTest, TimestampOperators) { + Timestamp begin, end; + EXPECT_TRUE(TimeUtil::FromString("0001-01-01T00:00:00Z", &begin)); + EXPECT_TRUE(TimeUtil::FromString("9999-12-31T23:59:59.999999999Z", &end)); + Duration d = end - begin; + EXPECT_TRUE(end == begin + d); + EXPECT_TRUE(end == d + begin); + EXPECT_TRUE(begin == end - d); + + // Test relational operators + Timestamp t1 = begin + d / 4; + Timestamp t2 = end - d / 4; + EXPECT_TRUE(t1 < t2); + EXPECT_FALSE(t1 < t1); + EXPECT_FALSE(t2 < t1); + EXPECT_FALSE(t1 > t2); + EXPECT_FALSE(t1 > t1); + EXPECT_TRUE(t2 > t1); + EXPECT_FALSE(t1 >= t2); + EXPECT_TRUE(t1 >= t1); + EXPECT_TRUE(t2 >= t1); + EXPECT_TRUE(t1 <= t2); + EXPECT_TRUE(t1 <= t1); + EXPECT_FALSE(t2 <= t1); + + EXPECT_FALSE(t1 == t2); + EXPECT_TRUE(t1 == t1); + EXPECT_FALSE(t2 == t1); + EXPECT_TRUE(t1 != t2); + EXPECT_FALSE(t1 != t1); + EXPECT_TRUE(t2 != t1); +} + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/type_resolver.h b/NorthstarDedicatedTest/include/protobuf/util/type_resolver.h new file mode 100644 index 00000000..941d6a32 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/type_resolver.h @@ -0,0 +1,76 @@ +// 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. + +// Defines a TypeResolver for the Any message. + +#ifndef GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_H__ +#define GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_H__ + +#include <string> + +#include <stubs/common.h> +#include <type.pb.h> +#include <stubs/status.h> +#include <stubs/status.h> + +#include <port_def.inc> + +namespace google { +namespace protobuf { +class DescriptorPool; +namespace util { + +// Abstract interface for a type resolver. +// +// Implementations of this interface must be thread-safe. +class PROTOBUF_EXPORT TypeResolver { + public: + TypeResolver() {} + virtual ~TypeResolver() {} + + // Resolves a type url for a message type. + virtual util::Status ResolveMessageType( + const std::string& type_url, google::protobuf::Type* message_type) = 0; + + // Resolves a type url for an enum type. + virtual util::Status ResolveEnumType(const std::string& type_url, + google::protobuf::Enum* enum_type) = 0; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(TypeResolver); +}; + +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util.cc b/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util.cc new file mode 100644 index 00000000..cdb1a1eb --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util.cc @@ -0,0 +1,370 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/type_resolver_util.h> + +#include <type.pb.h> +#include <wrappers.pb.h> +#include <descriptor.pb.h> +#include <descriptor.h> +#include <util/internal/utility.h> +#include <util/type_resolver.h> +#include <stubs/strutil.h> +#include <stubs/status.h> +#include <stubs/status.h> + +// clang-format off +#include <port_def.inc> +// clang-format on + +namespace google { +namespace protobuf { +namespace util { +namespace { +using google::protobuf::Any; +using google::protobuf::BoolValue; +using google::protobuf::BytesValue; +using google::protobuf::DoubleValue; +using google::protobuf::Enum; +using google::protobuf::EnumValue; +using google::protobuf::Field; +using google::protobuf::FloatValue; +using google::protobuf::Int32Value; +using google::protobuf::Int64Value; +using google::protobuf::Option; +using google::protobuf::StringValue; +using google::protobuf::Type; +using google::protobuf::UInt32Value; +using google::protobuf::UInt64Value; + +class DescriptorPoolTypeResolver : public TypeResolver { + public: + DescriptorPoolTypeResolver(const std::string& url_prefix, + const DescriptorPool* pool) + : url_prefix_(url_prefix), pool_(pool) {} + + util::Status ResolveMessageType(const std::string& type_url, + Type* type) override { + std::string type_name; + util::Status status = ParseTypeUrl(type_url, &type_name); + if (!status.ok()) { + return status; + } + + const Descriptor* descriptor = pool_->FindMessageTypeByName(type_name); + if (descriptor == NULL) { + return util::NotFoundError("Invalid type URL, unknown type: " + + type_name); + } + ConvertDescriptor(descriptor, type); + return util::Status(); + } + + util::Status ResolveEnumType(const std::string& type_url, + Enum* enum_type) override { + std::string type_name; + util::Status status = ParseTypeUrl(type_url, &type_name); + if (!status.ok()) { + return status; + } + + const EnumDescriptor* descriptor = pool_->FindEnumTypeByName(type_name); + if (descriptor == NULL) { + return util::InvalidArgumentError("Invalid type URL, unknown type: " + + type_name); + } + ConvertEnumDescriptor(descriptor, enum_type); + return util::Status(); + } + + private: + void ConvertDescriptor(const Descriptor* descriptor, Type* type) { + type->Clear(); + type->set_name(descriptor->full_name()); + for (int i = 0; i < descriptor->field_count(); ++i) { + ConvertFieldDescriptor(descriptor->field(i), type->add_fields()); + } + for (int i = 0; i < descriptor->oneof_decl_count(); ++i) { + type->add_oneofs(descriptor->oneof_decl(i)->name()); + } + type->mutable_source_context()->set_file_name(descriptor->file()->name()); + ConvertMessageOptions(descriptor->options(), type->mutable_options()); + } + + void ConvertMessageOptions(const MessageOptions& options, + RepeatedPtrField<Option>* output) { + return ConvertOptionsInternal(options, output); + } + + void ConvertFieldOptions(const FieldOptions& options, + RepeatedPtrField<Option>* output) { + return ConvertOptionsInternal(options, output); + } + + void ConvertEnumOptions(const EnumOptions& options, + RepeatedPtrField<Option>* output) { + return ConvertOptionsInternal(options, output); + } + + void ConvertEnumValueOptions(const EnumValueOptions& options, + RepeatedPtrField<Option>* output) { + return ConvertOptionsInternal(options, output); + } + + // Implementation details for Convert*Options. + void ConvertOptionsInternal(const Message& options, + RepeatedPtrField<Option>* output) { + const Reflection* reflection = options.GetReflection(); + std::vector<const FieldDescriptor*> fields; + reflection->ListFields(options, &fields); + for (const FieldDescriptor* field : fields) { + if (field->is_repeated()) { + const int size = reflection->FieldSize(options, field); + for (int i = 0; i < size; i++) { + ConvertOptionField(reflection, options, field, i, output->Add()); + } + } else { + ConvertOptionField(reflection, options, field, -1, output->Add()); + } + } + } + + static void ConvertOptionField(const Reflection* reflection, + const Message& options, + const FieldDescriptor* field, int index, + Option* out) { + out->set_name(field->is_extension() ? field->full_name() : field->name()); + Any* value = out->mutable_value(); + switch (field->cpp_type()) { + case FieldDescriptor::CPPTYPE_MESSAGE: + value->PackFrom( + field->is_repeated() + ? reflection->GetRepeatedMessage(options, field, index) + : reflection->GetMessage(options, field)); + return; + case FieldDescriptor::CPPTYPE_DOUBLE: + value->PackFrom(WrapValue<DoubleValue>( + field->is_repeated() + ? reflection->GetRepeatedDouble(options, field, index) + : reflection->GetDouble(options, field))); + return; + case FieldDescriptor::CPPTYPE_FLOAT: + value->PackFrom(WrapValue<FloatValue>( + field->is_repeated() + ? reflection->GetRepeatedFloat(options, field, index) + : reflection->GetFloat(options, field))); + return; + case FieldDescriptor::CPPTYPE_INT64: + value->PackFrom(WrapValue<Int64Value>( + field->is_repeated() + ? reflection->GetRepeatedInt64(options, field, index) + : reflection->GetInt64(options, field))); + return; + case FieldDescriptor::CPPTYPE_UINT64: + value->PackFrom(WrapValue<UInt64Value>( + field->is_repeated() + ? reflection->GetRepeatedUInt64(options, field, index) + : reflection->GetUInt64(options, field))); + return; + case FieldDescriptor::CPPTYPE_INT32: + value->PackFrom(WrapValue<Int32Value>( + field->is_repeated() + ? reflection->GetRepeatedInt32(options, field, index) + : reflection->GetInt32(options, field))); + return; + case FieldDescriptor::CPPTYPE_UINT32: + value->PackFrom(WrapValue<UInt32Value>( + field->is_repeated() + ? reflection->GetRepeatedUInt32(options, field, index) + : reflection->GetUInt32(options, field))); + return; + case FieldDescriptor::CPPTYPE_BOOL: + value->PackFrom(WrapValue<BoolValue>( + field->is_repeated() + ? reflection->GetRepeatedBool(options, field, index) + : reflection->GetBool(options, field))); + return; + case FieldDescriptor::CPPTYPE_STRING: { + const std::string& val = + field->is_repeated() + ? reflection->GetRepeatedString(options, field, index) + : reflection->GetString(options, field); + if (field->type() == FieldDescriptor::TYPE_STRING) { + value->PackFrom(WrapValue<StringValue>(val)); + } else { + value->PackFrom(WrapValue<BytesValue>(val)); + } + return; + } + case FieldDescriptor::CPPTYPE_ENUM: { + const EnumValueDescriptor* val = + field->is_repeated() + ? reflection->GetRepeatedEnum(options, field, index) + : reflection->GetEnum(options, field); + value->PackFrom(WrapValue<Int32Value>(val->number())); + return; + } + } + } + + template <typename WrapperT, typename T> + static WrapperT WrapValue(T value) { + WrapperT wrapper; + wrapper.set_value(value); + return wrapper; + } + + void ConvertFieldDescriptor(const FieldDescriptor* descriptor, Field* field) { + field->set_kind(static_cast<Field::Kind>(descriptor->type())); + switch (descriptor->label()) { + case FieldDescriptor::LABEL_OPTIONAL: + field->set_cardinality(Field::CARDINALITY_OPTIONAL); + break; + case FieldDescriptor::LABEL_REPEATED: + field->set_cardinality(Field::CARDINALITY_REPEATED); + break; + case FieldDescriptor::LABEL_REQUIRED: + field->set_cardinality(Field::CARDINALITY_REQUIRED); + break; + } + field->set_number(descriptor->number()); + field->set_name(descriptor->name()); + field->set_json_name(descriptor->json_name()); + if (descriptor->has_default_value()) { + field->set_default_value(DefaultValueAsString(descriptor)); + } + if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE || + descriptor->type() == FieldDescriptor::TYPE_GROUP) { + field->set_type_url(GetTypeUrl(descriptor->message_type())); + } else if (descriptor->type() == FieldDescriptor::TYPE_ENUM) { + field->set_type_url(GetTypeUrl(descriptor->enum_type())); + } + if (descriptor->containing_oneof() != NULL) { + field->set_oneof_index(descriptor->containing_oneof()->index() + 1); + } + if (descriptor->is_packed()) { + field->set_packed(true); + } + + ConvertFieldOptions(descriptor->options(), field->mutable_options()); + } + + void ConvertEnumDescriptor(const EnumDescriptor* descriptor, + Enum* enum_type) { + enum_type->Clear(); + enum_type->set_name(descriptor->full_name()); + enum_type->mutable_source_context()->set_file_name( + descriptor->file()->name()); + for (int i = 0; i < descriptor->value_count(); ++i) { + const EnumValueDescriptor* value_descriptor = descriptor->value(i); + EnumValue* value = enum_type->mutable_enumvalue()->Add(); + value->set_name(value_descriptor->name()); + value->set_number(value_descriptor->number()); + + ConvertEnumValueOptions(value_descriptor->options(), + value->mutable_options()); + } + + ConvertEnumOptions(descriptor->options(), enum_type->mutable_options()); + } + + std::string GetTypeUrl(const Descriptor* descriptor) { + return url_prefix_ + "/" + descriptor->full_name(); + } + + std::string GetTypeUrl(const EnumDescriptor* descriptor) { + return url_prefix_ + "/" + descriptor->full_name(); + } + + util::Status ParseTypeUrl(const std::string& type_url, + std::string* type_name) { + if (type_url.substr(0, url_prefix_.size() + 1) != url_prefix_ + "/") { + return util::InvalidArgumentError( + StrCat("Invalid type URL, type URLs must be of the form '", + url_prefix_, "/<typename>', got: ", type_url)); + } + *type_name = type_url.substr(url_prefix_.size() + 1); + return util::Status(); + } + + std::string DefaultValueAsString(const FieldDescriptor* descriptor) { + switch (descriptor->cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: + return StrCat(descriptor->default_value_int32()); + break; + case FieldDescriptor::CPPTYPE_INT64: + return StrCat(descriptor->default_value_int64()); + break; + case FieldDescriptor::CPPTYPE_UINT32: + return StrCat(descriptor->default_value_uint32()); + break; + case FieldDescriptor::CPPTYPE_UINT64: + return StrCat(descriptor->default_value_uint64()); + break; + case FieldDescriptor::CPPTYPE_FLOAT: + return SimpleFtoa(descriptor->default_value_float()); + break; + case FieldDescriptor::CPPTYPE_DOUBLE: + return SimpleDtoa(descriptor->default_value_double()); + break; + case FieldDescriptor::CPPTYPE_BOOL: + return descriptor->default_value_bool() ? "true" : "false"; + break; + case FieldDescriptor::CPPTYPE_STRING: + if (descriptor->type() == FieldDescriptor::TYPE_BYTES) { + return CEscape(descriptor->default_value_string()); + } else { + return descriptor->default_value_string(); + } + break; + case FieldDescriptor::CPPTYPE_ENUM: + return descriptor->default_value_enum()->name(); + break; + case FieldDescriptor::CPPTYPE_MESSAGE: + GOOGLE_LOG(DFATAL) << "Messages can't have default values!"; + break; + } + return ""; + } + + std::string url_prefix_; + const DescriptorPool* pool_; +}; + +} // namespace + +TypeResolver* NewTypeResolverForDescriptorPool(const std::string& url_prefix, + const DescriptorPool* pool) { + return new DescriptorPoolTypeResolver(url_prefix, pool); +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util.h b/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util.h new file mode 100644 index 00000000..4c5102dd --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util.h @@ -0,0 +1,57 @@ +// 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. + +// Defines utilities for the TypeResolver. + +#ifndef GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_UTIL_H__ + +#include <string> + +namespace google { +namespace protobuf { +class DescriptorPool; +namespace util { +class TypeResolver; + +#include <port_def.inc> + +// Creates a TypeResolver that serves type information in the given descriptor +// pool. Caller takes ownership of the returned TypeResolver. +PROTOBUF_EXPORT TypeResolver* NewTypeResolverForDescriptorPool( + const std::string& url_prefix, const DescriptorPool* pool); + +} // namespace util +} // namespace protobuf +} // namespace google + +#include <port_undef.inc> + +#endif // GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_UTIL_H__ diff --git a/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util_test.cc b/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util_test.cc new file mode 100644 index 00000000..bcd97109 --- /dev/null +++ b/NorthstarDedicatedTest/include/protobuf/util/type_resolver_util_test.cc @@ -0,0 +1,438 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <util/type_resolver_util.h> + +#include <cstdint> +#include <limits> +#include <memory> +#include <string> +#include <vector> + +#include <type.pb.h> +#include <wrappers.pb.h> +#include <map_unittest.pb.h> +#include <test_util.h> +#include <unittest.pb.h> +#include <unittest_custom_options.pb.h> +#include <util/json_format_proto3.pb.h> +#include <util/type_resolver.h> +#include <testing/googletest.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { +namespace { +using google::protobuf::BoolValue; +using google::protobuf::Enum; +using google::protobuf::EnumValue; +using google::protobuf::Field; +using google::protobuf::Int32Value; +using google::protobuf::Option; +using google::protobuf::Type; +using google::protobuf::UInt64Value; + +static const char kUrlPrefix[] = "type.googleapis.com"; + +class DescriptorPoolTypeResolverTest : public testing::Test { + public: + DescriptorPoolTypeResolverTest() { + resolver_.reset(NewTypeResolverForDescriptorPool( + kUrlPrefix, DescriptorPool::generated_pool())); + } + + const Field* FindField(const Type& type, const std::string& name) { + for (int i = 0; i < type.fields_size(); ++i) { + const Field& field = type.fields(i); + if (field.name() == name) { + return &field; + } + } + return NULL; + } + + bool HasField(const Type& type, Field::Cardinality cardinality, + Field::Kind kind, const std::string& name, int number) { + const Field* field = FindField(type, name); + if (field == NULL) { + return false; + } + return field->cardinality() == cardinality && field->kind() == kind && + field->number() == number; + } + + bool CheckFieldTypeUrl(const Type& type, const std::string& name, + const std::string& type_url) { + const Field* field = FindField(type, name); + if (field == NULL) { + return false; + } + return field->type_url() == type_url; + } + + bool FieldInOneof(const Type& type, const std::string& name, + const std::string& oneof_name) { + const Field* field = FindField(type, name); + if (field == NULL || field->oneof_index() <= 0 || + field->oneof_index() > type.oneofs_size()) { + return false; + } + return type.oneofs(field->oneof_index() - 1) == oneof_name; + } + + bool IsPacked(const Type& type, const std::string& name) { + const Field* field = FindField(type, name); + if (field == NULL) { + return false; + } + return field->packed(); + } + + const EnumValue* FindEnumValue(const Enum& type, const std::string& name) { + for (const EnumValue& value : type.enumvalue()) { + if (value.name() == name) { + return &value; + } + } + return nullptr; + } + + bool EnumHasValue(const Enum& type, const std::string& name, int number) { + const EnumValue* value = FindEnumValue(type, name); + return value != nullptr && value->number() == number; + } + + bool HasBoolOption(const RepeatedPtrField<Option>& options, + const std::string& name, bool value) { + return HasOption<BoolValue>(options, name, value); + } + + bool HasInt32Option(const RepeatedPtrField<Option>& options, + const std::string& name, int32_t value) { + return HasOption<Int32Value>(options, name, value); + } + + bool HasUInt64Option(const RepeatedPtrField<Option>& options, + const std::string& name, uint64_t value) { + return HasOption<UInt64Value>(options, name, value); + } + + template <typename WrapperT, typename T> + bool HasOption(const RepeatedPtrField<Option>& options, + const std::string& name, T value) { + for (const Option& option : options) { + if (option.name() == name) { + WrapperT wrapper; + if (option.value().UnpackTo(&wrapper) && wrapper.value() == value) { + return true; + } + } + } + return false; + } + + std::string GetTypeUrl(std::string full_name) { + return kUrlPrefix + std::string("/") + full_name; + } + + template <typename T> + std::string GetTypeUrl() { + return GetTypeUrl(T::descriptor()->full_name()); + } + + protected: + std::unique_ptr<TypeResolver> resolver_; +}; + +TEST_F(DescriptorPoolTypeResolverTest, TestAllTypes) { + Type type; + ASSERT_TRUE(resolver_ + ->ResolveMessageType( + GetTypeUrl<protobuf_unittest::TestAllTypes>(), &type) + .ok()); + // Check all optional fields. + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_INT32, + "optional_int32", 1)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_INT64, + "optional_int64", 2)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_UINT32, + "optional_uint32", 3)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_UINT64, + "optional_uint64", 4)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SINT32, + "optional_sint32", 5)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SINT64, + "optional_sint64", 6)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_FIXED32, + "optional_fixed32", 7)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_FIXED64, + "optional_fixed64", 8)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SFIXED32, + "optional_sfixed32", 9)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SFIXED64, + "optional_sfixed64", 10)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_FLOAT, + "optional_float", 11)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_DOUBLE, + "optional_double", 12)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_BOOL, + "optional_bool", 13)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_STRING, + "optional_string", 14)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_BYTES, + "optional_bytes", 15)); + + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_GROUP, + "optionalgroup", 16)); + + EXPECT_TRUE(CheckFieldTypeUrl( + type, "optionalgroup", + GetTypeUrl<protobuf_unittest::TestAllTypes::OptionalGroup>())); + + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_MESSAGE, + "optional_nested_message", 18)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_MESSAGE, + "optional_foreign_message", 19)); + + EXPECT_TRUE(CheckFieldTypeUrl( + type, "optional_nested_message", + GetTypeUrl<protobuf_unittest::TestAllTypes::NestedMessage>())); + EXPECT_TRUE(CheckFieldTypeUrl(type, "optional_foreign_message", + GetTypeUrl<protobuf_unittest::ForeignMessage>())); + + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_ENUM, + "optional_nested_enum", 21)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_ENUM, + "optional_foreign_enum", 22)); + + EXPECT_TRUE( + CheckFieldTypeUrl(type, "optional_nested_enum", + GetTypeUrl("protobuf_unittest.TestAllTypes.NestedEnum"))); + EXPECT_TRUE(CheckFieldTypeUrl(type, "optional_foreign_enum", + GetTypeUrl("protobuf_unittest.ForeignEnum"))); + + // Check all repeated fields. + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_INT32, + "repeated_int32", 31)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_INT64, + "repeated_int64", 32)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_UINT32, + "repeated_uint32", 33)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_UINT64, + "repeated_uint64", 34)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SINT32, + "repeated_sint32", 35)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SINT64, + "repeated_sint64", 36)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_FIXED32, + "repeated_fixed32", 37)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_FIXED64, + "repeated_fixed64", 38)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SFIXED32, + "repeated_sfixed32", 39)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SFIXED64, + "repeated_sfixed64", 40)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_FLOAT, + "repeated_float", 41)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_DOUBLE, + "repeated_double", 42)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_BOOL, + "repeated_bool", 43)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_STRING, + "repeated_string", 44)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_BYTES, + "repeated_bytes", 45)); + + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_GROUP, + "repeatedgroup", 46)); + + EXPECT_TRUE(CheckFieldTypeUrl( + type, "repeatedgroup", + GetTypeUrl<protobuf_unittest::TestAllTypes::RepeatedGroup>())); + + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_MESSAGE, + "repeated_nested_message", 48)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_MESSAGE, + "repeated_foreign_message", 49)); + + EXPECT_TRUE(CheckFieldTypeUrl( + type, "repeated_nested_message", + GetTypeUrl<protobuf_unittest::TestAllTypes::NestedMessage>())); + EXPECT_TRUE(CheckFieldTypeUrl(type, "repeated_foreign_message", + GetTypeUrl<protobuf_unittest::ForeignMessage>())); + + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_ENUM, + "repeated_nested_enum", 51)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_ENUM, + "repeated_foreign_enum", 52)); + + EXPECT_TRUE( + CheckFieldTypeUrl(type, "repeated_nested_enum", + GetTypeUrl("protobuf_unittest.TestAllTypes.NestedEnum"))); + EXPECT_TRUE(CheckFieldTypeUrl(type, "repeated_foreign_enum", + GetTypeUrl("protobuf_unittest.ForeignEnum"))); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestPackedField) { + Type type; + ASSERT_TRUE(resolver_ + ->ResolveMessageType( + GetTypeUrl<protobuf_unittest::TestPackedTypes>(), &type) + .ok()); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_INT32, + "packed_int32", 90)); + EXPECT_TRUE(IsPacked(type, "packed_int32")); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestOneof) { + Type type; + ASSERT_TRUE(resolver_ + ->ResolveMessageType( + GetTypeUrl<protobuf_unittest::TestAllTypes>(), &type) + .ok()); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_UINT32, + "oneof_uint32", 111)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_MESSAGE, + "oneof_nested_message", 112)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_STRING, + "oneof_string", 113)); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_BYTES, + "oneof_bytes", 114)); + EXPECT_TRUE(FieldInOneof(type, "oneof_uint32", "oneof_field")); + EXPECT_TRUE(FieldInOneof(type, "oneof_nested_message", "oneof_field")); + EXPECT_TRUE(FieldInOneof(type, "oneof_string", "oneof_field")); + EXPECT_TRUE(FieldInOneof(type, "oneof_bytes", "oneof_field")); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestMap) { + Type type; + ASSERT_TRUE( + resolver_ + ->ResolveMessageType(GetTypeUrl<protobuf_unittest::TestMap>(), &type) + .ok()); + EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_MESSAGE, + "map_int32_int32", 1)); + EXPECT_TRUE(CheckFieldTypeUrl( + type, "map_int32_int32", + GetTypeUrl("protobuf_unittest.TestMap.MapInt32Int32Entry"))); + + ASSERT_TRUE( + resolver_ + ->ResolveMessageType( + GetTypeUrl("protobuf_unittest.TestMap.MapInt32Int32Entry"), &type) + .ok()); + EXPECT_TRUE(HasBoolOption(type.options(), "map_entry", true)); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestCustomMessageOptions) { + Type type; + ASSERT_TRUE( + resolver_ + ->ResolveMessageType( + GetTypeUrl<protobuf_unittest::TestMessageWithCustomOptions>(), + &type) + .ok()); + EXPECT_TRUE( + HasInt32Option(type.options(), "protobuf_unittest.message_opt1", -56)); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestCustomFieldOptions) { + Type type; + ASSERT_TRUE( + resolver_ + ->ResolveMessageType( + GetTypeUrl<protobuf_unittest::TestMessageWithCustomOptions>(), + &type) + .ok()); + const Field* field = FindField(type, "field1"); + ASSERT_TRUE(field != nullptr); + EXPECT_TRUE(HasUInt64Option(field->options(), "protobuf_unittest.field_opt1", + 8765432109)); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestEnum) { + Enum type; + ASSERT_TRUE( + resolver_ + ->ResolveEnumType( + GetTypeUrl("protobuf_unittest.TestAllTypes.NestedEnum"), &type) + .ok()); + EnumHasValue(type, "FOO", 1); + EnumHasValue(type, "BAR", 2); + EnumHasValue(type, "BAZ", 3); + EnumHasValue(type, "NEG", -1); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestCustomEnumOptions) { + Enum type; + ASSERT_TRUE( + resolver_ + ->ResolveEnumType( + GetTypeUrl("protobuf_unittest.TestMessageWithCustomOptions.AnEnum"), + &type) + .ok()); + ASSERT_TRUE( + HasInt32Option(type.options(), "protobuf_unittest.enum_opt1", -789)); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestCustomValueOptions) { + Enum type; + ASSERT_TRUE( + resolver_ + ->ResolveEnumType( + GetTypeUrl("protobuf_unittest.TestMessageWithCustomOptions.AnEnum"), + &type) + .ok()); + const EnumValue* value = FindEnumValue(type, "ANENUM_VAL2"); + ASSERT_TRUE(value != nullptr); + ASSERT_TRUE( + HasInt32Option(value->options(), "protobuf_unittest.enum_value_opt1", 123)); +} + +TEST_F(DescriptorPoolTypeResolverTest, TestJsonName) { + Type type; + ASSERT_TRUE(resolver_ + ->ResolveMessageType( + GetTypeUrl<protobuf_unittest::TestAllTypes>(), &type) + .ok()); + EXPECT_EQ("optionalInt32", FindField(type, "optional_int32")->json_name()); + + ASSERT_TRUE( + resolver_ + ->ResolveMessageType(GetTypeUrl<proto3::TestCustomJsonName>(), &type) + .ok()); + EXPECT_EQ("@value", FindField(type, "value")->json_name()); +} + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google |