diff --git a/include/yaml-cpp/node/convert.h b/include/yaml-cpp/node/convert.h index 45a878a..19c86d1 100644 --- a/include/yaml-cpp/node/convert.h +++ b/include/yaml-cpp/node/convert.h @@ -8,10 +8,19 @@ #endif #include +#include +#include +#include +#include #include #include #include +#include #include +#include +#include +#include +#include #include #include "yaml-cpp/binary.h" @@ -76,7 +85,7 @@ struct convert { template struct convert { - static Node encode(const char(&rhs)[N]) { return Node(rhs); } + static Node encode(const char (&rhs)[N]) { return Node(rhs); } }; template <> @@ -161,83 +170,161 @@ struct convert { YAML_CPP_API static bool decode(const Node& node, bool& rhs); }; -// std::map -template -struct convert> { - static Node encode(const std::map& rhs) { +// Helper class for converting something which is "sequence-like" to and from a +// Node. +template +struct convert_as_sequence { + static Node encode(const T& sequence) { + Node node(NodeType::Sequence); + for (const auto& item : sequence) { + node.push_back(item); + } + return node; + } + static bool decode(const Node& node, T& sequence) { + if (!node.IsSequence()) + return false; + sequence.clear(); + for (const auto& item : node) { + sequence.push_back(item.template as()); + } + return true; + } +}; + +// Helper class for converting something which is "map-like" to and from a Node. +template +struct convert_as_map { + static Node encode(const T& map) { Node node(NodeType::Map); - for (typename std::map::const_iterator it = rhs.begin(); - it != rhs.end(); ++it) - node.force_insert(it->first, it->second); + for (const auto& kv : map) { + node.force_insert(kv.first, kv.second); + } return node; } - static bool decode(const Node& node, std::map& rhs) { + static bool decode(const Node& node, T& map) { if (!node.IsMap()) return false; + map.clear(); + for (const auto& kv : node) { +// if we're dealing with GCC (note: clang also defines __GNUC__) +#if defined(__GNUC__) +#if __GNUC__ < 4 + // gcc version < 4 + map.insert(std::make_pair(kv.first.template as(), + kv.second.template as())); +#elif __GNUC__ == 4 && __GNUC_MINOR__ < 8 + // 4.0 <= gcc version < 4.8 + map.insert(std::make_pair(kv.first.as(), kv.second.as())); +#else + // 4.8 <= gcc version + map.emplace(kv.first.as(), kv.second.as()); +#endif // __GNUC__ < 4 +#else + // for anything not GCC or clang... + // probably some more #ifdef guards are needed for MSVC. + map.emplace(kv.first.as(), kv.second.as()); +#endif // defined(__GNUC__) + } + return true; + } +}; + +// Helper class for converting something which is "set-like" to and from a Node. +// A set is realized as a yaml sequence. +template +struct convert_as_set { + static Node encode(const T& set) { + Node node(NodeType::Sequence); + for (const auto& item : set) { + node.push_back(item); + } + return node; + } + + static bool decode(const Node& node, T& set) { + if (!node.IsSequence()) + return false; + + set.clear(); + for (const auto& item : node) { - rhs.clear(); - for (const_iterator it = node.begin(); it != node.end(); ++it) #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: - rhs[it->first.template as()] = it->second.template as(); -#else - rhs[it->first.as()] = it->second.as(); + set.insert(item.template as()); +#else // __GNUC__ is not defined or __GNUC__ >= 4 + set.insert(item.as()); #endif + } return true; } }; // std::vector -template -struct convert> { - static Node encode(const std::vector& rhs) { - Node node(NodeType::Sequence); - for (typename std::vector::const_iterator it = rhs.begin(); - it != rhs.end(); ++it) - node.push_back(*it); - return node; - } +template +struct convert> + : public convert_as_sequence> {}; - static bool decode(const Node& node, std::vector& rhs) { - if (!node.IsSequence()) - return false; - - rhs.clear(); - for (const_iterator it = node.begin(); it != node.end(); ++it) -#if defined(__GNUC__) && __GNUC__ < 4 - // workaround for GCC 3: - rhs.push_back(it->template as()); -#else - rhs.push_back(it->as()); -#endif - return true; - } -}; +// std::deque +template +struct convert> + : public convert_as_sequence> {}; // std::list -template -struct convert> { - static Node encode(const std::list& rhs) { +template +struct convert> + : public convert_as_sequence> {}; + +// std::map +template +struct convert> + : public convert_as_map> {}; + +// std::unordered_map +template +struct convert> + : public convert_as_map> { +}; + +// std::set +template +struct convert> + : public convert_as_set> {}; + +// std::unordered_set +template +struct convert> + : public convert_as_set> {}; + +// std::forward_list +template +struct convert> { + static Node encode(const std::forward_list& sequence) { Node node(NodeType::Sequence); - for (typename std::list::const_iterator it = rhs.begin(); - it != rhs.end(); ++it) - node.push_back(*it); + for (const auto& item : sequence) { + node.push_back(item); + } return node; } - static bool decode(const Node& node, std::list& rhs) { + static bool decode(const Node& node, std::forward_list& sequence) { if (!node.IsSequence()) return false; - rhs.clear(); - for (const_iterator it = node.begin(); it != node.end(); ++it) + sequence.clear(); + + // Walk the node backwards, because std::forward_list does not have + // push_back, only push_front. + for (std::size_t i = node.size() - 1; i != (std::size_t)-1; --i) { #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: - rhs.push_back(it->template as()); + sequence.push_front(node[i].template as()); #else - rhs.push_back(it->as()); + sequence.push_front(node[i].as()); #endif + } return true; } }; @@ -245,15 +332,15 @@ struct convert> { // std::array template struct convert> { - static Node encode(const std::array& rhs) { + static Node encode(const std::array& sequence) { Node node(NodeType::Sequence); - for (const auto& element : rhs) { - node.push_back(element); + for (const auto& item : sequence) { + node.push_back(item); } return node; } - static bool decode(const Node& node, std::array& rhs) { + static bool decode(const Node& node, std::array& sequence) { if (!isNodeValid(node)) { return false; } @@ -261,9 +348,9 @@ struct convert> { for (auto i = 0u; i < node.size(); ++i) { #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: - rhs[i] = node[i].template as(); + sequence[i] = node[i].template as(); #else - rhs[i] = node[i].as(); + sequence[i] = node[i].as(); #endif } return true; @@ -275,34 +362,109 @@ struct convert> { } }; -// std::pair -template -struct convert> { - static Node encode(const std::pair& rhs) { +// std::bitset +template +struct convert> { + using value_type = std::bitset; + + static Node encode(const value_type& rhs) { + return convert::encode(rhs.to_string()); + } + + static bool decode(const Node& node, value_type& rhs) { + std::string representation; + + if (!convert::decode(node, representation)) + return false; + if (representation.size() != N) + return false; + try { + // bitset constructor will throw std:invalid_argument if the decoded + // string contains characters other than 0 and 1. + rhs = value_type(representation); + } catch (const std::invalid_argument& /*error*/) { + return false; + } + return true; + } +}; + +namespace detail { + +template +inline typename std::enable_if::type + encode_tuple(Node& /*node*/, const std::tuple& /*tup*/) {} + +template +inline typename std::enable_if::type + encode_tuple(Node& node, const std::tuple& tup) { + node.push_back(std::get(tup)); + encode_tuple(node, tup); +} + +template +inline typename std::enable_if::type + decode_tuple(const Node& /*node*/, std::tuple& /*tup*/) {} + +template +inline typename std::enable_if::type + decode_tuple(const Node& node, std::tuple& tup) { + std::get(tup) = + node[Index] + .template as< + typename std::tuple_element>::type>(); + decode_tuple(node, tup); +} + +} // namespace detail + +// std::tuple +template +struct convert> { + static Node encode(const std::tuple& tup) { + static_assert(sizeof...(Args) > 0, + "wrong template specialization selected"); Node node(NodeType::Sequence); - node.push_back(rhs.first); - node.push_back(rhs.second); + detail::encode_tuple(node, tup); return node; } - static bool decode(const Node& node, std::pair& rhs) { - if (!node.IsSequence()) - return false; - if (node.size() != 2) + static bool decode(const Node& node, std::tuple& tup) { + static_assert(sizeof...(Args) > 0, + "wrong template specialization selected"); + if (!node.IsSequence() || node.size() != sizeof...(Args)) return false; + detail::decode_tuple(node, tup); + return true; + } +}; -#if defined(__GNUC__) && __GNUC__ < 4 - // workaround for GCC 3: - rhs.first = node[0].template as(); -#else - rhs.first = node[0].as(); -#endif -#if defined(__GNUC__) && __GNUC__ < 4 - // workaround for GCC 3: - rhs.second = node[1].template as(); -#else - rhs.second = node[1].as(); -#endif +// std::tuple -- empty +template <> +struct convert> { + static Node encode(const std::tuple<>& /*tup*/) { + return convert<_Null>::encode(Null); + } + static bool decode(const Node& node, std::tuple<>& /*tup*/) { + return convert<_Null>::decode(node, Null); + } +}; + +// std::pair -- special case of std::tuple +template +struct convert> { + static Node encode(const std::pair& tup) { + Node node(NodeType::Sequence); + node.push_back(std::get<0>(tup)); + node.push_back(std::get<1>(tup)); + return node; + } + + static bool decode(const Node& node, std::pair& tup) { + if (!node.IsSequence() || node.size() != 2) + return false; + std::get<0>(tup) = node[0].template as(); + std::get<1>(tup) = node[1].template as(); return true; } }; diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index ff0a3ae..4e234fe 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -231,5 +231,55 @@ TEST(NodeTest, LoadTildeAsNull) { ASSERT_TRUE(node.IsNull()); } +TEST(NodeTest, MapOfSequences) { + const auto doc = Load(R"( +homebrews: + vita: + - name: "PSP2048" + author: dots-tb + - name: "PSVident" + author: Freakler + )"); + + const auto homebrews = doc["homebrews"]; + // should succeed + std::map>> info; + info = homebrews.as< + std::map>>>(); + const auto vita = info["vita"]; + const auto vita0 = vita[0]; + const auto vita1 = vita[1]; + EXPECT_NE(vita0.end(), vita0.find("name")); + EXPECT_NE(vita1.end(), vita1.find("name")); + EXPECT_NE(vita0.end(), vita0.find("author")); + EXPECT_NE(vita1.end(), vita1.find("author")); + + const auto name0 = vita0.find("name"); + const auto name1 = vita1.find("name"); + const auto author0 = vita0.find("author"); + const auto author1 = vita1.find("author"); + + EXPECT_EQ(name0->second, "PSP2048"); + EXPECT_EQ(name1->second, "PSVident"); + EXPECT_EQ(author0->second, "dots-tb"); + EXPECT_EQ(author1->second, "Freakler"); +} + +TEST(NodeTest, SeqMap) { + const auto doc = Load(R"( +- firstname: john + surname: doe + )"); + + const auto info = doc.as>>(); + + EXPECT_EQ(1, info.size()); + + auto name = info[0]; + EXPECT_EQ(2, name.size()); + EXPECT_EQ("john", name["firstname"]); + EXPECT_EQ("doe", name["surname"]); +} + } // namespace } // namespace YAML diff --git a/test/node/node_test.cpp b/test/node/node_test.cpp index 485ad09..9d34c10 100644 --- a/test/node/node_test.cpp +++ b/test/node/node_test.cpp @@ -1,10 +1,10 @@ #include "yaml-cpp/emitter.h" -#include "yaml-cpp/node/emit.h" -#include "yaml-cpp/node/node.h" -#include "yaml-cpp/node/impl.h" #include "yaml-cpp/node/convert.h" -#include "yaml-cpp/node/iterator.h" #include "yaml-cpp/node/detail/impl.h" +#include "yaml-cpp/node/emit.h" +#include "yaml-cpp/node/impl.h" +#include "yaml-cpp/node/iterator.h" +#include "yaml-cpp/node/node.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -211,6 +211,20 @@ TEST(NodeTest, StdVector) { EXPECT_EQ(primes, node["primes"].as>()); } +TEST(NodeTest, StdDeque) { + std::deque primes; + primes.push_back(2); + primes.push_back(3); + primes.push_back(5); + primes.push_back(7); + primes.push_back(11); + primes.push_back(13); + + Node node; + node["primes"] = primes; + EXPECT_EQ(primes, node["primes"].as>()); +} + TEST(NodeTest, StdList) { std::list primes; primes.push_back(2); @@ -225,6 +239,20 @@ TEST(NodeTest, StdList) { EXPECT_EQ(primes, node["primes"].as>()); } +TEST(NodeTest, StdForwardList) { + std::forward_list primes; + primes.push_front(13); + primes.push_front(11); + primes.push_front(7); + primes.push_front(5); + primes.push_front(3); + primes.push_front(2); + + Node node; + node["primes"] = primes; + EXPECT_EQ(primes, node["primes"].as>()); +} + TEST(NodeTest, StdMap) { std::map squares; squares[0] = 0; @@ -239,6 +267,291 @@ TEST(NodeTest, StdMap) { EXPECT_EQ(squares, actualSquares); } +TEST(NodeTest, StdUnorderedMap) { + std::unordered_map squares; + squares[0] = 0; + squares[1] = 1; + squares[2] = 4; + squares[3] = 9; + squares[4] = 16; + + Node node; + node["squares"] = squares; + const auto actualSquares = node["squares"].as>(); + EXPECT_EQ(squares, actualSquares); +} + +TEST(NodeTest, StdSet) { + std::set primes; + primes.insert(2); + primes.insert(3); + primes.insert(5); + primes.insert(7); + primes.insert(11); + primes.insert(13); + + Node node; + node["primes"] = primes; + const auto actualPrimes = node["primes"].as>(); + EXPECT_EQ(primes, actualPrimes); +} + +TEST(NodeTest, StdUnorderedSet) { + std::unordered_set primes; + primes.insert(0); + primes.insert(1); + primes.insert(4); + primes.insert(9); + primes.insert(16); + + Node node; + node["primes"] = primes; + const auto actualPrimes = node["primes"].as>(); + EXPECT_EQ(primes, actualPrimes); +} + +TEST(NodeTest, StdBitset) { + std::bitset<8> bits; + bits.set(0, true); + bits.set(1, true); + bits.set(2, false); + bits.set(3, true); + bits.set(4, true); + bits.set(5, false); + bits.set(6, false); + bits.set(7, false); + + Node node; + node["bits"] = bits; + const auto actual = node["bits"].as>(); + EXPECT_EQ(bits, actual); +} + +TEST(NodeTest, StdBitsetWrongSize) { + std::bitset<8> bits; + bits.set(0, true); + bits.set(1, true); + bits.set(2, false); + bits.set(3, true); + bits.set(4, true); + bits.set(5, false); + bits.set(6, false); + bits.set(7, false); + + Node node; + node["bits"] = bits; + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); + EXPECT_THROW_REPRESENTATION_EXCEPTION((node["bits"].as>()), + ErrorMsg::BAD_CONVERSION); +} + +TEST(NodeTest, StdBitsetWrongRepresentation) { + const std::string representation = + "0011" + "1000" + "1101" + "x000"; // 16 bits, note the wrong character 'x' here + Node node; + node["not_really_bits"] = representation; + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node["not_really_bits"].as>()), + ErrorMsg::BAD_CONVERSION); +} + +#define TEST_FROM_TO(TESTNAME, FROM, TO) \ + TEST(NodeTest, TESTNAME) { \ + const std::FROM primes = {{2, 3, 5, 7, 11, 13}}; \ + std::size_t count0 = std::distance(std::begin(primes), std::end(primes)); \ + const Node node(primes); \ + const auto decoded = node.as(); \ + auto iter0 = primes.begin(); \ + auto iter1 = decoded.begin(); \ + std::size_t count1 = 0; \ + while (iter0 != primes.end() && iter1 != decoded.end()) { \ + EXPECT_EQ(*iter0, *iter1); \ + ++iter0; \ + ++iter1; \ + ++count1; \ + } \ + EXPECT_EQ(count0, count1); \ + EXPECT_THROW_REPRESENTATION_EXCEPTION((node.as>()), \ + ErrorMsg::BAD_CONVERSION); \ + } + +// needed because the comma would otherwise be seen as the start of a new macro +// argument. +#define ARRAY_TYPE array + +TEST_FROM_TO(StdFromVectorToDeque, vector, deque); +TEST_FROM_TO(StdFromVectorToList, vector, list); +TEST_FROM_TO(StdFromVectorToForwardList, vector, forward_list); +TEST_FROM_TO(StdFromVectorToArray, vector, ARRAY_TYPE); + +TEST_FROM_TO(StdFromDequeToVector, deque, vector); +TEST_FROM_TO(StdFromDequeToList, deque, list); +TEST_FROM_TO(StdFromDequeToForwardList, deque, forward_list); +TEST_FROM_TO(StdFromDequeToArray, deque, ARRAY_TYPE); + +TEST_FROM_TO(StdFromListToVector, list, vector); +TEST_FROM_TO(StdFromListToDeque, list, deque); +TEST_FROM_TO(StdFromListToForwardList, list, forward_list); +TEST_FROM_TO(StdFromListToArray, list, ARRAY_TYPE); + +TEST_FROM_TO(StdFromForwardListToVector, forward_list, vector); +TEST_FROM_TO(StdFromForwardListToDeque, forward_list, deque); +TEST_FROM_TO(StdFromForwardListToList, forward_list, list); +TEST_FROM_TO(StdFromForwardListToArray, forward_list, ARRAY_TYPE); + +TEST_FROM_TO(StdFromArrayToVector, ARRAY_TYPE, vector); +TEST_FROM_TO(StdFromArrayToDeque, ARRAY_TYPE, deque); +TEST_FROM_TO(StdFromArrayToList, ARRAY_TYPE, list); +TEST_FROM_TO(StdFromArrayToForwardList, ARRAY_TYPE, forward_list); + +#undef ARRAY_TYPE +#undef TEST_FROM_TO + +TEST(NodeTest, SequenceWithDuplicatesCanBeParsedAsSet) { + + const std::vector withDuplicates{1, 5, 3, 6, 4, 7, 5, 3, 3, 4, 5, + 5, 3, 2, 7, 8, 9, 9, 9, 5, 2, 0}; + + const auto node = Node(withDuplicates); + + // should succeed -- an std::set is considered a yaml sequence + const auto withoutDuplicatesAndSorted = node.as>(); + EXPECT_EQ(withoutDuplicatesAndSorted.size(), 10); + std::size_t i = 0; + for (const auto digit : withoutDuplicatesAndSorted) { + EXPECT_EQ(digit, i); + ++i; + } + EXPECT_EQ(i, 10); +} + +TEST(NodeTest, SequenceWithDuplicatesCanBeParsedAsUnorderedSet) { + + const std::vector withDuplicates{1, 5, 3, 6, 4, 7, 5, 3, 3, 4, 5, + 5, 3, 2, 7, 8, 9, 9, 9, 5, 2, 0}; + + const auto node = Node(withDuplicates); + + // should succeed -- an std::unordered_set is considered a yaml sequence + const auto withoutDuplicates = node.as>(); + EXPECT_EQ(withoutDuplicates.size(), 10); + int occurences[10]; + std::fill(std::begin(occurences), std::end(occurences), 0); + for (const auto digit : withoutDuplicates) { + ++occurences[digit]; + } + for (std::size_t i = 0; i < 10; ++i) + EXPECT_EQ(1, occurences[i]); +} + +TEST(NodeTest, StdFromMapToUnorderedMap) { + std::map> typeSafeTravisFile{ + {"language", {"c++"}}, + {"os", {"linux", "osx"}}, + {"compiler", {"clang", "gcc"}}, + {"before_install", {"do some stuff", "do some more stuff"}}, + {"before_script", {"mkdir build", "cd build", "cmake .."}}, + {"script", {"make", "test/run-tests"}}}; + + const auto node = Node(typeSafeTravisFile); + + // should succeed -- precisely two values in the sequence + const auto os = node["os"].as>(); + EXPECT_EQ(os.first, "linux"); + EXPECT_EQ(os.second, "osx"); + + // should succeed -- yaml doesn't know the difference between ordered and + // unordered. + using T = std::unordered_map>; + EXPECT_THAT(node.as(), + testing::UnorderedElementsAreArray(typeSafeTravisFile.begin(), + typeSafeTravisFile.end())); + + // should fail -- a yaml map is not a yaml sequence + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::vector>>>()), + ErrorMsg::BAD_CONVERSION); + + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::deque>>>()), + ErrorMsg::BAD_CONVERSION); + + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::list>>>()), + ErrorMsg::BAD_CONVERSION); + + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::forward_list< + std::pair>>>()), + ErrorMsg::BAD_CONVERSION); +} + +TEST(NodeTest, StdFromUnorderedMapToMap) { + std::unordered_map> typeSafeTravisFile{ + {"language", {"c++"}}, + {"os", {"linux", "osx"}}, + {"compiler", {"clang", "gcc"}}, + {"before_install", {"do some stuff", "do some more stuff"}}, + {"before_script", {"mkdir build", "cd build", "cmake .."}}, + {"script", {"make", "test/run-tests"}}}; + + const auto node = Node(typeSafeTravisFile); + + // should succeed -- precisely two values in the sequence + const auto os = node["os"].as>(); + EXPECT_EQ(os.first, "linux"); + EXPECT_EQ(os.second, "osx"); + + // should succeed -- yaml doesn't know the difference between ordered and + // unordered. + using T = std::map>; + EXPECT_THAT(node.as(), + testing::UnorderedElementsAreArray(typeSafeTravisFile.begin(), + typeSafeTravisFile.end())); + + // should fail -- a yaml map is not a yaml sequence + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::vector>>>()), + ErrorMsg::BAD_CONVERSION); + + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::deque>>>()), + ErrorMsg::BAD_CONVERSION); + + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::list>>>()), + ErrorMsg::BAD_CONVERSION); + + EXPECT_THROW_REPRESENTATION_EXCEPTION( + (node.as< // looks structurally somewhat like a map + std::forward_list< + std::pair>>>()), + ErrorMsg::BAD_CONVERSION); +} + TEST(NodeTest, StdPair) { std::pair p; p.first = 5; @@ -251,6 +564,86 @@ TEST(NodeTest, StdPair) { EXPECT_EQ(p, actualP); } +TEST(NodeTest, StdPairFailure) { + std::vector triple{1, 2, 3}; + const auto node = Node(triple); + + // should fail -- an std::pair needs a sequence of length exactly 2, while + // the Node holds a sequence of length exactly 3. + EXPECT_THROW_REPRESENTATION_EXCEPTION((node.as>()), + ErrorMsg::BAD_CONVERSION); +} + +TEST(NodeTest, StdTupleSize0) { + const auto expected = std::tuple<>(); + const auto node = Node(expected); + const auto actual = node.as>(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(true, node.IsNull()); +} + +TEST(NodeTest, StdTupleSize1) { + const auto expected = std::make_tuple(42); + const auto node = Node(expected); + const auto actual = node.as::type>(); + EXPECT_EQ(expected, actual); + + // should succeed -- tuple is realized as a yaml sequence + const auto vec = node.as>(); + EXPECT_EQ(std::get<0>(expected), vec[0]); +} + +TEST(NodeTest, StdTupleSize2) { + const auto expected = std::make_tuple(42, 3.141592f); + const auto node = Node(expected); + const auto actual = node.as::type>(); + EXPECT_EQ(expected, actual); + + // should succeed -- tuple is realized as a yaml sequence + const auto vec = node.as>(); + EXPECT_EQ(std::get<0>(expected), vec[0]); + EXPECT_EQ(std::get<1>(expected), vec[1]); + + // should succeed -- can also be a pair in this case + const auto pair = node.as>(); + EXPECT_EQ(std::get<0>(expected), pair.first); + EXPECT_EQ(std::get<1>(expected), pair.second); +} + +TEST(NodeTest, StdTupleSize3) { + const auto expected = std::make_tuple(42, 3.141592f, std::string("hello")); + const auto node = Node(expected); + const auto actual = node.as::type>(); + EXPECT_EQ(expected, actual); +} + +TEST(NodeTest, StdTupleSize4) { + const auto expected = std::make_tuple(42, 3.141592f, std::string("hello"), + std::string("world")); + const auto node = Node(expected); + const auto actual = node.as::type>(); + EXPECT_EQ(expected, actual); +} + +TEST(NodeTest, StdTupleSize5) { + const auto expected = + std::make_tuple(42, 3.141592f, std::string("hello"), std::string("world"), + std::vector{2, 3, 5, 7}); + const auto node = Node(expected); + const auto actual = node.as::type>(); + EXPECT_EQ(expected, actual); +} + +TEST(NodeTest, StdTupleSize6) { + const auto expected = + std::make_tuple(42, 3.141592f, std::string("hello"), std::string("world"), + std::vector{2, 3, 5, 7}, + std::map{{1, 1}, {2, 4}, {3, 9}, {4, 16}}); + const auto node = Node(expected); + const auto actual = node.as::type>(); + EXPECT_EQ(expected, actual); +} + TEST(NodeTest, SimpleAlias) { Node node; node["foo"] = "value";