This is an automated email from the ASF dual-hosted git repository. qianzhang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mesos.git
commit ec82a516918ebd663816cb110f73bdee6e5268be Author: Qian Zhang <[email protected]> AuthorDate: Sun Feb 23 10:09:48 2020 +0800 Accommodated the "Infinity" value in the JSON <-> Protobuf conversion. Review: https://reviews.apache.org/r/72162 --- 3rdparty/stout/include/stout/jsonify.hpp | 1 + 3rdparty/stout/include/stout/protobuf.hpp | 19 ++++++- 3rdparty/stout/tests/protobuf_tests.cpp | 82 +++++++++++++++++++++++++++++++ 3rdparty/stout/tests/protobuf_tests.proto | 9 ++++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/3rdparty/stout/include/stout/jsonify.hpp b/3rdparty/stout/include/stout/jsonify.hpp index 7a239d8..e18da8f 100644 --- a/3rdparty/stout/include/stout/jsonify.hpp +++ b/3rdparty/stout/include/stout/jsonify.hpp @@ -14,6 +14,7 @@ #define __STOUT_JSONIFY__ #define RAPIDJSON_HAS_STDSTRING 1 +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNanAndInfFlag // TODO(bmahler): Consider enabling UTF-8 validation when writing // json. Prior to the introduction of rapidjson, we performed no diff --git a/3rdparty/stout/include/stout/protobuf.hpp b/3rdparty/stout/include/stout/protobuf.hpp index fcd91d5..9a82a30 100644 --- a/3rdparty/stout/include/stout/protobuf.hpp +++ b/3rdparty/stout/include/stout/protobuf.hpp @@ -540,7 +540,24 @@ struct Parser : boost::static_visitor<Try<Nothing>> break; } case google::protobuf::FieldDescriptor::TYPE_DOUBLE: - case google::protobuf::FieldDescriptor::TYPE_FLOAT: + case google::protobuf::FieldDescriptor::TYPE_FLOAT: { + if (string.value == "Infinity") { + return operator()(JSON::Number( + std::numeric_limits<double>::infinity())); + } else if (string.value == "-Infinity") { + return operator()(JSON::Number( + -std::numeric_limits<double>::infinity())); + } else { + Try<JSON::Number> number = JSON::parse<JSON::Number>(string.value); + if (number.isError()) { + return Error( + "Failed to parse '" + string.value + "' as a JSON number " + "for field '" + field->name() + "': " + number.error()); + } + + return operator()(number.get()); + } + } case google::protobuf::FieldDescriptor::TYPE_INT64: case google::protobuf::FieldDescriptor::TYPE_SINT64: case google::protobuf::FieldDescriptor::TYPE_SFIXED64: diff --git a/3rdparty/stout/tests/protobuf_tests.cpp b/3rdparty/stout/tests/protobuf_tests.cpp index 55889dc..46249ea 100644 --- a/3rdparty/stout/tests/protobuf_tests.cpp +++ b/3rdparty/stout/tests/protobuf_tests.cpp @@ -15,6 +15,7 @@ #include <gmock/gmock.h> #include <algorithm> +#include <limits> #include <string> #include <google/protobuf/util/message_differencer.h> @@ -53,6 +54,25 @@ bool operator!=(const SimpleMessage& left, const SimpleMessage& right) return !(left == right); } + +bool operator==(const FloatMessage& left, const FloatMessage& right) +{ + if (left.f1() == right.f1() && + left.f2() == right.f2() && + left.d1() == right.d1() && + left.d2() == right.d2()) { + return true; + } + + return false; +} + + +bool operator!=(const FloatMessage& left, const FloatMessage& right) +{ + return !(left == right); +} + } // namespace tests { @@ -852,3 +872,65 @@ TEST(ProtobufTest, JsonifyMap) EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( message, *protoFromJson)); } + + +TEST(ProtobufTest, JsonInfinity) +{ + tests::FloatMessage message; + message.set_f1(std::numeric_limits<float>::infinity()); + message.set_f2(-std::numeric_limits<float>::infinity()); + message.set_d1(std::numeric_limits<double>::infinity()); + message.set_d2(-std::numeric_limits<double>::infinity()); + + // The keys are in alphabetical order. + string expected = + R"~( + { + "d1": "Infinity", + "d2": "-Infinity", + "f1": "Infinity", + "f2": "-Infinity" + })~"; + + // Remove ' ' and '\n' from `expected` so that we can compare + // it with the JSON string parsed from protobuf message. + expected.erase( + std::remove_if(expected.begin(), expected.end(), ::isspace), + expected.end()); + + JSON::Object referenceObject; + referenceObject.values["d1"] = JSON::Number(std::numeric_limits<double>::infinity()); + referenceObject.values["d2"] = JSON::Number(-std::numeric_limits<double>::infinity()); + referenceObject.values["f1"] = JSON::Number(std::numeric_limits<double>::infinity()); + referenceObject.values["f2"] = JSON::Number(-std::numeric_limits<double>::infinity()); + + // Check Protobuf -> JSON. + JSON::Object protobufObject = JSON::protobuf(message); + EXPECT_EQ(referenceObject, protobufObject); + EXPECT_EQ(expected, stringify(protobufObject)); + + // Check JSON -> Protobuf. + Try<tests::FloatMessage> parse = + protobuf::parse<tests::FloatMessage>(protobufObject); + + ASSERT_SOME(parse); + EXPECT_EQ(message, parse.get()); + + // Check String -> JSON. + Try<JSON::Object> parsedObject = JSON::parse<JSON::Object>(expected); + + // Unlike `JSON::protobuf()`, `JSON::parse()` does not have the + // field type information, so it will just parse "Infinity" as a + // `JSON::String` instead of `JSON::Number`. + referenceObject.values["d1"] = JSON::String("Infinity"); + referenceObject.values["d2"] = JSON::String("-Infinity"); + referenceObject.values["f1"] = JSON::String("Infinity"); + referenceObject.values["f2"] = JSON::String("-Infinity"); + + EXPECT_SOME_EQ(referenceObject, parsedObject); + + // Check JSON -> Protobuf. + parse = protobuf::parse<tests::FloatMessage>(parsedObject.get()); + ASSERT_SOME(parse); + EXPECT_EQ(message, parse.get()); +} diff --git a/3rdparty/stout/tests/protobuf_tests.proto b/3rdparty/stout/tests/protobuf_tests.proto index 5e20215..3a6a458 100644 --- a/3rdparty/stout/tests/protobuf_tests.proto +++ b/3rdparty/stout/tests/protobuf_tests.proto @@ -130,3 +130,12 @@ message MapMessage { map<sint32, string> sint32_to_string = 19; map<sint64, string> sint64_to_string = 20; } + + +// A message for testing infinite floating point numbers. +message FloatMessage { + required float f1 = 1; + required float f2 = 2; + required double d1 = 3; + required double d2 = 4; +}
