mapping::Tree: better tests, improved attributes, improved interface of oatpp::Tree

This commit is contained in:
Leonid Stryzhevskyi 2024-05-10 04:26:38 +03:00
parent 917dd454ce
commit 6765c3e591
12 changed files with 406 additions and 27 deletions

View File

@ -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.
---

View File

@ -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<oatpp::Tree>(user); // remap to tree
auto fields = remapper.remap<oatpp::Fields<oatpp::Tree>>(user); // remap to Fields
auto otherDto = remapper.remap<oatpp::Object<OtherDto>>(user); // remap to OtherDto
auto values = remapper.remap<oatpp::Vector<oatpp::Tree>>(user); // remap to Vector
oatpp::String name = values[0];
v_uint32 age = values[1];
```
## Restructuring
### Files

View File

@ -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<const Tree*>(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);
}

View File

@ -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<class Wrapper>
Wrapper remap(const Tree& tree, ErrorStack& errorStack) const {
auto toType = Wrapper::Class::getType();
return remap(tree, toType, errorStack).template cast<Wrapper>();
}
template<class Wrapper>
Wrapper remap(const Tree& tree) const {
auto toType = Wrapper::Class::getType();
ErrorStack errorStack;
const auto& result = remap(tree, toType, errorStack).template cast<Wrapper>();
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<class Wrapper>

View File

@ -36,8 +36,10 @@ Tree::Attributes::Attributes()
{}
Tree::Attributes::Attributes(const Attributes& other)
: m_attributes(other.m_attributes != nullptr ? new std::unordered_map<type::String, type::String>(*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<type::String, type::String>(*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<type::String, std::reference_wrapper<type::String>> 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<type::String, std::reference_wrapper<const type::String>> 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;
}

View File

@ -72,7 +72,14 @@ public:
class Attributes {
private:
std::unordered_map<type::String, type::String>* m_attributes;
struct Attrs {
std::unordered_map<type::String, type::String> map;
std::vector<std::pair<std::weak_ptr<std::string>, 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<type::String, std::reference_wrapper<type::String>> operator [] (v_uint64 index);
std::pair<type::String, std::reference_wrapper<const type::String>> operator [] (v_uint64 index) const;
type::String get(const type::String& key) const;
bool empty() const;
v_uint64 size() const;
};

View File

@ -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;

View File

@ -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<mapping::Tree>();
}
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<mapping::Tree>();
}
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<mapping::Tree>();
}
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<mapping::Tree>();
}
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];
}
}}}

View File

@ -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;
};

View File

@ -247,8 +247,6 @@ void runTests() {
}
}
}

View File

@ -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<oatpp::UnorderedFields<String>>(tree[0]);
OATPP_ASSERT(map->size() == 2)
OATPP_ASSERT(map["field_1"] == "val1")
OATPP_ASSERT(map["field_2"] == "val2")
}
{
auto map = remapper.remap<oatpp::UnorderedFields<String>>(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<oatpp::UnorderedFields<String>>(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;
}
}
}}}

View File

@ -36,6 +36,8 @@ namespace {
template<typename T>
void testTreeValue(T value) {
OATPP_LOGD("TEST", "Test value retrieval for '%s'", Tree::NodePrimitiveType<T>::name)
Tree node;
//node.setValue<T>(value);
@ -70,6 +72,7 @@ void TreeTest::onRun() {
testTreeValue<v_float64>(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<Tree> 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<v_int32>(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())
}
}
}}}