blob: 12a8c6f77bc050ee48e75ae668e2177144a81fce [file] [log] [blame]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#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/test_zero_copy_stream.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::ContainsRegex;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::SizeIs;
// TODO: 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, TestAlwaysPrintFieldsWithNoPresence) {
TestMessage m;
EXPECT_THAT(ToJson(m), IsOkAndHolds("{}"));
PrintOptions options;
options.always_print_fields_with_no_presence = true;
EXPECT_THAT(ToJson(m, options), IsOkAndHolds(R"({"boolValue":false,)"
R"("int32Value":0,)"
R"("int64Value":"0",)"
R"("uint32Value":0,)"
R"("uint64Value":"0",)"
R"("floatValue":0,)"
R"("doubleValue":0,)"
R"("stringValue":"",)"
R"("bytesValue":"",)"
R"("enumValue":"FOO",)"
R"("repeatedBoolValue":[],)"
R"("repeatedInt32Value":[],)"
R"("repeatedInt64Value":[],)"
R"("repeatedUint32Value":[],)"
R"("repeatedUint64Value":[],)"
R"("repeatedFloatValue":[],)"
R"("repeatedDoubleValue":[],)"
R"("repeatedStringValue":[],)"
R"("repeatedBytesValue":[],)"
R"("repeatedEnumValue":[],)"
R"("repeatedMessageValue":[])"
"}"));
m.set_string_value("i am a test string value");
m.set_bytes_value("i am a test bytes value");
m.set_optional_bool_value(false);
m.set_optional_string_value("");
m.set_optional_bytes_value("");
EXPECT_THAT(ToJson(m, options),
IsOkAndHolds(R"({"boolValue":false,)"
R"("int32Value":0,)"
R"("int64Value":"0",)"
R"("uint32Value":0,)"
R"("uint64Value":"0",)"
R"("floatValue":0,)"
R"("doubleValue":0,)"
R"("stringValue":"i am a test string value",)"
R"("bytesValue":"aSBhbSBhIHRlc3QgYnl0ZXMgdmFsdWU=",)"
R"("enumValue":"FOO",)"
R"("repeatedBoolValue":[],)"
R"("repeatedInt32Value":[],)"
R"("repeatedInt64Value":[],)"
R"("repeatedUint32Value":[],)"
R"("repeatedUint64Value":[],)"
R"("repeatedFloatValue":[],)"
R"("repeatedDoubleValue":[],)"
R"("repeatedStringValue":[],)"
R"("repeatedBytesValue":[],)"
R"("repeatedEnumValue":[],)"
R"("repeatedMessageValue":[],)"
R"("optionalBoolValue":false,)"
R"("optionalStringValue":"",)"
R"("optionalBytesValue":"")"
"}"));
EXPECT_THAT(
ToJson(protobuf_unittest::TestAllTypes(), options),
IsOkAndHolds(
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":[]})"));
}
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_fields_with_no_presence = 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, 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_fields_with_no_presence = 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_fields_with_no_presence = 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"})"));
}
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]})");
}
TEST_P(JsonTest, UnknownGroupField) {
// $ protoscope -s <<< "999: !{1: 99}"
std::string out;
absl::Status s = BinaryToJsonString(resolver_.get(),
"type.googleapis.com/proto3.TestMessage",
"\273>\010c\274>", &out);
ASSERT_OK(s);
EXPECT_EQ(out, "{}");
}
// 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());
}
TEST(JsonErrorTest, FieldNameAndSyntaxErrorInSeparateChunks) {
std::unique_ptr<TypeResolver> resolver{
google::protobuf::util::NewTypeResolverForDescriptorPool(
"type.googleapis.com", DescriptorPool::generated_pool())};
io::internal::TestZeroCopyInputStream input_stream(
{"{\"bool_value\":", "5}"});
std::string result;
io::StringOutputStream output_stream(&result);
absl::Status s = JsonToBinaryStream(
resolver.get(), "type.googleapis.com/proto3.TestMessage", &input_stream,
&output_stream, ParseOptions{});
ASSERT_FALSE(s.ok());
EXPECT_THAT(
s.message(),
ContainsRegex("invalid *JSON *in *type.googleapis.com/proto3.TestMessage "
"*@ *bool_value"));
}
} // namespace
} // namespace json
} // namespace protobuf
} // namespace google
#include "google/protobuf/port_undef.inc"