diff --git a/README.md b/README.md index 70183898..aa2a491b 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ **News** -- ⚠️ Attention! Oat++ main repo is bumping it's version to 1.4.0. While 1.4.0 is **IN DEVELOPMENT** use `1.3.0-latest` tag. +- ⚠️ Attention! Oat++ main repo is bumping its version to 1.4.0. While 1.4.0 is **IN DEVELOPMENT** use `1.3.0-latest` tag. +- Follow the [changelog](https://github.com/oatpp/oatpp/blob/master/changelog/1.4.0.md) for news and features in version `1.4.0`. +- Consider supporting Oat++ via the [GitHub sponsors](https://github.com/sponsors/oatpp) page. --- diff --git a/changelog/1.4.0.md b/changelog/1.4.0.md index bfee515d..19bcd97d 100644 --- a/changelog/1.4.0.md +++ b/changelog/1.4.0.md @@ -8,6 +8,8 @@ Contents: - [URL Encoder And Decoder](#url-encoder-and-decoder) - [Introduce async::ConditionVariable](#async-condition-variable) +- [oatpp::Tree](#oatpptree) +- [Remapper](#remapper) - [Restructuring](#restructuring) @@ -49,6 +51,61 @@ OATPP_ASSERT(decoded == data) ... ``` +## oatpp::Tree + +New mapping-enabled type `oatpp::Tree` for flexible data access. + +```cpp +ENDPOINT("POST", "users", createUser, + BODY_DTO(oatpp::Tree, user)) +{ + oatpp::String name = user["name"]; + v_uint16 age = user["age"]; + + auto& subs = user["subscriptions"].getVector(); + for(auto& s : subs) { + ... + } + ... +} +``` + +Any node of oatpp::Tree can be mapped to DTO or to any mapping-enabled oatpp type - see [Remapper](#remapper) + +## Remapper + +`oatpp::data::mapping::Remapper`. + +Remapper can be user to remap any oatpp type to any oatpp type. +UnorderedFields can be mapped to DTOs, DTOs to vectors of values, vector items to other DTOs, DTOs to Trees, etc... + +```cpp +class User : public oatpp::DTO { + + DTO_INIT(User, DTO) + + DTO_FIELD(String, name); + DTO_FIELD(UInt32, age); + +}; + +... + +oatpp::data::mapping::Remapper remapper; + +auto user = User::createShared(); +user->name = "Jane"; +user->age = "25"; + +auto tree = remapper.remap(user); // remap to tree +auto fields = remapper.remap>(user); // remap to Fields +auto otherDto = remapper.remap>(user); // remap to OtherDto +auto values = remapper.remap>(user); // remap to Vector + +oatpp::String name = values[0]; +v_uint32 age = values[1]; +``` + ## Restructuring ### Files diff --git a/src/oatpp/data/mapping/ObjectRemapper.cpp b/src/oatpp/data/mapping/ObjectRemapper.cpp index ed301307..f9b43a5f 100644 --- a/src/oatpp/data/mapping/ObjectRemapper.cpp +++ b/src/oatpp/data/mapping/ObjectRemapper.cpp @@ -26,9 +26,9 @@ namespace oatpp { namespace data { namespace mapping { -oatpp::Void ObjectRemapper::mapTree(const Tree* tree, const oatpp::Type* toType, ErrorStack& errorStack) const { +oatpp::Void ObjectRemapper::remap(const Tree& tree, const oatpp::Type* toType, ErrorStack& errorStack) const { TreeToObjectMapper::State state; - state.tree = tree; + state.tree = std::addressof(tree); state.config = &m_treeToObjectConfig; const auto & result = m_treeToObjectMapper.map(state, toType); if(!state.errorStack.empty()) { @@ -40,10 +40,14 @@ oatpp::Void ObjectRemapper::mapTree(const Tree* tree, const oatpp::Type* toType, oatpp::Void ObjectRemapper::remap(const oatpp::Void& polymorph, const oatpp::Type* toType, ErrorStack& errorStack) const { + if(polymorph == nullptr) { + return nullptr; + } + /* if polymorph is a Tree - we can map it right away */ if(polymorph.getValueType() == oatpp::Tree::Class::getType()) { auto tree = static_cast(polymorph.get()); - return mapTree(tree, toType, errorStack); + return remap(*tree, toType, errorStack); } Tree tree; @@ -64,7 +68,7 @@ oatpp::Void ObjectRemapper::remap(const oatpp::Void& polymorph, const oatpp::Typ return oatpp::Tree(std::move(tree)); } - return mapTree(&tree, toType, errorStack); + return remap(tree, toType, errorStack); } diff --git a/src/oatpp/data/mapping/ObjectRemapper.hpp b/src/oatpp/data/mapping/ObjectRemapper.hpp index 979c0a47..674a0804 100644 --- a/src/oatpp/data/mapping/ObjectRemapper.hpp +++ b/src/oatpp/data/mapping/ObjectRemapper.hpp @@ -33,8 +33,6 @@ namespace oatpp { namespace data { namespace mapping { class ObjectRemapper { -protected: - oatpp::Void mapTree(const Tree* tree, const oatpp::Type* toType, ErrorStack& errorStack) const; protected: ObjectToTreeMapper::Config m_objectToTreeConfig; TreeToObjectMapper::Config m_treeToObjectConfig; @@ -45,6 +43,25 @@ public: ObjectRemapper() = default; virtual ~ObjectRemapper() = default; + oatpp::Void remap(const Tree& tree, const oatpp::Type* toType, ErrorStack& errorStack) const; + + template + Wrapper remap(const Tree& tree, ErrorStack& errorStack) const { + auto toType = Wrapper::Class::getType(); + return remap(tree, toType, errorStack).template cast(); + } + + template + Wrapper remap(const Tree& tree) const { + auto toType = Wrapper::Class::getType(); + ErrorStack errorStack; + const auto& result = remap(tree, toType, errorStack).template cast(); + if(!errorStack.empty()) { + throw MappingError(std::move(errorStack)); + } + return result; + } + oatpp::Void remap(const oatpp::Void& polymorph, const oatpp::Type* toType, ErrorStack& errorStack) const; template diff --git a/src/oatpp/data/mapping/Tree.cpp b/src/oatpp/data/mapping/Tree.cpp index ef6db988..3d52bffb 100644 --- a/src/oatpp/data/mapping/Tree.cpp +++ b/src/oatpp/data/mapping/Tree.cpp @@ -36,8 +36,10 @@ Tree::Attributes::Attributes() {} Tree::Attributes::Attributes(const Attributes& other) - : m_attributes(other.m_attributes != nullptr ? new std::unordered_map(*other.m_attributes) : nullptr) -{} + : Attributes() +{ + operator=(other); +} Tree::Attributes::Attributes(Attributes&& other) noexcept : m_attributes(other.m_attributes) @@ -46,37 +48,101 @@ Tree::Attributes::Attributes(Attributes&& other) noexcept } Tree::Attributes& Tree::Attributes::operator = (const Attributes& other) { + if(other.m_attributes) { + if(m_attributes) { *m_attributes = *other.m_attributes; } else { - m_attributes = new std::unordered_map(*other.m_attributes); + m_attributes = new Attrs(*other.m_attributes); } + + for(auto & po : m_attributes->order){ + po.second = &m_attributes->map.at(po.first.lock()); + } + } else { delete m_attributes; m_attributes = nullptr; } + return *this; } Tree::Attributes& Tree::Attributes::operator = (Attributes&& other) noexcept { + delete m_attributes; + m_attributes = other.m_attributes; other.m_attributes = nullptr; + return *this; } Tree::Attributes::~Attributes() { delete m_attributes; + m_attributes = nullptr; +} + +void Tree::Attributes::initAttributes() { + if(m_attributes == nullptr) { + m_attributes = new Attrs(); + } +} + +type::String& Tree::Attributes::operator [] (const type::String& key) { + initAttributes(); + auto it = m_attributes->map.find(key); + if(it == m_attributes->map.end()) { + auto& result = m_attributes->map[key]; + m_attributes->order.emplace_back(key.getPtr(), &result); + return result; + } + return it->second; +} + +const type::String& Tree::Attributes::operator [] (const type::String& key) const { + if(m_attributes != nullptr) { + auto it = m_attributes->map.find(key); + if (it != m_attributes->map.end()) { + return it->second; + } + } + throw std::runtime_error("[oatpp::data::mapping::Tree::Attributes::operator []]: const operator[] can't add items."); +} + +std::pair> Tree::Attributes::operator [] (v_uint64 index) { + if(m_attributes != nullptr) { + auto &item = m_attributes->order.at(index); + return {item.first.lock(), *item.second}; + } + throw std::runtime_error("[oatpp::data::mapping::Tree::Attributes::operator []]: const operator[] can't get item - empty attributes."); +} + +std::pair> Tree::Attributes::operator [] (v_uint64 index) const { + if(m_attributes != nullptr) { + auto &item = m_attributes->order.at(index); + return {item.first.lock(), *item.second}; + } + throw std::runtime_error("[oatpp::data::mapping::Tree::Attributes::operator []]: const operator[] can't get item - empty attributes."); +} + +type::String Tree::Attributes::get(const type::String& key) const { + if(m_attributes == nullptr) return nullptr; + auto it = m_attributes->map.find(key); + if(it != m_attributes->map.end()) { + return it->second; + } + return nullptr; } bool Tree::Attributes::empty() const { - return m_attributes == nullptr || m_attributes->empty(); + return m_attributes == nullptr || m_attributes->map.empty(); } v_uint64 Tree::Attributes::size() const { if(m_attributes) { - return m_attributes->size(); + return m_attributes->map.size(); } return 0; } @@ -725,13 +791,9 @@ TreeMap::TreeMap(TreeMap&& other) noexcept { TreeMap& TreeMap::operator = (const TreeMap& other) { m_map = other.m_map; - m_order.resize(other.m_order.size()); - for(v_uint64 i = 0; i < other.m_order.size(); i ++) { - auto& po = other.m_order[i]; - auto& pt = m_order[i]; - pt.first = po.first; - - pt.second = &m_map.at(po.first.lock()); + m_order = other.m_order; + for(auto & po : m_order){ + po.second = &m_map.at(po.first.lock()); } return *this; } diff --git a/src/oatpp/data/mapping/Tree.hpp b/src/oatpp/data/mapping/Tree.hpp index 7817a3a6..5b8f2383 100644 --- a/src/oatpp/data/mapping/Tree.hpp +++ b/src/oatpp/data/mapping/Tree.hpp @@ -72,7 +72,14 @@ public: class Attributes { private: - std::unordered_map* m_attributes; + struct Attrs { + std::unordered_map map; + std::vector, type::String*>> order; + }; + private: + void initAttributes(); + private: + Attrs* m_attributes; public: Attributes(); @@ -84,8 +91,15 @@ public: ~Attributes(); - bool empty() const; + type::String& operator [] (const type::String& key); + const type::String& operator [] (const type::String& key) const; + std::pair> operator [] (v_uint64 index); + std::pair> operator [] (v_uint64 index) const; + + type::String get(const type::String& key) const; + + bool empty() const; v_uint64 size() const; }; diff --git a/src/oatpp/data/type/Object.hpp b/src/oatpp/data/type/Object.hpp index 204e0d33..bd0634ae 100644 --- a/src/oatpp/data/type/Object.hpp +++ b/src/oatpp/data/type/Object.hpp @@ -435,6 +435,7 @@ private: public: typedef oatpp::data::type::Void Void; typedef oatpp::data::type::Any Any; + typedef oatpp::data::type::Tree Tree; typedef oatpp::data::type::String String; typedef oatpp::data::type::Int8 Int8; typedef oatpp::data::type::UInt8 UInt8; diff --git a/src/oatpp/data/type/Tree.cpp b/src/oatpp/data/type/Tree.cpp index 7a69c9de..91fc3661 100644 --- a/src/oatpp/data/type/Tree.cpp +++ b/src/oatpp/data/type/Tree.cpp @@ -123,6 +123,27 @@ bool Tree::operator != (const Tree& other) const { return !operator == (other); } +mapping::Tree* Tree::operator->() { + if(!m_ptr) { + m_ptr = std::make_shared(); + } + return m_ptr.get(); +} + +mapping::Tree* Tree::operator->() const { + if(!m_ptr) { + throw std::runtime_error("[oatpp::data::type::Tree::operator ->()]: null-pointer exception"); + } + return m_ptr.get(); +} + +mapping::Tree& Tree::operator*() { + if(!m_ptr) { + m_ptr = std::make_shared(); + } + return *m_ptr; +} + const mapping::Tree& Tree::operator*() const { if(!m_ptr) { throw std::runtime_error("[oatpp::data::type::Tree::operator *()]: null-pointer exception"); @@ -130,12 +151,32 @@ const mapping::Tree& Tree::operator*() const { return *m_ptr; } -mapping::Tree& Tree::operator*() { +mapping::Tree& Tree::operator [] (const String& key) { if(!m_ptr) { - throw std::runtime_error("[oatpp::data::type::Tree::operator *()]: null-pointer exception"); + m_ptr = std::make_shared(); } - return *m_ptr; + return (*m_ptr)[key]; } +const mapping::Tree& Tree::operator [] (const String& key) const { + if(!m_ptr) { + throw std::runtime_error("[oatpp::data::type::Tree::operator []]: null-pointer exception"); + } + return (*m_ptr)[key]; +} + +mapping::Tree& Tree::operator [] (v_uint64 index) { + if(!m_ptr) { + m_ptr = std::make_shared(); + } + return (*m_ptr)[index]; +} + +const mapping::Tree& Tree::operator [] (v_uint64 index) const { + if(!m_ptr) { + throw std::runtime_error("[oatpp::data::type::Tree::operator []]: null-pointer exception"); + } + return (*m_ptr)[index]; +} }}} diff --git a/src/oatpp/data/type/Tree.hpp b/src/oatpp/data/type/Tree.hpp index b59f4f4a..8aebb596 100644 --- a/src/oatpp/data/type/Tree.hpp +++ b/src/oatpp/data/type/Tree.hpp @@ -26,6 +26,7 @@ #define oatpp_data_type_Tree_hpp #include "./Type.hpp" +#include "./Primitive.hpp" namespace oatpp { namespace data { namespace mapping { @@ -108,8 +109,17 @@ public: bool operator == (const Tree& other) const; bool operator != (const Tree& other) const; - const mapping::Tree& operator*() const; + mapping::Tree* operator->(); + mapping::Tree* operator->() const; + mapping::Tree& operator*(); + const mapping::Tree& operator*() const; + + mapping::Tree& operator [] (const String& key); + const mapping::Tree& operator [] (const String& key) const; + + mapping::Tree& operator [] (v_uint64 index); + const mapping::Tree& operator [] (v_uint64 index) const; }; diff --git a/test/oatpp/AllTestsMain.cpp b/test/oatpp/AllTestsMain.cpp index 64ac350c..7720e06e 100644 --- a/test/oatpp/AllTestsMain.cpp +++ b/test/oatpp/AllTestsMain.cpp @@ -247,8 +247,6 @@ void runTests() { } - - } } diff --git a/test/oatpp/data/mapping/ObjectRemapperTest.cpp b/test/oatpp/data/mapping/ObjectRemapperTest.cpp index 863bc5b0..dd8c3000 100644 --- a/test/oatpp/data/mapping/ObjectRemapperTest.cpp +++ b/test/oatpp/data/mapping/ObjectRemapperTest.cpp @@ -168,6 +168,57 @@ void ObjectRemapperTest::onRun() { OATPP_ASSERT(vec[4] == nullptr) } + { + + OATPP_LOGD(TAG, "Remap tree fragments") + + Tree tree; + tree.setVector(3); + + tree[0]["field_1"] = "val1"; + tree[0]["field_2"] = "val2"; + + tree[1]["field_1"] = "val1.2"; + tree[1]["field_2"] = "val2.2"; + + tree[2]["field_1"] = "val1.3"; + tree[2]["field_2"] = "val2.3"; + + { + auto map = remapper.remap>(tree[0]); + OATPP_ASSERT(map->size() == 2) + OATPP_ASSERT(map["field_1"] == "val1") + OATPP_ASSERT(map["field_2"] == "val2") + } + + { + auto map = remapper.remap>(tree[1]); + OATPP_ASSERT(map->size() == 2) + OATPP_ASSERT(map["field_1"] == "val1.2") + OATPP_ASSERT(map["field_2"] == "val2.2") + } + + { + auto map = remapper.remap>(tree[2]); + OATPP_ASSERT(map->size() == 2) + OATPP_ASSERT(map["field_1"] == "val1.3") + OATPP_ASSERT(map["field_2"] == "val2.3") + } + + } + + { + oatpp::Tree tree; + OATPP_ASSERT(tree == nullptr) + + OATPP_ASSERT(tree->isUndefined()) // implicitly initialized + OATPP_ASSERT(tree != nullptr) + + tree["hello"] = "world"; + std::cout << *tree->debugPrint() << std::endl; + + } + } }}} diff --git a/test/oatpp/data/mapping/TreeTest.cpp b/test/oatpp/data/mapping/TreeTest.cpp index ef7721a8..62a44f35 100644 --- a/test/oatpp/data/mapping/TreeTest.cpp +++ b/test/oatpp/data/mapping/TreeTest.cpp @@ -36,6 +36,8 @@ namespace { template void testTreeValue(T value) { + OATPP_LOGD("TEST", "Test value retrieval for '%s'", Tree::NodePrimitiveType::name) + Tree node; //node.setValue(value); @@ -70,6 +72,7 @@ void TreeTest::onRun() { testTreeValue(16); { + OATPP_LOGD(TAG, "Case 1") Tree node; oatpp::String original = "Hello World!"; node.setString(original); @@ -79,6 +82,7 @@ void TreeTest::onRun() { } { + OATPP_LOGD(TAG, "Case 2") Tree node1; Tree node2; @@ -93,6 +97,7 @@ void TreeTest::onRun() { } { + OATPP_LOGD(TAG, "Case 3") Tree node1; Tree node2; @@ -105,6 +110,7 @@ void TreeTest::onRun() { } { + OATPP_LOGD(TAG, "Case 4") std::vector originalVector(10); for(v_uint32 i = 0; i < 10; i ++) { originalVector.at(i).setValue(i); @@ -132,6 +138,7 @@ void TreeTest::onRun() { } { + OATPP_LOGD(TAG, "Case 5") TreeMap originalMap; for(v_uint32 i = 0; i < 10; i ++) { originalMap["node_" + utils::Conversion::int32ToStr(static_cast(i))].setValue(i); @@ -156,6 +163,7 @@ void TreeTest::onRun() { } { + OATPP_LOGD(TAG, "Case 6") Tree article; oatpp::Tree ot; @@ -176,6 +184,120 @@ void TreeTest::onRun() { } + { + + OATPP_LOGD(TAG, "Attributes Case 1") + OATPP_LOGD(TAG, "size of Tree::Attributes='%lu'", sizeof(Tree::Attributes)) + + Tree::Attributes attr; + + attr["key1"] = "value1"; + attr["key1"] = "value1.2"; + attr["key2"] = "value2"; + + OATPP_ASSERT(attr.size() == 2) + + OATPP_ASSERT(attr["key1"] == "value1.2") + OATPP_ASSERT(attr["key2"] == "value2") + OATPP_ASSERT(attr["key3"] == nullptr) // key3 added + + OATPP_ASSERT(attr.size() == 3) + + OATPP_ASSERT(attr[0].second.get() == "value1.2") + OATPP_ASSERT(attr[1].second.get() == "value2") + OATPP_ASSERT(attr[2].second.get() == nullptr) + + } + + { + + OATPP_LOGD(TAG, "Attributes Case 2") + + Tree::Attributes attr1; + Tree::Attributes attr2; + + attr1["key1"] = "value1"; + attr1["key2"] = "value2"; + attr1["key3"] = nullptr; + + attr2["key1"] = "v1"; + attr2["key2"] = "v2"; + + attr2 = attr1; + + attr1["key1"] = "1"; + attr1["key2"] = "2"; + attr1["key3"] = "3"; + + OATPP_ASSERT(attr1[0].second.get() == "1") + OATPP_ASSERT(attr1[1].second.get() == "2") + OATPP_ASSERT(attr1[2].second.get() == "3") + + OATPP_ASSERT(attr1["key1"] == "1") + OATPP_ASSERT(attr1["key2"] == "2") + OATPP_ASSERT(attr1["key3"] == "3") + + OATPP_ASSERT(attr2[0].second.get() == "value1") + OATPP_ASSERT(attr2[1].second.get() == "value2") + OATPP_ASSERT(attr2[2].second.get() == nullptr) + + OATPP_ASSERT(attr2["key1"] == "value1") + OATPP_ASSERT(attr2["key2"] == "value2") + OATPP_ASSERT(attr2["key3"] == nullptr) + + Tree::Attributes attr3; + attr2 = attr3; + + OATPP_ASSERT(attr2.empty()) + + } + + { + + OATPP_LOGD(TAG, "Attributes Case 3") + + Tree tree1; + Tree tree2; + + tree1 = "hello"; + tree2 = "world"; + + tree1.attributes()["key1"] = "value1"; + tree1.attributes()["key2"] = "value2"; + tree1.attributes()["key3"] = nullptr; + + tree2.attributes()["key1"] = "v1"; + tree2.attributes()["key2"] = "v2"; + + tree2 = tree1; + + tree1.attributes()["key1"] = "1"; + tree1.attributes()["key2"] = "2"; + tree1.attributes()["key3"] = "3"; + + OATPP_ASSERT(tree1.attributes()[0].second.get() == "1") + OATPP_ASSERT(tree1.attributes()[1].second.get() == "2") + OATPP_ASSERT(tree1.attributes()[2].second.get() == "3") + + OATPP_ASSERT(tree1.attributes()["key1"] == "1") + OATPP_ASSERT(tree1.attributes()["key2"] == "2") + OATPP_ASSERT(tree1.attributes()["key3"] == "3") + + OATPP_ASSERT(tree2.attributes()[0].second.get() == "value1") + OATPP_ASSERT(tree2.attributes()[1].second.get() == "value2") + OATPP_ASSERT(tree2.attributes()[2].second.get() == nullptr) + + OATPP_ASSERT(tree2.attributes()["key1"] == "value1") + OATPP_ASSERT(tree2.attributes()["key2"] == "value2") + OATPP_ASSERT(tree2.attributes()["key3"] == nullptr) + + Tree tree3; + tree2 = tree3; + + OATPP_ASSERT(tree2.attributes().empty()) + + } + } }}}