diff --git a/docs/Tutorial.md b/docs/Tutorial.md index a7b0e21d0..5db31e9f8 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -178,11 +178,12 @@ struct convert { return node; } - static bool decode(const Node& node, Vec3& rhs) { + static Vec3 decode(const Node& node) { if(!node.IsSequence() || node.size() != 3) { - return false; + throw YAML::conversion::DecodeException(""); } + Vec3 rhs; rhs.x = node[0].as(); rhs.y = node[1].as(); rhs.z = node[2].as(); @@ -197,5 +198,50 @@ Then you could use `Vec3` wherever you could use any other type: ```cpp YAML::Node node = YAML::Load("start: [1, 3, 0]"); Vec3 v = node["start"].as(); -node["end"] = Vec3(2, -1, 0); -``` \ No newline at end of file +node["end"] = Vec3{2, -1, 0}; +``` + +Observe that in the above example the custom type is, decalred as +a struct, explicit default constructable and all its members are +exposed. For non default constructable types like + +```cpp +class NonDefCtorVec3 : public Vec3 { + using Vec3::x; + using Vec3::y; + using Vec3::z; + public: + NonDefCtorVec3(double x, double y, double z) + : Vec3() { this->x=x; this->y=y; this->z=z; + }; +}; +``` +a new API is available, that freshens up the signature of the 'convert::decode' +method and introduces the abortion of the deserialization process by throwing +an `DecodeException`. + +```cpp +namespace YAML { +template <> +struct convert { + static Node encode(const NonDefCtorVec3& rhs) { + return convert::encode(rhs); + } + + static NonDefCtorVec3 decode(const Node& node) { + if (!node.IsSequence() || node.size() != 3) { + throw YAML::conversion::DecodeException(); + } + return {node[0].as(), node[1].as(), node[2].as()}; + } +}; +} +``` + +The behavior is exactly the same + +```cpp +YAML::Node node = YAML::Load("start: [1, 3, 0]"); +NonDefCtorVec3 v = node["start"].as(); +node["end"] = NonDefCtorVec3(2, -1, 0); +``` diff --git a/include/yaml-cpp/exceptions.h b/include/yaml-cpp/exceptions.h index f6b2602ae..47884188a 100644 --- a/include/yaml-cpp/exceptions.h +++ b/include/yaml-cpp/exceptions.h @@ -298,6 +298,15 @@ class YAML_CPP_API BadFile : public Exception { BadFile(const BadFile&) = default; ~BadFile() YAML_CPP_NOEXCEPT override; }; + + +namespace conversion{ + class DecodeException : public std::runtime_error { + public: + DecodeException(const std::string& s="") : std::runtime_error(s) {}; + }; +} + } // namespace YAML #endif // EXCEPTIONS_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/include/yaml-cpp/node/api_switch.h b/include/yaml-cpp/node/api_switch.h new file mode 100644 index 000000000..716ab9819 --- /dev/null +++ b/include/yaml-cpp/node/api_switch.h @@ -0,0 +1,53 @@ +// +// Created by marcel on 3/3/22. +// + +#ifndef YAML_CPP_API_SWITCH_H +#define YAML_CPP_API_SWITCH_H + +#if defined(_MSC_VER) || \ + (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \ + (__GNUC__ >= 4)) // GCC supports "pragma once" correctly since 3.4 +#pragma once +#endif + +#include "yaml-cpp/node/node.h" +#include "yaml-cpp/exceptions.h" +#include + +namespace YAML{ +namespace detail { + + //detect the method of the new api + template + std::false_type has_decode_new_api(long); + + template + auto has_decode_new_api(int) + -> decltype( T::decode(std::declval()), std::true_type{}); + + template + struct static_api_switch; + + template<> //new api call-path + struct static_api_switch { + template + static T decode(const Node& node) { + return convert::decode(node); + } + }; + + template<> //old api call-path + struct static_api_switch { + template + static T decode(const Node& node) { + T t; + if (convert::decode(node, t)) + return t; + throw conversion::DecodeException(); + } + }; +} +} + +#endif // YAML_CPP_API_SWITCH_H diff --git a/include/yaml-cpp/node/convert.h b/include/yaml-cpp/node/convert.h index 8ab0cd4e2..ed77cf5b9 100644 --- a/include/yaml-cpp/node/convert.h +++ b/include/yaml-cpp/node/convert.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "yaml-cpp/binary.h" #include "yaml-cpp/node/impl.h" @@ -28,6 +29,7 @@ namespace YAML { class Binary; struct _Null; + template struct convert; } // namespace YAML @@ -57,6 +59,13 @@ struct convert { rhs.reset(node); return true; } + +// static Node decode(const Node& node) { +// throw std::runtime_error("this should not have been encountered"); +// Node rhs; +// rhs.reset(node); +// return rhs; +// } }; // std::string @@ -64,11 +73,10 @@ template <> struct convert { static Node encode(const std::string& rhs) { return Node(rhs); } - static bool decode(const Node& node, std::string& rhs) { + static std::string decode(const Node& node) { if (!node.IsScalar()) - return false; - rhs = node.Scalar(); - return true; + throw YAML::conversion::DecodeException(); + return node.Scalar(); } }; @@ -92,8 +100,10 @@ template <> struct convert<_Null> { static Node encode(const _Null& /* rhs */) { return Node(); } - static bool decode(const Node& node, _Null& /* rhs */) { - return node.IsNull(); + static _Null decode(const Node& node) { + if (!node.IsNull()) + throw YAML::conversion::DecodeException(); + return _Null(); } }; @@ -157,37 +167,35 @@ ConvertStreamTo(std::stringstream& stream, T& rhs) { return Node(stream.str()); \ } \ \ - static bool decode(const Node& node, type& rhs) { \ + static type decode(const Node& node) { \ if (node.Type() != NodeType::Scalar) { \ - return false; \ + throw YAML::conversion::DecodeException();; \ } \ const std::string& input = node.Scalar(); \ std::stringstream stream(input); \ stream.unsetf(std::ios::dec); \ if ((stream.peek() == '-') && std::is_unsigned::value) { \ - return false; \ + throw YAML::conversion::DecodeException(); \ } \ + type rhs; \ if (conversion::ConvertStreamTo(stream, rhs)) { \ - return true; \ + return rhs; \ } \ if (std::numeric_limits::has_infinity) { \ if (conversion::IsInfinity(input)) { \ - rhs = std::numeric_limits::infinity(); \ - return true; \ + return std::numeric_limits::infinity(); \ } else if (conversion::IsNegativeInfinity(input)) { \ - rhs = negative_op std::numeric_limits::infinity(); \ - return true; \ + return negative_op std::numeric_limits::infinity(); \ } \ } \ \ if (std::numeric_limits::has_quiet_NaN) { \ if (conversion::IsNaN(input)) { \ - rhs = std::numeric_limits::quiet_NaN(); \ - return true; \ + return std::numeric_limits::quiet_NaN(); \ } \ } \ \ - return false; \ + throw YAML::conversion::DecodeException(); \ } \ } @@ -223,7 +231,7 @@ template <> struct convert { static Node encode(bool rhs) { return rhs ? Node("true") : Node("false"); } - YAML_CPP_API static bool decode(const Node& node, bool& rhs); + YAML_CPP_API static bool decode(const Node& node); }; // std::map @@ -236,11 +244,11 @@ struct convert> { return node; } - static bool decode(const Node& node, std::map& rhs) { + static std::map decode(const Node& node) { if (!node.IsMap()) - return false; + throw YAML::conversion::DecodeException(); - rhs.clear(); + std::map rhs; for (const auto& element : node) #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: @@ -248,7 +256,7 @@ struct convert> { #else rhs[element.first.as()] = element.second.as(); #endif - return true; + return rhs; } }; @@ -262,11 +270,11 @@ struct convert> { return node; } - static bool decode(const Node& node, std::unordered_map& rhs) { + static std::unordered_map decode(const Node& node) { if (!node.IsMap()) - return false; + throw YAML::conversion::DecodeException(); - rhs.clear(); + std::unordered_map rhs; for (const auto& element : node) #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: @@ -274,7 +282,7 @@ struct convert> { #else rhs[element.first.as()] = element.second.as(); #endif - return true; + return rhs; } }; @@ -288,11 +296,11 @@ struct convert> { return node; } - static bool decode(const Node& node, std::vector& rhs) { + static std::vector decode(const Node& node) { if (!node.IsSequence()) - return false; + throw YAML::conversion::DecodeException(); - rhs.clear(); + std::vector rhs; for (const auto& element : node) #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: @@ -300,7 +308,7 @@ struct convert> { #else rhs.push_back(element.as()); #endif - return true; + return rhs; } }; @@ -314,11 +322,11 @@ struct convert> { return node; } - static bool decode(const Node& node, std::list& rhs) { + static std::list decode(const Node& node) { if (!node.IsSequence()) - return false; + throw YAML::conversion::DecodeException(); - rhs.clear(); + std::list rhs; for (const auto& element : node) #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: @@ -326,7 +334,7 @@ struct convert> { #else rhs.push_back(element.as()); #endif - return true; + return rhs; } }; @@ -341,11 +349,11 @@ struct convert> { return node; } - static bool decode(const Node& node, std::array& rhs) { - if (!isNodeValid(node)) { - return false; - } + static std::array decode(const Node& node) { + if (!isNodeValid(node)) + throw YAML::conversion::DecodeException(); + std::array rhs; for (auto i = 0u; i < node.size(); ++i) { #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: @@ -354,7 +362,7 @@ struct convert> { rhs[i] = node[i].as(); #endif } - return true; + return rhs; } private: @@ -373,12 +381,11 @@ struct convert> { return node; } - static bool decode(const Node& node, std::pair& rhs) { - if (!node.IsSequence()) - return false; - if (node.size() != 2) - return false; + static std::pair decode(const Node& node) { + if (!node.IsSequence() || node.size() != 2) + throw YAML::conversion::DecodeException(); + std::pair rhs; #if defined(__GNUC__) && __GNUC__ < 4 // workaround for GCC 3: rhs.first = node[0].template as(); @@ -391,7 +398,7 @@ struct convert> { #else rhs.second = node[1].as(); #endif - return true; + return rhs; } }; @@ -402,16 +409,17 @@ struct convert { return Node(EncodeBase64(rhs.data(), rhs.size())); } - static bool decode(const Node& node, Binary& rhs) { + static Binary decode(const Node& node) { if (!node.IsScalar()) - return false; + throw YAML::conversion::DecodeException(); std::vector data = DecodeBase64(node.Scalar()); if (data.empty() && !node.Scalar().empty()) - return false; + throw YAML::conversion::DecodeException(); + Binary rhs; rhs.swap(data); - return true; + return rhs; } }; } diff --git a/include/yaml-cpp/node/detail/impl.h b/include/yaml-cpp/node/detail/impl.h index b38038dfd..f574383d9 100644 --- a/include/yaml-cpp/node/detail/impl.h +++ b/include/yaml-cpp/node/detail/impl.h @@ -9,6 +9,7 @@ #include "yaml-cpp/node/detail/node.h" #include "yaml-cpp/node/detail/node_data.h" +#include "yaml-cpp/node/api_switch.h" #include #include @@ -98,19 +99,27 @@ struct remove_idx inline bool node::equals(const T& rhs, shared_memory_holder pMemory) { - T lhs; - if (convert::decode(Node(*this, pMemory), lhs)) { - return lhs == rhs; + try { + return static_api_switch>( + 0))::value>::template decode(Node(*this, pMemory)) == rhs; + } catch(const conversion::DecodeException& e) { + //throw; //prefer to throw over returning just the inability to deserialize + return false; //not doing this breaks upstream functionality + } catch (...) { + throw; } - return false; } inline bool node::equals(const char* rhs, shared_memory_holder pMemory) { - std::string lhs; - if (convert::decode(Node(*this, std::move(pMemory)), lhs)) { - return lhs == rhs; + try { + return static_api_switch>( + 0))::value>::template decode(Node(*this, std::move(pMemory))) == rhs; + } catch(const conversion::DecodeException& e) { + //throw; //prefer to throw over returning just the inability to deserialize + return false; //not doing this breaks upstream functionality + } catch (...) { + throw; } - return false; } // indexing diff --git a/include/yaml-cpp/node/impl.h b/include/yaml-cpp/node/impl.h index 312281f18..c565d6d0a 100644 --- a/include/yaml-cpp/node/impl.h +++ b/include/yaml-cpp/node/impl.h @@ -12,8 +12,10 @@ #include "yaml-cpp/node/detail/node.h" #include "yaml-cpp/node/iterator.h" #include "yaml-cpp/node/node.h" +#include "yaml-cpp/node/api_switch.h" #include #include +#include namespace YAML { inline Node::Node() @@ -97,10 +99,36 @@ struct as_if { if (!node.m_pNode) return fallback; - T t; - if (convert::decode(node, t)) - return t; - return fallback; + try { + return detail::static_api_switch>( + 0))::value>::template decode(node); + } catch (const conversion::DecodeException& e) { + return fallback; + } catch (...) { + throw; + } + }; +}; + +//specialize for Node +template +struct as_if { + explicit as_if(const Node& node_) : node(node_) {} + const Node& node; + + Node operator()(const S& fallback) const { + if (!node.m_pNode) + return fallback; + + try { + Node n; + n.reset(node); + return node; + } catch (const conversion::DecodeException& e) { + return fallback; + } catch (...) { + throw; + } } }; @@ -127,11 +155,15 @@ struct as_if { if (!node.m_pNode) throw TypedBadConversion(node.Mark()); - T t; - if (convert::decode(node, t)) - return t; - throw TypedBadConversion(node.Mark()); - } + try { + return detail::static_api_switch>( + 0))::value>::template decode(node); + } catch(const conversion::DecodeException& e) { + throw TypedBadConversion(node.Mark()); + } catch (...) { + throw; + } + }; }; template <> @@ -321,6 +353,16 @@ std::string key_to_string(const Key& key) { } // indexing +template +inline bool Node::ContainsKey(const Key& key) const { + EnsureNodeExists(); + if (! IsMap()) + return false; + detail::node* value = + static_cast(*m_pNode).get(key, m_pMemory); + return (bool)value; +} + template inline const Node Node::operator[](const Key& key) const { EnsureNodeExists(); diff --git a/include/yaml-cpp/node/node.h b/include/yaml-cpp/node/node.h index c9e9a0a4b..602036fc0 100644 --- a/include/yaml-cpp/node/node.h +++ b/include/yaml-cpp/node/node.h @@ -99,6 +99,8 @@ class YAML_CPP_API Node { // indexing template + bool ContainsKey(const Key& key) const; + template const Node operator[](const Key& key) const; template Node operator[](const Key& key); @@ -141,6 +143,7 @@ YAML_CPP_API bool operator==(const Node& lhs, const Node& rhs); YAML_CPP_API Node Clone(const Node& node); +//forward declare this customization point for all types template struct convert; } diff --git a/src/convert.cpp b/src/convert.cpp index 9658b3603..957aedaf2 100644 --- a/src/convert.cpp +++ b/src/convert.cpp @@ -38,9 +38,9 @@ bool IsFlexibleCase(const std::string& str) { } // namespace namespace YAML { -bool convert::decode(const Node& node, bool& rhs) { +bool convert::decode(const Node& node) { if (!node.IsScalar()) - return false; + throw YAML::conversion::DecodeException(); // we can't use iostream bool extraction operators as they don't // recognize all possible values in the table below (taken from @@ -55,20 +55,18 @@ bool convert::decode(const Node& node, bool& rhs) { }; if (!IsFlexibleCase(node.Scalar())) - return false; + throw YAML::conversion::DecodeException(); for (const auto& name : names) { if (name.truename == tolower(node.Scalar())) { - rhs = true; return true; } if (name.falsename == tolower(node.Scalar())) { - rhs = false; - return true; + return false; } } - return false; + throw YAML::conversion::DecodeException(); } } // namespace YAML diff --git a/test/node/node_test.cpp b/test/node/node_test.cpp index d4367c502..6dd4ba773 100644 --- a/test/node/node_test.cpp +++ b/test/node/node_test.cpp @@ -42,6 +42,30 @@ template using CustomList = std::list>; template > using CustomMap = std::map>>; template , class P=std::equal_to> using CustomUnorderedMap = std::unordered_map>>; +struct Vec3 { + double x, y, z; + bool operator==(const Vec3& rhs) const { + return x == rhs.x && y == rhs.y && z == rhs.z; + } +}; + +class NonDefCtorVec3 : public Vec3 { + using Vec3::x; + using Vec3::y; + using Vec3::z; + public: + NonDefCtorVec3(double x, double y, double z) + : Vec3() { + this->x=x; + this->y=y; + this->z=z; + }; + bool operator==(const NonDefCtorVec3& rhs) const { + return x == rhs.x && y == rhs.y && z == rhs.z; + } +}; + + } // anonymous namespace using ::testing::AnyOf; @@ -56,6 +80,45 @@ using ::testing::Eq; } namespace YAML { + +//define custom convert structs +template<> +struct convert { + static Node encode(const Vec3& rhs) { + Node node; + node.push_back(rhs.x); + node.push_back(rhs.y); + node.push_back(rhs.z); + return node; + } + + static bool decode(const Node& node, Vec3& rhs) { + if(!node.IsSequence() || node.size() != 3) { + return false; + } + + rhs.x = node[0].as(); + rhs.y = node[1].as(); + rhs.z = node[2].as(); + return true; + } +}; + +template <> +struct convert { + static Node encode(const NonDefCtorVec3& rhs) { + return convert::encode(rhs); + } + + static NonDefCtorVec3 decode(const Node& node) { + if (!node.IsSequence() || node.size() != 3) { + throw YAML::conversion::DecodeException(); + } + return {node[0].as(), node[1].as(), node[2].as()}; + } +}; + + namespace { TEST(NodeTest, SimpleScalar) { Node node = Node("Hello, World!"); @@ -674,6 +737,24 @@ TEST(NodeTest, AccessNonexistentKeyOnConstNode) { ASSERT_FALSE(other["5"]); } +TEST(NodeTest, CustomClassDecoding) { + YAML::Node node; + node.push_back(1.0); + node.push_back(2.0); + node.push_back(3.0); + ASSERT_TRUE(node.IsSequence()); + EXPECT_EQ(node.as(), (Vec3{1.0, 2.0, 3.0})); +} + +TEST(NodeTest, CustomNonDefaultConstructibleClassDecoding) { + YAML::Node node; + node.push_back(1.0); + node.push_back(2.0); + node.push_back(3.0); + ASSERT_TRUE(node.IsSequence()); + EXPECT_EQ(node.as(), (NonDefCtorVec3{1.0, 2.0, 3.0})); +} + class NodeEmitterTest : public ::testing::Test { protected: void ExpectOutput(const std::string& output, const Node& node) {