1361 lines
44 KiB
C++
1361 lines
44 KiB
C++
// 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 "google/protobuf/json/json.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "google/protobuf/duration.pb.h"
|
|
#include "google/protobuf/field_mask.pb.h"
|
|
#include "google/protobuf/struct.pb.h"
|
|
#include "google/protobuf/timestamp.pb.h"
|
|
#include "google/protobuf/wrappers.pb.h"
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
#include "absl/status/status.h"
|
|
#include "absl/status/statusor.h"
|
|
#include "absl/strings/string_view.h"
|
|
#include "google/protobuf/descriptor_database.h"
|
|
#include "google/protobuf/dynamic_message.h"
|
|
#include "google/protobuf/io/zero_copy_stream.h"
|
|
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
|
|
#include "google/protobuf/util/json_format.pb.h"
|
|
#include "google/protobuf/util/json_format_proto3.pb.h"
|
|
#include "google/protobuf/unittest.pb.h"
|
|
#include "google/protobuf/util/type_resolver.h"
|
|
#include "google/protobuf/util/type_resolver_util.h"
|
|
#include "google/protobuf/stubs/status_macros.h"
|
|
|
|
// Must be included last.
|
|
#include "google/protobuf/port_def.inc"
|
|
|
|
namespace google {
|
|
namespace protobuf {
|
|
namespace json {
|
|
namespace {
|
|
using ::google::protobuf::util::TypeResolver;
|
|
using ::proto3::MapIn;
|
|
using ::proto3::TestAny;
|
|
using ::proto3::TestEnumValue;
|
|
using ::proto3::TestMap;
|
|
using ::proto3::TestMessage;
|
|
using ::proto3::TestOneof;
|
|
using ::proto3::TestWrapper;
|
|
using ::testing::ElementsAre;
|
|
using ::testing::IsEmpty;
|
|
using ::testing::Not;
|
|
using ::testing::SizeIs;
|
|
|
|
// TODO(b/234474291): Use the gtest versions once that's available in OSS.
|
|
MATCHER_P(IsOkAndHolds, inner,
|
|
absl::StrCat("is OK and holds ", testing::PrintToString(inner))) {
|
|
if (!arg.ok()) {
|
|
*result_listener << arg.status();
|
|
return false;
|
|
}
|
|
return testing::ExplainMatchResult(inner, *arg, result_listener);
|
|
}
|
|
|
|
absl::Status GetStatus(const absl::Status& s) { return s; }
|
|
template <typename T>
|
|
absl::Status GetStatus(const absl::StatusOr<T>& s) {
|
|
return s.status();
|
|
}
|
|
|
|
MATCHER_P(StatusIs, status,
|
|
absl::StrCat(".status() is ", testing::PrintToString(status))) {
|
|
return GetStatus(arg).code() == status;
|
|
}
|
|
|
|
#define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk))
|
|
#define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk))
|
|
|
|
enum class Codec {
|
|
kReflective,
|
|
kResolver,
|
|
};
|
|
|
|
class JsonTest : public testing::TestWithParam<Codec> {
|
|
protected:
|
|
absl::StatusOr<std::string> ToJson(const Message& proto,
|
|
PrintOptions options = {}) {
|
|
if (GetParam() == Codec::kReflective) {
|
|
std::string result;
|
|
RETURN_IF_ERROR(MessageToJsonString(proto, &result, options));
|
|
return result;
|
|
}
|
|
std::string proto_data = proto.SerializeAsString();
|
|
io::ArrayInputStream in(proto_data.data(), proto_data.size());
|
|
|
|
std::string result;
|
|
io::StringOutputStream out(&result);
|
|
|
|
RETURN_IF_ERROR(BinaryToJsonStream(
|
|
resolver_.get(),
|
|
absl::StrCat("type.googleapis.com/", proto.GetTypeName()), &in, &out,
|
|
options));
|
|
return result;
|
|
}
|
|
|
|
// The out parameter comes first since `json` tends to be a very long string,
|
|
// and clang-format does a poor job if it is not the last parameter.
|
|
absl::Status ToProto(Message& proto, absl::string_view json,
|
|
ParseOptions options = {}) {
|
|
if (GetParam() == Codec::kReflective) {
|
|
return JsonStringToMessage(json, &proto, options);
|
|
}
|
|
io::ArrayInputStream in(json.data(), json.size());
|
|
|
|
std::string result;
|
|
io::StringOutputStream out(&result);
|
|
|
|
RETURN_IF_ERROR(JsonToBinaryStream(
|
|
resolver_.get(),
|
|
absl::StrCat("type.googleapis.com/", proto.GetTypeName()), &in, &out,
|
|
options));
|
|
|
|
if (!proto.ParseFromString(result)) {
|
|
return absl::InternalError("wire format parse failed");
|
|
}
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
template <typename Proto>
|
|
absl::StatusOr<Proto> ToProto(absl::string_view json,
|
|
ParseOptions options = {}) {
|
|
Proto proto;
|
|
RETURN_IF_ERROR(ToProto(proto, json, options));
|
|
return proto;
|
|
}
|
|
|
|
std::unique_ptr<TypeResolver> resolver_{
|
|
google::protobuf::util::NewTypeResolverForDescriptorPool(
|
|
"type.googleapis.com", DescriptorPool::generated_pool())};
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(JsonTestSuite, JsonTest,
|
|
testing::Values(Codec::kReflective, Codec::kResolver));
|
|
|
|
TEST_P(JsonTest, TestWhitespaces) {
|
|
TestMessage m;
|
|
m.mutable_message_value();
|
|
m.set_string_value("foo");
|
|
m.add_repeated_bool_value(true);
|
|
m.add_repeated_bool_value(false);
|
|
|
|
EXPECT_THAT(
|
|
ToJson(m),
|
|
IsOkAndHolds(
|
|
R"({"stringValue":"foo","messageValue":{},"repeatedBoolValue":[true,false]})"));
|
|
|
|
PrintOptions options;
|
|
options.add_whitespace = true;
|
|
// Note: whitespace here is significant.
|
|
EXPECT_THAT(ToJson(m, options), IsOkAndHolds(R"({
|
|
"stringValue": "foo",
|
|
"messageValue": {},
|
|
"repeatedBoolValue": [
|
|
true,
|
|
false
|
|
]
|
|
}
|
|
)"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestDefaultValues) {
|
|
TestMessage m;
|
|
EXPECT_THAT(ToJson(m), IsOkAndHolds("{}"));
|
|
|
|
PrintOptions options;
|
|
options.always_print_primitive_fields = true;
|
|
EXPECT_THAT(ToJson(m, options), IsOkAndHolds("{\"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\":[]"
|
|
"}"));
|
|
|
|
m.set_string_value("i am a test string value");
|
|
m.set_bytes_value("i am a test bytes value");
|
|
EXPECT_THAT(
|
|
ToJson(m, options),
|
|
IsOkAndHolds("{\"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\":[]"
|
|
"}"));
|
|
|
|
EXPECT_THAT(
|
|
ToJson(protobuf_unittest::TestAllTypes(), options),
|
|
IsOkAndHolds(
|
|
R"({"optionalInt32":0,"optionalInt64":"0","optionalUint32":0,)"
|
|
R"("optionalUint64":"0","optionalSint32":0,"optionalSint64":"0","optionalFixed32":0,)"
|
|
R"("optionalFixed64":"0","optionalSfixed32":0,"optionalSfixed64":"0",)"
|
|
R"("optionalFloat":0,"optionalDouble":0,"optionalBool":false,"optionalString":"",)"
|
|
R"("optionalBytes":"","optionalgroup":null,"optionalNestedEnum":"FOO","optionalForeignEnum":"FOREIGN_FOO",)"
|
|
R"("optionalImportEnum":"IMPORT_FOO","optionalStringPiece":"","optionalCord":"",)"
|
|
R"("repeatedInt32":[],"repeatedInt64":[],"repeatedUint32":[],"repeatedUint64":[],)"
|
|
R"("repeatedSint32":[],"repeatedSint64":[],"repeatedFixed32":[],"repeatedFixed64":[],)"
|
|
R"("repeatedSfixed32":[],"repeatedSfixed64":[],"repeatedFloat":[],"repeatedDouble":[],)"
|
|
R"("repeatedBool":[],"repeatedString":[],"repeatedBytes":[],"repeatedgroup":[],)"
|
|
R"("repeatedNestedMessage":[],"repeatedForeignMessage":[],"repeatedImportMessage":[],)"
|
|
R"("repeatedNestedEnum":[],"repeatedForeignEnum":[],"repeatedImportEnum":[],)"
|
|
R"("repeatedStringPiece":[],"repeatedCord":[],"repeatedLazyMessage":[],"defaultInt32":41,)"
|
|
R"("defaultInt64":"42","defaultUint32":43,"defaultUint64":"44","defaultSint32":-45,)"
|
|
R"("defaultSint64":"46","defaultFixed32":47,"defaultFixed64":"48","defaultSfixed32":49,)"
|
|
R"("defaultSfixed64":"-50","defaultFloat":51.5,"defaultDouble":52000,"defaultBool":true,)"
|
|
R"("defaultString":"hello","defaultBytes":"d29ybGQ=","defaultNestedEnum":"BAR",)"
|
|
R"("defaultForeignEnum":"FOREIGN_BAR","defaultImportEnum":"IMPORT_BAR",)"
|
|
R"("defaultStringPiece":"abc","defaultCord":"123"})"));
|
|
|
|
EXPECT_THAT(
|
|
ToJson(protobuf_unittest::TestExtremeDefaultValues(), options),
|
|
IsOkAndHolds(
|
|
R"({"escapedBytes":"XDAwMFwwMDFcMDA3XDAxMFwwMTRcblxyXHRcMDEzXFxcJ1wiXDM3Ng==")"
|
|
R"(,"largeUint32":4294967295,"largeUint64":"18446744073709551615",)"
|
|
R"("smallInt32":-2147483647,"smallInt64":"-9223372036854775807",)"
|
|
R"("utf8String":"ሴ","zeroFloat":0,"oneFloat":1,"smallFloat":1.5,)"
|
|
R"("negativeOneFloat":-1,"negativeFloat":-1.5,"largeFloat":2e+08,)"
|
|
R"("smallNegativeFloat":-8e-28,"infDouble":0,"negInfDouble":0,)"
|
|
R"("nanDouble":0,"infFloat":0,"negInfFloat":0,"nanFloat":0,)"
|
|
R"("cppTrigraph":"? ? ?? ?? ??? ??/ ??-","reallySmallInt32":-2147483648)"
|
|
R"(,"reallySmallInt64":"-9223372036854775808","stringWithZero":"hel\u0000lo")"
|
|
R"(,"bytesWithZero":"d29yXDAwMGxk","stringPieceWithZero":"ab\u0000c")"
|
|
R"(,"cordWithZero":"12\u00003","replacementString":"${unknown}"})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestPreserveProtoFieldNames) {
|
|
TestMessage m;
|
|
m.mutable_message_value();
|
|
|
|
PrintOptions options;
|
|
options.preserve_proto_field_names = true;
|
|
EXPECT_THAT(ToJson(m, options), IsOkAndHolds("{\"message_value\":{}}"));
|
|
|
|
}
|
|
|
|
TEST_P(JsonTest, Camels) {
|
|
protobuf_unittest::TestCamelCaseFieldNames m;
|
|
m.set_stringfield("sTRINGfIELD");
|
|
|
|
EXPECT_THAT(ToJson(m), IsOkAndHolds(R"({"StringField":"sTRINGfIELD"})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, EvilString) {
|
|
auto m = ToProto<TestMessage>(R"json(
|
|
{"string_value": ")json"
|
|
"\n\r\b\f\1\2\3"
|
|
"\"}");
|
|
ASSERT_OK(m);
|
|
EXPECT_EQ(m->string_value(), "\n\r\b\f\1\2\3");
|
|
}
|
|
|
|
TEST_P(JsonTest, Unquoted64) {
|
|
TestMessage m;
|
|
m.add_repeated_int64_value(0);
|
|
m.add_repeated_int64_value(42);
|
|
m.add_repeated_int64_value(-((int64_t{1} << 60) + 1));
|
|
m.add_repeated_int64_value(INT64_MAX);
|
|
// This is a power of two and is therefore representable.
|
|
m.add_repeated_int64_value(INT64_MIN);
|
|
m.add_repeated_uint64_value(0);
|
|
m.add_repeated_uint64_value(42);
|
|
m.add_repeated_uint64_value((uint64_t{1} << 60) + 1);
|
|
// This will be UB without the min/max check in RoundTripsThroughDouble().
|
|
m.add_repeated_uint64_value(UINT64_MAX);
|
|
|
|
PrintOptions opts;
|
|
opts.unquote_int64_if_possible = true;
|
|
EXPECT_THAT(
|
|
ToJson(m, opts),
|
|
R"({"repeatedInt64Value":[0,42,"-1152921504606846977","9223372036854775807",-9223372036854775808],)"
|
|
R"("repeatedUint64Value":[0,42,"1152921504606846977","18446744073709551615"]})");
|
|
}
|
|
|
|
TEST_P(JsonTest, TestAlwaysPrintEnumsAsInts) {
|
|
TestMessage orig;
|
|
orig.set_enum_value(proto3::BAR);
|
|
orig.add_repeated_enum_value(proto3::FOO);
|
|
orig.add_repeated_enum_value(proto3::BAR);
|
|
|
|
PrintOptions print_options;
|
|
print_options.always_print_enums_as_ints = true;
|
|
|
|
auto printed = ToJson(orig, print_options);
|
|
ASSERT_THAT(printed,
|
|
IsOkAndHolds("{\"enumValue\":1,\"repeatedEnumValue\":[0,1]}"));
|
|
|
|
auto parsed = ToProto<TestMessage>(*printed);
|
|
ASSERT_OK(parsed);
|
|
|
|
EXPECT_EQ(parsed->enum_value(), proto3::BAR);
|
|
EXPECT_THAT(parsed->repeated_enum_value(),
|
|
ElementsAre(proto3::FOO, proto3::BAR));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestPrintEnumsAsIntsWithDefaultValue) {
|
|
TestEnumValue orig;
|
|
// orig.set_enum_value1(proto3::FOO)
|
|
orig.set_enum_value2(proto3::FOO);
|
|
orig.set_enum_value3(proto3::BAR);
|
|
|
|
PrintOptions print_options;
|
|
print_options.always_print_enums_as_ints = true;
|
|
print_options.always_print_primitive_fields = true;
|
|
|
|
auto printed = ToJson(orig, print_options);
|
|
ASSERT_THAT(
|
|
printed,
|
|
IsOkAndHolds("{\"enumValue1\":0,\"enumValue2\":0,\"enumValue3\":1}"));
|
|
|
|
auto parsed = ToProto<TestEnumValue>(*printed);
|
|
|
|
EXPECT_EQ(parsed->enum_value1(), proto3::FOO);
|
|
EXPECT_EQ(parsed->enum_value2(), proto3::FOO);
|
|
EXPECT_EQ(parsed->enum_value3(), proto3::BAR);
|
|
}
|
|
|
|
TEST_P(JsonTest, TestPrintProto2EnumAsIntWithDefaultValue) {
|
|
protobuf_unittest::TestDefaultEnumValue orig;
|
|
|
|
PrintOptions print_options;
|
|
print_options.always_print_enums_as_ints = true;
|
|
print_options.always_print_primitive_fields = true;
|
|
|
|
auto printed = ToJson(orig, print_options);
|
|
ASSERT_THAT(printed, IsOkAndHolds("{\"enumValue\":2}"));
|
|
|
|
auto parsed = ToProto<protobuf_unittest::TestDefaultEnumValue>(*printed);
|
|
ASSERT_OK(parsed);
|
|
|
|
EXPECT_EQ(parsed->enum_value(), protobuf_unittest::DEFAULT);
|
|
}
|
|
|
|
TEST_P(JsonTest, QuotedEnumValue) {
|
|
auto m = ToProto<TestEnumValue>(R"json(
|
|
{"enumValue1": "1"}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
EXPECT_THAT(m->enum_value1(), proto3::BAR);
|
|
}
|
|
|
|
TEST_P(JsonTest, WebSafeBytes) {
|
|
auto m = ToProto<TestMessage>(R"json({
|
|
"bytesValue": "-_"
|
|
})json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_EQ(m->bytes_value(), "\xfb");
|
|
}
|
|
|
|
TEST_P(JsonTest, ParseMessage) {
|
|
auto m = ToProto<TestMessage>(R"json(
|
|
{
|
|
"boolValue": true,
|
|
"int32Value": 1234567891,
|
|
"int64Value": -5302428716536692736,
|
|
"uint32Value": 42,
|
|
"uint64Value": 530242871653669,
|
|
"floatValue": 3.4e+38,
|
|
"doubleValue": -55.3,
|
|
"stringValue": "foo bar baz",
|
|
"enumValue": "BAR",
|
|
"messageValue": {
|
|
"value": 2048
|
|
},
|
|
|
|
"repeatedBoolValue": [true],
|
|
"repeatedInt32Value": [0, -42],
|
|
"repeatedUint64Value": [1, 2],
|
|
"repeatedDoubleValue": [1.5, -2],
|
|
"repeatedStringValue": ["foo", "bar ", ""],
|
|
"repeatedEnumValue": [1, "FOO"],
|
|
"repeatedMessageValue": [
|
|
{"value": 40},
|
|
{},
|
|
{"value": 96}
|
|
]
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_TRUE(m->bool_value());
|
|
EXPECT_EQ(m->int32_value(), 1234567891);
|
|
EXPECT_EQ(m->int64_value(), -5302428716536692736);
|
|
EXPECT_EQ(m->uint32_value(), 42);
|
|
EXPECT_EQ(m->uint64_value(), 530242871653669);
|
|
EXPECT_EQ(m->float_value(), 3.4e+38f);
|
|
EXPECT_EQ(m->double_value(),
|
|
-55.3); // This value is intentionally not a nice
|
|
// round number in base 2, so its floating point
|
|
// representation has many digits at the end, which
|
|
// printing back to JSON must handle well.
|
|
EXPECT_EQ(m->string_value(), "foo bar baz");
|
|
EXPECT_EQ(m->enum_value(), proto3::EnumType::BAR);
|
|
EXPECT_EQ(m->message_value().value(), 2048);
|
|
|
|
EXPECT_THAT(m->repeated_bool_value(), ElementsAre(true));
|
|
EXPECT_THAT(m->repeated_int32_value(), ElementsAre(0, -42));
|
|
EXPECT_THAT(m->repeated_uint64_value(), ElementsAre(1, 2));
|
|
EXPECT_THAT(m->repeated_double_value(), ElementsAre(1.5, -2));
|
|
EXPECT_THAT(m->repeated_string_value(), ElementsAre("foo", "bar ", ""));
|
|
EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::BAR, proto3::FOO));
|
|
|
|
ASSERT_THAT(m->repeated_message_value(), SizeIs(3));
|
|
EXPECT_EQ(m->repeated_message_value(0).value(), 40);
|
|
EXPECT_EQ(m->repeated_message_value(1).value(), 0);
|
|
EXPECT_EQ(m->repeated_message_value(2).value(), 96);
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"boolValue":true,"int32Value":1234567891,"int64Value":"-5302428716536692736",)"
|
|
R"("uint32Value":42,"uint64Value":"530242871653669","floatValue":3.4e+38,)"
|
|
R"("doubleValue":-55.3,"stringValue":"foo bar baz","enumValue":"BAR",)"
|
|
R"("messageValue":{"value":2048},"repeatedBoolValue":[true],"repeatedInt32Value":[0,-42])"
|
|
R"(,"repeatedUint64Value":["1","2"],"repeatedDoubleValue":[1.5,-2],)"
|
|
R"("repeatedStringValue":["foo","bar ",""],"repeatedEnumValue":["BAR","FOO"],)"
|
|
R"("repeatedMessageValue":[{"value":40},{},{"value":96}]})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, CurseOfAtob) {
|
|
auto m = ToProto<TestMessage>(R"json(
|
|
{
|
|
repeatedBoolValue: ["0", "1", "false", "true", "f", "t", "no", "yes", "n", "y"]
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
EXPECT_THAT(m->repeated_bool_value(),
|
|
ElementsAre(false, true, false, true, false, true, false, true,
|
|
false, true));
|
|
}
|
|
|
|
TEST_P(JsonTest, FloatPrecision) {
|
|
google::protobuf::Value v;
|
|
v.mutable_list_value()->add_values()->set_number_value(0.9900000095367432);
|
|
v.mutable_list_value()->add_values()->set_number_value(0.8799999952316284);
|
|
|
|
EXPECT_THAT(ToJson(v),
|
|
IsOkAndHolds("[0.99000000953674316,0.87999999523162842]"));
|
|
}
|
|
|
|
TEST_P(JsonTest, FloatMinMaxValue) {
|
|
// 3.4028235e38 is FLT_MAX to 8-significant-digits. The final digit (5)
|
|
// is rounded up; that means that when parsing this as a 64-bit FP number,
|
|
// the value ends up higher than FLT_MAX. We still want to accept it though,
|
|
// as a reasonable representation of FLT_MAX.
|
|
auto m = ToProto<TestMessage>(R"json(
|
|
{
|
|
"repeatedFloatValue": [3.4028235e38, -3.4028235e38],
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
EXPECT_THAT(m->repeated_float_value(), ElementsAre(FLT_MAX, -FLT_MAX));
|
|
}
|
|
|
|
TEST_P(JsonTest, FloatOutOfRange) {
|
|
// Check that the slightly-lenient parsing demonstrated in FloatMinMaxValue
|
|
// doesn't mean we allow all values. The value being parsed differs only
|
|
// in the least significant (represented) digit.
|
|
auto m = ToProto<TestMessage>(R"json(
|
|
{
|
|
"floatValue": 3.4028236e38
|
|
}
|
|
)json");
|
|
EXPECT_THAT(m, StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
TEST_P(JsonTest, ParseLegacySingleRepeatedField) {
|
|
auto m = ToProto<TestMessage>(R"json({
|
|
"repeatedInt32Value": 1997,
|
|
"repeatedStringValue": "oh no",
|
|
"repeatedEnumValue": "BAR",
|
|
"repeatedMessageValue": {"value": -1}
|
|
})json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_THAT(m->repeated_int32_value(), ElementsAre(1997));
|
|
EXPECT_THAT(m->repeated_string_value(), ElementsAre("oh no"));
|
|
EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::EnumType::BAR));
|
|
|
|
ASSERT_THAT(m->repeated_message_value(), SizeIs(1));
|
|
EXPECT_EQ(m->repeated_message_value(0).value(), -1);
|
|
|
|
EXPECT_THAT(ToJson(*m),
|
|
IsOkAndHolds(R"({"repeatedInt32Value":[1997],)"
|
|
R"("repeatedStringValue":["oh no"],)"
|
|
R"("repeatedEnumValue":["BAR"],)"
|
|
R"("repeatedMessageValue":[{"value":-1}]})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, ParseMap) {
|
|
TestMap message;
|
|
(*message.mutable_string_map())["hello"] = 1234;
|
|
auto printed = ToJson(message);
|
|
ASSERT_THAT(printed, IsOkAndHolds(R"({"stringMap":{"hello":1234}})"));
|
|
|
|
auto other = ToProto<TestMap>(*printed);
|
|
ASSERT_OK(other);
|
|
EXPECT_EQ(other->DebugString(), message.DebugString());
|
|
}
|
|
|
|
TEST_P(JsonTest, RepeatedMapKey) {
|
|
EXPECT_THAT(ToProto<TestMap>(R"json({
|
|
"string_map": {
|
|
"twiceKey": 0,
|
|
"twiceKey": 1
|
|
}
|
|
})json"),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
TEST_P(JsonTest, ParsePrimitiveMapIn) {
|
|
MapIn message;
|
|
PrintOptions print_options;
|
|
print_options.always_print_primitive_fields = true;
|
|
auto printed = ToJson(message, print_options);
|
|
ASSERT_THAT(
|
|
ToJson(message, print_options),
|
|
IsOkAndHolds(R"({"other":"","things":[],"mapInput":{},"mapAny":{}})"));
|
|
|
|
auto other = ToProto<MapIn>(*printed);
|
|
ASSERT_OK(other);
|
|
EXPECT_EQ(other->DebugString(), message.DebugString());
|
|
}
|
|
|
|
TEST_P(JsonTest, PrintPrimitiveOneof) {
|
|
TestOneof message;
|
|
PrintOptions options;
|
|
options.always_print_primitive_fields = true;
|
|
message.mutable_oneof_message_value();
|
|
EXPECT_THAT(ToJson(message, options),
|
|
IsOkAndHolds(R"({"oneofMessageValue":{"value":0}})"));
|
|
|
|
message.set_oneof_int32_value(1);
|
|
EXPECT_THAT(ToJson(message, options),
|
|
IsOkAndHolds(R"({"oneofInt32Value":1})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, ParseOverOneof) {
|
|
TestOneof m;
|
|
m.set_oneof_string_value("foo");
|
|
ASSERT_OK(ToProto(m, R"json({
|
|
"oneofInt32Value": 5,
|
|
})json"));
|
|
EXPECT_EQ(m.oneof_int32_value(), 5);
|
|
}
|
|
|
|
TEST_P(JsonTest, RepeatedSingularKeys) {
|
|
auto m = ToProto<TestMessage>(R"json({
|
|
"int32Value": 1,
|
|
"int32Value": 2
|
|
})json");
|
|
EXPECT_OK(m);
|
|
EXPECT_EQ(m->int32_value(), 2);
|
|
}
|
|
|
|
TEST_P(JsonTest, RepeatedRepeatedKeys) {
|
|
auto m = ToProto<TestMessage>(R"json({
|
|
"repeatedInt32Value": [1],
|
|
"repeatedInt32Value": [2, 3]
|
|
})json");
|
|
EXPECT_OK(m);
|
|
EXPECT_THAT(m->repeated_int32_value(), ElementsAre(1, 2, 3));
|
|
}
|
|
|
|
TEST_P(JsonTest, RepeatedOneofKeys) {
|
|
EXPECT_THAT(ToProto<TestOneof>(R"json({
|
|
"oneofInt32Value": 1,
|
|
"oneofStringValue": "foo"
|
|
})json"),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParseIgnoreUnknownFields) {
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
EXPECT_OK(ToProto<TestMessage>(R"({"unknownName":0})", options));
|
|
|
|
TestMessage m;
|
|
m.GetReflection()->MutableUnknownFields(&m)->AddFixed32(9001, 9001);
|
|
m.GetReflection()->MutableUnknownFields(&m)->AddFixed64(9001, 9001);
|
|
m.GetReflection()->MutableUnknownFields(&m)->AddVarint(9001, 9001);
|
|
m.GetReflection()->MutableUnknownFields(&m)->AddLengthDelimited(9001, "9001");
|
|
EXPECT_THAT(ToJson(m), IsOkAndHolds("{}"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParseErrors) {
|
|
// Parsing should fail if the field name can not be recognized.
|
|
EXPECT_THAT(ToProto<TestMessage>(R"({"unknownName": 0})"),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
// Parsing should fail if the value is invalid.
|
|
EXPECT_THAT(ToProto<TestMessage>(R"("{"int32Value": 2147483648})"),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestDynamicMessage) {
|
|
// 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());
|
|
ASSERT_OK(ToProto(*message, R"json(
|
|
{
|
|
"int32Value": 1024,
|
|
"repeatedInt32Value": [1, 2],
|
|
"messageValue": {
|
|
"value": 2048
|
|
},
|
|
"repeatedMessageValue": [
|
|
{"value": 40},
|
|
{"value": 96}
|
|
]
|
|
}
|
|
)json"));
|
|
|
|
// Convert to generated message for easy inspection.
|
|
TestMessage generated;
|
|
EXPECT_TRUE(generated.ParseFromString(message->SerializeAsString()));
|
|
|
|
EXPECT_EQ(generated.int32_value(), 1024);
|
|
EXPECT_THAT(generated.repeated_int32_value(), ElementsAre(1, 2));
|
|
|
|
EXPECT_EQ(generated.message_value().value(), 2048);
|
|
ASSERT_EQ(generated.repeated_message_value_size(), 2);
|
|
EXPECT_EQ(generated.repeated_message_value(0).value(), 40);
|
|
EXPECT_EQ(generated.repeated_message_value(1).value(), 96);
|
|
|
|
auto message_json = ToJson(*message);
|
|
ASSERT_OK(message_json);
|
|
auto generated_json = ToJson(generated);
|
|
ASSERT_OK(generated_json);
|
|
EXPECT_EQ(*message_json, *generated_json);
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingAny) {
|
|
auto m = ToProto<TestAny>(R"json(
|
|
{
|
|
"value": {
|
|
"@type": "type.googleapis.com/proto3.TestMessage",
|
|
"int32_value": 5,
|
|
"string_value": "expected_value",
|
|
"message_value": {"value": 1}
|
|
}
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
TestMessage t;
|
|
ASSERT_TRUE(m->value().UnpackTo(&t));
|
|
EXPECT_EQ(t.int32_value(), 5);
|
|
EXPECT_EQ(t.string_value(), "expected_value");
|
|
EXPECT_EQ(t.message_value().value(), 1);
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"value":{"@type":"type.googleapis.com/proto3.TestMessage",)"
|
|
R"("int32Value":5,"stringValue":"expected_value","messageValue":{"value":1}}})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingAnyWithRequiredFields) {
|
|
auto m = ToProto<TestAny>(R"json(
|
|
{
|
|
"value": {
|
|
"@type": "type.googleapis.com/protobuf_unittest.TestRequired",
|
|
"a": 5,
|
|
"dummy2": 6,
|
|
"b": 7
|
|
}
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
protobuf_unittest::TestRequired t;
|
|
// Can't use UnpackTo directly, since that checks IsInitialized.
|
|
ASSERT_FALSE(m->value().UnpackTo(&t));
|
|
|
|
t.Clear();
|
|
EXPECT_TRUE(t.ParsePartialFromString(m->value().value()));
|
|
EXPECT_EQ(t.a(), 5);
|
|
EXPECT_EQ(t.dummy2(), 6);
|
|
EXPECT_EQ(t.b(), 7);
|
|
EXPECT_FALSE(t.has_c());
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"value":{"@type":"type.googleapis.com/protobuf_unittest.TestRequired",)"
|
|
R"("a":5,"dummy2":6,"b":7}})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingAnyEndAtType) {
|
|
auto m = ToProto<TestAny>(R"json(
|
|
{
|
|
"value": {
|
|
"int32_value": 5,
|
|
"string_value": "expected_value",
|
|
"message_value": {"value": 1},
|
|
"@type": "type.googleapis.com/proto3.TestMessage"
|
|
}
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
TestMessage t;
|
|
ASSERT_TRUE(m->value().UnpackTo(&t));
|
|
EXPECT_EQ(t.int32_value(), 5);
|
|
EXPECT_EQ(t.string_value(), "expected_value");
|
|
EXPECT_EQ(t.message_value().value(), 1);
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingNestedAnys) {
|
|
auto m = ToProto<TestAny>(R"json(
|
|
{
|
|
"value": {
|
|
"value": {
|
|
"int32_value": 5,
|
|
"string_value": "expected_value",
|
|
"message_value": {"value": 1},
|
|
"@type": "type.googleapis.com/proto3.TestMessage"
|
|
},
|
|
"@type": "type.googleapis.com/google.protobuf.Any"
|
|
}
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
google::protobuf::Any inner;
|
|
ASSERT_TRUE(m->value().UnpackTo(&inner));
|
|
|
|
TestMessage t;
|
|
ASSERT_TRUE(inner.UnpackTo(&t));
|
|
EXPECT_EQ(t.int32_value(), 5);
|
|
EXPECT_EQ(t.string_value(), "expected_value");
|
|
EXPECT_EQ(t.message_value().value(), 1);
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"value":{"@type":"type.googleapis.com/google.protobuf.Any",)"
|
|
R"("value":{"@type":"type.googleapis.com/proto3.TestMessage",)"
|
|
R"("int32Value":5,"stringValue":"expected_value","messageValue":{"value":1}}}})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingBrokenAny) {
|
|
auto m = ToProto<TestAny>(R"json(
|
|
{
|
|
"value": {}
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
EXPECT_EQ(m->value().type_url(), "");
|
|
EXPECT_EQ(m->value().value(), "");
|
|
|
|
EXPECT_THAT(ToProto<TestAny>(R"json(
|
|
{
|
|
"value": {
|
|
"type_url": "garbage",
|
|
"value": "bW9yZSBnYXJiYWdl"
|
|
}
|
|
}
|
|
)json"),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
|
|
TestAny m2;
|
|
m2.mutable_value();
|
|
EXPECT_THAT(ToJson(m2), IsOkAndHolds(R"({"value":{}})"));
|
|
m2.mutable_value()->set_value("garbage");
|
|
// The ESF parser does not return InvalidArgument for this error.
|
|
EXPECT_THAT(ToJson(m2), Not(StatusIs(absl::StatusCode::kOk)));
|
|
|
|
m2.Clear();
|
|
m2.mutable_value()->set_type_url("type.googleapis.com/proto3.TestMessage");
|
|
EXPECT_THAT(
|
|
ToJson(m2),
|
|
IsOkAndHolds(
|
|
R"({"value":{"@type":"type.googleapis.com/proto3.TestMessage"}})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestFlatList) {
|
|
auto m = ToProto<TestMessage>(R"json(
|
|
{
|
|
"repeatedInt32Value": [[[5]], [6]]
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
EXPECT_THAT(m->repeated_int32_value(), ElementsAre(5, 6));
|
|
|
|
// The above flatteing behavior is suppressed for google::protobuf::ListValue.
|
|
auto m2 = ToProto<google::protobuf::Value>(R"json(
|
|
{
|
|
"repeatedInt32Value": [[[5]], [6]]
|
|
}
|
|
)json");
|
|
ASSERT_OK(m2);
|
|
auto fields = m2->struct_value().fields();
|
|
auto list = fields["repeatedInt32Value"].list_value();
|
|
EXPECT_EQ(list.values(0)
|
|
.list_value()
|
|
.values(0)
|
|
.list_value()
|
|
.values(0)
|
|
.number_value(),
|
|
5);
|
|
EXPECT_EQ(list.values(1).list_value().values(0).number_value(), 6);
|
|
}
|
|
|
|
TEST_P(JsonTest, ParseWrappers) {
|
|
auto m = ToProto<TestWrapper>(R"json(
|
|
{
|
|
"boolValue": true,
|
|
"int32Value": 42,
|
|
"stringValue": "ieieo",
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_TRUE(m->bool_value().value());
|
|
EXPECT_EQ(m->int32_value().value(), 42);
|
|
EXPECT_EQ(m->string_value().value(), "ieieo");
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"boolValue":true,"int32Value":42,"stringValue":"ieieo"})"));
|
|
|
|
auto m2 = ToProto<TestWrapper>(R"json(
|
|
{
|
|
"boolValue": { "value": true },
|
|
"int32Value": { "value": 42 },
|
|
"stringValue": { "value": "ieieo" },
|
|
}
|
|
)json");
|
|
ASSERT_OK(m2);
|
|
|
|
EXPECT_TRUE(m2->bool_value().value());
|
|
EXPECT_EQ(m2->int32_value().value(), 42);
|
|
EXPECT_EQ(m2->string_value().value(), "ieieo");
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingUnknownAnyFields) {
|
|
absl::string_view input = R"json(
|
|
{
|
|
"value": {
|
|
"@type": "type.googleapis.com/proto3.TestMessage",
|
|
"unknown_field": "UNKNOWN_VALUE",
|
|
"string_value": "expected_value"
|
|
}
|
|
}
|
|
)json";
|
|
|
|
EXPECT_THAT(ToProto<TestAny>(input),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
auto m = ToProto<TestAny>(input, options);
|
|
ASSERT_OK(m);
|
|
|
|
TestMessage t;
|
|
ASSERT_TRUE(m->value().UnpackTo(&t));
|
|
EXPECT_EQ(t.string_value(), "expected_value");
|
|
}
|
|
|
|
TEST_P(JsonTest, TestHugeBareString) {
|
|
auto m = ToProto<TestMessage>(R"json({
|
|
"int64Value": 6009652459062546621
|
|
})json");
|
|
ASSERT_OK(m);
|
|
EXPECT_EQ(m->int64_value(), 6009652459062546621);
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingUnknownEnumsProto2) {
|
|
absl::string_view input = R"json({"ayuLmao": "UNKNOWN_VALUE"})json";
|
|
|
|
EXPECT_THAT(ToProto<protobuf_unittest::TestNumbers>(input),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
auto m = ToProto<protobuf_unittest::TestNumbers>(input, options);
|
|
ASSERT_OK(m);
|
|
EXPECT_FALSE(m->has_a());
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingUnknownEnumsProto3) {
|
|
TestMessage m;
|
|
absl::string_view input = R"json({"enum_value":"UNKNOWN_VALUE"})json";
|
|
|
|
m.set_enum_value(proto3::BAR);
|
|
ASSERT_THAT(ToProto(m, input), StatusIs(absl::StatusCode::kInvalidArgument));
|
|
EXPECT_EQ(m.enum_value(), proto3::BAR); // Keep previous value
|
|
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
ASSERT_OK(ToProto(m, input, options));
|
|
EXPECT_EQ(m.enum_value(), 0); // Unknown enum value must be decoded as 0
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingUnknownEnumsProto3FromInt) {
|
|
TestMessage m;
|
|
absl::string_view input = R"json({"enum_value":12345})json";
|
|
|
|
m.set_enum_value(proto3::BAR);
|
|
ASSERT_OK(ToProto(m, input));
|
|
EXPECT_EQ(m.enum_value(), 12345);
|
|
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
ASSERT_OK(ToProto(m, input, options));
|
|
EXPECT_EQ(m.enum_value(), 12345);
|
|
}
|
|
|
|
// Trying to pass an object as an enum field value is always treated as an
|
|
// error
|
|
TEST_P(JsonTest, TestParsingUnknownEnumsProto3FromObject) {
|
|
absl::string_view input = R"json({"enum_value": {}})json";
|
|
|
|
EXPECT_THAT(ToProto<TestMessage>(input),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
EXPECT_THAT(ToProto<TestMessage>(input, options),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingUnknownEnumsProto3FromArray) {
|
|
absl::string_view input = R"json({"enum_value": []})json";
|
|
|
|
EXPECT_THAT(ToProto<TestMessage>(input),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
EXPECT_THAT(ToProto<TestMessage>(input, options),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingRepeatedUnknownEnums) {
|
|
absl::string_view input = R"json({
|
|
"repeated_enum_value": ["FOO", "BAZ", "BAR"]
|
|
})json";
|
|
|
|
EXPECT_THAT(ToProto<TestMessage>(input),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
|
|
ParseOptions options;
|
|
options.ignore_unknown_fields = true;
|
|
auto m = ToProto<TestMessage>(input, options);
|
|
ASSERT_OK(m);
|
|
EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::FOO, proto3::BAR));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingEnumCaseSensitive) {
|
|
TestMessage m;
|
|
m.set_enum_value(proto3::FOO);
|
|
EXPECT_THAT(ToProto(m, R"json({"enum_value": "bar"})json"),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
// Default behavior is case-sensitive, so keep previous value.
|
|
EXPECT_EQ(m.enum_value(), proto3::FOO);
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingEnumLowercase) {
|
|
ParseOptions options;
|
|
options.case_insensitive_enum_parsing = true;
|
|
auto m =
|
|
ToProto<TestMessage>(R"json({"enum_value": "TLSv1_2"})json", options);
|
|
ASSERT_OK(m);
|
|
EXPECT_THAT(m->enum_value(), proto3::TLSv1_2);
|
|
}
|
|
|
|
TEST_P(JsonTest, TestParsingEnumIgnoreCase) {
|
|
TestMessage m;
|
|
m.set_enum_value(proto3::FOO);
|
|
|
|
ParseOptions options;
|
|
options.case_insensitive_enum_parsing = true;
|
|
ASSERT_OK(ToProto(m, R"json({"enum_value":"bar"})json", options));
|
|
EXPECT_EQ(m.enum_value(), proto3::BAR);
|
|
}
|
|
|
|
TEST_P(JsonTest, Extensions) {
|
|
if (GetParam() == Codec::kResolver) {
|
|
GTEST_SKIP();
|
|
}
|
|
|
|
auto m = ToProto<protobuf_unittest::TestMixedFieldsAndExtensions>(R"json({
|
|
"[protobuf_unittest.TestMixedFieldsAndExtensions.c]": 42,
|
|
"a": 5,
|
|
"b": [1, 2, 3],
|
|
"[protobuf_unittest.TestMixedFieldsAndExtensions.d]": [1, 1, 2, 3, 5, 8, 13]
|
|
})json");
|
|
ASSERT_OK(m);
|
|
EXPECT_EQ(m->a(), 5);
|
|
EXPECT_THAT(m->b(), ElementsAre(1, 2, 3));
|
|
EXPECT_EQ(m->GetExtension(protobuf_unittest::TestMixedFieldsAndExtensions::c),
|
|
42);
|
|
EXPECT_THAT(
|
|
m->GetRepeatedExtension(protobuf_unittest::TestMixedFieldsAndExtensions::d),
|
|
ElementsAre(1, 1, 2, 3, 5, 8, 13));
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"a":5,)"
|
|
R"("[protobuf_unittest.TestMixedFieldsAndExtensions.c]":42,)"
|
|
R"("b":[1,2,3],)"
|
|
R"("[protobuf_unittest.TestMixedFieldsAndExtensions.d]":[1,1,2,3,5,8,13]})"));
|
|
|
|
auto m2 = ToProto<protobuf_unittest::TestAllTypes>(R"json({
|
|
"[this.extension.does.not.exist]": 42
|
|
})json");
|
|
EXPECT_THAT(m2, StatusIs(absl::StatusCode::kInvalidArgument));
|
|
|
|
auto m3 = ToProto<protobuf_unittest::TestAllTypes>(R"json({
|
|
"[protobuf_unittest.TestMixedFieldsAndExtensions.c]": 42
|
|
})json");
|
|
EXPECT_THAT(m3, StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
// Parsing does NOT work like MergeFrom: existing repeated field values are
|
|
// clobbered, not appended to.
|
|
TEST_P(JsonTest, TestOverwriteRepeated) {
|
|
TestMessage m;
|
|
m.add_repeated_int32_value(5);
|
|
|
|
ASSERT_OK(ToProto(m, R"json({"repeated_int32_value": [1, 2, 3]})json"));
|
|
EXPECT_THAT(m.repeated_int32_value(), ElementsAre(1, 2, 3));
|
|
}
|
|
|
|
|
|
TEST_P(JsonTest, TestDuration) {
|
|
auto m = ToProto<proto3::TestDuration>(R"json(
|
|
{
|
|
"value": "123456.789s",
|
|
"repeated_value": ["0.1s", "999s"]
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_EQ(m->value().seconds(), 123456);
|
|
EXPECT_EQ(m->value().nanos(), 789000000);
|
|
|
|
EXPECT_THAT(m->repeated_value(), SizeIs(2));
|
|
EXPECT_EQ(m->repeated_value(0).seconds(), 0);
|
|
EXPECT_EQ(m->repeated_value(0).nanos(), 100000000);
|
|
EXPECT_EQ(m->repeated_value(1).seconds(), 999);
|
|
EXPECT_EQ(m->repeated_value(1).nanos(), 0);
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"value":"123456.789s","repeatedValue":["0.100s","999s"]})"));
|
|
|
|
auto m2 = ToProto<proto3::TestDuration>(R"json(
|
|
{
|
|
"value": {"seconds": 4, "nanos": 5},
|
|
}
|
|
)json");
|
|
ASSERT_OK(m2);
|
|
|
|
EXPECT_EQ(m2->value().seconds(), 4);
|
|
EXPECT_EQ(m2->value().nanos(), 5);
|
|
|
|
// Negative duration with zero seconds.
|
|
auto m3 = ToProto<proto3::TestDuration>(R"json(
|
|
{
|
|
"value": {"nanos": -5},
|
|
}
|
|
)json");
|
|
ASSERT_OK(m3);
|
|
EXPECT_EQ(m3->value().seconds(), 0);
|
|
EXPECT_EQ(m3->value().nanos(), -5);
|
|
EXPECT_THAT(ToJson(m3->value()), IsOkAndHolds("\"-0.000000005s\""));
|
|
|
|
// Negative duration with zero nanos.
|
|
auto m4 = ToProto<proto3::TestDuration>(R"json(
|
|
{
|
|
"value": {"seconds": -5},
|
|
}
|
|
)json");
|
|
ASSERT_OK(m4);
|
|
EXPECT_EQ(m4->value().seconds(), -5);
|
|
EXPECT_EQ(m4->value().nanos(), 0);
|
|
EXPECT_THAT(ToJson(m4->value()), IsOkAndHolds("\"-5s\""));
|
|
|
|
// Parse "0.5s" as a JSON string.
|
|
auto m5 = ToProto<proto3::TestDuration>(R"json(
|
|
{
|
|
"value": "0.5s",
|
|
}
|
|
)json");
|
|
ASSERT_OK(m5);
|
|
EXPECT_EQ(m5->value().seconds(), 0);
|
|
EXPECT_EQ(m5->value().nanos(), 500000000);
|
|
EXPECT_THAT(ToJson(m5->value()), IsOkAndHolds("\"0.500s\""));
|
|
}
|
|
|
|
// These tests are not exhaustive; tests in //google/protobuf/conformance
|
|
// are more comprehensive.
|
|
TEST_P(JsonTest, TestTimestamp) {
|
|
auto m = ToProto<proto3::TestTimestamp>(R"json(
|
|
{
|
|
"value": "1996-02-27T12:00:00Z",
|
|
"repeated_value": ["9999-12-31T23:59:59Z"]
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_EQ(m->value().seconds(), 825422400);
|
|
EXPECT_EQ(m->value().nanos(), 0);
|
|
EXPECT_THAT(m->repeated_value(), SizeIs(1));
|
|
EXPECT_EQ(m->repeated_value(0).seconds(), 253402300799);
|
|
EXPECT_EQ(m->repeated_value(0).nanos(), 0);
|
|
|
|
EXPECT_THAT(
|
|
ToJson(*m),
|
|
IsOkAndHolds(
|
|
R"({"value":"1996-02-27T12:00:00Z","repeatedValue":["9999-12-31T23:59:59Z"]})"));
|
|
|
|
auto m2 = ToProto<proto3::TestTimestamp>(R"json(
|
|
{
|
|
"value": {"seconds": 4, "nanos": 5},
|
|
}
|
|
)json");
|
|
ASSERT_OK(m2);
|
|
|
|
EXPECT_EQ(m2->value().seconds(), 4);
|
|
EXPECT_EQ(m2->value().nanos(), 5);
|
|
}
|
|
|
|
// This test case comes from Envoy's tests. They like to parse a Value out of
|
|
// YAML, turn it into JSON, and then parse it as a different proto. This means
|
|
// we must be extremely careful with integer fields, because they need to
|
|
// round-trip through doubles. This happens all over Envoy. :(
|
|
TEST_P(JsonTest, TestEnvoyRoundTrip) {
|
|
auto m = ToProto<google::protobuf::Value>(R"json(
|
|
{
|
|
"value": {"seconds": 1234567891, "nanos": 234000000},
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
auto j = ToJson(*m);
|
|
ASSERT_OK(j);
|
|
|
|
auto m2 = ToProto<proto3::TestTimestamp>(*j);
|
|
ASSERT_OK(m2);
|
|
|
|
EXPECT_EQ(m2->value().seconds(), 1234567891);
|
|
EXPECT_EQ(m2->value().nanos(), 234000000);
|
|
}
|
|
|
|
TEST_P(JsonTest, TestFieldMask) {
|
|
auto m = ToProto<proto3::TestFieldMask>(R"json(
|
|
{
|
|
"value": "foo,bar.bazBaz"
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_THAT(m->value().paths(), ElementsAre("foo", "bar.baz_baz"));
|
|
EXPECT_THAT(ToJson(*m), IsOkAndHolds(R"({"value":"foo,bar.bazBaz"})"));
|
|
|
|
auto m2 = ToProto<proto3::TestFieldMask>(R"json(
|
|
{
|
|
"value": {
|
|
"paths": ["yep.really"]
|
|
},
|
|
}
|
|
)json");
|
|
ASSERT_OK(m2);
|
|
|
|
EXPECT_THAT(m2->value().paths(), ElementsAre("yep.really"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestFieldMaskSnakeCase) {
|
|
auto m = ToProto<proto3::TestFieldMask>(R"json(
|
|
{
|
|
"value": "foo_bar"
|
|
}
|
|
)json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_THAT(m->value().paths(), ElementsAre("foo_bar"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TestLegalNullsInArray) {
|
|
auto m = ToProto<proto3::TestNullValue>(R"json({
|
|
"repeatedNullValue": [null]
|
|
})json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_THAT(m->repeated_null_value(),
|
|
ElementsAre(google::protobuf::NULL_VALUE));
|
|
|
|
auto m2 = ToProto<proto3::TestValue>(R"json({
|
|
"repeatedValue": [null]
|
|
})json");
|
|
ASSERT_OK(m2);
|
|
|
|
ASSERT_THAT(m2->repeated_value(), SizeIs(1));
|
|
EXPECT_TRUE(m2->repeated_value(0).has_null_value());
|
|
|
|
m2->Clear();
|
|
m2->mutable_value(); // Materialize an empty singular Value.
|
|
m2->add_repeated_value();
|
|
m2->add_repeated_value()->set_string_value("solitude");
|
|
m2->add_repeated_value();
|
|
EXPECT_THAT(ToJson(*m2), IsOkAndHolds(R"({"repeatedValue":["solitude"]})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, EmptyValue) {
|
|
EXPECT_THAT(ToJson(google::protobuf::Value()), IsOkAndHolds(""));
|
|
|
|
google::protobuf::Struct s;
|
|
s.mutable_fields()->emplace("empty", google::protobuf::Value());
|
|
EXPECT_THAT(ToJson(s), IsOkAndHolds("{}"));
|
|
}
|
|
|
|
TEST_P(JsonTest, TrailingGarbage) {
|
|
EXPECT_THAT(ToProto<TestMessage>("{}garbage"),
|
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
|
}
|
|
|
|
TEST_P(JsonTest, ListList) {
|
|
auto m = ToProto<proto3::TestListValue>(R"json({
|
|
"repeated_value": [["ayy", "lmao"]]
|
|
})json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy");
|
|
EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao");
|
|
|
|
m = ToProto<proto3::TestListValue>(R"json({
|
|
"repeated_value": [{
|
|
"values": ["ayy", "lmao"]
|
|
}]
|
|
})json");
|
|
ASSERT_OK(m);
|
|
|
|
EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy");
|
|
EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao");
|
|
}
|
|
|
|
TEST_P(JsonTest, HtmlEscape) {
|
|
TestMessage m;
|
|
m.set_string_value("</script>");
|
|
EXPECT_THAT(ToJson(m),
|
|
IsOkAndHolds(R"({"stringValue":"\u003c/script\u003e"})"));
|
|
|
|
proto3::TestEvilJson m2;
|
|
PrintOptions opts;
|
|
opts.always_print_primitive_fields = true;
|
|
EXPECT_THAT(
|
|
ToJson(m2, opts),
|
|
IsOkAndHolds(
|
|
R"({"regular_name":0,"\u003c/script\u003e":0,)"
|
|
R"("unbalanced\"quotes":0,)"
|
|
R"("\"\u003cscript\u003ealert('hello!);\u003c/script\u003e":0})"));
|
|
}
|
|
|
|
TEST_P(JsonTest, FieldOrder) {
|
|
// $ protoscope -s <<< "3: 3 22: 2 1: 1 22: 2"
|
|
std::string out;
|
|
absl::Status s = BinaryToJsonString(
|
|
resolver_.get(), "type.googleapis.com/proto3.TestMessage",
|
|
"\x18\x03\xb0\x01\x02\x08\x01\xb0\x01\x02", &out);
|
|
ASSERT_OK(s);
|
|
EXPECT_EQ(
|
|
out, R"({"boolValue":true,"int64Value":"3","repeatedInt32Value":[2,2]})");
|
|
}
|
|
|
|
// JSON values get special treatment when it comes to pre-existing values in
|
|
// their repeated fields, when parsing through their dedicated syntax.
|
|
TEST_P(JsonTest, ClearPreExistingRepeatedInJsonValues) {
|
|
google::protobuf::ListValue l;
|
|
l.add_values()->set_string_value("hello");
|
|
ASSERT_OK(JsonStringToMessage("[]", &l));
|
|
EXPECT_THAT(l.values(), IsEmpty());
|
|
|
|
google::protobuf::Struct s;
|
|
(*s.mutable_fields())["hello"].set_string_value("world");
|
|
ASSERT_OK(JsonStringToMessage("{}", &s));
|
|
EXPECT_THAT(s.fields(), IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace json
|
|
} // namespace protobuf
|
|
} // namespace google
|