From e671c0346ce4a47a1b691798db4fe009ab5712ca Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 9 Dec 2025 21:16:59 -0600 Subject: [PATCH] feat: add `tr_variant::Map` methods (#7910) * feat: add tr_variant::Map::contains() * feat: add tr_variant::Map::replace_key() * fixup! feat: add tr_variant::Map::replace_key() fix readability-braces-around-statements * fixup! feat: add tr_variant::Map::contains() readability-container-contains --- libtransmission/variant.h | 22 +++++ tests/libtransmission/variant-test.cc | 132 ++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/libtransmission/variant.h b/libtransmission/variant.h index 2421646de..18c604444 100644 --- a/libtransmission/variant.h +++ b/libtransmission/variant.h @@ -99,6 +99,11 @@ public: return Vector::const_iterator{ const_cast(this)->find(key) }; } + [[nodiscard]] auto contains(tr_quark const key) const noexcept + { + return find(key) != end(); // NOLINT(readability-container-contains) + } + [[nodiscard]] TR_CONSTEXPR20 auto find(std::initializer_list keys) noexcept { auto constexpr Predicate = [](auto const& item, tr_quark key) @@ -139,6 +144,23 @@ public: return 0U; } + bool replace_key(tr_quark const old_key, tr_quark const new_key) + { + if (contains(new_key)) + { + return false; + } + + auto iter = find(old_key); + if (iter == end()) + { + return false; + } + + iter->first = new_key; + return true; + } + [[nodiscard]] tr_variant& operator[](tr_quark const& key) { if (auto const iter = find(key); iter != end()) diff --git a/tests/libtransmission/variant-test.cc b/tests/libtransmission/variant-test.cc index 989ee41ab..74b889a8f 100644 --- a/tests/libtransmission/variant-test.cc +++ b/tests/libtransmission/variant-test.cc @@ -556,6 +556,138 @@ TEST_F(VariantTest, dictFindType) EXPECT_EQ(ExpectedInt, *i); } +TEST_F(VariantTest, mapContains) +{ + auto const key_bool = tr_quark_new("contains-bool"sv); + auto const key_int = tr_quark_new("contains-int"sv); + auto const key_double = tr_quark_new("contains-double"sv); + auto const key_string = tr_quark_new("contains-string"sv); + auto const key_vector = tr_quark_new("contains-vector"sv); + auto const key_map = tr_quark_new("contains-map"sv); + auto const key_missing = tr_quark_new("contains-missing"sv); + auto const nested_key = tr_quark_new("contains-nested"sv); + + // populate a test map + + auto top = tr_variant::make_map(6U); + auto* const map = top.get_if(); + ASSERT_NE(map, nullptr); + + map->try_emplace(key_bool, true); + map->try_emplace(key_int, int64_t{ 42 }); + map->try_emplace(key_double, 4.2); + map->try_emplace(key_string, "needle"sv); + + auto vec = tr_variant::Vector{}; + vec.emplace_back(true); + vec.emplace_back(int64_t{ 7 }); + map->try_emplace(key_vector, std::move(vec)); + + auto nested = tr_variant::make_map(1U); + auto* nested_map = nested.get_if(); + ASSERT_NE(nested_map, nullptr); + nested_map->try_emplace(nested_key, "nested"sv); + map->try_emplace(key_map, std::move(nested)); + + // --- + + // test: returns true for entries that exist + EXPECT_TRUE(map->contains(key_bool)); + EXPECT_TRUE(map->contains(key_double)); + EXPECT_TRUE(map->contains(key_int)); + EXPECT_TRUE(map->contains(key_map)); + EXPECT_TRUE(map->contains(key_string)); + EXPECT_TRUE(map->contains(key_vector)); + + // test: returns false for entries that never existed + EXPECT_FALSE(map->contains(key_missing)); + + // test: returns false for entries that were removed + EXPECT_EQ(1U, map->erase(key_vector)); + EXPECT_FALSE(map->contains(key_vector)); +} + +TEST_F(VariantTest, mapReplaceKey) +{ + auto constexpr IntVal = int64_t{ 73 }; + auto const key_bool = tr_quark_new("replace-bool"sv); + auto const key_int = tr_quark_new("replace-int"sv); + auto const key_double = tr_quark_new("replace-double"sv); + auto const key_string = tr_quark_new("replace-string"sv); + auto const key_vector = tr_quark_new("replace-vector"sv); + auto const key_map = tr_quark_new("replace-map"sv); + auto const key_duplicate = tr_quark_new("replace-duplicate"sv); + auto const key_missing_src = tr_quark_new("replace-missing-src"sv); + auto const key_missing_tgt = tr_quark_new("replace-missing-tgt"sv); + auto const key_replacement = tr_quark_new("replace-string-new"sv); + auto const key_nested = tr_quark_new("replace-nested"sv); + + // populate a sample map + + auto top = tr_variant::make_map(7U); + auto* const map = top.get_if(); + ASSERT_NE(map, nullptr); + + map->try_emplace(key_bool, true); + map->try_emplace(key_int, IntVal); + map->try_emplace(key_double, 7.3); + map->try_emplace(key_string, "string"sv); + + auto vec = tr_variant::Vector{}; + vec.emplace_back(false); + vec.emplace_back(int64_t{ 99 }); + map->try_emplace(key_vector, std::move(vec)); + + auto nested = tr_variant::make_map(1U); + auto* nested_map = nested.get_if(); + ASSERT_NE(nested_map, nullptr); + nested_map->try_emplace(key_nested, "nested"sv); + map->try_emplace(key_map, std::move(nested)); + + map->try_emplace(key_duplicate, "occupied"sv); + + // --- + + // test: neither src nor tgt exist + auto const serde = tr_variant_serde::json(); + auto expected = serde.to_string(top); + EXPECT_FALSE(map->contains(key_missing_src)); + EXPECT_FALSE(map->contains(key_missing_tgt)); + EXPECT_FALSE(map->replace_key(key_missing_src, key_missing_tgt)); + EXPECT_FALSE(map->contains(key_missing_src)); + EXPECT_FALSE(map->contains(key_missing_tgt)); + auto actual = serde.to_string(top); + EXPECT_EQ(expected, actual); // confirm variant is unchanged + + // test: src doesn't exist + expected = serde.to_string(top); + EXPECT_FALSE(map->contains(key_missing_src)); + EXPECT_EQ(IntVal, map->value_if(key_int).value_or(!IntVal)); + EXPECT_FALSE(map->replace_key(key_missing_src, key_int)); + EXPECT_FALSE(map->contains(key_missing_src)); + EXPECT_EQ(IntVal, map->value_if(key_int).value_or(!IntVal)); + actual = serde.to_string(top); + EXPECT_EQ(expected, actual); // confirm variant is unchanged + + // test: tgt already exists + expected = serde.to_string(top); + EXPECT_TRUE(map->contains(key_int)); + EXPECT_TRUE(map->contains(key_string)); + EXPECT_FALSE(map->replace_key(key_int, key_string)); + EXPECT_TRUE(map->contains(key_int)); + EXPECT_TRUE(map->contains(key_string)); + actual = serde.to_string(top); + EXPECT_EQ(expected, actual); // confirm variant is unchanged + + // test: successful replacement + EXPECT_TRUE(map->contains(key_int)); + EXPECT_FALSE(map->contains(key_replacement)); + EXPECT_TRUE(map->replace_key(key_int, key_replacement)); + EXPECT_FALSE(map->contains(key_int)); + EXPECT_TRUE(map->contains(key_replacement)); + EXPECT_EQ(IntVal, map->value_if(key_replacement).value_or(!IntVal)); +} + TEST_F(VariantTest, variantFromBufFuzz) { auto benc_serde = tr_variant_serde::json();