mirror of
https://gitee.com/zyjblog/oatpp.git
synced 2025-01-05 17:42:23 +08:00
Merge pull request #243 from oatpp/dto_fields_description
Dto fields description
This commit is contained in:
commit
8aae916f92
@ -12,6 +12,7 @@ Contents:
|
||||
- [Type oatpp::Any](#type-oatppany)
|
||||
- [Type oatpp::Enum](#object-mapping-enum)
|
||||
- [DTO - Hashcode & Equals](#dto-hashcode-and-equals)
|
||||
- [DTO - Fields Annotation](#dto-fields-annotation)
|
||||
|
||||
## No more explicit ObjectWrapper
|
||||
|
||||
@ -297,3 +298,25 @@ The `DTO_HC_EQ` macro works taking into account the `DTO_HC_EQ` declared in the
|
||||
If no `DTO_HC_EQ` is declared in none of the DTO's parent classes the default behavior is:
|
||||
- `std::hash` - is `v_uint64` representation of object address.
|
||||
- operators `==` and `!=` - is comparison of object addresses.
|
||||
|
||||
## DTO Fields Annotation
|
||||
|
||||
Now it's possible to add a description for DTO fields, which will be automatically
|
||||
displayed in swagger-UI.
|
||||
|
||||
```cpp
|
||||
class MyDto : public oatpp::Object {
|
||||
|
||||
DTO_INIT(MyDto, Object)
|
||||
|
||||
DTO_FIELD_INFO(id) {
|
||||
info->description = "identifier";
|
||||
}
|
||||
DTO_FIELD(String, id);
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
*Note: The `description` is currently the only info you can add to the DTO field
|
||||
(This may be extended later). In order to provide the list of possible values - use the
|
||||
new Enum feature - [Type oatpp::Enum](#object-mapping-enum).*
|
||||
|
@ -31,46 +31,29 @@
|
||||
* @param TYPE_EXTEND - name of the parent DTO class. If DTO extends &id:oatpp::data::mapping::type::Object; TYPE_EXETENDS should be `Object`.
|
||||
*/
|
||||
#define DTO_INIT(TYPE_NAME, TYPE_EXTEND) \
|
||||
template<class __Z__T__PARAM> \
|
||||
friend class oatpp::data::mapping::type::__class::Object; \
|
||||
public: \
|
||||
typedef TYPE_NAME Z__CLASS; \
|
||||
typedef TYPE_EXTEND Z__CLASS_EXTENDED; \
|
||||
typedef oatpp::data::mapping::type::DTOWrapper<Z__CLASS> ObjectWrapper; \
|
||||
typedef ObjectWrapper __Wrapper; \
|
||||
public: \
|
||||
OBJECT_POOL(DTO_OBJECT_POOL_##TYPE_NAME, TYPE_NAME, 32) \
|
||||
SHARED_OBJECT_POOL(SHARED_DTO_OBJECT_POOL_##TYPE_NAME, TYPE_NAME, 32) \
|
||||
protected: \
|
||||
oatpp::data::mapping::type::Type::Properties* Z__CLASS_INIT_FIELDS(oatpp::data::mapping::type::Type::Properties* properties, \
|
||||
oatpp::data::mapping::type::Type::Properties* extensionProperties) { \
|
||||
static oatpp::data::mapping::type::Type::Properties* ptr = Z__CLASS_EXTEND(properties, extensionProperties); \
|
||||
return ptr; \
|
||||
} \
|
||||
public: \
|
||||
TYPE_NAME() \
|
||||
{ \
|
||||
Z__CLASS_INIT_FIELDS(Z__CLASS::Z__CLASS_GET_FIELDS_MAP(), TYPE_EXTEND::Z__CLASS_GET_FIELDS_MAP()); \
|
||||
} \
|
||||
public: \
|
||||
\
|
||||
static ObjectWrapper createShared(){ \
|
||||
return ObjectWrapper(SHARED_DTO_OBJECT_POOL_##TYPE_NAME::allocateShared()); \
|
||||
private: \
|
||||
static const char* Z__CLASS_TYPE_NAME() { \
|
||||
return #TYPE_NAME; \
|
||||
} \
|
||||
\
|
||||
static oatpp::data::mapping::type::Type::Properties* Z__CLASS_GET_FIELDS_MAP(){ \
|
||||
static oatpp::data::mapping::type::Type::Properties map = oatpp::data::mapping::type::Type::Properties(); \
|
||||
return ↦ \
|
||||
} \
|
||||
public: \
|
||||
\
|
||||
static oatpp::data::mapping::type::Void Z__CLASS_OBJECT_CREATOR(){ \
|
||||
return oatpp::data::mapping::type::Void(SHARED_DTO_OBJECT_POOL_##TYPE_NAME::allocateShared(), Z__CLASS_GET_TYPE()); \
|
||||
} \
|
||||
TYPE_NAME() = default; \
|
||||
\
|
||||
static oatpp::data::mapping::type::Type* Z__CLASS_GET_TYPE(){ \
|
||||
static oatpp::data::mapping::type::Type type(oatpp::data::mapping::type::__class::AbstractObject::CLASS_ID, \
|
||||
#TYPE_NAME, \
|
||||
&Z__CLASS_OBJECT_CREATOR, \
|
||||
Z__CLASS_GET_FIELDS_MAP()); \
|
||||
return &type; \
|
||||
template<typename ... Args> \
|
||||
static ObjectWrapper createShared(Args... args){ \
|
||||
return ObjectWrapper(std::make_shared<Z__CLASS>(args...), ObjectWrapper::Class::getType()); \
|
||||
}
|
||||
|
||||
// Fields
|
||||
@ -92,9 +75,13 @@ static oatpp::data::mapping::type::Type::Property* Z__PROPERTY_SINGLETON_##NAME(
|
||||
return property; \
|
||||
} \
|
||||
\
|
||||
static bool Z__PROPERTY_INIT_##NAME(... /* default initializer for all cases */) { \
|
||||
Z__CLASS_GET_FIELDS_MAP()->pushBack(Z__PROPERTY_SINGLETON_##NAME()); \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static TYPE::__Wrapper Z__PROPERTY_INITIALIZER_PROXY_##NAME() { \
|
||||
static oatpp::data::mapping::type::Type::Property* property = \
|
||||
Z__CLASS_GET_FIELDS_MAP()->pushBack(Z__PROPERTY_SINGLETON_##NAME()); \
|
||||
static bool initialized = Z__PROPERTY_INIT_##NAME(1 /* init info if found */); \
|
||||
return TYPE::__Wrapper(); \
|
||||
} \
|
||||
\
|
||||
@ -117,9 +104,13 @@ static oatpp::data::mapping::type::Type::Property* Z__PROPERTY_SINGLETON_##NAME(
|
||||
return property; \
|
||||
} \
|
||||
\
|
||||
static bool Z__PROPERTY_INIT_##NAME(... /* default initializer for all cases */) { \
|
||||
Z__CLASS_GET_FIELDS_MAP()->pushBack(Z__PROPERTY_SINGLETON_##NAME()); \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static TYPE::__Wrapper Z__PROPERTY_INITIALIZER_PROXY_##NAME() { \
|
||||
static oatpp::data::mapping::type::Type::Property* property = \
|
||||
Z__CLASS_GET_FIELDS_MAP()->pushBack(Z__PROPERTY_SINGLETON_##NAME()); \
|
||||
static bool initialized = Z__PROPERTY_INIT_##NAME(1 /* init info if found */); \
|
||||
return TYPE::__Wrapper(); \
|
||||
} \
|
||||
\
|
||||
@ -134,6 +125,18 @@ TYPE::__Wrapper NAME = Z__PROPERTY_INITIALIZER_PROXY_##NAME()
|
||||
#define DTO_FIELD(TYPE, ...) \
|
||||
OATPP_MACRO_EXPAND(OATPP_MACRO_MACRO_SELECTOR(OATPP_MACRO_DTO_FIELD_, (__VA_ARGS__)) (TYPE, __VA_ARGS__))
|
||||
|
||||
// DTO_FIELD_INFO
|
||||
|
||||
#define DTO_FIELD_INFO(NAME) \
|
||||
\
|
||||
static bool Z__PROPERTY_INIT_##NAME(int) { \
|
||||
Z__PROPERTY_INIT_##NAME(); /* call first initialization */ \
|
||||
Z__PROPERTY_ADD_INFO_##NAME(&Z__PROPERTY_SINGLETON_##NAME()->info); \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static void Z__PROPERTY_ADD_INFO_##NAME(oatpp::data::mapping::type::Type::Property::Info* info)
|
||||
|
||||
// FOR EACH
|
||||
|
||||
#define OATPP_MACRO_DTO_HC_EQ_PARAM_HC(INDEX, COUNT, X) \
|
||||
|
@ -32,6 +32,10 @@
|
||||
#undef OATPP_MACRO_DTO_FIELD_2
|
||||
#undef DTO_FIELD
|
||||
|
||||
// Fields Info
|
||||
|
||||
#undef DTO_FIELD_INFO
|
||||
|
||||
// Hashcode & Equals
|
||||
|
||||
#undef OATPP_MACRO_DTO_HC_EQ_PARAM_HC
|
||||
|
@ -59,14 +59,31 @@ namespace __class {
|
||||
*/
|
||||
template<class T>
|
||||
class Object : public AbstractObject {
|
||||
private:
|
||||
|
||||
static type::Void creator() {
|
||||
return type::Void(std::make_shared<T>(), getType());
|
||||
}
|
||||
|
||||
static type::Type::Properties* initProperties() {
|
||||
T obj; // initializer;
|
||||
T::Z__CLASS_EXTEND(T::Z__CLASS::Z__CLASS_GET_FIELDS_MAP(), T::Z__CLASS_EXTENDED::Z__CLASS_GET_FIELDS_MAP());
|
||||
return T::Z__CLASS::Z__CLASS_GET_FIELDS_MAP();
|
||||
}
|
||||
|
||||
static const Type::Properties* propertiesGetter() {
|
||||
static type::Type::Properties* properties = initProperties();
|
||||
return properties;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Get type describing this class.
|
||||
* @return - &id:oatpp::data::mapping::type::Type;
|
||||
*/
|
||||
static Type* getType(){
|
||||
static Type* type = static_cast<Type*>(T::Z__CLASS_GET_TYPE());
|
||||
static Type* getType() {
|
||||
static Type* type = new Type(CLASS_ID, T::Z__CLASS_TYPE_NAME(), creator, propertiesGetter);
|
||||
return type;
|
||||
}
|
||||
|
||||
@ -79,6 +96,8 @@ namespace __class {
|
||||
* For more info about Data Transfer Object (DTO) see [Data Transfer Object (DTO)](https://oatpp.io/docs/components/dto/).
|
||||
*/
|
||||
class Object : public oatpp::base::Countable {
|
||||
template<class T>
|
||||
friend class __class::Object;
|
||||
public:
|
||||
typedef oatpp::data::mapping::type::Void Void;
|
||||
typedef oatpp::data::mapping::type::Any Any;
|
||||
@ -110,19 +129,19 @@ public:
|
||||
template <class Value>
|
||||
using UnorderedFields = oatpp::data::mapping::type::UnorderedMap<String, Value>;
|
||||
|
||||
protected:
|
||||
private:
|
||||
|
||||
static Type::Properties* Z__CLASS_EXTEND(Type::Properties* properties, Type::Properties* extensionProperties) {
|
||||
properties->pushFrontAll(extensionProperties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
|
||||
static oatpp::data::mapping::type::Type::Properties* Z__CLASS_GET_FIELDS_MAP(){
|
||||
static oatpp::data::mapping::type::Type::Properties map;
|
||||
return ↦
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
virtual v_uint64 defaultHashCode() const {
|
||||
return (v_uint64) reinterpret_cast<v_buff_usize>(this);
|
||||
|
@ -97,12 +97,12 @@ Void& Type::Property::getAsRef(void* object) {
|
||||
Type::Type(const ClassId& pClassId,
|
||||
const char* pNameQualifier,
|
||||
Creator pCreator,
|
||||
Properties* pProperties,
|
||||
PropertiesGetter pPropertiesGetter,
|
||||
void* pPolymorphicDispatcher)
|
||||
: classId(pClassId)
|
||||
, nameQualifier(pNameQualifier)
|
||||
, creator(pCreator)
|
||||
, properties(pProperties)
|
||||
, propertiesGetter(pPropertiesGetter)
|
||||
, polymorphicDispatcher(pPolymorphicDispatcher)
|
||||
{}
|
||||
|
||||
|
@ -226,8 +226,6 @@ struct ObjectWrapperByUnderlyingType {};
|
||||
* Object type data.
|
||||
*/
|
||||
class Type {
|
||||
public:
|
||||
typedef Void (*Creator)();
|
||||
public:
|
||||
class Property; // FWD
|
||||
public:
|
||||
@ -338,7 +336,10 @@ public:
|
||||
Void& getAsRef(void* object);
|
||||
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
typedef Void (*Creator)();
|
||||
typedef const Properties* (*PropertiesGetter)();
|
||||
public:
|
||||
|
||||
/**
|
||||
@ -346,12 +347,13 @@ public:
|
||||
* @param pClassId - type class id.
|
||||
* @param pNameQualifier - type name qualifier.
|
||||
* @param pCreator - function pointer of Creator - function to create instance of this type.
|
||||
* @param pProperties - pointer to type properties.
|
||||
* @param pPropertiesGetter - function to get properties of the type.
|
||||
* @param pPolymorphicDispatcher - dispatcher to correctly address methods of the type.
|
||||
*/
|
||||
Type(const ClassId& pClassId,
|
||||
const char* pNameQualifier,
|
||||
Creator pCreator = nullptr,
|
||||
Properties* pProperties = nullptr,
|
||||
PropertiesGetter pPropertiesGetter = nullptr,
|
||||
void* pPolymorphicDispatcher = nullptr);
|
||||
|
||||
/**
|
||||
@ -375,9 +377,9 @@ public:
|
||||
const Creator creator;
|
||||
|
||||
/**
|
||||
* Pointer to type properties.
|
||||
* PropertiesGetter - function to get properties of the type.
|
||||
*/
|
||||
const Properties* const properties;
|
||||
const PropertiesGetter propertiesGetter;
|
||||
|
||||
/**
|
||||
* PolymorphicDispatcher - is an object to forward polymorphic calls to a correct object of type `Type`.
|
||||
|
@ -296,7 +296,7 @@ oatpp::Void Deserializer::deserializeObject(Deserializer* deserializer, parser::
|
||||
if(caret.canContinueAtChar('{', 1)) {
|
||||
|
||||
auto object = type->creator();
|
||||
const auto& fieldsMap = type->properties->getMap();
|
||||
const auto& fieldsMap = type->propertiesGetter()->getMap();
|
||||
|
||||
caret.skipBlankChars();
|
||||
|
||||
|
@ -152,7 +152,7 @@ void Serializer::serializeObject(Serializer* serializer,
|
||||
stream->writeCharSimple('{');
|
||||
|
||||
bool first = true;
|
||||
auto fields = polymorph.valueType->properties->getList();
|
||||
auto fields = polymorph.valueType->propertiesGetter()->getList();
|
||||
Object* object = static_cast<Object*>(polymorph.get());
|
||||
|
||||
for (auto const& field : fields) {
|
||||
|
@ -27,6 +27,10 @@
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/Types.hpp"
|
||||
|
||||
#include "oatpp-test/Checker.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace oatpp { namespace test { namespace core { namespace data { namespace mapping { namespace type {
|
||||
|
||||
namespace {
|
||||
@ -41,17 +45,29 @@ class DtoA : public oatpp::Object {
|
||||
|
||||
DTO_INIT(DtoA, Object)
|
||||
|
||||
DTO_FIELD(String, id);
|
||||
DTO_FIELD_INFO(id) {
|
||||
info->description = "identifier";
|
||||
}
|
||||
DTO_FIELD(String, id) = "Some default id";
|
||||
|
||||
DTO_HC_EQ(id)
|
||||
|
||||
public:
|
||||
|
||||
DtoA(const String& pId)
|
||||
: id(pId)
|
||||
{}
|
||||
|
||||
};
|
||||
|
||||
class DtoB : public DtoA {
|
||||
|
||||
DTO_INIT(DtoB, DtoA)
|
||||
|
||||
DTO_FIELD(String, a);
|
||||
DTO_FIELD_INFO(a) {
|
||||
info->description = "some field with a qualified name";
|
||||
}
|
||||
DTO_FIELD(String, a, "field-a") = "default-value";
|
||||
|
||||
};
|
||||
|
||||
@ -69,10 +85,77 @@ class DtoC : public DtoA {
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
|
||||
void runDtoInitializations() {
|
||||
for(v_int32 i = 0; i < 1000; i ++) {
|
||||
auto dto = DtoB::createShared();
|
||||
}
|
||||
}
|
||||
|
||||
void runDtoInitializetionsInThreads() {
|
||||
|
||||
std::list<std::thread> threads;
|
||||
for(v_int32 i = 0; i < 500; i++) {
|
||||
threads.push_back(std::thread(runDtoInitializations));
|
||||
}
|
||||
|
||||
for(auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ObjectTest::onRun() {
|
||||
|
||||
{
|
||||
oatpp::test::PerformanceChecker timer("DTO - Initializations.");
|
||||
runDtoInitializetionsInThreads();
|
||||
}
|
||||
|
||||
{
|
||||
auto dto = DtoA::createShared("id1");
|
||||
OATPP_ASSERT(dto->id == "id1");
|
||||
}
|
||||
|
||||
{
|
||||
OATPP_LOGI(TAG, "Test Meta 1...");
|
||||
|
||||
auto type = DtoA::ObjectWrapper::Class::getType();
|
||||
const auto& propsMap = type->propertiesGetter()->getMap();
|
||||
|
||||
OATPP_ASSERT(propsMap.size() == 1);
|
||||
|
||||
auto it = propsMap.find("id");
|
||||
OATPP_ASSERT(it != propsMap.end());
|
||||
OATPP_ASSERT(it->second->info.description == "identifier");
|
||||
|
||||
OATPP_LOGI(TAG, "OK");
|
||||
}
|
||||
|
||||
{
|
||||
OATPP_LOGI(TAG, "Test Meta 2...");
|
||||
|
||||
auto type = DtoB::ObjectWrapper::Class::getType();
|
||||
const auto& propsMap = type->propertiesGetter()->getMap();
|
||||
|
||||
OATPP_ASSERT(propsMap.size() == 2);
|
||||
|
||||
{
|
||||
auto it = propsMap.find("id");
|
||||
OATPP_ASSERT("id" && it != propsMap.end());
|
||||
OATPP_ASSERT(it->second->info.description == "identifier");
|
||||
}
|
||||
|
||||
{
|
||||
auto it = propsMap.find("field-a");
|
||||
OATPP_ASSERT("field-a" && it != propsMap.end());
|
||||
OATPP_ASSERT(it->second->info.description == "some field with a qualified name");
|
||||
}
|
||||
|
||||
OATPP_LOGI(TAG, "OK");
|
||||
}
|
||||
|
||||
{
|
||||
OATPP_LOGI(TAG, "Test 1...");
|
||||
DtoA::ObjectWrapper a;
|
||||
@ -136,6 +219,9 @@ void ObjectTest::onRun() {
|
||||
auto a = DtoB::createShared();
|
||||
auto b = DtoB::createShared();
|
||||
|
||||
OATPP_ASSERT(a->a == "default-value");
|
||||
OATPP_ASSERT(b->a == "default-value");
|
||||
|
||||
a->a = "value1"; // value that is ignored in HC & EQ
|
||||
a->a = "value2"; // value that is ignored in HC & EQ
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/Types.hpp"
|
||||
|
||||
namespace oatpp { namespace test { namespace core { namespace data { namespace mapping { namespace type {
|
||||
|
||||
@ -33,11 +34,9 @@ namespace {
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
typedef oatpp::data::mapping::type::Object DTO;
|
||||
|
||||
class TestDto : public DTO {
|
||||
class TestDto : public oatpp::Object {
|
||||
|
||||
DTO_INIT(TestDto, DTO);
|
||||
DTO_INIT(TestDto, Object);
|
||||
|
||||
DTO_FIELD(String, field_string);
|
||||
DTO_FIELD(Int8, field_int8);
|
||||
@ -57,7 +56,7 @@ namespace {
|
||||
|
||||
DTO_FIELD(Fields<String>, field_map_string_string);
|
||||
|
||||
DTO_FIELD(TestDto::ObjectWrapper, obj1);
|
||||
DTO_FIELD(TestDto, obj1);
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user