Add experimental versioning for binary (de)serializer
This commit is contained in:
parent
5e72012ed5
commit
0a902ac30c
45
README.md
45
README.md
|
@ -353,6 +353,51 @@ An example for such custom (de)serialization can be found in the file
|
|||
`json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
|
||||
`TimeSpan` objects from the C++ utilities library mentioned under dependencies.
|
||||
|
||||
### Versioning
|
||||
#### JSON (de)serializer
|
||||
The JSON (de)serializer doesn't support versioning at this point. It'll simply read/write the
|
||||
members present in the struct. Additional members (which were e.g. present in older/newer
|
||||
versions of the struct) are ignored when reading and in consequence dropped when writing.
|
||||
|
||||
#### Binary (de)serializer
|
||||
The binary (de)serializer supports *very* experimental versioning. Otherwise adding/removing
|
||||
members is a breaking change. The versioning looks like this:
|
||||
|
||||
<pre>
|
||||
// enable definition of the macros shown below (otherwise use long macros defined in
|
||||
// `lib/versioning.h`)
|
||||
#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
|
||||
|
||||
#include <reflective_rapidjson/binary/serializable.h>
|
||||
|
||||
// example struct where version is *not* serialized/deserialized; defaults to version from
|
||||
// outer scope when reading/writing, defaults to version 0 on top-level
|
||||
struct Nested : public BinarySerializable<Nested> { //
|
||||
std::uint32_t foo; // will be read/written in any case
|
||||
|
||||
as_of_version(3):
|
||||
std::uint32_t bar; // will be read/written if outer scope version is >= 3
|
||||
};
|
||||
|
||||
// example struct where version is serialized/deserialized; defaults to version when writing
|
||||
struct Example : public BinarySerializable<Example, 3> {
|
||||
Nested nested; // will be read/written in any case, version is "propagated down"
|
||||
std::uint32_t a, b; // will be read/written in any case
|
||||
|
||||
until_version(2):
|
||||
std::uint32_t c, d; // will be read/written if version is <= 2
|
||||
|
||||
as_of_version(3):
|
||||
std::uint32_t e, f; // will be read/written if version is >= 3
|
||||
|
||||
as_of_version(4):
|
||||
std::uint32_t g; // will be read/written if version is >= 4
|
||||
};
|
||||
</pre>
|
||||
|
||||
A mechanism to catch unsupported versions during deserialization is yet to be implemented.
|
||||
Additionally, the versioning is completely untested at this point.
|
||||
|
||||
### Remarks
|
||||
* Static member variables and member functions are currently ignored by the generator.
|
||||
* It is currently not possible to ignore a specific member variable.
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
#include <clang/AST/DeclCXX.h>
|
||||
#include <clang/AST/DeclFriend.h>
|
||||
#include <clang/AST/DeclTemplate.h>
|
||||
#include <clang/AST/Expr.h>
|
||||
#include <clang/AST/RecursiveASTVisitor.h>
|
||||
|
||||
#include <llvm/ADT/APInt.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
@ -35,36 +39,159 @@ BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
|
||||
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
|
||||
*/
|
||||
string BinarySerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
|
||||
void BinarySerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
|
||||
{
|
||||
const string qualifiedName(record->getQualifiedNameAsString());
|
||||
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
|
||||
case IsRelevant::Yes:
|
||||
return qualifiedName;
|
||||
case IsRelevant::No:
|
||||
return string();
|
||||
default:;
|
||||
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
|
||||
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
|
||||
return;
|
||||
}
|
||||
|
||||
// consider all classes specified via "--additional-classes" argument relevant
|
||||
if (!m_options.additionalClassesArg.isPresent()) {
|
||||
return string();
|
||||
return;
|
||||
}
|
||||
for (const char *className : m_options.additionalClassesArg.values()) {
|
||||
if (className == qualifiedName) {
|
||||
return qualifiedName;
|
||||
for (const char *const className : m_options.additionalClassesArg.values()) {
|
||||
if (className == possiblyRelevantClass.qualifiedName) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string();
|
||||
/// \brief The RetrieveIntegerLiteralFromDeclaratorDecl struct is used to traverse a variable declaration to get the integer value.
|
||||
struct RetrieveIntegerLiteralFromDeclaratorDecl : public clang::RecursiveASTVisitor<RetrieveIntegerLiteralFromDeclaratorDecl> {
|
||||
explicit RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx);
|
||||
bool VisitStmt(clang::Stmt *st);
|
||||
const clang::ASTContext &ctx;
|
||||
std::uint64_t res;
|
||||
bool success;
|
||||
};
|
||||
|
||||
/// \brief Constructs a new instance for the specified AST context.
|
||||
RetrieveIntegerLiteralFromDeclaratorDecl::RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx)
|
||||
: ctx(ctx)
|
||||
, res(0)
|
||||
, success(false)
|
||||
{
|
||||
}
|
||||
|
||||
/// \brief Reads the integer value of \a st for integer literals.
|
||||
bool RetrieveIntegerLiteralFromDeclaratorDecl::VisitStmt(clang::Stmt *st)
|
||||
{
|
||||
if (st->getStmtClass() != clang::Stmt::IntegerLiteralClass) {
|
||||
return true;
|
||||
}
|
||||
const auto *const integerLiteral = static_cast<const clang::IntegerLiteral *>(st);
|
||||
auto evaluation = clang::Expr::EvalResult();
|
||||
integerLiteral->EvaluateAsInt(evaluation, ctx, clang::Expr::SE_NoSideEffects, true);
|
||||
if (!evaluation.Val.isInt()) {
|
||||
return true;
|
||||
}
|
||||
const auto &asInt = evaluation.Val.getInt();
|
||||
if (asInt.getActiveBits() > 64) {
|
||||
return true;
|
||||
}
|
||||
res = asInt.getZExtValue();
|
||||
success = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// \brief The MemberTracking struct is an internal helper for BinarySerializationCodeGenerator::generate().
|
||||
struct MemberTracking {
|
||||
bool membersWritten = false, withinCondition = false;
|
||||
BinaryVersion asOfVersion = BinaryVersion(), lastAsOfVersion = BinaryVersion();
|
||||
BinaryVersion untilVersion = BinaryVersion(), lastUntilVersion = BinaryVersion();
|
||||
|
||||
bool checkForVersionMarker(clang::Decl *decl);
|
||||
void concludeCondition(std::ostream &os);
|
||||
void writeVersionCondition(std::ostream &os);
|
||||
void writeExtraPadding(std::ostream &os);
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Returns whether \a delc is a static member variable and processes special static member variables
|
||||
* for versioning.
|
||||
*/
|
||||
bool MemberTracking::checkForVersionMarker(clang::Decl *decl)
|
||||
{
|
||||
if (decl->getKind() != clang::Decl::Kind::Var) {
|
||||
return false;
|
||||
}
|
||||
auto *const declarator = static_cast<clang::DeclaratorDecl *>(decl);
|
||||
const auto declarationName = declarator->getName();
|
||||
const auto isAsOfVersion = declarationName.startswith("rrjAsOfVersion");
|
||||
if (isAsOfVersion || declarationName.startswith("rrjUntilVersion")) {
|
||||
auto v = RetrieveIntegerLiteralFromDeclaratorDecl(declarator->getASTContext());
|
||||
v.TraverseDecl(declarator);
|
||||
if (v.success) {
|
||||
if (isAsOfVersion) {
|
||||
asOfVersion = v.res;
|
||||
if (asOfVersion > untilVersion) {
|
||||
untilVersion = 0;
|
||||
}
|
||||
} else {
|
||||
untilVersion = v.res;
|
||||
if (untilVersion < asOfVersion) {
|
||||
asOfVersion = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Concludes an unfinished version condition if-block.
|
||||
*/
|
||||
void MemberTracking::concludeCondition(std::ostream &os)
|
||||
{
|
||||
if (withinCondition) {
|
||||
os << " }\n";
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Starts a new version condition if-block if versioning parameters have changed.
|
||||
*/
|
||||
void MemberTracking::writeVersionCondition(std::ostream &os)
|
||||
{
|
||||
if (asOfVersion == lastAsOfVersion && untilVersion == lastUntilVersion) {
|
||||
return;
|
||||
}
|
||||
concludeCondition(os);
|
||||
lastAsOfVersion = asOfVersion;
|
||||
lastUntilVersion = untilVersion;
|
||||
if ((withinCondition = asOfVersion || untilVersion)) {
|
||||
os << " if (";
|
||||
if (asOfVersion) {
|
||||
os << "version >= " << asOfVersion;
|
||||
if (untilVersion) {
|
||||
os << " && ";
|
||||
}
|
||||
}
|
||||
if (untilVersion) {
|
||||
os << "version <= " << untilVersion;
|
||||
}
|
||||
os << ") {\n";
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Writes extra padding (if within a version condition).
|
||||
*/
|
||||
void MemberTracking::writeExtraPadding(std::ostream &os)
|
||||
{
|
||||
if (withinCondition) {
|
||||
os << " ";
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace for the relevant classes.
|
||||
*/
|
||||
void BinarySerializationCodeGenerator::generate(ostream &os) const
|
||||
void BinarySerializationCodeGenerator::generate(std::ostream &os) const
|
||||
{
|
||||
// initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
|
||||
lazyInitializeSourceManager();
|
||||
|
@ -118,22 +245,55 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
|
|||
|
||||
// print writeCustomType method
|
||||
os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
|
||||
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName
|
||||
<< " &customObject)\n{\n"
|
||||
" // write base classes\n";
|
||||
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
|
||||
if (!relevantClass.relevantBase.empty()) {
|
||||
os << " // write version\n"
|
||||
" using V = Versioning<"
|
||||
<< relevantClass.relevantBase
|
||||
<< ">;\n"
|
||||
" if constexpr (V::enabled) {\n"
|
||||
" serializer.writeVariableLengthUIntBE(V::applyDefault(version));\n"
|
||||
" }\n";
|
||||
}
|
||||
os << " // write base classes\n";
|
||||
for (const RelevantClass *baseClass : relevantBases) {
|
||||
os << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject));\n";
|
||||
os << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject), version);\n";
|
||||
}
|
||||
os << " // write members\n";
|
||||
auto membersWritten = false;
|
||||
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
|
||||
if (writePrivateMembers || field->getAccess() == clang::AS_public) {
|
||||
os << " serializer.write(customObject." << field->getName() << ");\n";
|
||||
membersWritten = true;
|
||||
auto mt = MemberTracking();
|
||||
for (clang::Decl *const decl : relevantClass.record->decls()) {
|
||||
// check static member variables for version markers
|
||||
if (mt.checkForVersionMarker(decl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip all further declarations but fields
|
||||
if (decl->getKind() != clang::Decl::Kind::Field) {
|
||||
continue;
|
||||
}
|
||||
if (relevantBases.empty() && !membersWritten) {
|
||||
os << " (void)serializer;\n (void)customObject;\n";
|
||||
|
||||
// skip const members
|
||||
const auto *const field = static_cast<const clang::FieldDecl *>(decl);
|
||||
if (field->getType().isConstant(field->getASTContext())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip private members conditionally
|
||||
if (!writePrivateMembers && field->getAccess() != clang::AS_public) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// write version markers
|
||||
mt.writeVersionCondition(os);
|
||||
mt.writeExtraPadding(os);
|
||||
|
||||
// write actual code for serialization
|
||||
os << " serializer.write(customObject." << field->getName() << ", version);\n";
|
||||
mt.membersWritten = true;
|
||||
}
|
||||
mt.concludeCondition(os);
|
||||
if (relevantBases.empty() && !mt.membersWritten) {
|
||||
os << " (void)serializer;\n (void)customObject;\n \n(void)version;";
|
||||
}
|
||||
os << "}\n";
|
||||
|
||||
|
@ -143,28 +303,53 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
|
|||
}
|
||||
|
||||
// print readCustomType method
|
||||
os << "template <> " << visibility << " void readCustomType<::" << relevantClass.qualifiedName
|
||||
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName
|
||||
<< " &customObject)\n{\n"
|
||||
" // read base classes\n";
|
||||
mt = MemberTracking();
|
||||
os << "template <> " << visibility << " BinaryVersion readCustomType<::" << relevantClass.qualifiedName
|
||||
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
|
||||
if (!relevantClass.relevantBase.empty()) {
|
||||
os << " // read version\n"
|
||||
" if constexpr (Versioning<"
|
||||
<< relevantClass.relevantBase
|
||||
<< ">::enabled) {\n"
|
||||
" version = deserializer.readVariableLengthUIntBE();\n"
|
||||
" }\n";
|
||||
}
|
||||
os << " // read base classes\n";
|
||||
for (const RelevantClass *baseClass : relevantBases) {
|
||||
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject));\n";
|
||||
}
|
||||
os << " // read members\n";
|
||||
auto membersRead = false;
|
||||
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
|
||||
for (clang::Decl *const decl : relevantClass.record->decls()) {
|
||||
// check static member variables for version markers
|
||||
if (mt.checkForVersionMarker(decl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip all further declarations but fields
|
||||
if (decl->getKind() != clang::Decl::Kind::Field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip const members
|
||||
const auto *const field = static_cast<const clang::FieldDecl *>(decl);
|
||||
if (field->getType().isConstant(field->getASTContext())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// write version markers
|
||||
mt.writeVersionCondition(os);
|
||||
mt.writeExtraPadding(os);
|
||||
|
||||
if (readPrivateMembers || field->getAccess() == clang::AS_public) {
|
||||
os << " deserializer.read(customObject." << field->getName() << ");\n";
|
||||
membersRead = true;
|
||||
os << " deserializer.read(customObject." << field->getName() << ", version);\n";
|
||||
mt.membersWritten = true;
|
||||
}
|
||||
}
|
||||
if (relevantBases.empty() && !membersRead) {
|
||||
mt.concludeCondition(os);
|
||||
if (relevantBases.empty() && !mt.membersWritten) {
|
||||
os << " (void)deserializer;\n (void)customObject;\n";
|
||||
}
|
||||
os << " return version;\n";
|
||||
os << "}\n\n";
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
void generate(std::ostream &os) const override;
|
||||
|
||||
protected:
|
||||
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
|
||||
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
|
||||
|
||||
const Options &m_options;
|
||||
};
|
||||
|
|
|
@ -44,17 +44,18 @@ bool CodeGenerator::isOnlyIncluded(const clang::Decl *declaration) const
|
|||
|
||||
/*!
|
||||
* \brief Returns whether the specified \a record inherits from an instantiation of the specified \a templateClass.
|
||||
* \returns Returns the relevant base class if that's the case and otherwise nullptr.
|
||||
* \remarks The specified \a record must be defined (not only forward-declared).
|
||||
*/
|
||||
bool CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
|
||||
clang::CXXBaseSpecifier *CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
|
||||
{
|
||||
for (const clang::CXXBaseSpecifier &base : record->bases()) {
|
||||
const clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
|
||||
for (clang::CXXBaseSpecifier &base : record->bases()) {
|
||||
clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
|
||||
if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) {
|
||||
return true;
|
||||
return &base;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace clang {
|
||||
class Decl;
|
||||
class CXXRecordDecl;
|
||||
class CXXBaseSpecifier;
|
||||
class SourceManager;
|
||||
} // namespace clang
|
||||
|
||||
|
@ -32,7 +33,7 @@ protected:
|
|||
CodeFactory &factory() const;
|
||||
void lazyInitializeSourceManager() const;
|
||||
bool isOnlyIncluded(const clang::Decl *declaration) const;
|
||||
static bool inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
|
||||
static clang::CXXBaseSpecifier *inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
|
||||
|
||||
private:
|
||||
CodeFactory &m_factory;
|
||||
|
|
|
@ -35,30 +35,25 @@ JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &fact
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
|
||||
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
|
||||
*/
|
||||
string JsonSerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
|
||||
void JsonSerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
|
||||
{
|
||||
const string qualifiedName(record->getQualifiedNameAsString());
|
||||
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
|
||||
case IsRelevant::Yes:
|
||||
return qualifiedName;
|
||||
case IsRelevant::No:
|
||||
return string();
|
||||
default:;
|
||||
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
|
||||
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
|
||||
return;
|
||||
}
|
||||
|
||||
// consider all classes specified via "--additional-classes" argument relevant
|
||||
if (!m_options.additionalClassesArg.isPresent()) {
|
||||
return string();
|
||||
return;
|
||||
}
|
||||
for (const char *className : m_options.additionalClassesArg.values()) {
|
||||
if (className == qualifiedName) {
|
||||
return qualifiedName;
|
||||
for (const char *const className : m_options.additionalClassesArg.values()) {
|
||||
if (className == possiblyRelevantClass.qualifiedName) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return string();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
void generate(std::ostream &os) const override;
|
||||
|
||||
protected:
|
||||
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
|
||||
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
|
||||
|
||||
const Options &m_options;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <clang/AST/DeclCXX.h>
|
||||
#include <clang/AST/DeclFriend.h>
|
||||
#include <clang/AST/DeclTemplate.h>
|
||||
#include <clang/AST/PrettyPrinter.h>
|
||||
#include <clang/AST/QualTypeNames.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
@ -66,12 +68,12 @@ void SerializationCodeGenerator::addDeclaration(clang::Decl *decl)
|
|||
}
|
||||
}
|
||||
|
||||
SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNameIfRelevant(
|
||||
clang::CXXRecordDecl *record, const std::string &qualifiedName) const
|
||||
void SerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
|
||||
{
|
||||
// skip all classes which are only forward-declared
|
||||
if (!record->isCompleteDefinition()) {
|
||||
return IsRelevant::No;
|
||||
if (!possiblyRelevantClass.record->isCompleteDefinition()) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::No;
|
||||
return;
|
||||
}
|
||||
|
||||
// consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available
|
||||
|
@ -80,31 +82,40 @@ SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNa
|
|||
if (isOnlyIncluded(adaptionRecord.record)) {
|
||||
continue;
|
||||
}
|
||||
if (adaptionRecord.qualifiedName == qualifiedName) {
|
||||
return IsRelevant::Yes;
|
||||
if (adaptionRecord.qualifiedName == possiblyRelevantClass.qualifiedName) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// skip all classes which are only included
|
||||
if (isOnlyIncluded(record)) {
|
||||
return IsRelevant::No;
|
||||
if (isOnlyIncluded(possiblyRelevantClass.record)) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::No;
|
||||
return;
|
||||
}
|
||||
|
||||
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant
|
||||
if (inheritsFromInstantiationOf(record, m_qualifiedNameOfRecords)) {
|
||||
return IsRelevant::Yes;
|
||||
if (const auto *const relevantBase = inheritsFromInstantiationOf(possiblyRelevantClass.record, m_qualifiedNameOfRecords)) {
|
||||
auto policy = clang::PrintingPolicy(possiblyRelevantClass.record->getLangOpts());
|
||||
policy.FullyQualifiedName = true;
|
||||
policy.SuppressScope = false;
|
||||
policy.SuppressUnwrittenScope = false;
|
||||
policy.SplitTemplateClosers = false;
|
||||
possiblyRelevantClass.relevantBase
|
||||
= clang::TypeName::getFullyQualifiedName(relevantBase->getType(), possiblyRelevantClass.record->getASTContext(), policy, true);
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
|
||||
return;
|
||||
}
|
||||
|
||||
return IsRelevant::Maybe;
|
||||
}
|
||||
|
||||
std::vector<SerializationCodeGenerator::RelevantClass> SerializationCodeGenerator::findRelevantClasses() const
|
||||
{
|
||||
std::vector<RelevantClass> relevantClasses;
|
||||
for (clang::CXXRecordDecl *record : m_records) {
|
||||
string qualifiedName(qualifiedNameIfRelevant(record));
|
||||
if (!qualifiedName.empty()) {
|
||||
relevantClasses.emplace_back(move(qualifiedName), record);
|
||||
for (clang::CXXRecordDecl *const record : m_records) {
|
||||
auto &relevantClass = relevantClasses.emplace_back(record->getQualifiedNameAsString(), record);
|
||||
computeRelevantClass(relevantClass);
|
||||
if (relevantClass.isRelevant != IsRelevant::Yes) {
|
||||
relevantClasses.pop_back();
|
||||
}
|
||||
}
|
||||
return relevantClasses;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <llvm/ADT/StringRef.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
|
||||
|
@ -15,11 +17,14 @@ std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
|
|||
*/
|
||||
class SerializationCodeGenerator : public CodeGenerator {
|
||||
public:
|
||||
enum class IsRelevant { Yes, No, Maybe };
|
||||
struct RelevantClass {
|
||||
explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record);
|
||||
|
||||
std::string qualifiedName;
|
||||
clang::CXXRecordDecl *record;
|
||||
std::string relevantBase;
|
||||
clang::CXXRecordDecl *record = nullptr;
|
||||
IsRelevant isRelevant = IsRelevant::Maybe;
|
||||
};
|
||||
|
||||
SerializationCodeGenerator(CodeFactory &factory);
|
||||
|
@ -27,9 +32,7 @@ public:
|
|||
void addDeclaration(clang::Decl *decl) override;
|
||||
|
||||
protected:
|
||||
enum class IsRelevant { Yes, No, Maybe };
|
||||
IsRelevant isQualifiedNameIfRelevant(clang::CXXRecordDecl *record, const std::string &qualifiedName) const;
|
||||
virtual std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const = 0;
|
||||
virtual void computeRelevantClass(RelevantClass &possiblyRelevantClass) const;
|
||||
std::vector<RelevantClass> findRelevantClasses() const;
|
||||
static std::vector<const RelevantClass *> findRelevantBaseClasses(
|
||||
const RelevantClass &relevantClass, const std::vector<RelevantClass> &relevantBases);
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
|
||||
#define REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
|
||||
|
||||
#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
|
||||
|
||||
#include "../../lib/binary/serializable.h"
|
||||
#include "../../lib/json/serializable.h"
|
||||
#include "../../lib/versioning.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
@ -67,4 +70,29 @@ struct PointerStruct : public BinarySerializable<PointerStruct> {
|
|||
std::shared_ptr<PointerTarget> s3;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief All of this is supposed to work if classes are within a namespace so let's use a namespace here.
|
||||
*/
|
||||
namespace SomeNamespace {
|
||||
|
||||
/*!
|
||||
* \brief The PointerStruct struct is used to test the behavior of the binary (de)serialization with smart pointer.
|
||||
*/
|
||||
// clang-format off
|
||||
struct VersionedStruct : public BinarySerializable<VersionedStruct, 3> {
|
||||
std::uint32_t a, b;
|
||||
|
||||
until_version(2):
|
||||
std::uint32_t c, d;
|
||||
|
||||
as_of_version(3):
|
||||
std::uint32_t e, f;
|
||||
|
||||
as_of_version(4):
|
||||
std::uint32_t g;
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
} // namespace SomeNamespace
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
|
||||
|
|
|
@ -5,7 +5,7 @@ set(META_PROJECT_TYPE library)
|
|||
set(META_HEADER_ONLY_LIB ON)
|
||||
|
||||
# add project files
|
||||
set(HEADER_FILES traits.h)
|
||||
set(HEADER_FILES traits.h versioning.h)
|
||||
set(SRC_FILES)
|
||||
set(TEST_HEADER_FILES)
|
||||
set(TEST_SRC_FILES)
|
||||
|
|
|
@ -24,16 +24,17 @@
|
|||
namespace ReflectiveRapidJSON {
|
||||
namespace BinaryReflector {
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void readCustomType(BinaryDeserializer &deserializer, Type &customType)
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
|
||||
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version)
|
||||
{
|
||||
boost::hana::for_each(
|
||||
boost::hana::keys(customType), [&deserializer, &customType](auto key) { deserializer.read(boost::hana::at_key(customType, key)); });
|
||||
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { deserializer.read(boost::hana::at_key(customType, key), version); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void writeCustomType(BinarySerializer &serializer, const Type &customType)
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
|
||||
void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version)
|
||||
{
|
||||
boost::hana::for_each(
|
||||
boost::hana::keys(customType), [&serializer, &customType](auto key) { serializer.write(boost::hana::at_key(customType, key)); });
|
||||
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { serializer.write(boost::hana::at_key(customType, key), version); });
|
||||
}
|
||||
|
||||
} // namespace BinaryReflector
|
||||
|
|
|
@ -16,23 +16,33 @@
|
|||
namespace ReflectiveRapidJSON {
|
||||
namespace BinaryReflector {
|
||||
|
||||
template <> inline void readCustomType<CppUtilities::DateTime>(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime)
|
||||
template <>
|
||||
inline BinaryVersion readCustomType<CppUtilities::DateTime>(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
deserializer.read(dateTime.ticks());
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <> inline void writeCustomType<CppUtilities::DateTime>(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime)
|
||||
template <>
|
||||
inline void writeCustomType<CppUtilities::DateTime>(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(dateTime.totalTicks());
|
||||
}
|
||||
|
||||
template <> inline void readCustomType<CppUtilities::TimeSpan>(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan)
|
||||
template <>
|
||||
inline BinaryVersion readCustomType<CppUtilities::TimeSpan>(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
deserializer.read(timeSpan.ticks());
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <> inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan)
|
||||
template <>
|
||||
inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(timeSpan.totalTicks());
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include "../traits.h"
|
||||
#include "../versioning.h"
|
||||
|
||||
#include <c++utilities/conversion/conversionexception.h>
|
||||
#include <c++utilities/io/binaryreader.h>
|
||||
|
@ -33,7 +34,8 @@ template <typename T> struct AdaptedBinarySerializable : public Traits::Bool<fal
|
|||
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedBinarySerializable";
|
||||
};
|
||||
|
||||
template <typename Type> struct BinarySerializable;
|
||||
using BinaryVersion = std::uint64_t;
|
||||
template <typename Type, BinaryVersion v = 0> struct BinarySerializable;
|
||||
|
||||
/*!
|
||||
* \brief The BinaryReflector namespace contains BinaryReader and BinaryWriter for automatic binary (de)serialization.
|
||||
|
@ -50,9 +52,22 @@ template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
|
|||
class BinaryDeserializer;
|
||||
class BinarySerializer;
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void readCustomType(BinaryDeserializer &deserializer, Type &customType);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void writeCustomType(BinarySerializer &serializer, const Type &customType);
|
||||
/// \brief Reads \a customType via \a deserializer.
|
||||
/// \remarks
|
||||
/// - If \tp Type is versioned, the version is determined from the data. Otherwise \a version is assumed.
|
||||
/// - The specified \a version shall be passed to nested invocations.
|
||||
/// \returns Returns the determined/assumed version.
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr>
|
||||
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version = 0);
|
||||
|
||||
/// \brief Writes \a customType via \a serializer.
|
||||
/// \remarks
|
||||
/// - If \tp Type is versioned, \a version is prepended to the data.
|
||||
/// - The specified \a version shall be passed to nested invocations.
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr>
|
||||
void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version = 0);
|
||||
|
||||
/// \brief The BinaryDeserializer class can read various data types, including custom ones, from an std::istream.
|
||||
class BinaryDeserializer : public CppUtilities::BinaryReader {
|
||||
friend class ::BinaryReflectorTests;
|
||||
|
||||
|
@ -71,12 +86,14 @@ public:
|
|||
void read(Type &iteratable);
|
||||
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &enumValue);
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr> void read(Type &variant);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void read(Type &customType);
|
||||
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> * = nullptr> BinaryVersion read(Type &builtInType, BinaryVersion version);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> BinaryVersion read(Type &customType, BinaryVersion version = 0);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, std::any> m_pointer;
|
||||
};
|
||||
|
||||
/// \brief The BinarySerializer class can write various data types, including custom ones, to an std::ostream.
|
||||
class BinarySerializer : public CppUtilities::BinaryWriter {
|
||||
friend class ::BinaryReflectorTests;
|
||||
|
||||
|
@ -90,7 +107,8 @@ public:
|
|||
template <typename Type, Traits::EnableIf<IsIteratableExceptString<Type>, Traits::HasSize<Type>> * = nullptr> void write(const Type &iteratable);
|
||||
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void write(const Type &enumValue);
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr> void write(const Type &variant);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType);
|
||||
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> * = nullptr> void write(const Type &builtInType, BinaryVersion version);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType, BinaryVersion version = 0);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, bool> m_pointer;
|
||||
|
@ -207,9 +225,15 @@ template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinaryDeseria
|
|||
Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinaryDeserializer::read(Type &customType)
|
||||
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &builtInType, BinaryVersion version)
|
||||
{
|
||||
readCustomType(*this, customType);
|
||||
read(builtInType);
|
||||
return version;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &customType, BinaryVersion version)
|
||||
{
|
||||
return readCustomType(*this, customType, version);
|
||||
}
|
||||
|
||||
inline BinarySerializer::BinarySerializer(std::ostream *stream)
|
||||
|
@ -286,9 +310,15 @@ template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySeriali
|
|||
variant);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType)
|
||||
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version)
|
||||
{
|
||||
writeCustomType(*this, customType);
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
write(builtInType);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType, BinaryVersion version)
|
||||
{
|
||||
writeCustomType(*this, customType, version);
|
||||
}
|
||||
|
||||
} // namespace BinaryReflector
|
||||
|
|
|
@ -17,25 +17,26 @@ namespace ReflectiveRapidJSON {
|
|||
/*!
|
||||
* \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects.
|
||||
*/
|
||||
template <typename Type> struct BinarySerializable {
|
||||
void toBinary(std::ostream &outputStream) const;
|
||||
void restoreFromBinary(std::istream &inputStream);
|
||||
template <typename Type, BinaryVersion v> struct BinarySerializable {
|
||||
void toBinary(std::ostream &outputStream, BinaryVersion version = 0) const;
|
||||
BinaryVersion restoreFromBinary(std::istream &inputStream);
|
||||
static Type fromBinary(std::istream &inputStream);
|
||||
|
||||
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::BinarySerializable";
|
||||
static constexpr auto version = v;
|
||||
};
|
||||
|
||||
template <typename Type> inline void BinarySerializable<Type>::toBinary(std::ostream &outputStream) const
|
||||
template <typename Type, BinaryVersion v> inline void BinarySerializable<Type, v>::toBinary(std::ostream &outputStream, BinaryVersion version) const
|
||||
{
|
||||
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this));
|
||||
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this), version);
|
||||
}
|
||||
|
||||
template <typename Type> inline void BinarySerializable<Type>::restoreFromBinary(std::istream &inputStream)
|
||||
template <typename Type, BinaryVersion v> inline BinaryVersion BinarySerializable<Type, v>::restoreFromBinary(std::istream &inputStream)
|
||||
{
|
||||
BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
|
||||
return BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
|
||||
}
|
||||
|
||||
template <typename Type> Type BinarySerializable<Type>::fromBinary(std::istream &inputStream)
|
||||
template <typename Type, BinaryVersion v> Type BinarySerializable<Type, v>::fromBinary(std::istream &inputStream)
|
||||
{
|
||||
Type object;
|
||||
static_cast<BinarySerializable<Type> &>(object).restoreFromBinary(inputStream);
|
||||
|
|
|
@ -75,8 +75,9 @@ struct ObjectWithVariantsBinary : public BinarySerializable<ObjectWithVariantsBi
|
|||
namespace ReflectiveRapidJSON {
|
||||
namespace BinaryReflector {
|
||||
|
||||
template <> void readCustomType<TestObjectBinary>(BinaryDeserializer &deserializer, TestObjectBinary &customType)
|
||||
template <> BinaryVersion readCustomType<TestObjectBinary>(BinaryDeserializer &deserializer, TestObjectBinary &customType, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
deserializer.read(customType.number);
|
||||
deserializer.read(customType.number2);
|
||||
deserializer.read(customType.numbers);
|
||||
|
@ -92,10 +93,12 @@ template <> void readCustomType<TestObjectBinary>(BinaryDeserializer &deserializ
|
|||
deserializer.read(customType.someEnumClass);
|
||||
deserializer.read(customType.timeSpan);
|
||||
deserializer.read(customType.dateTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer, const TestObjectBinary &customType)
|
||||
template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer, const TestObjectBinary &customType, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(customType.number);
|
||||
serializer.write(customType.number2);
|
||||
serializer.write(customType.numbers);
|
||||
|
@ -113,27 +116,35 @@ template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer,
|
|||
serializer.write(customType.dateTime);
|
||||
}
|
||||
|
||||
template <> void readCustomType<NestingArrayBinary>(BinaryDeserializer &deserializer, NestingArrayBinary &customType)
|
||||
template <> BinaryVersion readCustomType<NestingArrayBinary>(BinaryDeserializer &deserializer, NestingArrayBinary &customType, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
deserializer.read(customType.name);
|
||||
deserializer.read(customType.testObjects);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <> void writeCustomType<NestingArrayBinary>(BinarySerializer &serializer, const NestingArrayBinary &customType)
|
||||
template <> void writeCustomType<NestingArrayBinary>(BinarySerializer &serializer, const NestingArrayBinary &customType, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(customType.name);
|
||||
serializer.write(customType.testObjects);
|
||||
}
|
||||
|
||||
template <> void readCustomType<ObjectWithVariantsBinary>(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType)
|
||||
template <>
|
||||
BinaryVersion readCustomType<ObjectWithVariantsBinary>(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
deserializer.read(customType.someVariant);
|
||||
deserializer.read(customType.anotherVariant);
|
||||
deserializer.read(customType.yetAnotherVariant);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <> void writeCustomType<ObjectWithVariantsBinary>(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType)
|
||||
template <>
|
||||
void writeCustomType<ObjectWithVariantsBinary>(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(customType.someVariant);
|
||||
serializer.write(customType.anotherVariant);
|
||||
serializer.write(customType.yetAnotherVariant);
|
||||
|
|
|
@ -1,13 +1,27 @@
|
|||
#include "../traits.h"
|
||||
#include "../versioning.h"
|
||||
|
||||
#include "../binary/serializable.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
// treat some types differently to test Treat... traits
|
||||
// define structs for testing REFLECTIVE_RAPIDJSON_TREAT_AS_…
|
||||
struct Foo {
|
||||
};
|
||||
struct Bar {
|
||||
};
|
||||
|
||||
// define structs for testing versioning
|
||||
struct VersionlessBase : public ReflectiveRapidJSON::BinarySerializable<VersionlessBase> {
|
||||
};
|
||||
struct VersionedDerived : public VersionlessBase, public ReflectiveRapidJSON::BinarySerializable<VersionedDerived, 1> {
|
||||
};
|
||||
struct VersionedBase : public ReflectiveRapidJSON::BinarySerializable<VersionlessBase, 1> {
|
||||
};
|
||||
struct VersionlessDerived : public VersionedBase, public ReflectiveRapidJSON::BinarySerializable<VersionlessDerived> {
|
||||
};
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(Foo);
|
||||
REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(Foo);
|
||||
|
@ -48,3 +62,14 @@ static_assert(IsIteratableExceptString<std::vector<int>>::value, "vector is iter
|
|||
static_assert(!IsIteratableExceptString<std::string>::value, "string not iteratable");
|
||||
static_assert(!IsIteratableExceptString<std::wstring>::value, "wstring not iteratable");
|
||||
static_assert(!IsIteratableExceptString<const std::string>::value, "string not iteratable");
|
||||
|
||||
// test versioning traits
|
||||
static_assert(!Versioning<int>::enabled, "versioning for built-in types not enabled");
|
||||
static_assert(!Versioning<std::string>::enabled, "versioning for standard types not enabled");
|
||||
static_assert(!Versioning<VersionlessBase>::enabled, "versioning not enabled by default");
|
||||
static_assert(Versioning<BinarySerializable<VersionedDerived, 1>>::enabled, "versioning enabled if non-zero version parameter specified (derived)");
|
||||
static_assert(Versioning<VersionedBase>::enabled, "versioning enabled if non-zero version parameter specified (base)");
|
||||
static_assert(!Versioning<BinarySerializable<VersionlessDerived>>::enabled, "versioning disabled for derived, even if base is versioned");
|
||||
static_assert(!Versioning<BinarySerializable<Foo, 0>>::enabled, "versioning disabled if zero-version specified");
|
||||
static_assert(Versioning<BinarySerializable<Foo, 3>>::applyDefault(0) == 3, "default version returned");
|
||||
static_assert(Versioning<BinarySerializable<Foo, 3>>::applyDefault(2) == 2, "default version overridden");
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_VERSIONING
|
||||
#define REFLECTIVE_RAPIDJSON_VERSIONING
|
||||
|
||||
#include <c++utilities/misc/traits.h>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
#ifdef REFLECTIVE_RAPIDJSON_GENERATOR
|
||||
#define REFLECTIVE_RAPIDJSON_CAT_1(a, b) a##b
|
||||
#define REFLECTIVE_RAPIDJSON_CAT_2(a, b) REFLECTIVE_RAPIDJSON_CAT_1(a, b)
|
||||
#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) \
|
||||
static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjAsOfVersion, __COUNTER__) = version; \
|
||||
public
|
||||
#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) \
|
||||
static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjUntilVersion, __COUNTER__) = version; \
|
||||
public
|
||||
#else
|
||||
#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) public
|
||||
#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) public
|
||||
#endif
|
||||
|
||||
#ifdef REFLECTIVE_RAPIDJSON_SHORT_MACROS
|
||||
#define as_of_version(version) REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version)
|
||||
#define until_version(version) REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version)
|
||||
#endif
|
||||
|
||||
CPP_UTILITIES_TRAITS_DEFINE_TYPE_CHECK(IsVersioned, T::version);
|
||||
|
||||
template <typename Type, bool Condition = IsVersioned<Type>::value> struct Versioning {
|
||||
static constexpr auto enabled = false;
|
||||
};
|
||||
|
||||
template <typename Type> struct Versioning<Type, true> {
|
||||
static constexpr auto enabled = Type::version != 0;
|
||||
static constexpr auto serializationDefault = Type::version;
|
||||
static constexpr auto applyDefault(decltype(serializationDefault) version)
|
||||
{
|
||||
return version ? version : serializationDefault;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_TRAITS
|
Loading…
Reference in New Issue