blob: 68616af9cfcbaacb27e832e4d212fcf7ef647cc1 [file] [edit]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. 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
// JSON conformance tests for google.protobuf.Any fields: the "@type" member
// (in any position, or nested in another Any), the well-known types whose
// JSON form is a "value" member, the type URLs that must be rejected, an Any
// without a type, `null`, and an empty packed message. This replaces the
// legacy BinaryAndJsonConformanceSuiteImpl<M>::RunJsonTestsForAny(), which ran
// for the proto3-style message types only; the test names and the requests
// sent to the testee are identical to the legacy ones.
//
// Like the legacy RunValidJsonTest(), each valid input is sent twice: once to
// be serialized as binary ("<name>.ProtobufOutput") and once as JSON
// ("<name>.JsonOutput"). ExpectParseFailureForJson() asked for JSON output
// under a name without an output suffix. The inputs are the legacy raw
// string literals verbatim; the ones packing the test message itself get its
// type URL substituted per message type, as in the legacy suite.
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "conformance/binary_test_util.h"
#include "conformance/matchers.h"
#include "conformance/message_type_fixtures.h"
#include "conformance/testee.h"
namespace google {
namespace protobuf {
namespace conformance {
namespace {
using ::testing::ValuesIn;
using JsonAnyTest = MessageTypeConformanceTest;
TEST_P(JsonAnyTest, Any) {
const std::string type_url =
absl::StrCat("type.googleapis.com/", message()->full_name());
const std::string input = absl::Substitute(R"({
"optionalAny": {
"@type": "$0",
"optionalInt32": 12345
}
})",
type_url);
const std::string expected = absl::Substitute(R"(
optional_any: {
[$0] {
optional_int32: 12345
}
}
)",
type_url);
EXPECT_THAT(Testee("Any").ParseJson(message(), input).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(expected))));
EXPECT_THAT(Testee("Any").ParseJson(message(), input).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(expected))));
}
TEST_P(JsonAnyTest, AnyNested) {
const std::string type_url =
absl::StrCat("type.googleapis.com/", message()->full_name());
const std::string input = absl::Substitute(R"({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Any",
"value": {
"@type": "$0",
"optionalInt32": 12345
}
}
})",
type_url);
const std::string expected = absl::Substitute(R"(
optional_any: {
[type.googleapis.com/google.protobuf.Any] {
[$0] {
optional_int32: 12345
}
}
}
)",
type_url);
EXPECT_THAT(Testee("AnyNested").ParseJson(message(), input).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(expected))));
EXPECT_THAT(Testee("AnyNested").ParseJson(message(), input).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(expected))));
}
// The special "@type" tag is not required to appear first.
TEST_P(JsonAnyTest, AnyUnorderedTypeTag) {
const std::string type_url =
absl::StrCat("type.googleapis.com/", message()->full_name());
const std::string input = absl::Substitute(R"({
"optionalAny": {
"optionalInt32": 12345,
"@type": "$0"
}
})",
type_url);
const std::string expected = absl::Substitute(R"(
optional_any: {
[$0] {
optional_int32: 12345
}
}
)",
type_url);
EXPECT_THAT(Testee("AnyUnorderedTypeTag")
.ParseJson(message(), input)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(expected))));
EXPECT_THAT(
Testee("AnyUnorderedTypeTag").ParseJson(message(), input).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(expected))));
}
// Well-known types in Any.
TEST_P(JsonAnyTest, AnyWithInt32ValueWrapper) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Int32Value",
"value": 12345
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.Int32Value] { value: 12345 }
}
)pb";
EXPECT_THAT(Testee("AnyWithInt32ValueWrapper")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("AnyWithInt32ValueWrapper")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonAnyTest, AnyWithDuration) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "1.5s"
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.Duration] {
seconds: 1
nanos: 500000000
}
}
)pb";
EXPECT_THAT(
Testee("AnyWithDuration").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("AnyWithDuration").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonAnyTest, AnyWithTimestamp) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Timestamp",
"value": "1970-01-01T00:00:00Z"
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.Timestamp] { seconds: 0 nanos: 0 }
}
)pb";
EXPECT_THAT(
Testee("AnyWithTimestamp").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("AnyWithTimestamp").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonAnyTest, AnyWithFieldMask) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.FieldMask",
"value": "foo,barBaz"
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.FieldMask] {
paths: [ "foo", "bar_baz" ]
}
}
)pb";
EXPECT_THAT(
Testee("AnyWithFieldMask").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("AnyWithFieldMask").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonAnyTest, AnyWithStruct) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Struct",
"value": {
"foo": 1
}
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.Struct] {
fields: {
key: "foo"
value: { number_value: 1 }
}
}
}
)pb";
EXPECT_THAT(
Testee("AnyWithStruct").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("AnyWithStruct").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonAnyTest, AnyWithValueForJsonObject) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Value",
"value": {
"foo": 1
}
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.Value] {
struct_value: {
fields: {
key: "foo"
value: { number_value: 1 }
}
}
}
}
)pb";
EXPECT_THAT(Testee("AnyWithValueForJsonObject")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("AnyWithValueForJsonObject")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonAnyTest, AnyWithValueForInteger) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Value",
"value": 1
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.Value] { number_value: 1 }
}
)pb";
EXPECT_THAT(Testee("AnyWithValueForInteger")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("AnyWithValueForInteger")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
// When the Any is in WKT form (with "@type"), the type_url must be present
// and URL shaped, otherwise it should be a parse error (because it can't be
// parsed into the Any schema).
TEST_P(JsonAnyTest, AnyWktRepresentationWithEmptyTypeAndValue) {
EXPECT_THAT(Testee("AnyWktRepresentationWithEmptyTypeAndValue")
.ParseJson(message(), R"json({
"optionalAny": {
"@type": "",
"value": ""
}
})json")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonAnyTest, AnyWktRepresentationWithBadType) {
EXPECT_THAT(Testee("AnyWktRepresentationWithBadType")
.ParseJson(message(), R"json({
"optionalAny": {
"@type": "not_a_url",
"value": ""
}
})json")
.ParseOnly(),
Yields(IsParseError()));
}
// When the Any can be parsed as non-WKT form, the type_url could be missing
// or invalid, since that can still be parsed into the Any schema.
TEST_P(JsonAnyTest, AnyWithNoType) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {}
})json";
constexpr absl::string_view kExpected = R"pb(optional_any: {})pb";
EXPECT_THAT(
Testee("AnyWithNoType").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("AnyWithNoType").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
// `null` where an Any exists should just result in the field being unset.
TEST_P(JsonAnyTest, AnyNull) {
constexpr absl::string_view kInput = R"json({
"optionalAny": null
})json";
constexpr absl::string_view kExpected = "";
EXPECT_THAT(Testee("AnyNull").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("AnyNull").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
// google.protobuf.Empty packed into an Any, implementations must accept it
// without the "value" field set. This also confirms that what they round trip
// does not have `"value":{}` set on it, since the test harness uses the C++
// JSON parser which will reject it.
TEST_P(JsonAnyTest, AnyEmpty) {
constexpr absl::string_view kInput = R"json({
"optionalAny": {
"@type": "type.googleapis.com/google.protobuf.Empty"
}
})json";
constexpr absl::string_view kExpected = R"pb(
optional_any: {
[type.googleapis.com/google.protobuf.Empty] {}
}
)pb";
EXPECT_THAT(Testee("AnyEmpty").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("AnyEmpty").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
INSTANTIATE_TEST_SUITE_P(All, JsonAnyTest, ValuesIn(Proto3TestMessageTypes()),
MessageTypeParamName);
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google