| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2023 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 | |
| #include "upb/util/required_fields.h" | |
| #include <stdlib.h> | |
| #include <gtest/gtest.h> | |
| #include "absl/strings/string_view.h" | |
| #include "upb/base/status.hpp" | |
| #include "upb/base/upcast.h" | |
| #include "upb/json/decode.h" | |
| #include "upb/mem/arena.h" | |
| #include "upb/mem/arena.hpp" | |
| #include "upb/reflection/common.h" | |
| #include "upb/reflection/def.hpp" | |
| #include "upb/util/required_fields_editions_test.upb.h" | |
| #include "upb/util/required_fields_editions_test.upbdefs.h" | |
| #include "upb/util/required_fields_test.upb.h" | |
| #include "upb/util/required_fields_test.upbdefs.h" | |
| std::vector<std::string> PathsToText(upb_FieldPathEntry* entry) { | |
| std::vector<std::string> ret; | |
| char buf[1024]; // Larger than anything we'll use in this test. | |
| while (entry->field) { | |
| upb_FieldPathEntry* before = entry; | |
| size_t len = upb_FieldPath_ToText(&entry, buf, sizeof(buf)); | |
| EXPECT_LT(len, sizeof(buf)); | |
| assert(len <= sizeof(buf)); | |
| ret.push_back(buf); | |
| // Ensure that we can have a short buffer and that it will be | |
| // NULL-terminated. | |
| char shortbuf[4]; | |
| size_t len2 = upb_FieldPath_ToText(&before, shortbuf, sizeof(shortbuf)); | |
| EXPECT_EQ(len, len2); | |
| EXPECT_EQ(ret.back().substr(0, sizeof(shortbuf) - 1), | |
| std::string(shortbuf)); | |
| } | |
| return ret; | |
| } | |
| template <typename T> | |
| class RequiredFieldsTest : public testing::Test { | |
| public: | |
| void CheckRequired(absl::string_view json, | |
| const std::vector<std::string>& missing) { | |
| upb::Arena arena; | |
| upb::DefPool defpool; | |
| auto* test_msg = T::NewMessage(arena.ptr()); | |
| upb::MessageDefPtr m = T::GetMessageDef(defpool.ptr()); | |
| upb::Status status; | |
| EXPECT_TRUE(upb_JsonDecode(json.data(), json.size(), UPB_UPCAST(test_msg), | |
| m.ptr(), defpool.ptr(), 0, arena.ptr(), | |
| status.ptr())) | |
| << status.error_message(); | |
| upb_FieldPathEntry* entries = nullptr; | |
| EXPECT_EQ(!missing.empty(), | |
| upb_util_HasUnsetRequired(UPB_UPCAST(test_msg), m.ptr(), | |
| defpool.ptr(), &entries, status.ptr())); | |
| if (entries) { | |
| EXPECT_EQ(missing, PathsToText(entries)); | |
| free(entries); | |
| } | |
| // Verify that we can pass a NULL pointer to entries when we don't care | |
| // about them. | |
| EXPECT_EQ(!missing.empty(), | |
| upb_util_HasUnsetRequired(UPB_UPCAST(test_msg), m.ptr(), | |
| defpool.ptr(), nullptr, status.ptr())); | |
| } | |
| }; | |
| class Proto2Type { | |
| public: | |
| using MessageType = upb_util_test_TestRequiredFields; | |
| static MessageType* NewMessage(upb_Arena* arena) { | |
| return upb_util_test_TestRequiredFields_new(arena); | |
| } | |
| static upb::MessageDefPtr GetMessageDef(upb_DefPool* defpool) { | |
| return upb::MessageDefPtr( | |
| upb_util_test_TestRequiredFields_getmsgdef(defpool)); | |
| } | |
| }; | |
| class Edition2023Type { | |
| public: | |
| using MessageType = upb_util_2023_test_TestRequiredFields; | |
| static MessageType* NewMessage(upb_Arena* arena) { | |
| return upb_util_2023_test_TestRequiredFields_new(arena); | |
| } | |
| static upb::MessageDefPtr GetMessageDef(upb_DefPool* defpool) { | |
| return upb::MessageDefPtr( | |
| upb_util_2023_test_TestRequiredFields_getmsgdef(defpool)); | |
| } | |
| }; | |
| using MyTypes = ::testing::Types<Proto2Type, Edition2023Type>; | |
| TYPED_TEST_SUITE(RequiredFieldsTest, MyTypes); | |
| // message HasRequiredField { | |
| // required int32 required_int32 = 1; | |
| // } | |
| // | |
| // message TestRequiredFields { | |
| // required EmptyMessage required_message = 1; | |
| // optional TestRequiredFields optional_message = 2; | |
| // repeated HasRequiredField repeated_message = 3; | |
| // map<int32, HasRequiredField> map_int32_message = 4; | |
| // } | |
| TYPED_TEST(RequiredFieldsTest, TestRequired) { | |
| TestFixture::CheckRequired(R"json({})json", {"required_message"}); | |
| TestFixture::CheckRequired(R"json({"required_message": {}})json", {}); | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "optional_message": {} | |
| } | |
| )json", | |
| {"required_message", "optional_message.required_message"}); | |
| // Repeated field. | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "optional_message": { | |
| "repeated_message": [ | |
| {"required_int32": 1}, | |
| {}, | |
| {"required_int32": 2} | |
| ] | |
| } | |
| } | |
| )json", | |
| {"required_message", "optional_message.required_message", | |
| "optional_message.repeated_message[1].required_int32"}); | |
| // Int32 map key. | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "required_message": {}, | |
| "map_int32_message": { | |
| "1": {"required_int32": 1}, | |
| "5": {}, | |
| "9": {"required_int32": 1} | |
| } | |
| } | |
| )json", | |
| {"map_int32_message[5].required_int32"}); | |
| // Int64 map key. | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "required_message": {}, | |
| "map_int64_message": { | |
| "1": {"required_int32": 1}, | |
| "5": {}, | |
| "9": {"required_int32": 1} | |
| } | |
| } | |
| )json", | |
| {"map_int64_message[5].required_int32"}); | |
| // Uint32 map key. | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "required_message": {}, | |
| "map_uint32_message": { | |
| "1": {"required_int32": 1}, | |
| "5": {}, | |
| "9": {"required_int32": 1} | |
| } | |
| } | |
| )json", | |
| {"map_uint32_message[5].required_int32"}); | |
| // Uint64 map key. | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "required_message": {}, | |
| "map_uint64_message": { | |
| "1": {"required_int32": 1}, | |
| "5": {}, | |
| "9": {"required_int32": 1} | |
| } | |
| } | |
| )json", | |
| {"map_uint64_message[5].required_int32"}); | |
| // Bool map key. | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "required_message": {}, | |
| "map_bool_message": { | |
| "false": {"required_int32": 1}, | |
| "true": {} | |
| } | |
| } | |
| )json", | |
| {"map_bool_message[true].required_int32"}); | |
| // String map key. | |
| TestFixture::CheckRequired( | |
| R"json( | |
| { | |
| "required_message": {}, | |
| "map_string_message": { | |
| "abc": {"required_int32": 1}, | |
| "d\"ef": {} | |
| } | |
| } | |
| )json", | |
| {R"(map_string_message["d\"ef"].required_int32)"}); | |
| } |