Merge pull request #243 from oatpp/dto_fields_description

Dto fields description
This commit is contained in:
Leonid Stryzhevskyi 2020-05-16 03:29:44 +03:00 committed by GitHub
commit 8aae916f92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 191 additions and 55 deletions

View File

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

View File

@ -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 &map; \
} \
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) \

View File

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

View File

@ -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 &map;
}
public:
virtual v_uint64 defaultHashCode() const {
return (v_uint64) reinterpret_cast<v_buff_usize>(this);

View File

@ -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)
{}

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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