Compare commits

..

3 Commits

Author SHA1 Message Date
Martchus 3f6f790849 WIP: Allow specifying version of binary serializable 2021-05-23 18:20:17 +02:00
Martchus cb98e348f1 WIP: Cope with macros 2021-05-23 18:10:09 +02:00
Martchus a6461795a7 WIP: Experiment with annotations 2021-05-16 19:25:51 +02:00
34 changed files with 271 additions and 819 deletions

19
.github/stale.yml vendored
View File

@ -1,19 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- feature request
- enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
# set metadata
project(reflective_rapidjson)

View File

@ -62,19 +62,18 @@ The following table shows the mapping of supported C++ types to supported JSON t
| iteratable lists (`std::vector`, `std::list`, ...) | array |
| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array |
| `std::pair`, `std::tuple` | array |
| `std::unique_ptr`, `std::shared_ptr`, `std::optional` | depends/null |
| `std::unique_ptr`, `std::shared_ptr` | depends/null |
| `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` | object |
| `std::variant` | object |
| `JsonSerializable` | object |
### Remarks
* Raw pointers are not supported. This prevents
* Raw pointer are not supported. This prevents
forgetting to free memory which would have to be allocated when deserializing.
* For the same reason `const char *` and `std::string_view` are only supported for serialization.
* Enums are (de)serialized as their underlying integer value. When deserializing, it is currently *not* checked
whether the present integer value is a valid enumeration item.
* The JSON type for smart pointers and `std::optional` depends on the type the pointer/optional refers to.
It can also be `null` for null pointers or `std::optional` without value.
* The JSON type for smart pointer depends on the type the pointer refers to. It can also be `null`.
* If multiple `std::shared_ptr` instances point to the same object this object is serialized multiple times.
When deserializing those identical objects, it is currently not possible to share the memory (again). So each
`std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary
@ -354,54 +353,6 @@ 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 &lt;reflective_rapidjson/binary/serializable.h&gt;
// 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&lt;Nested&gt; { //
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 &gt;= 3
};
// example struct where version is serialized/deserialized; defaults to version 3 when writing
struct Example : public BinarySerializable&lt;Example, 3&gt; {
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 &lt;= 2
as_of_version(3):
std::uint32_t e, f; // will be read/written if version is &gt;= 3
as_of_version(4):
std::uint32_t g; // will be read/written if version is &gt;= 4
};
</pre>
The version specified as template argument is also assumed to be the highest supported version.
If a higher version is encountered during deserialization, `BinaryVersionNotSupported` is thrown
and the deserialization aborted.
Note that the versioning is mostly 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.
@ -511,7 +462,7 @@ make
make check
# build tests but do not run them (optional, requires CppUnit)
make tests
# generate API documentation (optional, requires Doxygen)
# generate API documentation (optional, reqquires Doxygen)
make apidoc
# install header files, libraries and generator
make install DESTDIR="/temporary/install/location"
@ -534,8 +485,3 @@ Add eg. `-j$(nproc)` to `make` arguments for using all cores.
These packages shows the required dependencies and commands to build in a plain way. So they might be useful for
making Reflective RapidJSON available under other platforms, too.
## Copyright notice and license
Copyright © 2017-2023 Marius Kittler
All code is licensed under [GPL-2-or-later](LICENSE).

View File

@ -7,14 +7,14 @@
- [x] Add documentation (install instructions, usage)
- [x] Allow making 3rdparty classes/structs reflectable
- [x] Add additional parameter for code generator to allow specifying relevant classes
explicitly
explicitely
- [x] Fix traits currently relying on `JsonSerializable` being base class
- [x] Allow exporting symbols
- [x] Fix the massive number of warnings which are currently being created by the code generator (missing `-resource-dir` was the problem)
- [ ] Test with libc++ (currently only tested with libstdc++)
- [ ] Support templated classes
- [ ] Allow (de)serialization of static members (if that makes sense?)
- [ ] Allow ignoring particular members or selecting specifically which member variables should be considered
- [ ] Allow ignoring particular members or selecting specificly which member variables should be considered
* This could work similar to Qt's Signals & Slots macros.
* but there should also be a way to do this for 3rdparty types.
* Note that currently, *all* public member variables are (de)serialized.
@ -29,5 +29,5 @@
- [x] Support `std::unique_ptr` and `std::shared_ptr`
- [x] Support `std::map` and `std::unordered_map`
- [ ] Support `std::any`
- [x] Support `std::optional`
- [ ] Support `std::optional`
- [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
# metadata
set(META_PROJECT_NAME reflective_rapidjson_generator)
@ -60,9 +60,6 @@ endif ()
# also add reflective_rapidjson which is header-only but might pull additional include dirs for RapidJSON
list(APPEND PRIVATE_LIBRARIES "${REFLECTIVE_RAPIDJSON_TARGET_NAME}")
# avoid warning "'this' pointer is null" from GCC 12 about code included from libclang
list(APPEND META_PRIVATE_COMPILE_OPTIONS "-Wno-error=nonnull")
# include modules to apply configuration
include(BasicConfig)
include(WindowsResources)

View File

@ -5,10 +5,6 @@
#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>
@ -39,159 +35,36 @@ BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &
}
/*!
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
*/
void BinarySerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
string BinarySerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
{
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
return;
const string qualifiedName(record->getQualifiedNameAsString());
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
case IsRelevant::Yes:
return qualifiedName;
case IsRelevant::No:
return string();
default:;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
return;
return string();
}
for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
for (const char *className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) {
return qualifiedName;
}
}
}
/// \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 << " ";
}
return string();
}
/*!
* \brief Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace for the relevant classes.
*/
void BinarySerializationCodeGenerator::generate(std::ostream &os) const
void BinarySerializationCodeGenerator::generate(ostream &os) const
{
// initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
lazyInitializeSourceManager();
@ -245,55 +118,25 @@ void BinarySerializationCodeGenerator::generate(std::ostream &os) const
// print writeCustomType method
os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
os << " // write base classes\n";
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName
<< " &customObject)\n{\n"
" // write base classes\n";
for (const RelevantClass *baseClass : relevantBases) {
os << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject), version);\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 << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject));\n";
}
os << " // write members\n";
auto mt = MemberTracking();
for (clang::Decl *const decl : relevantClass.record->decls()) {
// check static member variables for version markers
if (mt.checkForVersionMarker(decl)) {
continue;
auto membersWritten = false;
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
for (const auto &attr : field->getAttrs()) {
cout << " // annotation: " << readAnnotation(attr) << '\n';
}
// skip all further declarations but fields
if (decl->getKind() != clang::Decl::Kind::Field) {
continue;
if (writePrivateMembers || field->getAccess() == clang::AS_public) {
os << " serializer.write(customObject." << field->getName() << ");\n";
membersWritten = true;
}
// 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;";
if (relevantBases.empty() && !membersWritten) {
os << " (void)serializer;\n (void)customObject;\n";
}
os << "}\n";
@ -303,56 +146,28 @@ void BinarySerializationCodeGenerator::generate(std::ostream &os) const
}
// print readCustomType method
mt = MemberTracking();
os << "template <> " << visibility << " BinaryVersion readCustomType<::" << relevantClass.qualifiedName
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
os << " // read base classes\n";
os << "template <> " << visibility << " void readCustomType<::" << relevantClass.qualifiedName
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName
<< " &customObject)\n{\n"
" // read base classes\n";
for (const RelevantClass *baseClass : relevantBases) {
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject), version);\n";
}
if (!relevantClass.relevantBase.empty()) {
os << " // read version\n"
" using V = Versioning<"
<< relevantClass.relevantBase
<< ">;\n"
" if constexpr (V::enabled) {\n"
" V::assertVersion(version = deserializer.readVariableLengthUIntBE(), \""
<< relevantClass.qualifiedName
<< "\");\n"
" }\n";
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject));\n";
}
os << " // read members\n";
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;
}
auto membersRead = false;
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
// 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() << ", version);\n";
mt.membersWritten = true;
os << " deserializer.read(customObject." << field->getName() << ");\n";
membersRead = true;
}
}
mt.concludeCondition(os);
if (relevantBases.empty() && !mt.membersWritten) {
if (relevantBases.empty() && !membersRead) {
os << " (void)deserializer;\n (void)customObject;\n";
}
os << " return version;\n";
os << "}\n\n";
}

View File

@ -27,7 +27,7 @@ public:
void generate(std::ostream &os) const override;
protected:
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
const Options &m_options;
};

View File

@ -85,7 +85,7 @@ bool CodeFactory::generate() const
}
/*!
* \brief Reads (relevant) AST elements using Clang and generates code.
* \brief Reads (relevent) AST elements using Clang and generates code.
*/
bool CodeFactory::run()
{

View File

@ -3,8 +3,10 @@
#include <c++utilities/application/global.h>
#include <clang/AST/Attr.h>
#include <clang/AST/DeclCXX.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Lex/Lexer.h>
using namespace std;
@ -24,7 +26,7 @@ void CodeGenerator::addDeclaration(clang::Decl *decl)
/*!
* \brief Lazy initializes the source manager.
* \remarks This method must be called in generate() when subclassing to make use of isOnlyIncluded().
* \remarks This method must be called in generate() when subclassing to make use of isOnlyIncluded() and readAnnotation().
*/
void CodeGenerator::lazyInitializeSourceManager() const
{
@ -42,20 +44,34 @@ bool CodeGenerator::isOnlyIncluded(const clang::Decl *declaration) const
&& m_sourceManager->getFileID(m_sourceManager->getExpansionLoc(declaration->getSourceRange().getBegin())) != m_sourceManager->getMainFileID();
}
/*!
* \brief Returns the specified \a annotation's text.
*/
std::string_view CodeGenerator::readAnnotation(const clang::Attr *annotation) const
{
if (!m_sourceManager) {
return std::string_view();
}
auto text = clang::Lexer::getSourceText(sourceManager()->getExpansionRange(annotation->getRange()), *sourceManager(), clang::LangOptions());
if (text.size() >= 12 && text.startswith("annotate(\"") && text.endswith("\")")) {
text = text.substr(10, text.size() - 12);
}
return text;
}
/*!
* \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).
*/
clang::CXXBaseSpecifier *CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
bool CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
{
for (clang::CXXBaseSpecifier &base : record->bases()) {
clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
for (const clang::CXXBaseSpecifier &base : record->bases()) {
const clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) {
return &base;
return true;
}
}
return nullptr;
return false;
}
} // namespace ReflectiveRapidJSON

View File

@ -6,9 +6,9 @@
#include <vector>
namespace clang {
class Attr;
class Decl;
class CXXRecordDecl;
class CXXBaseSpecifier;
class SourceManager;
} // namespace clang
@ -33,7 +33,9 @@ protected:
CodeFactory &factory() const;
void lazyInitializeSourceManager() const;
bool isOnlyIncluded(const clang::Decl *declaration) const;
static clang::CXXBaseSpecifier *inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
std::string_view readAnnotation(const clang::Attr *annotation) const;
const clang::SourceManager *sourceManager() const;
static bool inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
private:
CodeFactory &m_factory;
@ -51,6 +53,11 @@ inline CodeFactory &CodeGenerator::factory() const
return m_factory;
}
inline const clang::SourceManager *CodeGenerator::sourceManager() const
{
return m_sourceManager;
}
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_CODE_GENERATOR_H

View File

@ -35,25 +35,30 @@ JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &fact
}
/*!
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
*/
void JsonSerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
string JsonSerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
{
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
return;
const string qualifiedName(record->getQualifiedNameAsString());
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
case IsRelevant::Yes:
return qualifiedName;
case IsRelevant::No:
return string();
default:;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
return;
return string();
}
for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
for (const char *className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) {
return qualifiedName;
}
}
return string();
}
/*!
@ -127,7 +132,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
}
}
if (relevantBases.empty() && !pushWritten) {
os << " (void)reflectable;\n (void)value;\n (void)allocator;\n";
os << " (void)reflectable;\n (void)value;\n";
}
os << "}\n";
@ -146,7 +151,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
os << " pull(static_cast<::" << baseClass->qualifiedName << " &>(reflectable), value, errors);\n";
}
os << " // set error context for current record\n"
" const char *previousRecord = nullptr;\n"
" const char *previousRecord;\n"
" if (errors) {\n"
" previousRecord = errors->currentRecord;\n"
" errors->currentRecord = \""

View File

@ -27,7 +27,7 @@ public:
void generate(std::ostream &os) const override;
protected:
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
const Options &m_options;
};

View File

@ -123,14 +123,14 @@ int main(int argc, char *argv[])
// read AST elements from input files and run the code generator
if (!factory.run()) {
cerr << Phrases::Error << "Errors occurred." << Phrases::EndFlush;
cerr << Phrases::Error << "Errors occured." << Phrases::EndFlush;
return -2;
}
} catch (const std::ios_base::failure &failure) {
const char *errorMessage = failure.what();
if (os) {
errorMessage = os->fail() || os->bad() ? "An IO error occurred when writing to the output stream." : "An IO error occurred.";
errorMessage = os->fail() || os->bad() ? "An IO error occured when writing to the output stream." : "An IO error occured.";
} else {
errorMessage = "An IO error when opening output stream.";
}

View File

@ -5,8 +5,6 @@
#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>
@ -68,12 +66,12 @@ void SerializationCodeGenerator::addDeclaration(clang::Decl *decl)
}
}
void SerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNameIfRelevant(
clang::CXXRecordDecl *record, const std::string &qualifiedName) const
{
// skip all classes which are only forward-declared
if (!possiblyRelevantClass.record->isCompleteDefinition()) {
possiblyRelevantClass.isRelevant = IsRelevant::No;
return;
if (!record->isCompleteDefinition()) {
return IsRelevant::No;
}
// consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available
@ -82,40 +80,31 @@ void SerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRel
if (isOnlyIncluded(adaptionRecord.record)) {
continue;
}
if (adaptionRecord.qualifiedName == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
if (adaptionRecord.qualifiedName == qualifiedName) {
return IsRelevant::Yes;
}
}
// skip all classes which are only included
if (isOnlyIncluded(possiblyRelevantClass.record)) {
possiblyRelevantClass.isRelevant = IsRelevant::No;
return;
if (isOnlyIncluded(record)) {
return IsRelevant::No;
}
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant
if (const auto *const relevantBase = inheritsFromInstantiationOf(possiblyRelevantClass.record, m_qualifiedNameOfRecords)) {
auto policy = clang::PrintingPolicy(possiblyRelevantClass.record->getASTContext().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;
if (inheritsFromInstantiationOf(record, m_qualifiedNameOfRecords)) {
return IsRelevant::Yes;
}
return IsRelevant::Maybe;
}
std::vector<SerializationCodeGenerator::RelevantClass> SerializationCodeGenerator::findRelevantClasses() const
{
std::vector<RelevantClass> relevantClasses;
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();
for (clang::CXXRecordDecl *record : m_records) {
string qualifiedName(qualifiedNameIfRelevant(record));
if (!qualifiedName.empty()) {
relevantClasses.emplace_back(move(qualifiedName), record);
}
}
return relevantClasses;

View File

@ -5,8 +5,6 @@
#include <llvm/ADT/StringRef.h>
#include <optional>
namespace ReflectiveRapidJSON {
std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
@ -17,14 +15,11 @@ 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;
std::string relevantBase;
clang::CXXRecordDecl *record = nullptr;
IsRelevant isRelevant = IsRelevant::Maybe;
clang::CXXRecordDecl *record;
};
SerializationCodeGenerator(CodeFactory &factory);
@ -32,7 +27,9 @@ public:
void addDeclaration(clang::Decl *decl) override;
protected:
virtual void computeRelevantClass(RelevantClass &possiblyRelevantClass) const;
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;
std::vector<RelevantClass> findRelevantClasses() const;
static std::vector<const RelevantClass *> findRelevantBaseClasses(
const RelevantClass &relevantClass, const std::vector<RelevantClass> &relevantBases);

View File

@ -13,7 +13,7 @@ template <> void pull<::TestNamespace1::Person>(::TestNamespace1::Person &refle
{
// pull base classes
// set error context for current record
const char *previousRecord = nullptr;
const char *previousRecord;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "TestNamespace1::Person";
@ -39,7 +39,7 @@ template <> void pull<::TestNamespace2::ThirdPartyStruct>(::TestNamespace2::Thi
{
// pull base classes
// set error context for current record
const char *previousRecord = nullptr;
const char *previousRecord;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "TestNamespace2::ThirdPartyStruct";

View File

@ -84,7 +84,7 @@ void JsonGeneratorTests::testGeneratorItself()
}
/*!
* \brief Tests the generator CLI explicitly.
* \brief Tests the generator CLI explicitely.
* \remarks Only available under UNIX (like) systems so far, because TESTUTILS_ASSERT_EXEC has not been implemented
* for other platforms.
*/
@ -164,7 +164,7 @@ void JsonGeneratorTests::testNesting()
}
/*!
* \brief Like testIncludingGeneratedHeader() but also tests single inheritance.
* \brief Like testIncludingGeneratedHeader() but also tests single inheritence.
*/
void JsonGeneratorTests::testSingleInheritence()
{
@ -193,7 +193,7 @@ void JsonGeneratorTests::testSingleInheritence()
}
/*!
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritance.
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritence.
*/
void JsonGeneratorTests::testMultipleInheritence()
{

View File

@ -1,11 +1,8 @@
#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;
@ -70,29 +67,26 @@ 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 {
#ifdef REFLECTIVE_RAPIDJSON_GENERATOR
#define RR_ATTR(text) __attribute__((annotate(text)))
#define RR_V1 RR_ATTR("cond: version >= 1")
#else
#define RR_ATTR(text)
#define RR_V1
#endif
/*!
* \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;
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"
#endif
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;
struct AnnotatedStruct : public BinarySerializable<AnnotatedStruct> {
int anyVersion;
RR_V1 RR_ATTR("cond: version >= 2") RR_ATTR("foo") __attribute__((annotate("bar"))) int newInVersion1;
};
// clang-format on
} // namespace SomeNamespace
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#endif // REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H

View File

@ -128,7 +128,7 @@ REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(NotJsonSerializable);
REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(NestedNotJsonSerializable);
/*!
* \brief The OtherNotJsonSerializable struct is used to test whether code for (de)serialization is generated for classes explicitly
* \brief The OtherNotJsonSerializable struct is used to test whether code for (de)serialization is generated for classes explicitely
* specified via CMake macro (despite use of REFLECTIVE_RAPIDJSON_ADAPT_JSON_SERIALIZABLE or JsonSerializable is
* missing).
*/

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
# metadata
set(META_PROJECT_TYPE library)
set(META_HEADER_ONLY_LIB ON)
# add project files
set(HEADER_FILES traits.h versioning.h)
set(HEADER_FILES traits.h)
set(SRC_FILES)
set(TEST_HEADER_FILES)
set(TEST_SRC_FILES)

View File

@ -24,17 +24,16 @@
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version)
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void readCustomType(BinaryDeserializer &deserializer, Type &customType)
{
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { deserializer.read(boost::hana::at_key(customType, key), version); });
return 0;
boost::hana::for_each(
boost::hana::keys(customType), [&deserializer, &customType](auto key) { deserializer.read(boost::hana::at_key(customType, key)); });
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version)
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void writeCustomType(BinarySerializer &serializer, const Type &customType)
{
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { serializer.write(boost::hana::at_key(customType, key), version); });
boost::hana::for_each(
boost::hana::keys(customType), [&serializer, &customType](auto key) { serializer.write(boost::hana::at_key(customType, key)); });
}
} // namespace BinaryReflector

View File

@ -16,33 +16,23 @@
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <>
inline BinaryVersion readCustomType<CppUtilities::DateTime>(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime, BinaryVersion version)
template <> inline void readCustomType<CppUtilities::DateTime>(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(dateTime.ticks());
return 0;
}
template <>
inline void writeCustomType<CppUtilities::DateTime>(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime, BinaryVersion version)
template <> inline void writeCustomType<CppUtilities::DateTime>(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(dateTime.totalTicks());
}
template <>
inline BinaryVersion readCustomType<CppUtilities::TimeSpan>(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
template <> inline void readCustomType<CppUtilities::TimeSpan>(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(timeSpan.ticks());
return 0;
}
template <>
inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
template <> inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(timeSpan.totalTicks());
}

View File

@ -8,7 +8,6 @@
*/
#include "../traits.h"
#include "../versioning.h"
#include <c++utilities/conversion/conversionexception.h>
#include <c++utilities/io/binaryreader.h>
@ -17,7 +16,6 @@
#include <any>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <variant>
@ -35,8 +33,11 @@ template <typename T> struct AdaptedBinarySerializable : public Traits::Bool<fal
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedBinarySerializable";
};
using BinaryVersion = std::uint64_t;
template <typename Type, BinaryVersion v = 0> struct BinarySerializable;
template <typename Type> struct BinarySerializable;
template <typename Type> struct BinarySerializableMeta {
static constexpr std::uint64_t version = 0;
};
/*!
* \brief The BinaryReflector namespace contains BinaryReader and BinaryWriter for automatic binary (de)serialization.
@ -47,29 +48,15 @@ namespace BinaryReflector {
template <typename Type>
using IsBuiltInType = Traits::Any<Traits::IsAnyOf<Type, char, std::uint8_t, bool, std::string, std::int16_t, std::uint16_t, std::int32_t,
std::uint32_t, std::int64_t, std::uint64_t, float, double>,
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr, std::optional>, std::is_enum<Type>,
IsVariant<Type>>;
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr>, std::is_enum<Type>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
class BinaryDeserializer;
class BinarySerializer;
/// \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 determined or 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);
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 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;
@ -80,7 +67,6 @@ public:
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void read(Type &pair);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type,
@ -89,14 +75,12 @@ 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<IsBuiltInType<Type>> * = nullptr> BinaryVersion read(Type &builtInType, BinaryVersion version);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> BinaryVersion read(Type &customType, BinaryVersion version = 0);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void read(Type &customType);
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;
@ -105,14 +89,12 @@ public:
using CppUtilities::BinaryWriter::write;
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void write(const Type &pair);
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::optional>> * = nullptr>
void write(const Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr>> * = nullptr> void write(const Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::shared_ptr>> * = nullptr> void write(const Type &pointer);
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<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);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType);
private:
std::unordered_map<std::uint64_t, bool> m_pointer;
@ -150,12 +132,12 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
const auto id = (mode & 0x4) ? readUInt64BE() : readVariableLengthUIntBE(); // the 3rd bit being flagged indicates a big ID
if ((mode & 0x3) == 1) {
// first occurrence: make a new pointer
// first occurence: make a new pointer
m_pointer[id] = pointer = std::make_shared<typename Type::element_type>();
read(*pointer);
return;
}
// further occurrences: copy previous pointer
// further occurences: copy previous pointer
try {
pointer = std::any_cast<Type>(m_pointer[id]);
} catch (const std::bad_any_cast &) {
@ -163,16 +145,6 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
}
}
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> *> void BinaryDeserializer::read(Type &opt)
{
if (!readBool()) {
opt.reset();
return;
}
opt = std::make_optional<typename Type::value_type>();
read(*opt);
}
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> *> void BinaryDeserializer::read(Type &iteratable)
{
const auto size = readVariableLengthUIntBE();
@ -239,15 +211,9 @@ template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinaryDeseria
Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this);
}
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &builtInType, BinaryVersion version)
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinaryDeserializer::read(Type &customType)
{
read(builtInType);
return version;
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &customType, BinaryVersion version)
{
return readCustomType(*this, customType, version);
readCustomType(*this, customType);
}
inline BinarySerializer::BinarySerializer(std::ostream *stream)
@ -261,12 +227,12 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
write(pair.second);
}
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::optional>> *>
void BinarySerializer::write(const Type &opt)
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr>> *> void BinarySerializer::write(const Type &pointer)
{
writeBool(static_cast<bool>(opt));
if (opt) {
write(*opt);
const bool hasValue = pointer != nullptr;
writeBool(hasValue);
if (hasValue) {
write(*pointer);
}
}
@ -324,15 +290,9 @@ template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySeriali
variant);
}
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version)
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &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);
writeCustomType(*this, customType);
}
} // namespace BinaryReflector

View File

@ -3,7 +3,7 @@
/*!
* \file serializable.h
* \brief Contains only the definition of the BinarySerializable template class which makes the reflection
* \brief Contains only the definiation of the BinarySerializable template class which makes the reflection
* accessible. The actual implementation is found in binaryreflector.h and generated files.
*/
@ -14,36 +14,29 @@
namespace ReflectiveRapidJSON {
using BinaryVersionNotSupported = VersionNotSupported<BinaryVersion>;
/*!
* \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects.
*/
template <typename Type, BinaryVersion v> struct BinarySerializable {
using VersionNotSupported = BinaryVersionNotSupported;
void toBinary(std::ostream &outputStream, BinaryVersion version = 0) const;
BinaryVersion restoreFromBinary(std::istream &inputStream);
template <typename Type> struct BinarySerializable {
void toBinary(std::ostream &outputStream) const;
void restoreFromBinary(std::istream &inputStream);
static Type fromBinary(std::istream &inputStream);
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::BinarySerializable";
static constexpr auto version = v;
#if __cplusplus > 201707L
bool operator==(const BinarySerializable<Type, v> &) const = default;
#endif
static constexpr auto version = BinarySerializableMeta<Type>::version;
};
template <typename Type, BinaryVersion v> inline void BinarySerializable<Type, v>::toBinary(std::ostream &outputStream, BinaryVersion version) const
template <typename Type> inline void BinarySerializable<Type>::toBinary(std::ostream &outputStream) const
{
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this), version);
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this));
}
template <typename Type, BinaryVersion v> inline BinaryVersion BinarySerializable<Type, v>::restoreFromBinary(std::istream &inputStream)
template <typename Type> inline void BinarySerializable<Type>::restoreFromBinary(std::istream &inputStream)
{
return BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
}
template <typename Type, BinaryVersion v> Type BinarySerializable<Type, v>::fromBinary(std::istream &inputStream)
template <typename Type> Type BinarySerializable<Type>::fromBinary(std::istream &inputStream)
{
Type object;
static_cast<BinarySerializable<Type> &>(object).restoreFromBinary(inputStream);
@ -58,7 +51,16 @@ template <typename Type, BinaryVersion v> Type BinarySerializable<Type, v>::from
* Find out whether this is a compiler bug or a correct error message.
*/
#define REFLECTIVE_RAPIDJSON_MAKE_BINARY_SERIALIZABLE(T) \
template <> struct ReflectiveRapidJSON::AdaptedBinarySerializable<T> : Traits::Bool<true> {}
template <> struct ReflectiveRapidJSON::AdaptedBinarySerializable<T> : Traits::Bool<true> { \
}
/*!
* \def The REFLECTIVE_RAPIDJSON_DECLARE_BINARY_SERIALIZABLE_VERSION macro allows to declare the version of a BinarySerializable.
*/
#define REFLECTIVE_RAPIDJSON_DECLARE_BINARY_SERIALIZABLE_VERSION(T, v) \
template <> struct ReflectiveRapidJSON::BinarySerializableMeta<T> { \
static constexpr std::uint64_t version = v; \
}
} // namespace ReflectiveRapidJSON

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED REFLECTION_GENERATOR_MODULE_LOADED)
@ -44,10 +44,10 @@ if (NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR)
message(FATAL_ERROR "Unable to find the clang executable to determine Clang's resource directory")
endif ()
endif ()
execute_process(
COMMAND ${REFLECTION_GENERATOR_CLANG_BIN} -print-resource-dir
OUTPUT_VARIABLE REFLECTION_GENERATOR_CLANG_RESOURCE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
exec_program(
${REFLECTION_GENERATOR_CLANG_BIN} ARGS
-print-resource-dir
OUTPUT_VARIABLE REFLECTION_GENERATOR_CLANG_RESOURCE_DIR)
endif ()
if (NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR OR NOT IS_DIRECTORY "${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}")
message(
@ -87,7 +87,7 @@ endfunction ()
include(CMakeParseArguments)
function (add_reflection_generator_invocation)
# parse arguments
set(OPTIONAL_ARGS ERROR_RESILIENT)
set(OPTIONAL_ARGS)
set(ONE_VALUE_ARGS OUTPUT_DIRECTORY JSON_VISIBILITY BINARY_VISBILITY)
set(MULTI_VALUE_ARGS
INPUT_FILES
@ -121,25 +121,30 @@ function (add_reflection_generator_invocation)
list(APPEND ARGS_CLANG_OPTIONS -I "${INCLUDE_DIR}")
endforeach ()
# avoid including headers from host when cross compiling
if (CMAKE_CROSSCOMPILING)
list(APPEND ARGS_CLANG_OPTIONS -nostdinc)
# add workaround for cross compiling with mingw-w64 to prevent host stdlib.h being included (not sure why specifying
# REFLECTION_GENERATOR_INCLUDE_DIRECTORIES is not enough to let it find this particular header file)
if (MINGW)
# find MinGW version of stdlib.h
find_file(MINGW_W64_STDLIB_H stdlib.h ${REFLECTION_GENERATOR_INCLUDE_DIRECTORIES})
if (NOT EXISTS "${MINGW_W64_STDLIB_H}")
message(
FATAL_ERROR
"Unable to locate MinGW version of stdlib.h. Ensure it is in REFLECTION_GENERATOR_INCLUDE_DIRECTORIES.")
endif ()
# ensure libtooling includes the MinGW version of stdlib.h rather than the host version
list(APPEND ARGS_CLANG_OPTIONS -include "${MINGW_W64_STDLIB_H}" -D_STDLIB_H # prevent processing of host stdlib.h
)
endif ()
# add options to be passed to clang from the specified targets
if (ARGS_CLANG_OPTIONS_FROM_TARGETS)
foreach (TARGET_NAME ${ARGS_CLANG_OPTIONS_FROM_TARGETS})
# set c++ standard
list(
APPEND
ARGS_CLANG_OPTIONS
"$<$<BOOL:$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>>:-std=c++$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>>"
)
# add compile flags and options
list(APPEND ARGS_CLANG_OPTIONS "-std=c++$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>")
# add compile flags
_reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_FLAGS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
_reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_OPTIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
# add compile definitions
_reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_DEFINITIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>")
@ -190,9 +195,6 @@ function (add_reflection_generator_invocation)
if (ARGS_BINARY_VISBILITY)
list(APPEND CLI_ARGUMENTS --binary-visibility "${ARGS_BINARY_VISBILITY}")
endif ()
if (ARGS_ERROR_RESILIENT)
list(APPEND CLI_ARGUMENTS --error-resilient)
endif ()
add_custom_command(
OUTPUT "${OUTPUT_FILE}"
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}" ARGS ${CLI_ARGUMENTS}

View File

@ -1 +0,0 @@
../../versioning.h

View File

@ -110,7 +110,7 @@ struct JsonDeserializationError {
JsonDeserializationError(JsonDeserializationErrorKind kind, JsonType expectedType, JsonType actualType, const char *record,
const char *member = nullptr, std::size_t index = noIndex);
/// \brief Which kind of error occurred.
/// \brief Which kind of error occured.
JsonDeserializationErrorKind kind;
/// \brief The expected type (might not be relevant for all error kinds).
JsonType expectedType;
@ -123,7 +123,7 @@ struct JsonDeserializationError {
/// \brief The index in the array which was being processed when the error was ascertained.
std::size_t index;
/// \brief Indicates no array was being processed when the error occurred.
/// \brief Indicates no array was being processed when the error occured.
static constexpr std::size_t noIndex = std::numeric_limits<std::size_t>::max();
};
@ -155,7 +155,6 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
JsonDeserializationErrors();
template <typename ExpectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
template <RAPIDJSON_NAMESPACE::Type expectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
void reportArraySizeMismatch();
void reportConversionError(JsonType jsonType);
void reportUnexpectedDuplicate(JsonType jsonType);
@ -221,16 +220,6 @@ template <typename ExpectedType> inline void JsonDeserializationErrors::reportTy
throwMaybe(ThrowOn::TypeMismatch);
}
/*!
* \brief Reports a type mismatch between \tparam expectedType and \a presentType within the current context.
*/
template <RAPIDJSON_NAMESPACE::Type expectedType> inline void JsonDeserializationErrors::reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType)
{
emplace_back(
JsonDeserializationErrorKind::TypeMismatch, jsonType(expectedType), jsonType(presentType), currentRecord, currentMember, currentIndex);
throwMaybe(ThrowOn::TypeMismatch);
}
/*!
* \brief Reports an array size mismatch.
* \todo Allow specifying expected and actual size.

View File

@ -19,13 +19,11 @@
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include "./errorhandling.h"
@ -83,8 +81,9 @@ inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, st
// define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes
template <typename Type>
using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>,
Traits::IsSpecializingAnyOf<Type, std::tuple, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>, IsVariant<Type>>;
Traits::IsSpecializationOf<Type, std::tuple>, Traits::IsSpecializationOf<Type, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializationOf<Type, std::shared_ptr>,
Traits::IsSpecializationOf<Type, std::weak_ptr>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// define trait to check for custom structs/classes which are JSON serializable
@ -148,24 +147,12 @@ inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAP
/*!
* \brief Pushes the specified integer/float/boolean to the specified value.
*/
template <typename Type,
Traits::EnableIfAny<
Traits::All<std::is_integral<Type>, Traits::Not<std::is_same<Type, std::uint8_t>>, Traits::Not<std::is_same<Type, std::int8_t>>>,
std::is_floating_point<Type>> * = nullptr>
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.Set(reflectable, allocator);
}
/*!
* \brief Pushes the specified 8-bit integer to the specified value.
*/
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::uint8_t>, std::is_same<Type, std::int8_t>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.Set(static_cast<int>(reflectable), allocator);
}
/*!
* \brief Pushes the specified enumeration item to the specified value.
*/
@ -319,10 +306,11 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
}
/*!
* \brief Pushes the specified unique_ptr, shared_ptr, weak_ptr or optional to the specified value.
* \brief Pushes the specified unique_ptr, shared_ptr or weak_ptr to the specified value.
*/
template <typename Type,
Traits::EnableIfAny<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>> * = nullptr>
Traits::EnableIfAny<Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializationOf<Type, std::shared_ptr>,
Traits::IsSpecializationOf<Type, std::weak_ptr>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
if (!reflectable) {
@ -491,12 +479,6 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
*/
@ -529,8 +511,7 @@ void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_N
* \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type.
*/
template <typename Type,
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Not<std::is_same<Type, std::uint8_t>>,
Traits::Not<std::is_same<Type, std::int8_t>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>> * = nullptr>
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>> * = nullptr>
inline void pull(
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
@ -543,20 +524,6 @@ inline void pull(
reflectable = value.Is<Type>() ? value.Get<Type>() : static_cast<Type>(value.GetDouble());
}
/*!
* \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type.
*/
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::uint8_t>, std::is_same<Type, std::int8_t>> * = nullptr>
inline void pull(
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
int i = 0;
pull(i, value, errors);
if (value.IsNumber()) {
reflectable = static_cast<Type>(i);
}
}
/*!
* \brief Pulls the boolean from the specified value which is supposed and checked to contain the right type.
*/
@ -701,7 +668,7 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
++index;
typename Type::value_type itemObj;
pull(itemObj, item, errors);
reflectable.emplace(std::move(itemObj));
reflectable.emplace(move(itemObj));
}
// clear error context
@ -729,7 +696,7 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
++index;
typename Type::value_type itemObj;
pull(itemObj, item, errors);
if (!reflectable.emplace(std::move(itemObj)).second) {
if (!reflectable.emplace(move(itemObj)).second) {
errors->reportUnexpectedDuplicate(JsonType::Array);
}
}
@ -882,20 +849,6 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
pull(*reflectable, value, errors);
}
/*!
* \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (value.IsNull()) {
reflectable.reset();
return;
}
reflectable = std::make_optional<typename Type::value_type>();
pull(*reflectable, value, errors);
}
/// \cond
namespace Detail {
template <typename Variant, std::size_t compiletimeIndex = 0>
@ -982,7 +935,7 @@ inline void pull(Type &reflectable, const char *name, const rapidjson::GenericVa
}
// set error context for current member
const char *previousMember = nullptr;
const char *previousMember;
if (errors) {
previousMember = errors->currentMember;
errors->currentMember = name;

View File

@ -3,7 +3,7 @@
/*!
* \file serializable.h
* \brief Contains only the definition of the JsonSerializable template class which makes the reflection
* \brief Contains only the definiation of the JsonSerializable template class which makes the reflection
* accessible. The actual implementation is found in jsonreflector.h and generated files.
*/
@ -31,10 +31,6 @@ template <typename Type> struct JsonSerializable {
static Type fromJson(const std::string &json, JsonDeserializationErrors *errors = nullptr);
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::JsonSerializable";
#if __cplusplus > 201707L
bool operator==(const JsonSerializable<Type> &) const = default;
#endif
};
/*!
@ -120,7 +116,8 @@ const JsonSerializable<Type> &as(const Type &serializable)
* Find out whether this is a compiler bug or a correct error message.
*/
#define REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(T) \
template <> struct ReflectiveRapidJSON::AdaptedJsonSerializable<T> : Traits::Bool<true> {}
template <> struct ReflectiveRapidJSON::AdaptedJsonSerializable<T> : Traits::Bool<true> { \
}
/*!
* \def The REFLECTIVE_RAPIDJSON_PUSH_PRIVATE_MEMBERS macro enables serialization of private members.

View File

@ -75,9 +75,8 @@ struct ObjectWithVariantsBinary : public BinarySerializable<ObjectWithVariantsBi
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <> BinaryVersion readCustomType<TestObjectBinary>(BinaryDeserializer &deserializer, TestObjectBinary &customType, BinaryVersion version)
template <> void readCustomType<TestObjectBinary>(BinaryDeserializer &deserializer, TestObjectBinary &customType)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.number);
deserializer.read(customType.number2);
deserializer.read(customType.numbers);
@ -93,12 +92,10 @@ template <> BinaryVersion readCustomType<TestObjectBinary>(BinaryDeserializer &d
deserializer.read(customType.someEnumClass);
deserializer.read(customType.timeSpan);
deserializer.read(customType.dateTime);
return 0;
}
template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer, const TestObjectBinary &customType, BinaryVersion version)
template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer, const TestObjectBinary &customType)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(customType.number);
serializer.write(customType.number2);
serializer.write(customType.numbers);
@ -116,35 +113,27 @@ template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer,
serializer.write(customType.dateTime);
}
template <> BinaryVersion readCustomType<NestingArrayBinary>(BinaryDeserializer &deserializer, NestingArrayBinary &customType, BinaryVersion version)
template <> void readCustomType<NestingArrayBinary>(BinaryDeserializer &deserializer, NestingArrayBinary &customType)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.name);
deserializer.read(customType.testObjects);
return 0;
}
template <> void writeCustomType<NestingArrayBinary>(BinarySerializer &serializer, const NestingArrayBinary &customType, BinaryVersion version)
template <> void writeCustomType<NestingArrayBinary>(BinarySerializer &serializer, const NestingArrayBinary &customType)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(customType.name);
serializer.write(customType.testObjects);
}
template <>
BinaryVersion readCustomType<ObjectWithVariantsBinary>(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType, BinaryVersion version)
template <> void readCustomType<ObjectWithVariantsBinary>(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType)
{
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, BinaryVersion version)
template <> void writeCustomType<ObjectWithVariantsBinary>(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(customType.someVariant);
serializer.write(customType.anotherVariant);
serializer.write(customType.yetAnotherVariant);
@ -170,7 +159,6 @@ class BinaryReflectorTests : public TestFixture {
CPPUNIT_TEST(testSmallSharedPointer);
CPPUNIT_TEST(testBigSharedPointer);
CPPUNIT_TEST(testVariant);
CPPUNIT_TEST(testOptional);
CPPUNIT_TEST_SUITE_END();
public:
@ -188,7 +176,6 @@ public:
void testSmallSharedPointer();
void testBigSharedPointer();
void testVariant();
void testOptional();
private:
vector<unsigned char> m_buffer;
@ -273,44 +260,13 @@ void BinaryReflectorTests::tearDown()
{
}
static void setBuffer(std::stringstream &stream, unsigned char *buffer, std::size_t bufferSize)
{
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(buffer), static_cast<std::streamsize>(bufferSize));
#else
CPP_UTILITIES_UNUSED(stream)
CPP_UTILITIES_UNUSED(buffer)
CPP_UTILITIES_UNUSED(bufferSize)
#endif
}
static void readBuffer(std::stringstream &stream, unsigned char *buffer, std::size_t bufferSize)
{
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
CPP_UTILITIES_UNUSED(stream)
CPP_UTILITIES_UNUSED(buffer)
CPP_UTILITIES_UNUSED(bufferSize)
#else
stream.read(reinterpret_cast<char *>(buffer), static_cast<std::streamsize>(bufferSize));
#endif
}
static void writeBuffer(std::stringstream &stream, unsigned char *buffer, std::size_t bufferSize)
{
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(buffer), static_cast<std::streamsize>(bufferSize));
#else
stream.write(reinterpret_cast<const char *>(buffer), static_cast<std::streamsize>(bufferSize));
#endif
}
void BinaryReflectorTests::testSerializeSimpleStruct()
{
stringstream stream(ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
m_buffer.resize(m_expectedTestObj.size());
setBuffer(stream, m_buffer.data(), m_buffer.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_buffer.data()), static_cast<streamsize>(m_buffer.size()));
m_testObj.toBinary(stream);
readBuffer(stream, m_buffer.data(), m_buffer.size());
CPPUNIT_ASSERT_EQUAL(m_expectedTestObj, m_buffer);
}
@ -319,7 +275,7 @@ void BinaryReflectorTests::testDeserializeSimpleStruct()
{
stringstream stream(ios_base::in | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
writeBuffer(stream, m_expectedTestObj.data(), m_expectedTestObj.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_expectedTestObj.data()), static_cast<streamsize>(m_expectedTestObj.size()));
const auto deserialized(TestObjectBinary::fromBinary(stream));
assertTestObject(deserialized);
}
@ -329,9 +285,8 @@ void BinaryReflectorTests::testSerializeNestedStruct()
stringstream stream(ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
m_buffer.resize(m_expectedNestedTestObj.size());
setBuffer(stream, m_buffer.data(), m_buffer.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_buffer.data()), static_cast<streamsize>(m_buffer.size()));
m_nestedTestObj.toBinary(stream);
readBuffer(stream, m_buffer.data(), m_buffer.size());
CPPUNIT_ASSERT_EQUAL(m_expectedNestedTestObj, m_buffer);
}
@ -340,7 +295,7 @@ void BinaryReflectorTests::testDeserializeNestedStruct()
{
stringstream stream(ios_base::in | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
writeBuffer(stream, m_expectedNestedTestObj.data(), m_expectedNestedTestObj.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_expectedNestedTestObj.data()), static_cast<streamsize>(m_expectedNestedTestObj.size()));
const auto deserialized(NestingArrayBinary::fromBinary(stream));
CPPUNIT_ASSERT_EQUAL(m_nestedTestObj.name, deserialized.name);
@ -425,28 +380,3 @@ void BinaryReflectorTests::testVariant()
CPPUNIT_ASSERT_EQUAL("foo"s, get<0>(deserializedVariants.anotherVariant));
CPPUNIT_ASSERT_EQUAL(42, get<1>(deserializedVariants.yetAnotherVariant));
}
void BinaryReflectorTests::testOptional()
{
// create test objects
const auto str = std::make_optional<std::string>("foo");
const auto nullStr = std::optional<std::string>();
// serialize test object
auto stream = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
stream.exceptions(std::ios_base::failbit | std::ios_base::badbit);
auto ser = BinaryReflector::BinarySerializer(&stream);
ser.write(str);
ser.write(nullStr);
// deserialize the object again
auto deser = BinaryReflector::BinaryDeserializer(&stream);
auto deserStr = std::optional<std::string>();
auto deserNullStr = std::optional<std::string>();
deser.read(deserStr);
deser.read(deserNullStr);
CPPUNIT_ASSERT(deserStr.has_value());
CPPUNIT_ASSERT_EQUAL("foo"s, deserStr.value());
CPPUNIT_ASSERT(!nullStr.has_value());
}

View File

@ -112,7 +112,7 @@ template <> inline void push<NestingArray>(const NestingArray &reflectable, Valu
template <>
inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord = nullptr;
const char *previousRecord;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "TestObject";
@ -141,7 +141,7 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
template <>
inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord = nullptr;
const char *previousRecord;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "NestingObject";
@ -156,7 +156,7 @@ inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<U
template <>
inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord = nullptr;
const char *previousRecord;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "NestingArray";
@ -186,13 +186,11 @@ class JsonReflectorTests : public TestFixture {
CPPUNIT_TEST(testSerializeNestedObjects);
CPPUNIT_TEST(testSerializeUniquePtr);
CPPUNIT_TEST(testSerializeSharedPtr);
CPPUNIT_TEST(testSerializeOptional);
CPPUNIT_TEST(testDeserializePrimitives);
CPPUNIT_TEST(testDeserializeSimpleObjects);
CPPUNIT_TEST(testDeserializeNestedObjects);
CPPUNIT_TEST(testDeserializeUniquePtr);
CPPUNIT_TEST(testDeserializeSharedPtr);
CPPUNIT_TEST(testDeserializeOptional);
CPPUNIT_TEST(testHandlingParseError);
CPPUNIT_TEST(testHandlingTypeMismatch);
CPPUNIT_TEST_SUITE_END();
@ -207,13 +205,11 @@ public:
void testSerializeNestedObjects();
void testSerializeUniquePtr();
void testSerializeSharedPtr();
void testSerializeOptional();
void testDeserializePrimitives();
void testDeserializeSimpleObjects();
void testDeserializeNestedObjects();
void testDeserializeUniquePtr();
void testDeserializeSharedPtr();
void testDeserializeOptional();
void testHandlingParseError();
void testHandlingTypeMismatch();
@ -241,7 +237,7 @@ void JsonReflectorTests::testSerializePrimitives()
Document::Array array(doc.GetArray());
// string
const string foo("foo"); // mustn't be destroyed until JSON is actually written
const string foo("foo"); // musn't be destroyed until JSON is actually written
JsonReflector::push<string>(foo, array, alloc);
JsonReflector::push<const char *>("bar", array, alloc);
// number
@ -383,28 +379,6 @@ void JsonReflectorTests::testSerializeSharedPtr()
string(strbuf.GetString()));
}
/*!
* \brief Tests serializing std::optional.
*/
void JsonReflectorTests::testSerializeOptional()
{
Document doc(kArrayType);
Document::AllocatorType &alloc = doc.GetAllocator();
doc.SetArray();
Document::Array array(doc.GetArray());
const auto str = make_optional<std::string>("foo");
const auto nullStr = std::optional<std::string>();
JsonReflector::push(str, array, alloc);
JsonReflector::push(nullStr, array, alloc);
StringBuffer strbuf;
Writer<StringBuffer> jsonWriter(strbuf);
doc.Accept(jsonWriter);
CPPUNIT_ASSERT_EQUAL("[\"foo\",null]"s, std::string(strbuf.GetString()));
}
/*!
* \brief Tests deserializing strings, numbers (int, float, double) and boolean.
*/
@ -543,9 +517,6 @@ void JsonReflectorTests::testDeserializeNestedObjects()
CPPUNIT_ASSERT_EQUAL("test"s, nestedInVector[0].text);
}
/*!
* \brief Tests deserializing std::optional.
*/
void JsonReflectorTests::testDeserializeUniquePtr()
{
Document doc(kArrayType);
@ -590,22 +561,6 @@ void JsonReflectorTests::testDeserializeSharedPtr()
CPPUNIT_ASSERT_EQUAL("bar"s, obj->text);
}
void JsonReflectorTests::testDeserializeOptional()
{
Document doc(kArrayType);
doc.Parse("[\"foo\",null]");
auto array = doc.GetArray().begin();
optional<string> str = "foo"s;
optional<string> nullStr;
JsonDeserializationErrors errors;
JsonReflector::pull(str, array, &errors);
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
CPPUNIT_ASSERT(str.has_value());
CPPUNIT_ASSERT_EQUAL("foo"s, *str);
CPPUNIT_ASSERT(!nullStr.has_value());
}
/*!
* \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
*/

View File

@ -1,21 +1,13 @@
#include "../traits.h"
#include "../versioning.h"
#include "../binary/serializable.h"
#include <list>
#include <vector>
// 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> {};
// treat some types differently to test Treat... traits
struct Foo {
};
struct Bar {
};
namespace ReflectiveRapidJSON {
REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(Foo);
REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(Foo);
@ -56,14 +48,3 @@ 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");

View File

@ -16,22 +16,30 @@ namespace Traits = ::CppUtilities::Traits;
// define structs and macros to allow treating custom data types as std::map, std::set, ...
/// \brief \brief The TreatAsMapOrHash class allows treating custom classes as std::map or std::unordered_map.
template <typename T> struct TreatAsMapOrHash : public Traits::Bool<false> {};
template <typename T> struct TreatAsMapOrHash : public Traits::Bool<false> {
};
/// \brief \brief The TreatAsMultiMapOrHash class allows treating custom classes as std::multimap or std::unordered_multimap.
template <typename T> struct TreatAsMultiMapOrHash : public Traits::Bool<false> {};
template <typename T> struct TreatAsMultiMapOrHash : public Traits::Bool<false> {
};
/// \brief \brief The TreatAsSet class allows treating custom classes as std::set or std::unordered_set.
template <typename T> struct TreatAsSet : public Traits::Bool<false> {};
template <typename T> struct TreatAsSet : public Traits::Bool<false> {
};
/// \brief \brief The TreatAsMultiSet class allows treating custom classes as std::multiset or std::unordered_multiset.
template <typename T> struct TreatAsMultiSet : public Traits::Bool<false> {};
template <typename T> struct TreatAsMultiSet : public Traits::Bool<false> {
};
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(T) \
template <> struct TreatAsMapOrHash<T> : public Traits::Bool<true> {}
template <> struct TreatAsMapOrHash<T> : public Traits::Bool<true> { \
}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(T) \
template <> struct TreatAsMultiMapOrHash<T> : public Traits::Bool<true> {}
template <> struct TreatAsMultiMapOrHash<T> : public Traits::Bool<true> { \
}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_SET(T) \
template <> struct TreatAsSet<T> : public Traits::Bool<true> {}
template <> struct TreatAsSet<T> : public Traits::Bool<true> { \
}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET(T) \
template <> struct TreatAsMultiSet<T> : public Traits::Bool<true> {}
template <> struct TreatAsMultiSet<T> : public Traits::Bool<true> { \
}
// define traits to check for arrays, sets and maps
template <typename Type>

View File

@ -1,60 +0,0 @@
#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 VersionType> struct VersionNotSupported {
VersionType presentVersion = 0, maxVersion = 0;
const char *record = nullptr;
};
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 maxSupported = Type::version;
static constexpr auto applyDefault(decltype(serializationDefault) version)
{
return version ? version : serializationDefault;
}
static constexpr auto isSupported(decltype(maxSupported) version)
{
return version <= maxSupported;
}
static constexpr auto assertVersion(decltype(maxSupported) version, const char *record = nullptr)
{
if (!isSupported(version)) {
throw typename Type::VersionNotSupported({ .presentVersion = version, .maxVersion = maxSupported, .record = record });
}
}
};
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_TRAITS