Compare commits

...

106 Commits

Author SHA1 Message Date
Martchus 743fd60424 Update server domain 2024-05-30 18:49:57 +02:00
Martchus 5813ee21c5 Update year in copyright notice 2024-05-30 18:49:37 +02:00
Martchus cc1641e0f8 Avoid use of deprecated `exec_program` 2023-12-19 20:28:41 +01:00
Martchus 1ea5b1e744 Avoid CMake deprecation warning by bumping version 2023-07-23 21:20:51 +02:00
Martchus 7c8ef68155 Avoid including headers from host when cross compiling 2023-06-06 16:31:06 +02:00
Martchus e3e4596481 Allow configuring error resilience via CMake helper 2023-05-28 20:59:08 +02:00
Martchus 091d521152 Use `pubsetbuf` only with `libstdc++`
This usage of the function seems only to work as intended with that
standard lib. With `libc++` and the MSVC standard lib the call has no
effect.
2023-02-28 21:19:40 +01:00
Martchus 9a6d550d8f Apply clang-format/cmake-format 2023-02-03 13:34:19 +01:00
Martchus e82d834bf2 Update copyright notice 2023-01-17 18:36:21 +01:00
Martchus 27b029ba67 Avoid invalid `-std=c++…` flag when `CXX_STANDARD` is empty in CMake macro 2022-12-23 20:54:54 +01:00
Martchus d50a4c6004 Consider the `COMPILE_OPTIONS` target property as well in CMake macro 2022-12-23 20:53:22 +01:00
Martchus 10bb97bbc4 Apply clang-format 2022-10-13 19:59:20 +02:00
Martchus b1bd782910 Fix typo in README 2022-10-13 19:58:40 +02:00
Martchus 2b0048f144 Change (de)serialization order of versioning and base classes
This allows deserializing the first base class on its own, even then the
derived class has been serialized. That makes sense if the full object (of
the derived class) should be stored but sometimes only the "base fields"
(of the base class) are needed.
2022-05-28 18:46:21 +02:00
Martchus 5c7a6cba8c Add default operator== to serializable classes
Otherwise derived classes cannot use add a default themselves. Maybe this
makes sense for more comparision operators. Note that I'm still testing the
usefulness. It already breaks when using multiple inheritane so maybe there
is a better way.

The operator is guarded by a version constraint because it is a C++20
feature.
2022-05-15 21:03:20 +02:00
Martchus 60d761f7ed Pass version down to base classes in binary deserializer
So the behavior is consistent with readng members and with serialization.

It should be fine because if the base class is versioned it won't make a
difference but if it is versioned the version from the derived class can be
utilized.
2022-05-15 21:00:02 +02:00
Martchus 9c3bf01c8f Avoid warnings about unused var `allocator` for empty classes 2022-05-15 20:52:30 +02:00
Martchus 1e3417f8d0 Fix (de)serialization of std::int8_t/std::uint8_t
Those types are not supported by RapidJSON and therefore need extra
handling.
2022-05-15 03:11:39 +02:00
Martchus a6b9d771aa Do not treat warning "'this' pointer is null" appearing with GCC 12 as error
It is about code included from libclang and likely wrong so let's just not
abort the compilation due to it.
2022-05-12 20:23:33 +02:00
Martchus 762540f5e5 Add support for `std::optional` 2022-05-07 18:40:37 +02:00
Martchus 0633923935 Simplify use of type traits in `json/reflector.h` 2022-05-07 16:52:32 +02:00
Martchus 11491b1387 Add stalebot config 2022-04-12 01:05:03 +02:00
Martchus df787f3105 Add copyright notice 2022-04-05 20:23:32 +02:00
Martchus 2cc044c705 Adapt tests to efaa8a8 2022-03-28 17:06:28 +02:00
Martchus 4966625d8b Clarify that license is "GPL-2-or-later" 2022-03-15 21:46:56 +01:00
Martchus efaa8a8441 Avoid warnings about using uninitialized variable 2022-02-22 20:03:49 +01:00
Martchus 22611457f9 Fix plural of pointer in README.md 2022-02-05 22:28:20 +01:00
Martchus 59ff3c19eb Allow reporting a type mismatch specifying the expected RapidJSON type directly 2022-01-03 23:24:40 +01:00
Martchus f8f551a78a Fix include and namespace-prefix for using `std::move` in JSON reflector 2021-12-05 23:34:39 +01:00
Martchus 8b66ca3e6b Avoid using `DeclBase::getLangOpts()` to support older Clang versions
The `DeclBase::getLangOpts()` is merely a convenience but only available
in Clang 11 or newer.
2021-09-19 01:17:30 +02:00
Martchus 44c6b8c609 Throw exception during binary deserialization when version is not supported 2021-07-25 19:19:02 +02:00
Martchus 13428667f8 Remove surplus space 2021-07-25 19:13:29 +02:00
Martchus 852dfb7e3c Add symlink for versioning 2021-07-25 17:31:07 +02:00
Martchus 0a902ac30c Add experimental versioning for binary (de)serializer 2021-07-13 00:37:05 +02:00
Martchus 5e72012ed5 Fix typos found via `codespell --skip .git -w` 2021-07-03 19:50:25 +02:00
Martchus a4dd52acfa Define REFLECTIVE_RAPIDJSON_GENERATOR macro during generator runs
So one can distinguish regular compilation from the generator run by
checking this macro.
2021-05-16 19:25:36 +02:00
Martchus e3d32ddfa1 Use std::string_view for CodeFactory parameters (where possible) 2021-05-16 19:25:30 +02:00
Martchus 8f1909dfdf Exit when parsing arguments fails 2021-05-13 16:56:27 +02:00
Martchus 874c964e0b Prevent warnings about unused variables in generated code 2021-03-22 14:16:40 +01:00
Martchus 30735ba187 Fix warnings 2021-03-20 21:25:56 +01:00
Martchus 5110cff5eb Fix documentation for std::string_view's push/toJsonDocument function 2021-01-15 17:48:46 +01:00
Martchus dc7c74c497 Update minimum C++ version mentioned in README
This likely doesn't work anymore with C++14 and I won't put any effort into
C++14 compatibility anymore at this point.
2021-01-15 15:27:28 +01:00
Martchus 80183f5269 Remove list of supported versions as it is too much work to maintain 2021-01-15 15:24:50 +01:00
Martchus 5c49a438ad Use header-only target of c++utilities
The current approach even distinguished between the build and install
interface. However, CPP_UTILITIES_INCLUDE_DIRS is not correctly evaluated
at the time PUBLIC_INCLUDE_DIRS is populated here because the variable
PACKAGE_PREFIX_DIR from the c++utilities config module still points to the
build directory at build time and using $<INSTALL_INTERFACE:…> can not help
with that.
2021-01-01 18:48:14 +01:00
Martchus 6252a7335a Apply cmake-format and clang-format 2020-12-05 21:29:55 +01:00
Martchus 2b6634d574 Allow supplying RapidJSON from outer scope 2020-12-05 21:29:38 +01:00
Martchus 0010e32515 Improve download section of README 2020-12-05 21:28:22 +01:00
Martchus 1970145b90 Add documentation regarding direct RapidJSON usage 2020-06-29 20:59:01 +02:00
Martchus 1e95c3d1ca Don't use StringRef with RapidJSON's SetString and allocator
There is no overload taking a RAPIDJSON_NAMESPACE::StringRef and an
allocator. So the StringRef will be implicitly converted back to a plain
CharType pointer causing an additional length calculation.
2020-06-29 20:26:19 +02:00
Martchus c33e1687cb Support serializing std::string_view 2020-06-26 22:08:05 +02:00
Martchus 4ff49156da Apply clang-format 2020-05-28 12:02:22 +02:00
Martchus 18716b17be
Merge pull request #3 from otreblan/master
Fix missing header in gcc10
2020-05-28 11:51:42 +02:00
otreblan beb4491b18
Fix missing header in gcc10 2020-05-27 22:20:22 -05:00
Martchus 8c032ee7a6 Fix compatibility with LLVM/Clang 10 2020-04-12 17:22:37 +02:00
Martchus c4f622df8d Apply cmake-format 2020-03-08 13:52:01 +01:00
Martchus 442359f5bf Add a function to format JSON deserialization errors 2020-02-29 22:35:02 +01:00
Martchus 86481fa459 Prevent CMake error when using CMake < 3.15.0 2020-02-18 19:40:48 +01:00
Martchus cf41f4596a Prevent passing "empty" flags to the code generator
Otherwise flags like "-D -I/foo" might be passed to clang which
will then complain that "-I/foo" is not a valid name for a macro.
2020-02-14 16:26:50 +01:00
Martchus 4b4d674f56 Allow adding clang options from dependencies 2020-01-31 20:43:01 +01:00
Martchus 891b96a38f Fix build with custom target suffix 2020-01-31 19:49:18 +01:00
Martchus c68d9ea384 Format CMake files with cmake-format 0.6.7 2020-01-26 20:42:03 +01:00
Martchus e7bbdd0af6 Allow logging the exact list of options passed to clang
Since the options get splitted and not just passed as-is
this can be useful for debugging.
2020-01-26 20:41:34 +01:00
Martchus ed3f89953f Add missing symlinks to include directory 2020-01-26 20:38:47 +01:00
Martchus 6e4077eed2 Add JsonDeserializationErrors::ThrowOn::All 2020-01-26 20:38:24 +01:00
Martchus 2f1098ae3d Make m_pointer of BinarySerializer and BinaryDeserializer private 2020-01-12 00:50:30 +01:00
Martchus b37f467dcd Fix name of CMake package in example 2020-01-12 00:33:02 +01:00
Martchus 7482c64931 Support --binary-visibility in CMake function 2020-01-11 14:54:14 +01:00
Martchus f9fc9e02b7 Fix URL in project meta-data 2020-01-02 20:31:09 +01:00
Martchus 72a11c22b1 Apply cmake-format 0.6.5 2019-12-27 01:43:41 +01:00
Martchus 48639b42b2 Support serialization of std::pair 2019-12-27 01:43:28 +01:00
Martchus 717fb2f037 Polish lib/binary/reflector.h 2019-12-27 01:42:34 +01:00
Martchus 450588af89 Support multimap and unordered_multimap
The previously (undocumented) implementation used non-unique
keys in JSON objects. This is not strictly forbidden by the
RFC but not recommended. Multiple values are now stored within
an array instead.
2019-12-27 01:40:34 +01:00
Martchus 022a174028 Remove wrong quote in README.md 2019-12-06 16:52:10 +01:00
Martchus 1192c2d74a Apply cmake-format 0.6.2 2019-12-06 16:51:47 +01:00
Martchus 03563aafbf Fix generator tests; it needs C++17 2019-11-14 18:13:39 +01:00
Martchus f75c8d77a2 Fix finding LibTooling when 'clang-cpp' target is used
See https://releases.llvm.org/9.0.0/tools/clang/docs/ReleaseNotes.html#build-system-changes
2019-11-14 17:18:49 +01:00
Martchus 456702e009 Remove unused std::monostate handling
This wasn't useful after all.
2019-11-03 22:38:27 +01:00
Martchus 3374e4ea6c Fix passing C++ version in CMake macro 2019-11-03 22:37:55 +01:00
Martchus e29db0fa87 Prevent warnings about unused lambda captures 2019-11-03 22:37:19 +01:00
Martchus 1024b8e391 Use override in test fixtures 2019-11-03 22:33:29 +01:00
Martchus c3dc381425 Support `std::variant` 2019-11-03 22:02:56 +01:00
Martchus dd652b4de7 Apply CMake format 2019-10-12 19:04:17 +02:00
Martchus 7e5a32265d Add CLI help and completion for binary generator 2019-10-05 01:36:21 +02:00
Martchus 00dd569869 Update/improve README.md 2019-10-05 01:31:38 +02:00
Martchus d8e626d259 Fix toJsonDocument() for maps 2019-10-05 01:05:48 +02:00
Martchus d7e7bdb703 Add std::optional to TODOs 2019-08-09 17:59:00 +02:00
Martchus fecde7d2d1 Improve adding RapidJSON include dirs to tests
* Also check for `RapidJSON_INCLUDE_DIRS` which seems to be used now
* Do not add `-I` without subsequent path if the include directory
  is missing/empty which would lead to the Clang error:
  `error: unable to handle compilation, expected exactly one compiler job in ''`
2019-08-09 17:54:08 +02:00
Martchus f21f255e94 Don't use the same enum names twice
Prevents

```
[  168s] /home/abuild/rpmbuild/BUILD/reflective-rapidjson-1563638877.5f7c18b/lib/tests/jsonreflector.cpp:41:12: warning: type 'SomeEnumClass' violates the C++ One Definition Rule [-Wodr]
[  168s]    41 | enum class SomeEnumClass {
[  168s]       |            ^
[  168s] /home/abuild/rpmbuild/BUILD/reflective-rapidjson-1563638877.5f7c18b/lib/tests/binaryreflector.cpp:39:12: note: a type with different precision is defined in another translation unit
[  168s]    39 | enum class SomeEnumClass : std::uint16_t {
[  168s]       |            ^
```

observed in LTO-enabled builds
2019-08-09 17:48:08 +02:00
Martchus 8394c145f6 Update version 2019-08-05 20:27:21 +02:00
Martchus 5f7c18b59e Set project() on top-level
See https://github.com/Martchus/cpp-utilities/pull/15
2019-07-20 18:07:57 +02:00
Martchus 8f06bf0272 Do not call vector::reserve() in loop 2019-06-20 22:54:17 +02:00
Martchus a2b40753c3 Adapt adding include dirs to c++utilities 5 2019-06-20 22:53:03 +02:00
Martchus 6542eab80d Adapt to changes in c++utilities 2019-06-14 19:16:51 +02:00
Martchus 8cac133104 Adapt to changes in c++utilities 2019-06-10 22:46:06 +02:00
Martchus 2194000938 Apply cmake-format 0.5.1 2019-06-01 12:16:27 +02:00
Martchus dd174920fe Adapt to c++utilities v5 2019-05-04 16:09:58 +02:00
Martchus 9b80d662bb Catch polymorphic type by reference 2019-04-19 22:24:32 +02:00
Martchus 669aa16479 Fix building against LLVM/Clang 8 2019-03-27 14:41:57 +01:00
Martchus 2b0acbdd1e Cast pointer to the right type on non 64-bit architectures
That could explain why the tests sometimes fail under armv7l (before
making the fix for big pointer IDs).
2019-03-08 17:44:20 +01:00
Martchus 0bf19df0d9 Add explicit test for type mismatch on binary serialization 2019-03-08 17:40:36 +01:00
Martchus 04852627b2 Fix binary serializiation of shared_ptr too big for variable length encoding
* Retain backward compatibility by using either variable length int
  or regular 64-bit int extending the flags
* Add test for both cases
2019-03-08 17:40:08 +01:00
Martchus b058e9e9b9 Fix include for "empty" case in generator tests 2019-02-13 11:39:09 +01:00
Martchus 1f6fade6c2 Apply cmake-format 2019-02-09 21:25:11 +01:00
Martchus 39dcba215e Let CMake generate code for test application 2019-01-13 22:27:35 +01:00
Martchus 974c0b0396 Allow to obtain only the JSON document
So the final serialization can be customized
2018-11-11 22:55:59 +01:00
Martchus ef27d71f43 Fix multiple definition error caused by forward declarations 2018-11-08 00:38:45 +01:00
50 changed files with 1812 additions and 620 deletions

19
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,19 @@
# 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,21 +1,20 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata # set metadata
project(reflective_rapidjson)
set(META_PROJECT_NAME reflective_rapidjson) set(META_PROJECT_NAME reflective_rapidjson)
set(META_APP_NAME "Reflection for RapidJSON") set(META_APP_NAME "Reflection for RapidJSON")
set(META_APP_AUTHOR "Martchus") set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/reflective-rapidjson")
set(META_APP_DESCRIPTION "Reflection for serializing/deserializing with RapidJSON") set(META_APP_DESCRIPTION "Reflection for serializing/deserializing with RapidJSON")
set(META_APP_CATEGORIES "Utility;") set(META_APP_CATEGORIES "Utility;")
set(META_GUI_OPTIONAL false) set(META_GUI_OPTIONAL false)
set(META_VERSION_MAJOR 0) set(META_VERSION_MAJOR 0)
set(META_VERSION_MINOR 0) set(META_VERSION_MINOR 0)
set(META_VERSION_PATCH 6) set(META_VERSION_PATCH 16)
set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}) set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH})
set(META_CXX_STANDARD 17) set(META_CXX_STANDARD 17)
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
# set project name for IDEs like Qt Creator
project(${META_PROJECT_NAME})
# ensure testing is enabled at this level (and not only for particular sub directories) # ensure testing is enabled at this level (and not only for particular sub directories)
enable_testing() enable_testing()
@ -46,16 +45,13 @@ else()
endif() endif()
# find c++utilities # find c++utilities
find_package(c++utilities 4.16.0 REQUIRED) set(CONFIGURATION_PACKAGE_SUFFIX "" CACHE STRING "sets the suffix for find_package() calls to packages configured via c++utilities")
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.10.0 REQUIRED)
# use the source directory of c++utilities for includes rather than the location where headers are going to be installed
# note: this enables the tests to find the header files for c++utilities in case it is built within the same project
if(CPP_UTILITIES_SOURCE_DIR)
set(CPP_UTILITIES_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..")
endif()
# find RapidJSON # find RapidJSON
find_package(RapidJSON) if(NOT RapidJSON_FOUND)
find_package(RapidJSON)
endif()
if(NOT RapidJSON_FOUND) if(NOT RapidJSON_FOUND)
message(FATAL_ERROR "Unable to find RapidJSON. Since this is the only supported reflection application at this time, it makes no sense to continue.") message(FATAL_ERROR "Unable to find RapidJSON. Since this is the only supported reflection application at this time, it makes no sense to continue.")
endif() endif()

162
README.md
View File

@ -49,33 +49,36 @@ For a full list of further ideas, see [TODOs.md](./TODOs.md).
## Supported datatypes ## Supported datatypes
The following table shows the mapping of supported C++ types to supported JSON types: The following table shows the mapping of supported C++ types to supported JSON types:
| C++ type | JSON type | | C++ type | JSON type |
| ------------------------------------------------------------- |:------------:| | ---------------------------------------------------------------------------- |:------------:|
| custom structures/classes | object | | custom structures/classes | object |
| `bool` | true/false | | `bool` | true/false |
| signed and unsigned integral types | number | | signed and unsigned integral types | number |
| `float` and `double` | number | | `float` and `double` | number |
| `enum` and `enum class` | number | | `enum` and `enum class` | number |
| `std::string` | string | | `std::string` | string |
| `const char *` | string | | `std::string_view` | string/null |
| iteratable lists (`std::vector`, `std::list`, ...) | array | | `const char *` | string/null |
| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array | | iteratable lists (`std::vector`, `std::list`, ...) | array |
| `std::tuple` | array | | sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array |
| `std::unique_ptr`, `std::shared_ptr` | depends/null | | `std::pair`, `std::tuple` | array |
| `std::map`, `std::unordered_map` | object | | `std::unique_ptr`, `std::shared_ptr`, `std::optional` | depends/null |
| `JsonSerializable` | object | | `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` | object |
| `std::variant` | object |
| `JsonSerializable` | object |
### Remarks ### Remarks
* Raw pointer are not supported. This prevents * Raw pointers are not supported. This prevents
forgetting to free memory which would have to be allocated when deserializing. forgetting to free memory which would have to be allocated when deserializing.
* For the same reason `const char *` strings are only supported for serialization. * 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 * 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. whether the present integer value is a valid enumeration item.
* The JSON type for smart pointer depends on the type the pointer refers to. It can also be `null`. * The JSON type for smart pointers and `std::optional` depends on the type the pointer/optional refers to.
* If multiple `std::shared_ptr` instance might point to the same object this object is serialized multiple times. It can also be `null` for null pointers or `std::optional` without value.
* 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 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* true when using binary `std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary
serialization. (de)serialization instead of JSON.
* For deserialization * For deserialization
* iteratables must provide an `emplace_back` method. So deserialization of eg. `std::forward_list` * iteratables must provide an `emplace_back` method. So deserialization of eg. `std::forward_list`
is currently not supported. is currently not supported.
@ -84,11 +87,19 @@ The following table shows the mapping of supported C++ types to supported JSON t
* It is possible to treat custom types as set/map using the macro `REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH`, * It is possible to treat custom types as set/map using the macro `REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH`,
`REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH`, `REFLECTIVE_RAPIDJSON_TREAT_AS_SET` or `REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH`, `REFLECTIVE_RAPIDJSON_TREAT_AS_SET` or
`REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET`. `REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET`.
* The key type of the `std::map`, `std::unordered_map` must be `std::string`. * The key type of `std::map`, `std::unordered_map`, `std::multimap` and `std::unordered_multimap` must be
`std::string`.
* An array is used to represent the multiple values of an `std::multimap` and `std::unordered_multimap` (for
consistency also when there is only one value present). This is because the JSON RFC says that
"The names within an object SHOULD be unique".
* An `std::variant` is represented by an object like `{"index": ..., "data": ...}` where `index` is the
zero-based index of the alternative held by the variant and `data` the value held by the variant. The
type of `data` is `null` for `std::monostate` and otherwise deduced as usual.
* For custom (de)serialization, see the section below. * For custom (de)serialization, see the section below.
* The binary (de)serializer supports approximately the same C++ types but obviously maps them to a platform * The binary (de)serializer supports approximately the same C++ types but obviously maps them to a platform
independent binary representation rather than a JSON type. independent binary representation rather than a JSON type.
## Usage ## Usage
This example shows how the library can be used to make a `struct` serializable: This example shows how the library can be used to make a `struct` serializable:
<pre> <pre>
@ -134,6 +145,22 @@ There are further arguments available, see:
reflective_rapidjson_generator --help reflective_rapidjson_generator --help
</pre> </pre>
### Mixing with direct RapidJSON usage and further notes
It is of course possible to mix automatic serialization/deserialization with direct RapidJSON usage. This can be
done by invoking the `push` and `pull` functions within the `ReflectiveRapidJSON::JsonReflector` namespace directly.
The `push` functions are used on serialization to populate intermediate data structures for the serializer of the
RapidJSON library. The intermediate JSON document can also easily be obtained via
`JsonSerializable<Type>::toJsonDocument()`.
Note that this means a copy of the provided data will be made. That includes all strings as well. Currently there
is no way to use RapidJSON's copy-free `SetString`-overloads instead. As a consequence the mentioned intermediate
JSON document can be serialized without causing any further read accesses to the actual data structures.
The `pull` functions are used to populate your data structures from intermediate data structures produced by the
parser of RapidJSON. Also in this case a copy will be made so only owning data structures can be used when
deserializing (see remarks regarding supported datatypes).
#### Binary (de)serialization #### Binary (de)serialization
It works very similar to the example above. Just use the `BinarySerializable` class instead (or in addition): It works very similar to the example above. Just use the `BinarySerializable` class instead (or in addition):
@ -146,7 +173,7 @@ struct TestObject : public ReflectiveRapidJSON::BinarySerializable&lt;TestObject
It is possible to use the provided CMake macro to automate the code generator invocation: It is possible to use the provided CMake macro to automate the code generator invocation:
<pre> <pre>
# find the package and make macro available # find the package and make macro available
find_package(reflective-rapidjson REQUIRED) find_package(reflective_rapidjson REQUIRED)
list(APPEND CMAKE_MODULE_PATH ${REFLECTIVE_RAPIDJSON_MODULE_DIRS}) list(APPEND CMAKE_MODULE_PATH ${REFLECTIVE_RAPIDJSON_MODULE_DIRS})
include(ReflectionGenerator) include(ReflectionGenerator)
@ -158,7 +185,7 @@ target_link_libraries(mytarget PRIVATE reflective_rapidjson)
# invoke macro # invoke macro
add_reflection_generator_invocation( add_reflection_generator_invocation(
INPUT_FILES code-defining-structs.cpp INPUT_FILES code-defining-structs.cpp
GENERATORS json GENERATORS json binary
OUTPUT_LISTS LIST_OF_GENERATED_HEADERS OUTPUT_LISTS LIST_OF_GENERATED_HEADERS
CLANG_OPTIONS_FROM_TARGETS mytarget CLANG_OPTIONS_FROM_TARGETS mytarget
) )
@ -174,6 +201,9 @@ will always have the extension `.h`, independently of the extension of the input
The full paths of the generated files are also appended to the variable `LIST_OF_GENERATED_HEADERS` which then can be added The full paths of the generated files are also appended to the variable `LIST_OF_GENERATED_HEADERS` which then can be added
to the sources of your target. Of course this can be skipped if not required/wanted. to the sources of your target. Of course this can be skipped if not required/wanted.
The `GENERATORS` argument specifies the generators to run. Use `json` to generate code for JSON (de)serialization and `binary`
to generate code for binary (de)serialization. As shown in the example, multiple generators can be specified at a time.
The macro will also automatically pass Clang's resource directory which is detected by invoking `clang -print-resource-dir`. The macro will also automatically pass Clang's resource directory which is detected by invoking `clang -print-resource-dir`.
To adjust that, just set the cache variable `REFLECTION_GENERATOR_CLANG_RESOURCE_DIR` before including the module. To adjust that, just set the cache variable `REFLECTION_GENERATOR_CLANG_RESOURCE_DIR` before including the module.
@ -198,8 +228,7 @@ To adjust the default location, just add eg. `--clang-opt -resource-dir /usr/lib
It makes most sense to specify the same options for the code generator as during the actual compilation. This way the code It makes most sense to specify the same options for the code generator as during the actual compilation. This way the code
generator uses the same flags, defines and include directories as the compiler and hence behaves like the compiler. generator uses the same flags, defines and include directories as the compiler and hence behaves like the compiler.
When using the CMake macro, it is possible to automatically pass all compile flags, compile definitions and include directories When using the CMake macro, it is possible to automatically pass all compile flags, compile definitions and include directories
from certain targets to the code generator. Those targets can be specified using the from certain targets to the code generator. Those targets can be specified using the macro's `CLANG_OPTIONS_FROM_TARGETS` argument.
Macro's `CLANG_OPTIONS_FROM_TARGETS` argument.
#### Notes regarding cross-compilation #### Notes regarding cross-compilation
* For cross compilation, it is required to build the code generator for the platform you're building on. * For cross compilation, it is required to build the code generator for the platform you're building on.
@ -224,7 +253,7 @@ Macro's `CLANG_OPTIONS_FROM_TARGETS` argument.
The same example as above. However, this time Boost.Hana is used - so it doesn't require invoking the generator. The same example as above. However, this time Boost.Hana is used - so it doesn't require invoking the generator.
<pre> <pre>
#include "&lt;reflective_rapidjson/json/serializable-boosthana.h&gt; #include &lt;reflective_rapidjson/json/serializable-boosthana.h&gt;
// define structures using BOOST_HANA_DEFINE_STRUCT, eg. // define structures using BOOST_HANA_DEFINE_STRUCT, eg.
struct TestObject : public JsonSerializable&lt;TestObject&gt; { struct TestObject : public JsonSerializable&lt;TestObject&gt; {
@ -325,6 +354,54 @@ An example for such custom (de)serialization can be found in the file
`json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and `json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
`TimeSpan` objects from the C++ utilities library mentioned under dependencies. `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 ### Remarks
* Static member variables and member functions are currently ignored by the generator. * Static member variables and member functions are currently ignored by the generator.
* It is currently not possible to ignore a specific member variable. * It is currently not possible to ignore a specific member variable.
@ -354,7 +431,7 @@ The following dependencies are required at build time. Note that Reflective Rapi
and *none* of these dependencies are required at runtime by an application which makes use of and *none* of these dependencies are required at runtime by an application which makes use of
Reflective RapidJSON. Reflective RapidJSON.
* C++ compiler and C++ standard library supporting at least C++14 * C++ compiler and C++ standard library supporting at least C++17
* the [CMake](https://cmake.org) build system * the [CMake](https://cmake.org) build system
* LibTooling from [Clang](https://clang.llvm.org) for the code generator (optional when using * LibTooling from [Clang](https://clang.llvm.org) for the code generator (optional when using
Boost.Hana) Boost.Hana)
@ -373,17 +450,8 @@ Reflective RapidJSON.
different build system, there is no helper for adding the code generator to the build process different build system, there is no helper for adding the code generator to the build process
provided (so far). provided (so far).
* I usually develop using the latest version of those dependencies. So it is recommend to get the * I usually develop using the latest version of those dependencies. So it is recommend to get the
the latest versions as well. I tested the following versions so far: the latest versions as well although very likely older versions might work as well. When adapting
* GCC 7.2.1/7.3.0/8.1.0 or Clang 5.0/6.0/7.0 as compiler to new versions of LLVM/Clang I usually take care that it also still works with previous versions.
* libstdc++ from GCC 7.2.1/7.3.0/8.1.0
* CMake 3.10.1
* Clang 5.0.0/5.0.1 for LibTooling
* RapidJSON 1.1.0
* C++ utilities 4.12
* Boost.Hana 1.65.1, 1.66.0, 1.67.0, 1.68.0
* CppUnit 1.14.0
* Doxygen 1.8.13
* Graphviz 2.40.1
* The binary (de)serializer requires C++ utilities at runtime. So when using it, it is required to * The binary (de)serializer requires C++ utilities at runtime. So when using it, it is required to
link against C++ utilities. link against C++ utilities.
@ -443,7 +511,7 @@ make
make check make check
# build tests but do not run them (optional, requires CppUnit) # build tests but do not run them (optional, requires CppUnit)
make tests make tests
# generate API documentation (optional, reqquires Doxygen) # generate API documentation (optional, requires Doxygen)
make apidoc make apidoc
# install header files, libraries and generator # install header files, libraries and generator
make install DESTDIR="/temporary/install/location" make install DESTDIR="/temporary/install/location"
@ -454,12 +522,20 @@ Add eg. `-j$(nproc)` to `make` arguments for using all cores.
* Arch Linux * Arch Linux
* for PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs) or * for PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs) or
[the AUR](https://aur.archlinux.org/packages?SeB=m&K=Martchus) [the AUR](https://aur.archlinux.org/packages?SeB=m&K=Martchus)
* for a binary repository checkout [my website](http://martchus.no-ip.biz/website/page.php?name=programming) * there is also a [binary repository](https://martchus.dyn.f3l.de/repo/arch/ownstuff)
* Tumbleweed * Tumbleweed
* for RPM \*.spec files and binary repository checkout * RPM \*.spec files and binaries are available via openSUSE Build Service
[openSUSE Build Servide](https://build.opensuse.org/project/show/home:mkittler) * latest releases: [download page](https://software.opensuse.org/download.html?project=home:mkittler&package=reflective-rapidjson-devel),
[project page](https://build.opensuse.org/project/show/home:mkittler)
* Git master: [download page](https://software.opensuse.org/download.html?project=home:mkittler:vcs&package=reflective-rapidjson-devel),
[project page](https://build.opensuse.org/project/show/home:mkittler:vcs)
* Windows * Windows
* for mingw-w64 PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs) * for mingw-w64 PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs)
These packages shows the required dependencies and commands to build in a plain way. So they might be useful for 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. making Reflective RapidJSON available under other platforms, too.
## Copyright notice and license
Copyright © 2017-2024 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] Add documentation (install instructions, usage)
- [x] Allow making 3rdparty classes/structs reflectable - [x] Allow making 3rdparty classes/structs reflectable
- [x] Add additional parameter for code generator to allow specifying relevant classes - [x] Add additional parameter for code generator to allow specifying relevant classes
explicitely explicitly
- [x] Fix traits currently relying on `JsonSerializable` being base class - [x] Fix traits currently relying on `JsonSerializable` being base class
- [x] Allow exporting symbols - [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) - [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++) - [ ] Test with libc++ (currently only tested with libstdc++)
- [ ] Support templated classes - [ ] Support templated classes
- [ ] Allow (de)serialization of static members (if that makes sense?) - [ ] Allow (de)serialization of static members (if that makes sense?)
- [ ] Allow ignoring particular members or selecting specificly which member variables should be considered - [ ] Allow ignoring particular members or selecting specifically which member variables should be considered
* This could work similar to Qt's Signals & Slots macros. * This could work similar to Qt's Signals & Slots macros.
* but there should also be a way to do this for 3rdparty types. * but there should also be a way to do this for 3rdparty types.
* Note that currently, *all* public member variables are (de)serialized. * Note that currently, *all* public member variables are (de)serialized.
@ -29,4 +29,5 @@
- [x] Support `std::unique_ptr` and `std::shared_ptr` - [x] Support `std::unique_ptr` and `std::shared_ptr`
- [x] Support `std::map` and `std::unordered_map` - [x] Support `std::map` and `std::unordered_map`
- [ ] Support `std::any` - [ ] Support `std::any`
- [x] Support `std::optional`
- [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation) - [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.1.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata # metadata
set(META_PROJECT_NAME reflective_rapidjson_generator) set(META_PROJECT_NAME reflective_rapidjson_generator)
@ -15,8 +15,7 @@ set(HEADER_FILES
frontendaction.h frontendaction.h
consumer.h consumer.h
visitor.h visitor.h
clangversionabstraction.h clangversionabstraction.h)
)
set(SRC_FILES set(SRC_FILES
codegenerator.cpp codegenerator.cpp
serializationcodegenerator.cpp serializationcodegenerator.cpp
@ -27,36 +26,42 @@ set(SRC_FILES
consumer.cpp consumer.cpp
clangversionabstraction.cpp clangversionabstraction.cpp
visitor.cpp visitor.cpp
main.cpp main.cpp)
) set(TEST_HEADER_FILES tests/helper.h)
set(TEST_HEADER_FILES set(TEST_SRC_FILES tests/binarygenerator.cpp)
tests/helper.h
)
set(TEST_SRC_FILES
tests/cppunit.cpp
tests/binarygenerator.cpp
)
# add JSON-specific test cases # add JSON-specific test cases
if(RapidJSON_FOUND) if (RapidJSON_FOUND)
list(APPEND TEST_HEADER_FILES list(APPEND TEST_HEADER_FILES tests/structs.h tests/morestructs.h)
tests/structs.h list(APPEND TEST_SRC_FILES tests/jsongenerator.cpp)
tests/morestructs.h endif ()
)
list(APPEND TEST_SRC_FILES
tests/jsongenerator.cpp
)
endif()
# link against c++utilities # link against c++utilities
use_cpp_utilities() use_cpp_utilities()
# find Clang for LibTooling; adding clangTooling should be sufficient as it pulls all transitive dependencies # find Clang for LibTooling
find_package(Clang REQUIRED) find_package(Clang REQUIRED)
list(APPEND PRIVATE_LIBRARIES clangTooling clangFrontend clangAST clangLex clangSema clangBasic LLVM) if (TARGET clang-cpp)
list(APPEND PRIVATE_LIBRARIES clang-cpp LLVM)
else ()
list(
APPEND
PRIVATE_LIBRARIES
clangTooling
clangFrontend
clangSerialization
clangSema
clangAST
clangLex
clangBasic
LLVM)
endif ()
# also add reflective_rapidjson which is header-only but might pull additional include dirs for RapidJSON # also add reflective_rapidjson which is header-only but might pull additional include dirs for RapidJSON
list(APPEND PRIVATE_LIBRARIES reflective_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 modules to apply configuration
include(BasicConfig) include(BasicConfig)
@ -67,13 +72,14 @@ include(ShellCompletion)
include(Doxygen) include(Doxygen)
# trigger code generator for tests because the tests already contain structs to be (de)serialized # trigger code generator for tests because the tests already contain structs to be (de)serialized
if(TARGET reflective_rapidjson_generator_tests) if (TARGET "${META_TARGET_NAME}_tests")
include(ReflectionGenerator) include(ReflectionGenerator)
# cmake-format: off
add_reflection_generator_invocation( add_reflection_generator_invocation(
INPUT_FILES INPUT_FILES
tests/structs.h # used by test cases tests/structs.h # used by test cases
tests/morestructs.h # used by test cases tests/morestructs.h # used by test cases
tests/cppunit.cpp # just for testing multiple input files and the "empty file" case visitor.cpp # arbitrarily chosen source file (just for testing the "no relevant structs/classes" case)
GENERATORS GENERATORS
json json
binary binary
@ -82,26 +88,33 @@ if(TARGET reflective_rapidjson_generator_tests)
CLANG_OPTIONS CLANG_OPTIONS
-std=c++17 -std=c++17
CLANG_OPTIONS_FROM_TARGETS CLANG_OPTIONS_FROM_TARGETS
reflective_rapidjson_generator_tests "${META_TARGET_NAME}_tests"
JSON_CLASSES JSON_CLASSES
OtherNotJsonSerializable # test specifying classes for JSON (de)serialization manually OtherNotJsonSerializable # test specifying classes for JSON (de)serialization manually
SomeOtherClassName # specifying a class that does not exist should not cause any problems SomeOtherClassName # specifying a class that does not exist should not cause any problems
JSON_VISIBILITY JSON_VISIBILITY
LIB_EXPORT # not required, just to test setting visibility CPP_UTILITIES_GENERIC_LIB_EXPORT # not required, just to test setting visibility
) )
# cmake-format: on
list(APPEND TEST_HEADER_FILES ${TEST_GENERATED_HEADER_FILES}) list(APPEND TEST_HEADER_FILES ${TEST_GENERATED_HEADER_FILES})
target_sources(reflective_rapidjson_generator_tests PRIVATE ${TEST_GENERATED_HEADER_FILES}) target_sources("${META_TARGET_NAME}_tests" PRIVATE ${TEST_GENERATED_HEADER_FILES})
endif() endif ()
# add paths for include dirs of c++utilities and RapidJSON to config header so test cases can use it # add paths for include dirs of c++utilities and RapidJSON to config header so test cases can use it
set(META_CUSTOM_CONFIG "#define CPP_UTILITIES_INCLUDE_DIRS \"${CPP_UTILITIES_INCLUDE_DIRS}\"\n") set(META_CUSTOM_CONFIG "#define CPP_UTILITIES_INCLUDE_DIRS \"${CPP_UTILITIES_INCLUDE_DIRS}\"\n")
if(RapidJSON_FOUND) if (RapidJSON_FOUND)
string(APPEND META_CUSTOM_CONFIG "#define RAPIDJSON_INCLUDE_DIRS \"${RAPIDJSON_INCLUDE_DIRS}\"\n") # add include dirs either from RapidJSON_INCLUDE_DIRS or RAPIDJSON_INCLUDE_DIRS
endif() if (RapidJSON_INCLUDE_DIRS)
set(RAPIDJSON_INCLUDE_DIRS ${RapidJSON_INCLUDE_DIRS})
endif ()
if (RAPIDJSON_INCLUDE_DIRS)
string(APPEND META_CUSTOM_CONFIG "#define RAPIDJSON_INCLUDE_DIRS \"${RAPIDJSON_INCLUDE_DIRS}\"\n")
endif ()
endif ()
# add path of Clang's resource dir to config header so test cases can use it # add path of Clang's resource dir to config header so test cases can use it
string(APPEND META_CUSTOM_CONFIG "#define REFLECTION_GENERATOR_CLANG_RESOURCE_DIR \"${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}\"\n") string(APPEND META_CUSTOM_CONFIG
"#define REFLECTION_GENERATOR_CLANG_RESOURCE_DIR \"${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}\"\n")
# make config header # make config header
include(ConfigHeader) include(ConfigHeader)

View File

@ -5,11 +5,15 @@
#include <clang/AST/DeclCXX.h> #include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclFriend.h> #include <clang/AST/DeclFriend.h>
#include <clang/AST/DeclTemplate.h> #include <clang/AST/DeclTemplate.h>
#include <clang/AST/Expr.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <llvm/ADT/APInt.h>
#include <iostream> #include <iostream>
using namespace std; using namespace std;
using namespace ApplicationUtilities; using namespace CppUtilities;
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
@ -23,7 +27,7 @@ BinarySerializationCodeGenerator::Options::Options()
{ {
additionalClassesArg.setRequiredValueCount(Argument::varValueCount); additionalClassesArg.setRequiredValueCount(Argument::varValueCount);
additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None); additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None);
visibilityArg.setPreDefinedCompletionValues("LIB_EXPORT"); visibilityArg.setPreDefinedCompletionValues("CPP_UTILITIES_GENERIC_LIB_EXPORT");
} }
BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options) BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options)
@ -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()); SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
switch (isQualifiedNameIfRelevant(record, qualifiedName)) { if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
case IsRelevant::Yes: return;
return qualifiedName;
case IsRelevant::No:
return string();
default:;
} }
// consider all classes specified via "--additional-classes" argument relevant // consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) { if (!m_options.additionalClassesArg.isPresent()) {
return string(); return;
} }
for (const char *className : m_options.additionalClassesArg.values()) { for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) { if (className == possiblyRelevantClass.qualifiedName) {
return 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. * \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 // initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
lazyInitializeSourceManager(); lazyInitializeSourceManager();
@ -118,17 +245,55 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
// print writeCustomType method // print writeCustomType method
os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
<< " &customObject)\n{\n" os << " // write base classes\n";
" // write base classes\n";
for (const RelevantClass *baseClass : relevantBases) { 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";
}
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 members\n"; os << " // write members\n";
for (const clang::FieldDecl *field : relevantClass.record->fields()) { auto mt = MemberTracking();
if (writePrivateMembers || field->getAccess() == clang::AS_public) { for (clang::Decl *const decl : relevantClass.record->decls()) {
os << " serializer.write(customObject." << field->getName() << ");\n"; // 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;
}
// 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"; os << "}\n";
@ -138,23 +303,56 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
} }
// print readCustomType method // print readCustomType method
os << "template <> " << visibility << " void readCustomType<::" << relevantClass.qualifiedName mt = MemberTracking();
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName os << "template <> " << visibility << " BinaryVersion readCustomType<::" << relevantClass.qualifiedName
<< " &customObject)\n{\n" << ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
" // read base classes\n"; os << " // read base classes\n";
for (const RelevantClass *baseClass : relevantBases) { for (const RelevantClass *baseClass : relevantBases) {
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject));\n"; 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 << " // read members\n"; os << " // read members\n";
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 // skip const members
const auto *const field = static_cast<const clang::FieldDecl *>(decl);
if (field->getType().isConstant(field->getASTContext())) { if (field->getType().isConstant(field->getASTContext())) {
continue; continue;
} }
// write version markers
mt.writeVersionCondition(os);
mt.writeExtraPadding(os);
if (readPrivateMembers || field->getAccess() == clang::AS_public) { if (readPrivateMembers || field->getAccess() == clang::AS_public) {
os << " deserializer.read(customObject." << field->getName() << ");\n"; os << " deserializer.read(customObject." << field->getName() << ", version);\n";
mt.membersWritten = true;
} }
} }
mt.concludeCondition(os);
if (relevantBases.empty() && !mt.membersWritten) {
os << " (void)deserializer;\n (void)customObject;\n";
}
os << " return version;\n";
os << "}\n\n"; os << "}\n\n";
} }

View File

@ -16,10 +16,10 @@ public:
struct Options { struct Options {
Options(); Options();
Options(const Options &other) = delete; Options(const Options &other) = delete;
void appendTo(ApplicationUtilities::Argument *arg); void appendTo(CppUtilities::Argument *arg);
ApplicationUtilities::ConfigValueArgument additionalClassesArg; CppUtilities::ConfigValueArgument additionalClassesArg;
ApplicationUtilities::ConfigValueArgument visibilityArg; CppUtilities::ConfigValueArgument visibilityArg;
}; };
BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options); BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options);
@ -27,12 +27,12 @@ public:
void generate(std::ostream &os) const override; void generate(std::ostream &os) const override;
protected: protected:
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override; void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
const Options &m_options; const Options &m_options;
}; };
inline void BinarySerializationCodeGenerator::Options::appendTo(ApplicationUtilities::Argument *arg) inline void BinarySerializationCodeGenerator::Options::appendTo(CppUtilities::Argument *arg)
{ {
arg->addSubArgument(&additionalClassesArg); arg->addSubArgument(&additionalClassesArg);
arg->addSubArgument(&visibilityArg); arg->addSubArgument(&visibilityArg);

View File

@ -1,6 +1,9 @@
#include "./codefactory.h" #include "./codefactory.h"
#include "./clangversionabstraction.h"
#include "./frontendaction.h" #include "./frontendaction.h"
#include "resources/config.h"
#include <clang/Basic/FileManager.h> #include <clang/Basic/FileManager.h>
#include <clang/Frontend/FrontendActions.h> #include <clang/Frontend/FrontendActions.h>
#include <clang/Tooling/Tooling.h> #include <clang/Tooling/Tooling.h>
@ -20,7 +23,7 @@ struct CodeFactory::ToolInvocation {
CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory) CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory)
: fileManager({ "." }) : fileManager({ "." })
, invocation(factory.makeClangArgs(), new FrontendAction(factory), &fileManager) , invocation(factory.makeClangArgs(), maybe_unique(new FrontendAction(factory)), &fileManager)
{ {
fileManager.Retain(); fileManager.Retain();
} }
@ -29,8 +32,8 @@ CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory)
* \brief Constructs a new instance. * \brief Constructs a new instance.
* \remarks The specified arguments are not copied and must remain valid for the live-time of the code factory. * \remarks The specified arguments are not copied and must remain valid for the live-time of the code factory.
*/ */
CodeFactory::CodeFactory( CodeFactory::CodeFactory(std::string_view applicationPath, const std::vector<const char *> &sourceFiles,
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<string> &clangOptions, std::ostream &os) const std::vector<std::string_view> &clangOptions, std::ostream &os)
: m_applicationPath(applicationPath) : m_applicationPath(applicationPath)
, m_sourceFiles(sourceFiles) , m_sourceFiles(sourceFiles)
, m_clangOptions(clangOptions) , m_clangOptions(clangOptions)
@ -49,8 +52,8 @@ CodeFactory::~CodeFactory()
*/ */
std::vector<string> CodeFactory::makeClangArgs() const std::vector<string> CodeFactory::makeClangArgs() const
{ {
static const initializer_list<const char *> flags static const initializer_list<std::string_view> flags
= { m_applicationPath, "-x", "c++", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only" }; = { m_applicationPath, "-x", "c++", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only", "-D" PROJECT_VARNAME_UPPER };
vector<string> clangArgs; vector<string> clangArgs;
clangArgs.reserve(flags.size() + m_clangOptions.size() + m_sourceFiles.size()); clangArgs.reserve(flags.size() + m_clangOptions.size() + m_sourceFiles.size());
clangArgs.insert(clangArgs.end(), flags.begin(), flags.end()); clangArgs.insert(clangArgs.end(), flags.begin(), flags.end());
@ -82,7 +85,7 @@ bool CodeFactory::generate() const
} }
/*! /*!
* \brief Reads (relevent) AST elements using Clang and generates code. * \brief Reads (relevant) AST elements using Clang and generates code.
*/ */
bool CodeFactory::run() bool CodeFactory::run()
{ {

View File

@ -29,13 +29,13 @@ class CodeFactory {
friend class Visitor; friend class Visitor;
public: public:
CodeFactory( CodeFactory(std::string_view applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<std::string_view> &clangOptions,
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<std::string> &clangOptions, std::ostream &os); std::ostream &os);
~CodeFactory(); ~CodeFactory();
const std::vector<std::unique_ptr<CodeGenerator>> &generators() const; const std::vector<std::unique_ptr<CodeGenerator>> &generators() const;
template <typename GeneratorType, typename... Args> void addGenerator(Args &&... args); template <typename GeneratorType, typename... Args> void addGenerator(Args &&...args);
template <typename GeneratorType, typename... Args> auto bindGenerator(Args &&... args); template <typename GeneratorType, typename... Args> auto bindGenerator(Args &&...args);
bool run(); bool run();
clang::CompilerInstance *compilerInstance(); clang::CompilerInstance *compilerInstance();
@ -50,9 +50,9 @@ private:
bool generate() const; bool generate() const;
std::vector<std::string> makeClangArgs() const; std::vector<std::string> makeClangArgs() const;
const char *const m_applicationPath; std::string_view m_applicationPath;
const std::vector<const char *> &m_sourceFiles; const std::vector<const char *> &m_sourceFiles;
const std::vector<std::string> &m_clangOptions; const std::vector<std::string_view> &m_clangOptions;
std::ostream &m_os; std::ostream &m_os;
std::vector<std::unique_ptr<CodeGenerator>> m_generators; std::vector<std::unique_ptr<CodeGenerator>> m_generators;
std::unique_ptr<ToolInvocation> m_toolInvocation; std::unique_ptr<ToolInvocation> m_toolInvocation;
@ -64,7 +64,7 @@ private:
* \brief Instantiates a code generator of the specified type and adds it to the current instance. * \brief Instantiates a code generator of the specified type and adds it to the current instance.
* \remarks The specified \a args are forwarded to the generator's constructor. * \remarks The specified \a args are forwarded to the generator's constructor.
*/ */
template <typename GeneratorType, typename... Args> void CodeFactory::addGenerator(Args &&... args) template <typename GeneratorType, typename... Args> void CodeFactory::addGenerator(Args &&...args)
{ {
m_generators.emplace_back(std::make_unique<GeneratorType>(*this, std::forward<Args>(args)...)); m_generators.emplace_back(std::make_unique<GeneratorType>(*this, std::forward<Args>(args)...));
} }
@ -101,7 +101,7 @@ template <typename T> T &&wrapReferences(T &&val)
* - The specified \a args are forwarded to the generator's constructor. * - The specified \a args are forwarded to the generator's constructor.
* - No copy of \a args passed by reference is made. * - No copy of \a args passed by reference is made.
*/ */
template <typename GeneratorType, typename... Args> auto CodeFactory::bindGenerator(Args &&... args) template <typename GeneratorType, typename... Args> auto CodeFactory::bindGenerator(Args &&...args)
{ {
return std::bind(&CodeFactory::addGenerator<GeneratorType, Args...>, this, Detail::wrapReferences(std::forward<Args>(args)...)); return std::bind(&CodeFactory::addGenerator<GeneratorType, Args...>, this, Detail::wrapReferences(std::forward<Args>(args)...));
} }

View File

@ -19,7 +19,7 @@ CodeGenerator::~CodeGenerator()
*/ */
void CodeGenerator::addDeclaration(clang::Decl *decl) void CodeGenerator::addDeclaration(clang::Decl *decl)
{ {
VAR_UNUSED(decl) CPP_UTILITIES_UNUSED(decl)
} }
/*! /*!
@ -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. * \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). * \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()) { for (clang::CXXBaseSpecifier &base : record->bases()) {
const clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl(); clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) { if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) {
return true; return &base;
} }
} }
return false; return nullptr;
} }
} // namespace ReflectiveRapidJSON } // namespace ReflectiveRapidJSON

View File

@ -8,6 +8,7 @@
namespace clang { namespace clang {
class Decl; class Decl;
class CXXRecordDecl; class CXXRecordDecl;
class CXXBaseSpecifier;
class SourceManager; class SourceManager;
} // namespace clang } // namespace clang
@ -32,7 +33,7 @@ protected:
CodeFactory &factory() const; CodeFactory &factory() const;
void lazyInitializeSourceManager() const; void lazyInitializeSourceManager() const;
bool isOnlyIncluded(const clang::Decl *declaration) 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: private:
CodeFactory &m_factory; CodeFactory &m_factory;

View File

@ -16,7 +16,7 @@ bool FrontendAction::hasCodeCompletionSupport() const
REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE(clang::ASTConsumer) REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE(clang::ASTConsumer)
FrontendAction::CreateASTConsumer(clang::CompilerInstance &compilerInstance, llvm::StringRef inputFile) FrontendAction::CreateASTConsumer(clang::CompilerInstance &compilerInstance, llvm::StringRef inputFile)
{ {
VAR_UNUSED(inputFile) CPP_UTILITIES_UNUSED(inputFile)
// propagate compiler instance to factory // propagate compiler instance to factory
m_factory.setCompilerInstance(&compilerInstance); m_factory.setCompilerInstance(&compilerInstance);

View File

@ -9,7 +9,7 @@
#include <iostream> #include <iostream>
using namespace std; using namespace std;
using namespace ApplicationUtilities; using namespace CppUtilities;
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
@ -23,7 +23,7 @@ JsonSerializationCodeGenerator::Options::Options()
{ {
additionalClassesArg.setRequiredValueCount(Argument::varValueCount); additionalClassesArg.setRequiredValueCount(Argument::varValueCount);
additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None); additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None);
visibilityArg.setPreDefinedCompletionValues("LIB_EXPORT"); visibilityArg.setPreDefinedCompletionValues("CPP_UTILITIES_GENERIC_LIB_EXPORT");
} }
JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options) JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options)
@ -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()); SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
switch (isQualifiedNameIfRelevant(record, qualifiedName)) { if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
case IsRelevant::Yes: return;
return qualifiedName;
case IsRelevant::No:
return string();
default:;
} }
// consider all classes specified via "--additional-classes" argument relevant // consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) { if (!m_options.additionalClassesArg.isPresent()) {
return string(); return;
} }
for (const char *className : m_options.additionalClassesArg.values()) { for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) { if (className == possiblyRelevantClass.qualifiedName) {
return qualifiedName; possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
} }
} }
return string();
} }
/*! /*!
@ -124,11 +119,16 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
os << " push(static_cast<const ::" << baseClass->qualifiedName << " &>(reflectable), value, allocator);\n"; os << " push(static_cast<const ::" << baseClass->qualifiedName << " &>(reflectable), value, allocator);\n";
} }
os << " // push members\n"; os << " // push members\n";
auto pushWritten = false;
for (const clang::FieldDecl *field : relevantClass.record->fields()) { for (const clang::FieldDecl *field : relevantClass.record->fields()) {
if (pushPrivateMembers || field->getAccess() == clang::AS_public) { if (pushPrivateMembers || field->getAccess() == clang::AS_public) {
os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n"; os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n";
pushWritten = true;
} }
} }
if (relevantBases.empty() && !pushWritten) {
os << " (void)reflectable;\n (void)value;\n (void)allocator;\n";
}
os << "}\n"; os << "}\n";
// skip printing the pull method for classes without default constructor because deserializing those is currently not supported // skip printing the pull method for classes without default constructor because deserializing those is currently not supported
@ -146,7 +146,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
os << " pull(static_cast<::" << baseClass->qualifiedName << " &>(reflectable), value, errors);\n"; os << " pull(static_cast<::" << baseClass->qualifiedName << " &>(reflectable), value, errors);\n";
} }
os << " // set error context for current record\n" os << " // set error context for current record\n"
" const char *previousRecord;\n" " const char *previousRecord = nullptr;\n"
" if (errors) {\n" " if (errors) {\n"
" previousRecord = errors->currentRecord;\n" " previousRecord = errors->currentRecord;\n"
" errors->currentRecord = \"" " errors->currentRecord = \""
@ -154,6 +154,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
<< "\";\n" << "\";\n"
" }\n" " }\n"
" // pull members\n"; " // pull members\n";
auto pullWritten = false;
for (const clang::FieldDecl *field : relevantClass.record->fields()) { for (const clang::FieldDecl *field : relevantClass.record->fields()) {
// skip const members // skip const members
if (field->getType().isConstant(field->getASTContext())) { if (field->getType().isConstant(field->getASTContext())) {
@ -161,8 +162,12 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
} }
if (pullPrivateMembers || field->getAccess() == clang::AS_public) { if (pullPrivateMembers || field->getAccess() == clang::AS_public) {
os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, errors);\n"; os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, errors);\n";
pullWritten = true;
} }
} }
if (relevantBases.empty() && !pullWritten) {
os << " (void)reflectable;\n (void)value;\n";
}
os << " // restore error context for previous record\n" os << " // restore error context for previous record\n"
" if (errors) {\n" " if (errors) {\n"
" errors->currentRecord = previousRecord;\n" " errors->currentRecord = previousRecord;\n"

View File

@ -16,10 +16,10 @@ public:
struct Options { struct Options {
Options(); Options();
Options(const Options &other) = delete; Options(const Options &other) = delete;
void appendTo(ApplicationUtilities::Argument *arg); void appendTo(CppUtilities::Argument *arg);
ApplicationUtilities::ConfigValueArgument additionalClassesArg; CppUtilities::ConfigValueArgument additionalClassesArg;
ApplicationUtilities::ConfigValueArgument visibilityArg; CppUtilities::ConfigValueArgument visibilityArg;
}; };
JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options); JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options);
@ -27,12 +27,12 @@ public:
void generate(std::ostream &os) const override; void generate(std::ostream &os) const override;
protected: protected:
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override; void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
const Options &m_options; const Options &m_options;
}; };
inline void JsonSerializationCodeGenerator::Options::appendTo(ApplicationUtilities::Argument *arg) inline void JsonSerializationCodeGenerator::Options::appendTo(CppUtilities::Argument *arg)
{ {
arg->addSubArgument(&additionalClassesArg); arg->addSubArgument(&additionalClassesArg);
arg->addSubArgument(&visibilityArg); arg->addSubArgument(&visibilityArg);

View File

@ -6,10 +6,8 @@
#include <c++utilities/application/argumentparser.h> #include <c++utilities/application/argumentparser.h>
#include <c++utilities/application/commandlineutils.h> #include <c++utilities/application/commandlineutils.h>
#include <c++utilities/application/failure.h>
#include <c++utilities/conversion/stringconversion.h> #include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/ansiescapecodes.h> #include <c++utilities/io/ansiescapecodes.h>
#include <c++utilities/io/catchiofailure.h>
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
#include <cstring> #include <cstring>
@ -18,10 +16,8 @@
#include <unordered_map> #include <unordered_map>
using namespace std; using namespace std;
using namespace ApplicationUtilities; using namespace CppUtilities;
using namespace ConversionUtilities; using namespace CppUtilities::EscapeCodes;
using namespace EscapeCodes;
using namespace IoUtilities;
using namespace ReflectiveRapidJSON; using namespace ReflectiveRapidJSON;
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@ -37,16 +33,17 @@ int main(int argc, char *argv[])
inputFileArg.setRequired(true); inputFileArg.setRequired(true);
ConfigValueArgument outputFileArg("output-file", '\0', "specifies the output file", { "path" }); ConfigValueArgument outputFileArg("output-file", '\0', "specifies the output file", { "path" });
Argument generatorsArg("generators", '\0', "specifies the generators (by default all generators are enabled)"); Argument generatorsArg("generators", '\0', "specifies the generators (by default all generators are enabled)");
generatorsArg.setValueNames({ "json" }); generatorsArg.setValueNames({ "json", "binary" });
generatorsArg.setPreDefinedCompletionValues("json"); generatorsArg.setPreDefinedCompletionValues("json binary");
generatorsArg.setRequiredValueCount(Argument::varValueCount); generatorsArg.setRequiredValueCount(Argument::varValueCount);
generatorsArg.setCombinable(true); generatorsArg.setCombinable(true);
ConfigValueArgument clangOptionsArg("clang-opt", '\0', "specifies arguments/options to be passed to Clang", { "option" }); ConfigValueArgument clangOptionsArg("clang-opt", '\0', "specifies arguments/options to be passed to Clang", { "option" });
clangOptionsArg.setRequiredValueCount(Argument::varValueCount); clangOptionsArg.setRequiredValueCount(Argument::varValueCount);
ConfigValueArgument logClangOptions("log-clang-opt", '\0', "logs the options passed to Clang");
ConfigValueArgument errorResilientArg("error-resilient", '\0', "turns most errors into warnings"); ConfigValueArgument errorResilientArg("error-resilient", '\0', "turns most errors into warnings");
HelpArgument helpArg(parser); HelpArgument helpArg(parser);
NoColorArgument noColorArg; NoColorArgument noColorArg;
generateArg.setSubArguments({ &inputFileArg, &outputFileArg, &generatorsArg, &clangOptionsArg, &errorResilientArg }); generateArg.setSubArguments({ &inputFileArg, &outputFileArg, &generatorsArg, &clangOptionsArg, &logClangOptions, &errorResilientArg });
JsonSerializationCodeGenerator::Options jsonOptions; JsonSerializationCodeGenerator::Options jsonOptions;
jsonOptions.appendTo(&generateArg); jsonOptions.appendTo(&generateArg);
BinarySerializationCodeGenerator::Options binaryOptions; BinarySerializationCodeGenerator::Options binaryOptions;
@ -54,7 +51,7 @@ int main(int argc, char *argv[])
parser.setMainArguments({ &generateArg, &noColorArg, &helpArg }); parser.setMainArguments({ &generateArg, &noColorArg, &helpArg });
// parse arguments // parse arguments
parser.parseArgsOrExit(argc, argv); parser.parseArgs(argc, argv);
if (helpArg.isPresent() || !generateArg.isPresent()) { if (helpArg.isPresent() || !generateArg.isPresent()) {
return 0; return 0;
} }
@ -72,21 +69,28 @@ int main(int argc, char *argv[])
} }
// compose options passed to the clang tool invocation // compose options passed to the clang tool invocation
vector<string> clangOptions; auto clangOptions = std::vector<std::string_view>();
if (clangOptionsArg.isPresent()) { if (clangOptionsArg.isPresent()) {
// add additional options specified via CLI argument // add additional options specified via CLI argument
for (const auto *const value : clangOptionsArg.values(0)) { for (const auto *const value : clangOptionsArg.values(0)) {
// split options by ";" - not nice but this eases using CMake generator expressions // split options by ";" - not nice but this eases using CMake generator expressions
const auto splittedValues(splitString<vector<string>>(value, ";", EmptyPartsTreat::Omit)); const auto splittedValues = splitStringSimple<std::vector<std::string_view>>(value, ";");
clangOptions.reserve(clangOptions.size() + splittedValues.size());
for (const auto &splittedValue : splittedValues) { for (const auto &splittedValue : splittedValues) {
clangOptions.emplace_back(move(splittedValue)); if (!splittedValue.empty()) {
clangOptions.emplace_back(splittedValue);
}
} }
} }
} }
if (logClangOptions.isPresent()) {
cerr << Phrases::Info << "Options passed to clang:" << Phrases::End;
for (const auto &opt : clangOptions) {
cerr << opt << '\n';
}
}
// instantiate the code factory and add generators to it // instantiate the code factory and add generators to it
CodeFactory factory(parser.executable(), inputFileArg.values(0), clangOptions, *os); auto factory = CodeFactory(parser.executable(), inputFileArg.values(0), clangOptions, *os);
factory.setErrorResilient(errorResilientArg.isPresent()); factory.setErrorResilient(errorResilientArg.isPresent());
// add specified generators if the --generator argument is present; otherwise add default generators // add specified generators if the --generator argument is present; otherwise add default generators
if (generatorsArg.isPresent()) { if (generatorsArg.isPresent()) {
@ -119,15 +123,14 @@ int main(int argc, char *argv[])
// read AST elements from input files and run the code generator // read AST elements from input files and run the code generator
if (!factory.run()) { if (!factory.run()) {
cerr << Phrases::Error << "Errors occured." << Phrases::EndFlush; cerr << Phrases::Error << "Errors occurred." << Phrases::EndFlush;
return -2; return -2;
} }
} catch (...) { } catch (const std::ios_base::failure &failure) {
catchIoFailure(); const char *errorMessage = failure.what();
const char *errorMessage;
if (os) { if (os) {
errorMessage = os->fail() || os->bad() ? "An IO error occured when writing to the output stream." : "An IO error occured."; errorMessage = os->fail() || os->bad() ? "An IO error occurred when writing to the output stream." : "An IO error occurred.";
} else { } else {
errorMessage = "An IO error when opening output stream."; errorMessage = "An IO error when opening output stream.";
} }

View File

@ -5,6 +5,10 @@
#include <clang/AST/DeclCXX.h> #include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclFriend.h> #include <clang/AST/DeclFriend.h>
#include <clang/AST/DeclTemplate.h> #include <clang/AST/DeclTemplate.h>
#include <clang/AST/PrettyPrinter.h>
#include <clang/AST/QualTypeNames.h>
#include <iostream>
using namespace std; using namespace std;
@ -64,40 +68,54 @@ void SerializationCodeGenerator::addDeclaration(clang::Decl *decl)
} }
} }
SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNameIfRelevant( void SerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
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;
}
// consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available // consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available
for (const auto &adaptionRecord : m_adaptionRecords) { for (const auto &adaptionRecord : m_adaptionRecords) {
// skip all adaption records which are only included // skip all adaption records which are only included
if (isOnlyIncluded(adaptionRecord.record)) { if (isOnlyIncluded(adaptionRecord.record)) {
continue; continue;
} }
if (adaptionRecord.qualifiedName == qualifiedName) { if (adaptionRecord.qualifiedName == possiblyRelevantClass.qualifiedName) {
return IsRelevant::Yes; possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
} }
} }
// skip all classes which are only included // skip all classes which are only included
if (isOnlyIncluded(record)) { if (isOnlyIncluded(possiblyRelevantClass.record)) {
return IsRelevant::No; possiblyRelevantClass.isRelevant = IsRelevant::No;
return;
} }
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant // consider all classes inheriting from an instantiation of "JsonSerializable" relevant
if (inheritsFromInstantiationOf(record, m_qualifiedNameOfRecords)) { if (const auto *const relevantBase = inheritsFromInstantiationOf(possiblyRelevantClass.record, m_qualifiedNameOfRecords)) {
return IsRelevant::Yes; 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;
} }
return IsRelevant::Maybe;
} }
std::vector<SerializationCodeGenerator::RelevantClass> SerializationCodeGenerator::findRelevantClasses() const std::vector<SerializationCodeGenerator::RelevantClass> SerializationCodeGenerator::findRelevantClasses() const
{ {
std::vector<RelevantClass> relevantClasses; std::vector<RelevantClass> relevantClasses;
for (clang::CXXRecordDecl *record : m_records) { for (clang::CXXRecordDecl *const record : m_records) {
string qualifiedName(qualifiedNameIfRelevant(record)); auto &relevantClass = relevantClasses.emplace_back(record->getQualifiedNameAsString(), record);
if (!qualifiedName.empty()) { computeRelevantClass(relevantClass);
relevantClasses.emplace_back(move(qualifiedName), record); if (relevantClass.isRelevant != IsRelevant::Yes) {
relevantClasses.pop_back();
} }
} }
return relevantClasses; return relevantClasses;

View File

@ -5,6 +5,8 @@
#include <llvm/ADT/StringRef.h> #include <llvm/ADT/StringRef.h>
#include <optional>
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
std::ostream &operator<<(std::ostream &os, llvm::StringRef str); 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 { class SerializationCodeGenerator : public CodeGenerator {
public: public:
enum class IsRelevant { Yes, No, Maybe };
struct RelevantClass { struct RelevantClass {
explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record); explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record);
std::string qualifiedName; std::string qualifiedName;
clang::CXXRecordDecl *record; std::string relevantBase;
clang::CXXRecordDecl *record = nullptr;
IsRelevant isRelevant = IsRelevant::Maybe;
}; };
SerializationCodeGenerator(CodeFactory &factory); SerializationCodeGenerator(CodeFactory &factory);
@ -27,9 +32,7 @@ public:
void addDeclaration(clang::Decl *decl) override; void addDeclaration(clang::Decl *decl) override;
protected: protected:
enum class IsRelevant { Yes, No, Maybe }; virtual void computeRelevantClass(RelevantClass &possiblyRelevantClass) const;
IsRelevant isQualifiedNameIfRelevant(clang::CXXRecordDecl *record, const std::string &qualifiedName) const;
virtual std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const = 0;
std::vector<RelevantClass> findRelevantClasses() const; std::vector<RelevantClass> findRelevantClasses() const;
static std::vector<const RelevantClass *> findRelevantBaseClasses( static std::vector<const RelevantClass *> findRelevantBaseClasses(
const RelevantClass &relevantClass, const std::vector<RelevantClass> &relevantBases); 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 // pull base classes
// set error context for current record // set error context for current record
const char *previousRecord; const char *previousRecord = nullptr;
if (errors) { if (errors) {
previousRecord = errors->currentRecord; previousRecord = errors->currentRecord;
errors->currentRecord = "TestNamespace1::Person"; errors->currentRecord = "TestNamespace1::Person";
@ -39,7 +39,7 @@ template <> void pull<::TestNamespace2::ThirdPartyStruct>(::TestNamespace2::Thi
{ {
// pull base classes // pull base classes
// set error context for current record // set error context for current record
const char *previousRecord; const char *previousRecord = nullptr;
if (errors) { if (errors) {
previousRecord = errors->currentRecord; previousRecord = errors->currentRecord;
errors->currentRecord = "TestNamespace2::ThirdPartyStruct"; errors->currentRecord = "TestNamespace2::ThirdPartyStruct";

View File

@ -19,10 +19,8 @@
#include <sstream> #include <sstream>
using namespace CPPUNIT_NS; using namespace CPPUNIT_NS;
using namespace IoUtilities; using namespace CppUtilities;
using namespace TestUtilities; using namespace CppUtilities::Literals;
using namespace TestUtilities::Literals;
using namespace ConversionUtilities;
/*! /*!
* \brief The BinaryGeneratorTests class tests the binary generator. * \brief The BinaryGeneratorTests class tests the binary generator.

View File

@ -1 +0,0 @@
#include <c++utilities/tests/cppunit.h>

View File

@ -6,10 +6,12 @@
#include <c++utilities/tests/testutils.h> #include <c++utilities/tests/testutils.h>
// ensure "operator<<" from TestUtilities is visible prior to the call site // ensure "operator<<" from TestUtilities is visible prior to the call site
using TestUtilities::operator<<; using CppUtilities::operator<<;
#include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/HelperMacros.h>
namespace Traits = CppUtilities::Traits;
/*! /*!
* \brief Asserts equality of two iteratables printing the differing indices. * \brief Asserts equality of two iteratables printing the differing indices.
*/ */
@ -22,20 +24,19 @@ inline void assertEqualityLinewise(const Iteratable &iteratable1, const Iteratab
for (auto i1 = iteratable1.cbegin(), i2 = iteratable2.cbegin(); i1 != iteratable1.cend() || i2 != iteratable2.cend(); ++currentLine) { for (auto i1 = iteratable1.cbegin(), i2 = iteratable2.cbegin(); i1 != iteratable1.cend() || i2 != iteratable2.cend(); ++currentLine) {
if (i1 != iteratable1.cend() && i2 != iteratable2.cend()) { if (i1 != iteratable1.cend() && i2 != iteratable2.cend()) {
if (*i1 != *i2) { if (*i1 != *i2) {
differentLines.push_back(ConversionUtilities::numberToString(currentLine)); differentLines.push_back(CppUtilities::numberToString(currentLine));
} }
++i1, ++i2; ++i1, ++i2;
} else if (i1 != iteratable1.cend()) { } else if (i1 != iteratable1.cend()) {
differentLines.push_back(ConversionUtilities::numberToString(currentLine)); differentLines.push_back(CppUtilities::numberToString(currentLine));
++i1; ++i1;
} else if (i2 != iteratable1.cend()) { } else if (i2 != iteratable1.cend()) {
differentLines.push_back(ConversionUtilities::numberToString(currentLine)); differentLines.push_back(CppUtilities::numberToString(currentLine));
++i2; ++i2;
} }
} }
if (!differentLines.empty()) { if (!differentLines.empty()) {
CPPUNIT_ASSERT_EQUAL_MESSAGE( CPPUNIT_ASSERT_EQUAL_MESSAGE("the following lines differ: " + CppUtilities::joinStrings(differentLines, ", "), iteratable1, iteratable2);
"the following lines differ: " + ConversionUtilities::joinStrings(differentLines, ", "), iteratable1, iteratable2);
} }
} }

View File

@ -17,10 +17,8 @@
#include <iostream> #include <iostream>
using namespace CPPUNIT_NS; using namespace CPPUNIT_NS;
using namespace IoUtilities; using namespace CppUtilities;
using namespace TestUtilities; using namespace CppUtilities::Literals;
using namespace TestUtilities::Literals;
using namespace ConversionUtilities;
/*! /*!
* \brief The JsonGeneratorTests class tests the overall functionality of the code generator (CLI and generator itself) and JSON specific parts. * \brief The JsonGeneratorTests class tests the overall functionality of the code generator (CLI and generator itself) and JSON specific parts.
@ -66,10 +64,14 @@ JsonGeneratorTests::JsonGeneratorTests()
*/ */
void JsonGeneratorTests::testGeneratorItself() void JsonGeneratorTests::testGeneratorItself()
{ {
const string inputFilePath(testFilePath("some_structs.h")); const auto inputFilePath = testFilePath("some_structs.h");
const vector<const char *> inputFiles{ inputFilePath.data() }; const auto inputFiles = vector<const char *>{ inputFilePath.data() };
const vector<string> clangOptions{ "-resource-dir", REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-I", CPP_UTILITIES_INCLUDE_DIRS, "-I", const auto clangOptions
RAPIDJSON_INCLUDE_DIRS }; = vector<std::string_view>{ "-resource-dir", REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-std=c++17", "-I", CPP_UTILITIES_INCLUDE_DIRS,
#ifdef RAPIDJSON_INCLUDE_DIRS
"-I", RAPIDJSON_INCLUDE_DIRS
#endif
};
stringstream buffer; stringstream buffer;
JsonSerializationCodeGenerator::Options jsonOptions; JsonSerializationCodeGenerator::Options jsonOptions;
@ -82,7 +84,7 @@ void JsonGeneratorTests::testGeneratorItself()
} }
/*! /*!
* \brief Tests the generator CLI explicitely. * \brief Tests the generator CLI explicitly.
* \remarks Only available under UNIX (like) systems so far, because TESTUTILS_ASSERT_EXEC has not been implemented * \remarks Only available under UNIX (like) systems so far, because TESTUTILS_ASSERT_EXEC has not been implemented
* for other platforms. * for other platforms.
*/ */
@ -92,9 +94,12 @@ void JsonGeneratorTests::testCLI()
string stdout, stderr; string stdout, stderr;
const string inputFilePath(testFilePath("some_structs.h")); const string inputFilePath(testFilePath("some_structs.h"));
const char *const args1[] const char *const args1[] = { PROJECT_NAME, "--input-file", inputFilePath.data(), "--json-classes", "TestNamespace2::ThirdPartyStruct",
= { PROJECT_NAME, "--input-file", inputFilePath.data(), "--json-classes", "TestNamespace2::ThirdPartyStruct", "--clang-opt", "-resource-dir", "--clang-opt", "-resource-dir", REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-std=c++17", "-I", CPP_UTILITIES_INCLUDE_DIRS,
REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-I", CPP_UTILITIES_INCLUDE_DIRS, "-I", RAPIDJSON_INCLUDE_DIRS, nullptr }; #ifdef RAPIDJSON_INCLUDE_DIRS
"-I", RAPIDJSON_INCLUDE_DIRS,
#endif
nullptr };
TESTUTILS_ASSERT_EXEC(args1); TESTUTILS_ASSERT_EXEC(args1);
assertEqualityLinewise(m_expectedCode, toArrayOfLines(stdout)); assertEqualityLinewise(m_expectedCode, toArrayOfLines(stdout));
#endif #endif
@ -159,7 +164,7 @@ void JsonGeneratorTests::testNesting()
} }
/*! /*!
* \brief Like testIncludingGeneratedHeader() but also tests single inheritence. * \brief Like testIncludingGeneratedHeader() but also tests single inheritance.
*/ */
void JsonGeneratorTests::testSingleInheritence() void JsonGeneratorTests::testSingleInheritence()
{ {
@ -188,7 +193,7 @@ void JsonGeneratorTests::testSingleInheritence()
} }
/*! /*!
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritence. * \brief Like testIncludingGeneratedHeader() but also tests multiple inheritance.
*/ */
void JsonGeneratorTests::testMultipleInheritence() void JsonGeneratorTests::testMultipleInheritence()
{ {
@ -304,4 +309,4 @@ void JsonGeneratorTests::testHandlingConstMembers()
// this file should also be generated via add_reflection_generator_invocation() and hence includeable // this file should also be generated via add_reflection_generator_invocation() and hence includeable
// it is included to test the "empty" case when a unit doesn't contain relevant classes // it is included to test the "empty" case when a unit doesn't contain relevant classes
#include "reflection/cppunit.h" #include "reflection/visitor.h"

View File

@ -1,8 +1,11 @@
#ifndef REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H #ifndef REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
#define 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/binary/serializable.h"
#include "../../lib/json/serializable.h" #include "../../lib/json/serializable.h"
#include "../../lib/versioning.h"
using namespace std; using namespace std;
using namespace ReflectiveRapidJSON; using namespace ReflectiveRapidJSON;
@ -67,4 +70,29 @@ struct PointerStruct : public BinarySerializable<PointerStruct> {
std::shared_ptr<PointerTarget> s3; 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 #endif // REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H

View File

@ -37,6 +37,11 @@ private:
string privateString = "not going to be serialized"; string privateString = "not going to be serialized";
}; };
// forward declarations shouldn't cause the generator to emit the code multiple times
struct TestStruct;
struct TestStruct;
struct TestStruct;
class JsonGeneratorTests; class JsonGeneratorTests;
/*! /*!
@ -94,8 +99,8 @@ struct MultipleDerivedTestStruct : public TestStruct,
* and toJson() methods. This is asserted in JsonGeneratorTests::testCustomSerialization(); * and toJson() methods. This is asserted in JsonGeneratorTests::testCustomSerialization();
*/ */
struct StructWithCustomTypes : public JsonSerializable<StructWithCustomTypes> { struct StructWithCustomTypes : public JsonSerializable<StructWithCustomTypes> {
ChronoUtilities::DateTime dt = ChronoUtilities::DateTime::fromDateAndTime(2017, 4, 2, 15, 31, 21, 165.125); CppUtilities::DateTime dt = CppUtilities::DateTime::fromDateAndTime(2017, 4, 2, 15, 31, 21, 165.125);
ChronoUtilities::TimeSpan ts = ChronoUtilities::TimeSpan::fromHours(3.25) + ChronoUtilities::TimeSpan::fromSeconds(19.125); CppUtilities::TimeSpan ts = CppUtilities::TimeSpan::fromHours(3.25) + CppUtilities::TimeSpan::fromSeconds(19.125);
}; };
/*! /*!
@ -123,7 +128,7 @@ REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(NotJsonSerializable);
REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(NestedNotJsonSerializable); REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(NestedNotJsonSerializable);
/*! /*!
* \brief The OtherNotJsonSerializable struct is used to test whether code for (de)serialization is generated for classes explicitely * \brief The OtherNotJsonSerializable struct is used to test whether code for (de)serialization is generated for classes explicitly
* specified via CMake macro (despite use of REFLECTIVE_RAPIDJSON_ADAPT_JSON_SERIALIZABLE or JsonSerializable is * specified via CMake macro (despite use of REFLECTIVE_RAPIDJSON_ADAPT_JSON_SERIALIZABLE or JsonSerializable is
* missing). * missing).
*/ */

View File

@ -34,7 +34,7 @@ bool Visitor::VisitDecl(clang::Decl *decl)
*/ */
bool ReflectiveRapidJSON::Visitor::VisitFunctionDecl(clang::FunctionDecl *func) bool ReflectiveRapidJSON::Visitor::VisitFunctionDecl(clang::FunctionDecl *func)
{ {
VAR_UNUSED(func) CPP_UTILITIES_UNUSED(func)
return true; return true;
} }
@ -43,7 +43,7 @@ bool ReflectiveRapidJSON::Visitor::VisitFunctionDecl(clang::FunctionDecl *func)
*/ */
bool ReflectiveRapidJSON::Visitor::VisitStmt(clang::Stmt *st) bool ReflectiveRapidJSON::Visitor::VisitStmt(clang::Stmt *st)
{ {
VAR_UNUSED(st) CPP_UTILITIES_UNUSED(st)
return true; return true;
} }
@ -52,7 +52,7 @@ bool ReflectiveRapidJSON::Visitor::VisitStmt(clang::Stmt *st)
*/ */
bool Visitor::VisitNamespaceDecl(clang::NamespaceDecl *decl) bool Visitor::VisitNamespaceDecl(clang::NamespaceDecl *decl)
{ {
VAR_UNUSED(decl) CPP_UTILITIES_UNUSED(decl)
return true; return true;
} }
@ -61,7 +61,7 @@ bool Visitor::VisitNamespaceDecl(clang::NamespaceDecl *decl)
*/ */
bool Visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *decl) bool Visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *decl)
{ {
VAR_UNUSED(decl) CPP_UTILITIES_UNUSED(decl)
return true; return true;
} }

View File

@ -1,71 +1,44 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata # metadata
set(META_PROJECT_TYPE library) set(META_PROJECT_TYPE library)
set(META_HEADER_ONLY_LIB ON) set(META_HEADER_ONLY_LIB ON)
# add project files # add project files
set(HEADER_FILES set(HEADER_FILES traits.h versioning.h)
traits.h set(SRC_FILES)
) set(TEST_HEADER_FILES)
set(SRC_FILES set(TEST_SRC_FILES)
) set(CMAKE_MODULE_FILES cmake/modules/ReflectionGenerator.cmake)
set(TEST_HEADER_FILES set(DOC_FILES README.md)
)
set(TEST_SRC_FILES
tests/cppunit.cpp
)
set(CMAKE_MODULE_FILES
cmake/modules/ReflectionGenerator.cmake
)
set(DOC_FILES
README.md
)
# add JSON-specific sources # add JSON-specific sources
if(RapidJSON_FOUND) if (RapidJSON_FOUND)
list(APPEND HEADER_FILES list(
APPEND
HEADER_FILES
json/reflector.h json/reflector.h
json/reflector-boosthana.h json/reflector-boosthana.h
json/reflector-chronoutilities.h json/reflector-chronoutilities.h
json/serializable.h json/serializable.h
json/errorhandling.h json/errorhandling.h
) json/errorformatting.h)
list(APPEND TEST_SRC_FILES list(APPEND TEST_SRC_FILES tests/jsonreflector.cpp tests/jsonreflector-boosthana.cpp
tests/jsonreflector.cpp tests/jsonreflector-chronoutilities.cpp)
tests/jsonreflector-boosthana.cpp endif ()
tests/jsonreflector-chronoutilities.cpp
)
endif()
# add binary (de)serialization specific sources # add binary (de)serialization specific sources
list(APPEND HEADER_FILES list(APPEND HEADER_FILES binary/reflector.h binary/reflector-boosthana.h binary/reflector-chronoutilities.h
binary/reflector.h binary/serializable.h)
binary/reflector-boosthana.h list(APPEND TEST_SRC_FILES tests/traits.cpp tests/binaryreflector.cpp tests/binaryreflector-boosthana.cpp)
binary/reflector-chronoutilities.h
binary/serializable.h
)
list(APPEND TEST_SRC_FILES
tests/traits.cpp
tests/binaryreflector.cpp
tests/binaryreflector-boosthana.cpp
)
# add (only) the CMake module and include dirs for c++utilities because we're not depending on the actual library # add (only) the CMake module and include dirs for c++utilities because we're not depending on the actual library
list(APPEND CMAKE_MODULE_PATH ${CPP_UTILITIES_MODULE_DIRS}) use_cpp_utilities(ONLY_HEADERS VISIBILITY PUBLIC)
if (CPP_UTILITIES_SOURCE_DIR)
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..")
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..")
else()
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}")
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}")
endif()
# find RapidJSON, also add only the include dirs because RapidJSON is a header-only library # find RapidJSON, also add only the include dirs because RapidJSON is a header-only library
if(RapidJSON_FOUND) if (RapidJSON_FOUND)
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS}) list(APPEND PUBLIC_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS})
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS}) endif ()
endif()
# include modules to apply configuration # include modules to apply configuration
include(BasicConfig) include(BasicConfig)
@ -73,3 +46,8 @@ include(LibraryTarget)
include(TestTarget) include(TestTarget)
include(Doxygen) include(Doxygen)
include(ConfigHeader) include(ConfigHeader)
# export target name so the generator can link against it
set(${META_PROJECT_VARNAME_UPPER}_TARGET_NAME
"${META_TARGET_NAME}"
PARENT_SCOPE)

View File

@ -24,16 +24,17 @@
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
namespace BinaryReflector { 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::for_each(boost::hana::keys(customType), [&](auto key) { deserializer.read(boost::hana::at_key(customType, key), version); });
boost::hana::keys(customType), [&deserializer, &customType](auto key) { deserializer.read(boost::hana::at_key(customType, key)); }); 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::for_each(boost::hana::keys(customType), [&](auto key) { serializer.write(boost::hana::at_key(customType, key), version); });
boost::hana::keys(customType), [&serializer, &customType](auto key) { serializer.write(boost::hana::at_key(customType, key)); });
} }
} // namespace BinaryReflector } // namespace BinaryReflector

View File

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

View File

@ -8,17 +8,22 @@
*/ */
#include "../traits.h" #include "../traits.h"
#include "../versioning.h"
#include <c++utilities/conversion/conversionexception.h> #include <c++utilities/conversion/conversionexception.h>
#include <c++utilities/conversion/types.h>
#include <c++utilities/io/binaryreader.h> #include <c++utilities/io/binaryreader.h>
#include <c++utilities/io/binarywriter.h> #include <c++utilities/io/binarywriter.h>
#include <any> #include <any>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <tuple> #include <variant>
/// \cond
class BinaryReflectorTests;
/// \endcond
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
@ -30,7 +35,8 @@ template <typename T> struct AdaptedBinarySerializable : public Traits::Bool<fal
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedBinarySerializable"; 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. * \brief The BinaryReflector namespace contains BinaryReader and BinaryWriter for automatic binary (de)serialization.
@ -39,53 +45,81 @@ namespace BinaryReflector {
// define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes // define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes
template <typename Type> template <typename Type>
using IsBuiltInType = Traits::Any<Traits::IsAnyOf<Type, char, byte, bool, std::string, int16, uint16, int32, uint32, int64, uint64, float32, float64>, using IsBuiltInType = Traits::Any<Traits::IsAnyOf<Type, char, std::uint8_t, bool, std::string, std::int16_t, std::uint16_t, std::int32_t,
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr>, std::is_enum<Type>>; 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>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>; template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
class BinaryDeserializer; class BinaryDeserializer;
class BinarySerializer; class BinarySerializer;
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void readCustomType(BinaryDeserializer &deserializer, Type &customType); /// \brief Reads \a customType via \a deserializer.
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void writeCustomType(BinarySerializer &serializer, const Type &customType); /// \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);
/// \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;
class BinaryDeserializer : public IoUtilities::BinaryReader {
public: public:
BinaryDeserializer(std::istream *stream); explicit BinaryDeserializer(std::istream *stream);
using IoUtilities::BinaryReader::read; using CppUtilities::BinaryReader::read;
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void read(Type &pair); 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 &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 &pair); 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::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, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type, template <typename Type,
Traits::EnableIf<IsIteratableExceptString<Type>, Traits::EnableIf<IsIteratableExceptString<Type>,
Traits::None<IsMapOrHash<Type>, IsMultiMapOrHash<Type>, Traits::All<IsArray<Type>, Traits::IsResizable<Type>>>> * = nullptr> Traits::None<IsMapOrHash<Type>, IsMultiMapOrHash<Type>, Traits::All<IsArray<Type>, Traits::IsResizable<Type>>>> * = nullptr>
void read(Type &iteratable); void read(Type &iteratable);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &customType); template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &enumValue);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void read(Type &customType); 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);
std::unordered_map<uint64, std::any> m_pointer; private:
std::unordered_map<std::uint64_t, std::any> m_pointer;
}; };
class BinarySerializer : public IoUtilities::BinaryWriter { /// \brief The BinarySerializer class can write various data types, including custom ones, to an std::ostream.
public: class BinarySerializer : public CppUtilities::BinaryWriter {
BinarySerializer(std::ostream *stream); friend class ::BinaryReflectorTests;
using IoUtilities::BinaryWriter::write; public:
explicit BinarySerializer(std::ostream *stream);
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::IsSpecializationOf<Type, std::pair>> * = nullptr> void write(const Type &pair);
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::unique_ptr, std::optional>> * = 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<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<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 &customType); template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void write(const Type &enumValue);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType); 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);
std::unordered_map<uint64, bool> m_pointer; private:
std::unordered_map<std::uint64_t, bool> m_pointer;
}; };
inline BinaryDeserializer::BinaryDeserializer(std::istream *stream) inline BinaryDeserializer::BinaryDeserializer(std::istream *stream)
: IoUtilities::BinaryReader(stream) : CppUtilities::BinaryReader(stream)
{ {
} }
@ -107,28 +141,38 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> *> void BinaryDeserializer::read(Type &pointer) template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> *> void BinaryDeserializer::read(Type &pointer)
{ {
const auto occurence = readByte(); auto mode = readByte();
if (!occurence) { if (!mode) {
// pointer not set // pointer not set
pointer.reset(); pointer.reset();
return; return;
} }
const auto id = readVariableLengthUIntBE(); const auto id = (mode & 0x4) ? readUInt64BE() : readVariableLengthUIntBE(); // the 3rd bit being flagged indicates a big ID
if (occurence == 1) { if ((mode & 0x3) == 1) {
// first occurence: make a new pointer // first occurrence: make a new pointer
m_pointer[id] = pointer = std::make_shared<typename Type::element_type>(); m_pointer[id] = pointer = std::make_shared<typename Type::element_type>();
read(*pointer); read(*pointer);
return; return;
} }
// further occurences: copy previous pointer // further occurrences: copy previous pointer
try { try {
pointer = std::any_cast<Type>(m_pointer[id]); pointer = std::any_cast<Type>(m_pointer[id]);
} catch (const std::bad_any_cast) { } catch (const std::bad_any_cast &) {
throw ConversionUtilities::ConversionException("Referenced pointer type does not match"); throw CppUtilities::ConversionException("Referenced pointer type does not match");
} }
} }
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) template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> *> void BinaryDeserializer::read(Type &iteratable)
{ {
const auto size = readVariableLengthUIntBE(); const auto size = readVariableLengthUIntBE();
@ -168,13 +212,46 @@ template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinaryDese
enumValue = static_cast<Type>(value); enumValue = static_cast<Type>(value);
} }
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinaryDeserializer::read(Type &customType) /// \cond
namespace Detail {
template <typename Variant, std::size_t compiletimeIndex = 0>
void readVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant, BinaryDeserializer &deserializer)
{ {
readCustomType(*this, customType); if constexpr (compiletimeIndex < std::variant_size_v<Variant>) {
if (compiletimeIndex == runtimeIndex) {
if constexpr (std::is_same_v<std::variant_alternative_t<compiletimeIndex, Variant>, std::monostate>) {
variant = std::monostate{};
} else {
deserializer.read(variant.template emplace<compiletimeIndex>());
}
} else {
readVariantValueByRuntimeIndex<Variant, compiletimeIndex + 1>(runtimeIndex, variant, deserializer);
}
} else {
throw CppUtilities::ConversionException("Variant index is out of expected range");
}
}
} // namespace Detail
/// \endcond
template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinaryDeserializer::read(Type &variant)
{
Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this);
}
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &builtInType, BinaryVersion version)
{
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) inline BinarySerializer::BinarySerializer(std::ostream *stream)
: IoUtilities::BinaryWriter(stream) : CppUtilities::BinaryWriter(stream)
{ {
} }
@ -184,12 +261,12 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
write(pair.second); write(pair.second);
} }
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr>> *> void BinarySerializer::write(const Type &pointer) template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::optional>> *>
void BinarySerializer::write(const Type &opt)
{ {
const bool hasValue = pointer != nullptr; writeBool(static_cast<bool>(opt));
writeBool(hasValue); if (opt) {
if (hasValue) { write(*opt);
write(*pointer);
} }
} }
@ -199,10 +276,19 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std:
writeByte(0); writeByte(0);
return; return;
} }
const auto id = reinterpret_cast<uint64>(pointer.get()); const auto id = reinterpret_cast<std::uintptr_t>(pointer.get());
const auto bigId = id >= 0x80000000000000;
auto &alreadyWritten = m_pointer[id]; auto &alreadyWritten = m_pointer[id];
writeByte(alreadyWritten ? 2 : 1); std::uint8_t mode = alreadyWritten ? 2 : 1;
writeVariableLengthUIntBE(id); if (bigId) {
mode = mode | 0x4; // "flag" 3rd bit to indicate big ID
}
writeByte(mode);
if (bigId) {
writeUInt64BE(id);
} else {
writeVariableLengthUIntBE(id);
}
if (!alreadyWritten) { if (!alreadyWritten) {
alreadyWritten = true; alreadyWritten = true;
write(*pointer); write(*pointer);
@ -223,9 +309,30 @@ template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinarySeri
write(static_cast<typename std::underlying_type<Type>::type>(enumValue)); write(static_cast<typename std::underlying_type<Type>::type>(enumValue));
} }
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType) template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySerializer::write(const Type &variant)
{ {
writeCustomType(*this, customType); static_assert(std::variant_size_v<Type> < std::numeric_limits<std::uint8_t>::max(), "index will not exceed limit");
writeByte(static_cast<std::uint8_t>(variant.index()));
std::visit(
[this](const auto &valueOfActualType) {
if constexpr (!std::is_same_v<std::decay_t<decltype(valueOfActualType)>, std::monostate>) {
write(valueOfActualType);
} else {
CPP_UTILITIES_UNUSED(this)
}
},
variant);
}
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version)
{
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 } // namespace BinaryReflector

View File

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

View File

@ -1,137 +1,172 @@
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# prevent multiple inclusion # prevent multiple inclusion
if(DEFINED REFLECTION_GENERATOR_MODULE_LOADED) if (DEFINED REFLECTION_GENERATOR_MODULE_LOADED)
return() return()
endif() endif ()
set(REFLECTION_GENERATOR_MODULE_LOADED YES) set(REFLECTION_GENERATOR_MODULE_LOADED YES)
# find code generator # find code generator
set(DEFAULT_REFLECTION_GENERATOR_EXECUTABLE "reflective_rapidjson_generator") set(DEFAULT_REFLECTION_GENERATOR_EXECUTABLE "${TARGET_PREFIX}reflective_rapidjson_generator${TARGET_SUFFIX}")
set(REFLECTION_GENERATOR_EXECUTABLE "" CACHE FILEPATH "path to executable of reflection generator") set(REFLECTION_GENERATOR_EXECUTABLE
if(REFLECTION_GENERATOR_EXECUTABLE) ""
CACHE FILEPATH "path to executable of reflection generator")
if (REFLECTION_GENERATOR_EXECUTABLE)
# use custom generator executable # use custom generator executable
if(NOT EXISTS "${REFLECTION_GENERATOR_EXECUTABLE}" OR IS_DIRECTORY "${REFLECTION_GENERATOR_EXECUTABLE}") if (NOT EXISTS "${REFLECTION_GENERATOR_EXECUTABLE}" OR IS_DIRECTORY "${REFLECTION_GENERATOR_EXECUTABLE}")
message(FATAL_ERROR "The specified code generator executable \"${REFLECTION_GENERATOR_EXECUTABLE}\" is not a file.") message(FATAL_ERROR "The specified code generator executable \"${REFLECTION_GENERATOR_EXECUTABLE}\" is not a file.")
endif() endif ()
elseif(CMAKE_CROSSCOMPILING OR NOT TARGET "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}") elseif (CMAKE_CROSSCOMPILING OR NOT TARGET "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}")
# find native/external "reflective_rapidjson_generator" # find native/external "reflective_rapidjson_generator"
find_program(REFLECTION_GENERATOR_EXECUTABLE find_program(REFLECTION_GENERATOR_EXECUTABLE "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}" PATHS "/usr/bin" "/bin")
"${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}" else ()
PATHS "/usr/bin" "/bin"
)
else()
# use "reflective_rapidjson_generator" target # use "reflective_rapidjson_generator" target
set(REFLECTION_GENERATOR_EXECUTABLE "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}") set(REFLECTION_GENERATOR_EXECUTABLE "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}")
endif() endif ()
if(NOT REFLECTION_GENERATOR_EXECUTABLE) if (NOT REFLECTION_GENERATOR_EXECUTABLE)
message(FATAL_ERROR "Unable to find executable of generator for reflection code. Set REFLECTION_GENERATOR_EXECUTABLE to specify the path.") message(
endif() FATAL_ERROR
"Unable to find executable of generator for reflection code. Set REFLECTION_GENERATOR_EXECUTABLE to specify the path."
)
endif ()
# determine Clang's resource directory # determine Clang's resource directory
set(REFLECTION_GENERATOR_CLANG_RESOURCE_DIR "" CACHE PATH "directory containing Clang's builtin headers, usually /usr/lib/clang/version") set(REFLECTION_GENERATOR_CLANG_RESOURCE_DIR
if(NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR) ""
if(NOT REFLECTION_GENERATOR_CLANG_BIN) CACHE PATH "directory containing Clang's builtin headers, usually /usr/lib/clang/version")
find_program(REFLECTION_GENERATOR_CLANG_BIN clang if (NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR)
if (NOT REFLECTION_GENERATOR_CLANG_BIN)
find_program(
REFLECTION_GENERATOR_CLANG_BIN clang
NAMES clang++ NAMES clang++
PATHS "/usr/bin" "/bin" PATHS "/usr/bin" "/bin")
) if (NOT REFLECTION_GENERATOR_CLANG_BIN)
if(NOT REFLECTION_GENERATOR_CLANG_BIN)
message(FATAL_ERROR "Unable to find the clang executable to determine Clang's resource directory") message(FATAL_ERROR "Unable to find the clang executable to determine Clang's resource directory")
endif() endif ()
endif() endif ()
exec_program(${REFLECTION_GENERATOR_CLANG_BIN} ARGS -print-resource-dir OUTPUT_VARIABLE REFLECTION_GENERATOR_CLANG_RESOURCE_DIR) execute_process(
endif() COMMAND ${REFLECTION_GENERATOR_CLANG_BIN} -print-resource-dir
if(NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR OR NOT IS_DIRECTORY "${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}") OUTPUT_VARIABLE REFLECTION_GENERATOR_CLANG_RESOURCE_DIR
message(FATAL_ERROR "Unable to find Clang's resource directory. Set REFLECTION_GENERATOR_CLANG_RESOURCE_DIR manually to the corresponding path (usually /usr/lib/clang/\$version).") OUTPUT_STRIP_TRAILING_WHITESPACE)
endif() endif ()
if (NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR OR NOT IS_DIRECTORY "${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}")
message(
FATAL_ERROR
"Unable to find Clang's resource directory. Set REFLECTION_GENERATOR_CLANG_RESOURCE_DIR manually to the corresponding path (usually /usr/lib/clang/\$version)."
)
endif ()
message(STATUS "Using ${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR} as Clang's resource directory for Reflective RapidJSON") message(STATUS "Using ${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR} as Clang's resource directory for Reflective RapidJSON")
# allow to specify a custom include paths (useful for cross-compilation when header files are under custom prefix) # allow to specify a custom include paths (useful for cross-compilation when header files are under custom prefix)
set(REFLECTION_GENERATOR_INCLUDE_DIRECTORIES "" CACHE FILEPATH "include directories for code generator") set(REFLECTION_GENERATOR_INCLUDE_DIRECTORIES
""
CACHE FILEPATH "include directories for code generator")
# allow to specify a custom platform tiple (useful for cross-compilation to specify the target platform) # allow to specify a custom platform tiple (useful for cross-compilation to specify the target platform)
set(REFLECTION_GENERATOR_TRIPLE "" CACHE STRING "platform triple for code generator") set(REFLECTION_GENERATOR_TRIPLE
""
CACHE STRING "platform triple for code generator")
function (_reflective_rapidjson_set_prop TARGET_NAME PROPERTY_NAME)
if ("${CMAKE_VERSION}" VERSION_LESS "3.15.0")
set(PROP
"$<TARGET_PROPERTY:${TARGET_NAME},${PROPERTY_NAME}>"
PARENT_SCOPE)
message(
WARNING
"Passing empty flags to the code generator for property ${PROPERTY_NAME} of target ${TARGET_NAME} might not be prevented. Consider updating to CMake 3.15.0 or newer."
)
else ()
set(PROP
"$<FILTER:$<TARGET_PROPERTY:${TARGET_NAME},${PROPERTY_NAME}>,EXCLUDE,^$>"
PARENT_SCOPE)
endif ()
endfunction ()
# define helper function to add a reflection generator invocation for a specified list of source files # define helper function to add a reflection generator invocation for a specified list of source files
include(CMakeParseArguments) include(CMakeParseArguments)
function(add_reflection_generator_invocation) function (add_reflection_generator_invocation)
# parse arguments # parse arguments
set(OPTIONAL_ARGS set(OPTIONAL_ARGS ERROR_RESILIENT)
) set(ONE_VALUE_ARGS OUTPUT_DIRECTORY JSON_VISIBILITY BINARY_VISBILITY)
set(ONE_VALUE_ARGS
OUTPUT_DIRECTORY
JSON_VISIBILITY
)
set(MULTI_VALUE_ARGS set(MULTI_VALUE_ARGS
INPUT_FILES INPUT_FILES
GENERATORS GENERATORS
OUTPUT_LISTS OUTPUT_LISTS
CLANG_OPTIONS CLANG_OPTIONS
CLANG_OPTIONS_FROM_TARGETS CLANG_OPTIONS_FROM_TARGETS
CLANG_OPTIONS_FROM_DEPENDENCIES
JSON_CLASSES) JSON_CLASSES)
cmake_parse_arguments(ARGS "${OPTIONAL_ARGS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN}) cmake_parse_arguments(ARGS "${OPTIONAL_ARGS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
# determine file name or file path if none specified # determine file name or file path if none specified
if(NOT ARGS_OUTPUT_DIRECTORY) if (NOT ARGS_OUTPUT_DIRECTORY)
set(ARGS_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/reflection") set(ARGS_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/reflection")
file(MAKE_DIRECTORY "${ARGS_OUTPUT_DIRECTORY}") file(MAKE_DIRECTORY "${ARGS_OUTPUT_DIRECTORY}")
endif() endif ()
# specify Clang's resource directory # specify Clang's resource directory
list(APPEND ARGS_CLANG_OPTIONS -resource-dir "${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}") list(APPEND ARGS_CLANG_OPTIONS -resource-dir "${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}")
# apply specified REFLECTION_GENERATOR_TRIPLET # apply specified REFLECTION_GENERATOR_TRIPLET
if(REFLECTION_GENERATOR_TRIPLE) if (REFLECTION_GENERATOR_TRIPLE)
list(APPEND ARGS_CLANG_OPTIONS list(APPEND ARGS_CLANG_OPTIONS -Xclang -triple -Xclang "${REFLECTION_GENERATOR_TRIPLE}")
-Xclang -triple endif ()
-Xclang "${REFLECTION_GENERATOR_TRIPLE}"
)
endif()
# apply specified REFLECTION_GENERATOR_INCLUDE_DIRECTORIES # apply specified REFLECTION_GENERATOR_INCLUDE_DIRECTORIES
foreach(INCLUDE_DIR ${REFLECTION_GENERATOR_INCLUDE_DIRECTORIES}) foreach (INCLUDE_DIR ${REFLECTION_GENERATOR_INCLUDE_DIRECTORIES})
if(NOT IS_DIRECTORY "${INCLUDE_DIR}") if (NOT IS_DIRECTORY "${INCLUDE_DIR}")
message(FATAL_ERROR "Specified include directory \"${INCLUDE_DIR})\" for reflection generator doesn't exists.") message(FATAL_ERROR "Specified include directory \"${INCLUDE_DIR})\" for reflection generator doesn't exists.")
endif() endif ()
list(APPEND ARGS_CLANG_OPTIONS -I "${INCLUDE_DIR}") list(APPEND ARGS_CLANG_OPTIONS -I "${INCLUDE_DIR}")
endforeach() endforeach ()
# add workaround for cross compiling with mingw-w64 to prevent host stdlib.h being included # avoid including headers from host when cross compiling
# (not sure why specifying REFLECTION_GENERATOR_INCLUDE_DIRECTORIES is not enough to let it find this particular header file) if (CMAKE_CROSSCOMPILING)
if(MINGW) list(APPEND ARGS_CLANG_OPTIONS -nostdinc)
# find MinGW version of stdlib.h endif ()
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 # add options to be passed to clang from the specified targets
if(ARGS_CLANG_OPTIONS_FROM_TARGETS) if (ARGS_CLANG_OPTIONS_FROM_TARGETS)
foreach(TARGET_NAME ${ARGS_CLANG_OPTIONS_FROM_TARGETS}) foreach (TARGET_NAME ${ARGS_CLANG_OPTIONS_FROM_TARGETS})
# set c++ standard # set c++ standard
list(APPEND CLANG_TIDY_CXX_FLAGS "-std=c++$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>") list(
# add compile flags APPEND
set(PROP "$<TARGET_PROPERTY:${TARGET_NAME},COMPILE_FLAGS>") ARGS_CLANG_OPTIONS
"$<$<BOOL:$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>>:-std=c++$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>>"
)
# add compile flags and options
_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>>>") list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
# add compile definitions # add compile definitions
set(PROP "$<TARGET_PROPERTY:${TARGET_NAME},COMPILE_DEFINITIONS>") _reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_DEFINITIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>") list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>")
# add include directories # add include directories
set(PROP "$<TARGET_PROPERTY:${TARGET_NAME},INCLUDE_DIRECTORIES>") _reflective_rapidjson_set_prop("${TARGET_NAME}" INCLUDE_DIRECTORIES)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-I$<JOIN:${PROP},$<SEMICOLON>-I>>") list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-I$<JOIN:${PROP},$<SEMICOLON>-I>>")
endforeach() endforeach ()
endif() endif ()
if (ARGS_CLANG_OPTIONS_FROM_DEPENDENCIES)
foreach (TARGET_NAME ${ARGS_CLANG_OPTIONS_FROM_DEPENDENCIES})
if (NOT TARGET "${TARGET_NAME}")
continue()
endif ()
# add interface compile options
_reflective_rapidjson_set_prop("${TARGET_NAME}" INTERFACE_COMPILE_OPTIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
# add interface compile definitions
_reflective_rapidjson_set_prop("${TARGET_NAME}" INTERFACE_COMPILE_DEFINITIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>")
# add interface include directories
_reflective_rapidjson_set_prop("${TARGET_NAME}" INTERFACE_INCLUDE_DIRECTORIES)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-I$<JOIN:${PROP},$<SEMICOLON>-I>>")
endforeach ()
endif ()
# create a custom command for each input file # create a custom command for each input file
foreach(INPUT_FILE ${ARGS_INPUT_FILES}) foreach (INPUT_FILE ${ARGS_INPUT_FILES})
# determine the output file # determine the output file
get_filename_component(OUTPUT_NAME "${INPUT_FILE}" NAME_WE) get_filename_component(OUTPUT_NAME "${INPUT_FILE}" NAME_WE)
set(OUTPUT_FILE "${ARGS_OUTPUT_DIRECTORY}/${OUTPUT_NAME}.h") set(OUTPUT_FILE "${ARGS_OUTPUT_DIRECTORY}/${OUTPUT_NAME}.h")
@ -139,34 +174,44 @@ function(add_reflection_generator_invocation)
# compose the CLI arguments and actually add the custom command # compose the CLI arguments and actually add the custom command
set(CLI_ARGUMENTS set(CLI_ARGUMENTS
--output-file "${OUTPUT_FILE}" --output-file
--input-file "${INPUT_FILE}" "${OUTPUT_FILE}"
--generators ${ARGS_GENERATORS} --input-file
--clang-opt ${ARGS_CLANG_OPTIONS} "${INPUT_FILE}"
--json-classes ${ARGS_JSON_CLASSES} --generators
) ${ARGS_GENERATORS}
if(ARGS_JSON_VISIBILITY) --clang-opt
${ARGS_CLANG_OPTIONS}
--json-classes
${ARGS_JSON_CLASSES})
if (ARGS_JSON_VISIBILITY)
list(APPEND CLI_ARGUMENTS --json-visibility "${ARGS_JSON_VISIBILITY}") list(APPEND CLI_ARGUMENTS --json-visibility "${ARGS_JSON_VISIBILITY}")
endif() endif ()
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( add_custom_command(
OUTPUT "${OUTPUT_FILE}" OUTPUT "${OUTPUT_FILE}"
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}" COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}" ARGS ${CLI_ARGUMENTS}
ARGS ${CLI_ARGUMENTS}
DEPENDS "${INPUT_FILE}" DEPENDS "${INPUT_FILE}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating reflection code for ${INPUT_FILE}" COMMENT "Generating reflection code for ${INPUT_FILE}"
VERBATIM VERBATIM)
)
# prevent Qt's code generator to be executed on the files generated by this code generator # prevent Qt's code generator to be executed on the files generated by this code generator
set_property(SOURCE "${OUTPUT_FILE}" PROPERTY SKIP_AUTOGEN ON) set_property(SOURCE "${OUTPUT_FILE}" PROPERTY SKIP_AUTOGEN ON)
# append the output file to lists specified via OUTPUT_LISTS # append the output file to lists specified via OUTPUT_LISTS
if(ARGS_OUTPUT_LISTS) if (ARGS_OUTPUT_LISTS)
foreach(OUTPUT_LIST ${ARGS_OUTPUT_LISTS}) foreach (OUTPUT_LIST ${ARGS_OUTPUT_LISTS})
list(APPEND "${OUTPUT_LIST}" "${OUTPUT_FILE}") list(APPEND "${OUTPUT_LIST}" "${OUTPUT_FILE}")
set("${OUTPUT_LIST}" "${${OUTPUT_LIST}}" PARENT_SCOPE) set("${OUTPUT_LIST}"
endforeach() "${${OUTPUT_LIST}}"
endif() PARENT_SCOPE)
endforeach() endforeach ()
endfunction() endif ()
endforeach ()
endfunction ()

View File

@ -10,8 +10,8 @@
#define REFLECTIVE_RAPIDJSON_EXPORT #define REFLECTIVE_RAPIDJSON_EXPORT
#define REFLECTIVE_RAPIDJSON_IMPORT #define REFLECTIVE_RAPIDJSON_IMPORT
#else #else
#define REFLECTIVE_RAPIDJSON_EXPORT LIB_EXPORT #define REFLECTIVE_RAPIDJSON_EXPORT CPP_UTILITIES_GENERIC_LIB_EXPORT
#define REFLECTIVE_RAPIDJSON_IMPORT LIB_IMPORT #define REFLECTIVE_RAPIDJSON_IMPORT CPP_UTILITIES_GENERIC_LIB_IMPORT
#endif #endif
/*! /*!

View File

@ -0,0 +1 @@
../../binary

View File

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

View File

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

View File

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

View File

@ -0,0 +1,78 @@
#ifndef REFLECTIVE_RAPIDJSON_JSON_ERROR_FORMATTING_H
#define REFLECTIVE_RAPIDJSON_JSON_ERROR_FORMATTING_H
/*!
* \file errorformatting.h
* \brief Contains helper functions to format errors when deserializing JSON files.
*/
#include "./errorhandling.h"
#include <c++utilities/conversion/stringbuilder.h>
#include <string_view>
namespace ReflectiveRapidJSON {
inline std::string_view jsonTypeToString(JsonType jsonType)
{
switch (jsonType) {
case ReflectiveRapidJSON::JsonType::Null:
return "null";
case ReflectiveRapidJSON::JsonType::Number:
return "number";
case ReflectiveRapidJSON::JsonType::Bool:
return "bool";
case ReflectiveRapidJSON::JsonType::String:
return "string";
case ReflectiveRapidJSON::JsonType::Array:
return "array";
case ReflectiveRapidJSON::JsonType::Object:
return "object";
default:
return "?";
}
}
inline std::string formatJsonDeserializationError(const JsonDeserializationError &error)
{
using namespace CppUtilities;
std::string_view errorKind;
std::string additionalInfo;
switch (error.kind) {
case JsonDeserializationErrorKind::TypeMismatch:
errorKind = "type mismatch";
additionalInfo = ": expected \"" % jsonTypeToString(error.expectedType) % "\", got \"" % jsonTypeToString(error.actualType) + '\"';
break;
case JsonDeserializationErrorKind::ArraySizeMismatch:
errorKind = "array size mismatch";
break;
case JsonDeserializationErrorKind::ConversionError:
errorKind = "conversion error";
break;
case JsonDeserializationErrorKind::UnexpectedDuplicate:
errorKind = "unexpected duplicate";
break;
case JsonDeserializationErrorKind::InvalidVariantObject:
errorKind = "invalid variant object";
break;
case JsonDeserializationErrorKind::InvalidVariantIndex:
errorKind = "invalid variant index";
break;
default:
errorKind = "semantic error";
}
if (error.record && error.member) {
return errorKind % " within record \"" % error.record % "\" and member \"" % error.member % '\"' + additionalInfo;
} else if (error.record && error.index != JsonDeserializationError::noIndex) {
return errorKind % " within record \"" % error.record % "\" and index \"" % error.index % '\"' + additionalInfo;
} else if (error.record) {
return errorKind % " within record \"" % error.record % '\"' + additionalInfo;
} else {
return errorKind % " in document" + additionalInfo;
}
}
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_JSON_ERROR_FORMATTING_H

View File

@ -6,11 +6,11 @@
* \brief Contains helper for error handling when deserializing JSON files. * \brief Contains helper for error handling when deserializing JSON files.
*/ */
#include <c++utilities/conversion/types.h>
#include <c++utilities/misc/traits.h> #include <c++utilities/misc/traits.h>
#include <rapidjson/rapidjson.h> #include <rapidjson/rapidjson.h>
#include <cstdint>
#include <limits> #include <limits>
#include <list> #include <list>
#include <string> #include <string>
@ -21,18 +21,20 @@ namespace ReflectiveRapidJSON {
/*! /*!
* \brief The JsonDeserializationErrorKind enum specifies which kind of error happend when populating variables from parsing results. * \brief The JsonDeserializationErrorKind enum specifies which kind of error happend when populating variables from parsing results.
*/ */
enum class JsonDeserializationErrorKind : byte { enum class JsonDeserializationErrorKind : std::uint8_t {
TypeMismatch, /**< The expected type does not match the type actually present in the JSON document. */ TypeMismatch, /**< The expected type does not match the type actually present in the JSON document. */
ArraySizeMismatch, /**< The expected array size does not match the actual size of the JSON array. A fixed size is expected when deserializing an std::tuple. */ ArraySizeMismatch, /**< The expected array size does not match the actual size of the JSON array. A fixed size is expected when deserializing an std::tuple. */
ConversionError, /**< The expected type matches the type present in the JSON document, but further conversion of the value failed. */ ConversionError, /**< The expected type matches the type present in the JSON document, but further conversion of the value failed. */
UnexpectedDuplicate, /**< The expected type matches the type present in the JSON document, but the value can not be added to the container because it is already present and duplicates are not allowed. */ UnexpectedDuplicate, /**< The expected type matches the type present in the JSON document, but the value can not be added to the container because it is already present and duplicates are not allowed. */
InvalidVariantObject, /**< The present object is supposed to represent an std::variant but lacks the index or data member. */
InvalidVariantIndex, /**< The present variant index is not a number of outside of the expected range. */
}; };
/*! /*!
* \brief The JsonType enum specifies the JSON data type. * \brief The JsonType enum specifies the JSON data type.
* \remarks This is currently only used for error handling to propagate expected and actual types in case of a mismatch. * \remarks This is currently only used for error handling to propagate expected and actual types in case of a mismatch.
*/ */
enum class JsonType : byte { enum class JsonType : std::uint8_t {
Null, Null,
Number, Number,
Bool, Bool,
@ -108,7 +110,7 @@ struct JsonDeserializationError {
JsonDeserializationError(JsonDeserializationErrorKind kind, JsonType expectedType, JsonType actualType, const char *record, JsonDeserializationError(JsonDeserializationErrorKind kind, JsonType expectedType, JsonType actualType, const char *record,
const char *member = nullptr, std::size_t index = noIndex); const char *member = nullptr, std::size_t index = noIndex);
/// \brief Which kind of error occured. /// \brief Which kind of error occurred.
JsonDeserializationErrorKind kind; JsonDeserializationErrorKind kind;
/// \brief The expected type (might not be relevant for all error kinds). /// \brief The expected type (might not be relevant for all error kinds).
JsonType expectedType; JsonType expectedType;
@ -121,7 +123,7 @@ struct JsonDeserializationError {
/// \brief The index in the array which was being processed when the error was ascertained. /// \brief The index in the array which was being processed when the error was ascertained.
std::size_t index; std::size_t index;
/// \brief Indicates no array was being processed when the error occured. /// \brief Indicates no array was being processed when the error occurred.
static constexpr std::size_t noIndex = std::numeric_limits<std::size_t>::max(); static constexpr std::size_t noIndex = std::numeric_limits<std::size_t>::max();
}; };
@ -153,6 +155,7 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
JsonDeserializationErrors(); JsonDeserializationErrors();
template <typename ExpectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType); template <typename ExpectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
template <RAPIDJSON_NAMESPACE::Type expectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
void reportArraySizeMismatch(); void reportArraySizeMismatch();
void reportConversionError(JsonType jsonType); void reportConversionError(JsonType jsonType);
void reportUnexpectedDuplicate(JsonType jsonType); void reportUnexpectedDuplicate(JsonType jsonType);
@ -164,7 +167,14 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
/// \brief The index in the array which is currently processed. /// \brief The index in the array which is currently processed.
std::size_t currentIndex; std::size_t currentIndex;
/// \brief The list of fatal error types in form of flags. /// \brief The list of fatal error types in form of flags.
enum class ThrowOn : byte { None = 0, TypeMismatch = 0x1, ArraySizeMismatch = 0x2, ConversionError = 0x4, UnexpectedDuplicate = 0x8 } throwOn; enum class ThrowOn : std::uint8_t {
None = 0,
TypeMismatch = 0x1,
ArraySizeMismatch = 0x2,
ConversionError = 0x4,
UnexpectedDuplicate = 0x8,
All = 0xFF,
} throwOn;
private: private:
void throwMaybe(ThrowOn on) const; void throwMaybe(ThrowOn on) const;
@ -186,7 +196,7 @@ inline JsonDeserializationErrors::JsonDeserializationErrors()
*/ */
constexpr JsonDeserializationErrors::ThrowOn operator|(JsonDeserializationErrors::ThrowOn lhs, JsonDeserializationErrors::ThrowOn rhs) constexpr JsonDeserializationErrors::ThrowOn operator|(JsonDeserializationErrors::ThrowOn lhs, JsonDeserializationErrors::ThrowOn rhs)
{ {
return static_cast<JsonDeserializationErrors::ThrowOn>(static_cast<byte>(lhs) | static_cast<byte>(rhs)); return static_cast<JsonDeserializationErrors::ThrowOn>(static_cast<std::uint8_t>(lhs) | static_cast<std::uint8_t>(rhs));
} }
/*! /*!
@ -196,7 +206,7 @@ constexpr JsonDeserializationErrors::ThrowOn operator|(JsonDeserializationErrors
*/ */
inline void JsonDeserializationErrors::throwMaybe(ThrowOn on) const inline void JsonDeserializationErrors::throwMaybe(ThrowOn on) const
{ {
if (static_cast<byte>(throwOn) & static_cast<byte>(on)) { if (static_cast<std::uint8_t>(throwOn) & static_cast<std::uint8_t>(on)) {
throw back(); throw back();
} }
} }
@ -211,6 +221,16 @@ template <typename ExpectedType> inline void JsonDeserializationErrors::reportTy
throwMaybe(ThrowOn::TypeMismatch); 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. * \brief Reports an array size mismatch.
* \todo Allow specifying expected and actual size. * \todo Allow specifying expected and actual size.

View File

@ -20,16 +20,16 @@ namespace JsonReflector {
// define functions to "push" values to a RapidJSON array or object // define functions to "push" values to a RapidJSON array or object
template <> template <>
inline void push<ChronoUtilities::DateTime>( inline void push<CppUtilities::DateTime>(
const ChronoUtilities::DateTime &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) const CppUtilities::DateTime &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
const std::string str(reflectable.toIsoString()); const std::string str(reflectable.toIsoString());
value.SetString(str.data(), rapidJsonSize(str.size()), allocator); value.SetString(str.data(), rapidJsonSize(str.size()), allocator);
} }
template <> template <>
inline void push<ChronoUtilities::TimeSpan>( inline void push<CppUtilities::TimeSpan>(
const ChronoUtilities::TimeSpan &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) const CppUtilities::TimeSpan &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
const std::string str(reflectable.toString()); const std::string str(reflectable.toString());
value.SetString(str.data(), rapidJsonSize(str.size()), allocator); value.SetString(str.data(), rapidJsonSize(str.size()), allocator);
@ -38,14 +38,14 @@ inline void push<ChronoUtilities::TimeSpan>(
// define functions to "pull" values from a RapidJSON array or object // define functions to "pull" values from a RapidJSON array or object
template <> template <>
inline void pull<ChronoUtilities::DateTime>(ChronoUtilities::DateTime &reflectable, inline void pull<CppUtilities::DateTime>(CppUtilities::DateTime &reflectable,
const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors) const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{ {
std::string str; std::string str;
pull(str, value, errors); pull(str, value, errors);
try { try {
reflectable = ChronoUtilities::DateTime::fromIsoStringGmt(str.data()); reflectable = CppUtilities::DateTime::fromIsoStringGmt(str.data());
} catch (const ConversionUtilities::ConversionException &) { } catch (const CppUtilities::ConversionException &) {
if (errors) { if (errors) {
errors->reportConversionError(JsonType::String); errors->reportConversionError(JsonType::String);
} }
@ -53,14 +53,14 @@ inline void pull<ChronoUtilities::DateTime>(ChronoUtilities::DateTime &reflectab
} }
template <> template <>
inline void pull<ChronoUtilities::TimeSpan>(ChronoUtilities::TimeSpan &reflectable, inline void pull<CppUtilities::TimeSpan>(CppUtilities::TimeSpan &reflectable,
const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors) const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{ {
std::string str; std::string str;
pull(str, value, errors); pull(str, value, errors);
try { try {
reflectable = ChronoUtilities::TimeSpan::fromString(str.data()); reflectable = CppUtilities::TimeSpan::fromString(str.data());
} catch (const ConversionUtilities::ConversionException &) { } catch (const CppUtilities::ConversionException &) {
if (errors) { if (errors) {
errors->reportConversionError(JsonType::String); errors->reportConversionError(JsonType::String);
} }

View File

@ -9,7 +9,7 @@
#include "../traits.h" #include "../traits.h"
#include <c++utilities/conversion/types.h> #include <c++utilities/application/global.h>
#include <rapidjson/document.h> #include <rapidjson/document.h>
#include <rapidjson/rapidjson.h> #include <rapidjson/rapidjson.h>
@ -17,9 +17,16 @@
#include <rapidjson/writer.h> #include <rapidjson/writer.h>
#include <limits> #include <limits>
#include <map>
#include <memory> #include <memory>
#include <optional>
#include <set>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include "./errorhandling.h" #include "./errorhandling.h"
@ -76,8 +83,8 @@ 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 // define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes
template <typename Type> template <typename Type>
using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>, using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>,
Traits::IsSpecializationOf<Type, std::tuple>, Traits::IsIteratable<Type>, Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializingAnyOf<Type, std::tuple, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializationOf<Type, std::shared_ptr>, Traits::IsSpecializationOf<Type, std::weak_ptr>>; Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>; template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// define trait to check for custom structs/classes which are JSON serializable // define trait to check for custom structs/classes which are JSON serializable
@ -141,37 +148,58 @@ inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAP
/*! /*!
* \brief Pushes the specified integer/float/boolean to the specified value. * \brief Pushes the specified integer/float/boolean to the specified value.
*/ */
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr> 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>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
value.Set(reflectable, 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. * \brief Pushes the specified enumeration item to the specified value.
*/ */
template <typename Type, Traits::EnableIfAny<std::is_enum<Type>> * = nullptr> template <typename Type, Traits::EnableIfAny<std::is_enum<Type>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
value.Set(static_cast<Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, uint64, int64>>(reflectable), allocator); value.Set(static_cast<Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, std::uint64_t, std::int64_t>>(reflectable),
allocator);
} }
/*! /*!
* \brief Pushes the specified C-string to the specified value. * \brief Pushes the specified C-string to the specified value.
*/ */
template <typename Type, Traits::EnableIf<std::is_same<Type, const char *>> * = nullptr> template <typename Type, Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
value.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator); if (reflectable) {
value.SetString(reflectable, allocator);
} else {
value.SetNull();
}
} }
/*! /*!
* \brief Pushes the specified constant C-string to the specified value. * \brief Pushes the specified std::string_view to the specified value.
*/ */
template <typename Type, Traits::EnableIf<std::is_same<Type, const char *const &>> * = nullptr> template <typename Type, Traits::EnableIf<std::is_same<Type, std::string_view>> * = nullptr>
inline void push(const char *const &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
value.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator); if (reflectable.data()) {
value.SetString(reflectable.data(), rapidJsonSize(reflectable.size()), allocator);
} else {
value.SetNull();
}
} }
/*! /*!
@ -180,7 +208,7 @@ inline void push(const char *const &reflectable, RAPIDJSON_NAMESPACE::Value &val
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr> template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
value.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), allocator); value.SetString(reflectable.data(), rapidJsonSize(reflectable.size()), allocator);
} }
/*! /*!
@ -191,7 +219,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
{ {
value.SetArray(); value.SetArray();
RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray()); RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
array.Reserve(reflectable.size(), allocator); array.Reserve(rapidJsonSize(reflectable.size()), allocator);
for (const auto &item : reflectable) { for (const auto &item : reflectable) {
push(item, array, allocator); push(item, array, allocator);
} }
@ -211,10 +239,9 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
} }
/*! /*!
* \brief Pushes the specified map (std::map, std::unordered_map) or multimap (std::multimap, std::unordered_multimap) to the * \brief Pushes the specified map (std::map, std::unordered_map) to the specified value.
* specified value.
*/ */
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
value.SetObject(); value.SetObject();
@ -224,6 +251,27 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
} }
} }
/*!
* \brief Pushes the specified multimap (std::multimap, std::unordered_multimap) to the specified value.
*/
template <typename Type, Traits::EnableIfAny<IsMultiMapOrHash<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.SetObject();
for (const auto &item : reflectable) {
const auto memberName = RAPIDJSON_NAMESPACE::Value::StringRefType(item.first.data(), rapidJsonSize(item.first.size()));
const auto existingMember = value.FindMember(memberName);
const auto arrayAlreadyExists
= existingMember != value.MemberEnd() && existingMember->value.GetType() == RAPIDJSON_NAMESPACE::Type::kArrayType;
auto newArrayValue = RAPIDJSON_NAMESPACE::Value{ RAPIDJSON_NAMESPACE::kArrayType };
RAPIDJSON_NAMESPACE::Value::Array array = arrayAlreadyExists ? existingMember->value.GetArray() : newArrayValue.GetArray();
push(item.second, array, allocator);
if (!arrayAlreadyExists) {
value.AddMember(memberName, newArrayValue, allocator);
}
}
}
namespace Detail { namespace Detail {
/*! /*!
@ -258,11 +306,23 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
} }
/*! /*!
* \brief Pushes the specified unique_ptr, shared_ptr or weak_ptr to the specified value. * \brief Pushes the specified pair to the specified value.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.SetArray();
RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
array.Reserve(2, allocator);
push(reflectable.first, array, allocator);
push(reflectable.second, array, allocator);
}
/*!
* \brief Pushes the specified unique_ptr, shared_ptr, weak_ptr or optional to the specified value.
*/ */
template <typename Type, template <typename Type,
Traits::EnableIfAny<Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializationOf<Type, std::shared_ptr>, Traits::EnableIfAny<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>> * = nullptr>
Traits::IsSpecializationOf<Type, std::weak_ptr>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
if (!reflectable) { if (!reflectable) {
@ -272,6 +332,35 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
push(*reflectable, value, allocator); push(*reflectable, value, allocator);
} }
/*!
* \brief Pushes the specified variant to the specified value.
*/
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
if (reflectable.valueless_by_exception()) {
value.SetNull();
return;
}
RAPIDJSON_NAMESPACE::Value index, data;
index.SetUint64(reflectable.index());
std::visit(
[&data, &allocator](const auto &reflectableOfActualType) {
if constexpr (!std::is_same_v<std::decay_t<decltype(reflectableOfActualType)>, std::monostate>) {
push(reflectableOfActualType, data, allocator);
} else {
CPP_UTILITIES_UNUSED(data)
CPP_UTILITIES_UNUSED(allocator)
}
},
reflectable);
value.SetObject();
value.AddMember("index", index, allocator);
value.AddMember("data", data, allocator);
}
/*! /*!
* \brief Pushes the specified \a reflectable which has a custom type to the specified array. * \brief Pushes the specified \a reflectable which has a custom type to the specified array.
*/ */
@ -384,6 +473,12 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> * = nullptr> template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors); void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the specified \a reflectable which is a pair from the specified value which is checked to contain an array.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*! /*!
* \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null. * \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null.
*/ */
@ -396,6 +491,18 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr> 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); 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.
*/
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*! /*!
* \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type. * \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type.
*/ */
@ -422,7 +529,8 @@ 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. * \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type.
*/ */
template <typename Type, template <typename Type,
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>> * = nullptr> 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>
inline void pull( inline void pull(
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors) Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{ {
@ -435,6 +543,20 @@ inline void pull(
reflectable = value.Is<Type>() ? value.Get<Type>() : static_cast<Type>(value.GetDouble()); 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. * \brief Pulls the boolean from the specified value which is supposed and checked to contain the right type.
*/ */
@ -459,7 +581,7 @@ template <typename Type, Traits::EnableIfAny<std::is_enum<Type>> * = nullptr>
inline void pull( inline void pull(
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors) Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{ {
using ExpectedType = Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, uint64, int64>; using ExpectedType = Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, std::uint64_t, std::int64_t>;
if (!value.Is<ExpectedType>()) { if (!value.Is<ExpectedType>()) {
if (errors) { if (errors) {
errors->reportTypeMismatch<ExpectedType>(value.GetType()); errors->reportTypeMismatch<ExpectedType>(value.GetType());
@ -489,7 +611,8 @@ inline void pull(
* \brief Checks whether the specified value contains a string. * \brief Checks whether the specified value contains a string.
* \remarks Does not actually store the value since the ownership would not be clear (see README.md). * \remarks Does not actually store the value since the ownership would not be clear (see README.md).
*/ */
template <typename Type, Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>> * = nullptr> template <typename Type,
Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>, std::is_same<Type, std::string_view>> * = nullptr>
inline void pull(Type &, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors) inline void pull(Type &, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{ {
if (!value.IsString()) { if (!value.IsString()) {
@ -578,7 +701,7 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
++index; ++index;
typename Type::value_type itemObj; typename Type::value_type itemObj;
pull(itemObj, item, errors); pull(itemObj, item, errors);
reflectable.emplace(move(itemObj)); reflectable.emplace(std::move(itemObj));
} }
// clear error context // clear error context
@ -606,7 +729,7 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
++index; ++index;
typename Type::value_type itemObj; typename Type::value_type itemObj;
pull(itemObj, item, errors); pull(itemObj, item, errors);
if (!reflectable.emplace(move(itemObj)).second) { if (!reflectable.emplace(std::move(itemObj)).second) {
errors->reportUnexpectedDuplicate(JsonType::Array); errors->reportUnexpectedDuplicate(JsonType::Array);
} }
} }
@ -649,8 +772,16 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
} }
auto obj = value.GetObject(); auto obj = value.GetObject();
for (auto i = obj.MemberBegin(), end = obj.MemberEnd(); i != end; ++i) { for (auto i = obj.MemberBegin(), end = obj.MemberEnd(); i != end; ++i) {
auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type())); if (i->value.GetType() != RAPIDJSON_NAMESPACE::kArrayType) {
pull(insertedIterator->second, i->value, errors); auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
pull(insertedIterator->second, i->value, errors);
continue;
}
const auto array = i->value.GetArray();
for (const auto &arrayValue : array) {
auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
pull(insertedIterator->second, arrayValue, errors);
}
} }
} }
@ -688,7 +819,7 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
} }
return; return;
} }
auto array = value.GetArray(); const auto array = value.GetArray();
if (array.Size() != std::tuple_size<Type>::value) { if (array.Size() != std::tuple_size<Type>::value) {
if (errors) { if (errors) {
// FIXME: report expected and actual size // FIXME: report expected and actual size
@ -699,6 +830,30 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
Detail::TuplePullHelper<Type, std::tuple_size<Type>::value>::pull(reflectable, array, errors); Detail::TuplePullHelper<Type, std::tuple_size<Type>::value>::pull(reflectable, array, errors);
} }
/*!
* \brief Pulls the specified \a reflectable which is a pair from the specified value which is checked to contain an array.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsArray()) {
if (errors) {
errors->reportTypeMismatch<Type>(value.GetType());
}
return;
}
const auto array = value.GetArray();
if (array.Size() != 2) {
if (errors) {
// FIXME: report expected and actual size
errors->reportArraySizeMismatch();
}
return;
}
pull(reflectable.first, array[0], errors);
pull(reflectable.second, array[1], errors);
}
/*! /*!
* \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null. * \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null.
*/ */
@ -727,6 +882,80 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
pull(*reflectable, value, errors); 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>
void assignVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant,
const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if constexpr (compiletimeIndex < std::variant_size_v<Variant>) {
if (compiletimeIndex == runtimeIndex) {
if constexpr (std::is_same_v<std::variant_alternative_t<compiletimeIndex, Variant>, std::monostate>) {
variant = std::monostate{};
} else {
pull(variant.template emplace<compiletimeIndex>(), value, errors);
}
} else {
assignVariantValueByRuntimeIndex<Variant, compiletimeIndex + 1>(runtimeIndex, variant, value, errors);
}
} else {
if (errors) {
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, JsonType::Number, errors->currentRecord,
errors->currentMember, errors->currentIndex);
}
}
}
} // namespace Detail
/// \endcond
/*!
* \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<IsVariant<Type>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsObject()) {
if (errors) {
errors->reportTypeMismatch<Type>(value.GetType());
}
return;
}
auto obj = value.GetObject();
auto indexIterator = obj.FindMember("index");
auto dataIterator = obj.FindMember("data");
if (indexIterator == obj.MemberEnd() || dataIterator == obj.MemberEnd()) {
if (errors) {
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantObject, JsonType::Object, JsonType::Object, errors->currentRecord,
errors->currentMember, errors->currentIndex);
}
return;
}
const auto &indexValue = indexIterator->value;
if (!indexValue.IsUint64()) {
if (errors) {
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, jsonType(indexValue.GetType()),
errors->currentRecord, errors->currentMember, errors->currentIndex);
}
return;
}
Detail::assignVariantValueByRuntimeIndex(indexValue.GetUint64(), reflectable, dataIterator->value, errors);
}
/*! /*!
* \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type. * \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type.
*/ */
@ -753,7 +982,7 @@ inline void pull(Type &reflectable, const char *name, const rapidjson::GenericVa
} }
// set error context for current member // set error context for current member
const char *previousMember; const char *previousMember = nullptr;
if (errors) { if (errors) {
previousMember = errors->currentMember; previousMember = errors->currentMember;
errors->currentMember = name; errors->currentMember = name;
@ -789,54 +1018,76 @@ void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_N
* \brief Serializes the specified \a reflectable which has a custom type or can be mapped to and object. * \brief Serializes the specified \a reflectable which has a custom type or can be mapped to and object.
*/ */
template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr>
RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable) RAPIDJSON_NAMESPACE::Document toJsonDocument(const Type &reflectable)
{ {
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kObjectType); RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kObjectType);
RAPIDJSON_NAMESPACE::Document::Object object(document.GetObject()); push(reflectable, document, document.GetAllocator());
push(reflectable, object, document.GetAllocator()); return document;
return serializeJsonDocToString(document);
} }
/*! /*!
* \brief Serializes the specified \a reflectable which is an integer, float or boolean. * \brief Serializes the specified \a reflectable which is an integer, float or boolean.
*/ */
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr> template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
RAPIDJSON_NAMESPACE::StringBuffer toJson(Type reflectable) RAPIDJSON_NAMESPACE::Document toJsonDocument(Type reflectable)
{ {
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kNumberType); RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kNumberType);
document.Set(reflectable, document.GetAllocator()); document.Set(reflectable, document.GetAllocator());
return serializeJsonDocToString(document); return document;
} }
/*! /*!
* \brief Serializes the specified \a reflectable which is an std::string. * \brief Serializes the specified \a reflectable which is an std::string.
*/ */
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr> template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
RAPIDJSON_NAMESPACE::StringBuffer toJson(const std::string &reflectable) RAPIDJSON_NAMESPACE::Document toJsonDocument(const std::string &reflectable)
{ {
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType); RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator()); document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator());
return serializeJsonDocToString(document); return document;
} }
/*! /*!
* \brief Serializes the specified \a reflectable which is a C-string. * \brief Serializes the specified \a reflectable which is a C-string.
*/ */
template <typename Type, Traits::EnableIf<std::is_same<Type, const char *>> * = nullptr> template <typename Type, Traits::EnableIf<std::is_same<Type, const char *>> * = nullptr>
RAPIDJSON_NAMESPACE::StringBuffer toJson(const char *reflectable) RAPIDJSON_NAMESPACE::Document toJsonDocument(const char *reflectable)
{ {
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType); RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), document.GetAllocator()); document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), document.GetAllocator());
return serializeJsonDocToString(document); return document;
}
/*!
* \brief Serializes the specified \a reflectable which is an std::string_view.
*/
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string_view>> * = nullptr>
RAPIDJSON_NAMESPACE::Document toJsonDocument(std::string_view reflectable)
{
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator());
return document;
} }
/*! /*!
* \brief Serializes the specified \a reflectable which can be mapped to an array. * \brief Serializes the specified \a reflectable which can be mapped to an array.
*/ */
template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr> RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable) template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr> RAPIDJSON_NAMESPACE::Document toJsonDocument(const Type &reflectable)
{ {
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kArrayType); RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kArrayType);
push(reflectable, document, document.GetAllocator()); push(reflectable, document, document.GetAllocator());
return document;
}
/*!
* \brief Serializes the specified \a reflectable.
*/
template <typename Type,
Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>, std::is_integral<Type>, std::is_floating_point<Type>,
Traits::IsString<Type>, IsArray<Type>> * = nullptr>
RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable)
{
auto document(toJsonDocument(reflectable));
return serializeJsonDocToString(document); return serializeJsonDocToString(document);
} }

View File

@ -3,7 +3,7 @@
/*! /*!
* \file serializable.h * \file serializable.h
* \brief Contains only the definiation of the JsonSerializable template class which makes the reflection * \brief Contains only the definition of the JsonSerializable template class which makes the reflection
* accessible. The actual implementation is found in jsonreflector.h and generated files. * accessible. The actual implementation is found in jsonreflector.h and generated files.
*/ */
@ -25,11 +25,16 @@ template <typename Type> struct JsonSerializable {
// high-level API // high-level API
RAPIDJSON_NAMESPACE::StringBuffer toJson() const; RAPIDJSON_NAMESPACE::StringBuffer toJson() const;
RAPIDJSON_NAMESPACE::Document toJsonDocument() const;
static Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr); static Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr);
static Type fromJson(const char *json, JsonDeserializationErrors *errors = nullptr); static Type fromJson(const char *json, JsonDeserializationErrors *errors = nullptr);
static Type fromJson(const std::string &json, JsonDeserializationErrors *errors = nullptr); static Type fromJson(const std::string &json, JsonDeserializationErrors *errors = nullptr);
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::JsonSerializable"; static constexpr const char *qualifiedName = "ReflectiveRapidJSON::JsonSerializable";
#if __cplusplus > 201707L
bool operator==(const JsonSerializable<Type> &) const = default;
#endif
}; };
/*! /*!
@ -49,7 +54,7 @@ template <typename Type> void JsonSerializable<Type>::push(RAPIDJSON_NAMESPACE::
} }
/*! /*!
* \brief Converts the object to its JSON representation. * \brief Converts the object to its JSON representation (rapidjson::StringBuffer).
* \remarks To obtain a string from the returned buffer, just use its GetString() method. * \remarks To obtain a string from the returned buffer, just use its GetString() method.
*/ */
template <typename Type> RAPIDJSON_NAMESPACE::StringBuffer JsonSerializable<Type>::toJson() const template <typename Type> RAPIDJSON_NAMESPACE::StringBuffer JsonSerializable<Type>::toJson() const
@ -57,6 +62,15 @@ template <typename Type> RAPIDJSON_NAMESPACE::StringBuffer JsonSerializable<Type
return JsonReflector::toJson<Type>(static_cast<const Type &>(*this)); return JsonReflector::toJson<Type>(static_cast<const Type &>(*this));
} }
/*!
* \brief Converts the object to its JSON representation (rapidjson::Document).
* \remarks To obtain a string from the returned buffer, just use its GetString() method.
*/
template <typename Type> RAPIDJSON_NAMESPACE::Document JsonSerializable<Type>::toJsonDocument() const
{
return JsonReflector::toJsonDocument<Type>(static_cast<const Type &>(*this));
}
/*! /*!
* \brief Constructs a new object from the specified JSON. * \brief Constructs a new object from the specified JSON.
*/ */
@ -106,8 +120,7 @@ const JsonSerializable<Type> &as(const Type &serializable)
* Find out whether this is a compiler bug or a correct error message. * Find out whether this is a compiler bug or a correct error message.
*/ */
#define REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(T) \ #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. * \def The REFLECTIVE_RAPIDJSON_PUSH_PRIVATE_MEMBERS macro enables serialization of private members.

View File

@ -6,7 +6,7 @@
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
#include <c++utilities/tests/testutils.h> #include <c++utilities/tests/testutils.h>
using TestUtilities::operator<<; // must be visible prior to the call site using CppUtilities::operator<<; // must be visible prior to the call site
#include <cppunit/TestFixture.h> #include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/HelperMacros.h>
@ -21,10 +21,8 @@ using TestUtilities::operator<<; // must be visible prior to the call site
using namespace std; using namespace std;
using namespace CPPUNIT_NS; using namespace CPPUNIT_NS;
using namespace RAPIDJSON_NAMESPACE; using namespace RAPIDJSON_NAMESPACE;
using namespace IoUtilities; using namespace CppUtilities;
using namespace ConversionUtilities; using namespace CppUtilities::Literals;
using namespace TestUtilities;
using namespace TestUtilities::Literals;
using namespace ReflectiveRapidJSON; using namespace ReflectiveRapidJSON;
/// \cond /// \cond
@ -50,8 +48,8 @@ class BinaryReflectorBoostHanaTests : public TestFixture {
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
void setUp(); void setUp() override;
void tearDown(); void tearDown() override;
void testSerializingAndDeserializing(); void testSerializingAndDeserializing();

View File

@ -7,11 +7,13 @@
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
#include <c++utilities/tests/testutils.h> #include <c++utilities/tests/testutils.h>
using TestUtilities::operator<<; // must be visible prior to the call site using CppUtilities::operator<<; // must be visible prior to the call site
#include <cppunit/TestFixture.h> #include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/HelperMacros.h>
#include <cstdint>
#include <iostream> #include <iostream>
#include <limits>
#include <map> #include <map>
#include <sstream> #include <sstream>
#include <string> #include <string>
@ -21,23 +23,20 @@ using TestUtilities::operator<<; // must be visible prior to the call site
using namespace std; using namespace std;
using namespace CPPUNIT_NS; using namespace CPPUNIT_NS;
using namespace IoUtilities; using namespace CppUtilities;
using namespace ChronoUtilities; using namespace CppUtilities::Literals;
using namespace ConversionUtilities;
using namespace TestUtilities;
using namespace TestUtilities::Literals;
using namespace ReflectiveRapidJSON; using namespace ReflectiveRapidJSON;
/// \cond /// \cond
// define some enums and structs for testing serialization // define some enums and structs for testing serialization
enum SomeEnum { enum SomeEnumBinary {
SomeEnumItem1, SomeEnumItem1,
SomeEnumItem2, SomeEnumItem2,
SomeEnumItem3, SomeEnumItem3,
}; };
enum class SomeEnumClass : uint16 { enum class SomeEnumClassBinary : std::uint16_t {
Item1, Item1,
Item2, Item2,
Item3, Item3,
@ -55,8 +54,8 @@ struct TestObjectBinary : public BinarySerializable<TestObjectBinary> {
multiset<string> someMultiset; multiset<string> someMultiset;
unordered_set<string> someUnorderedSet; unordered_set<string> someUnorderedSet;
unordered_multiset<string> someUnorderedMultiset; unordered_multiset<string> someUnorderedMultiset;
SomeEnum someEnum; SomeEnumBinary someEnum;
SomeEnumClass someEnumClass; SomeEnumClassBinary someEnumClass;
TimeSpan timeSpan; TimeSpan timeSpan;
DateTime dateTime; DateTime dateTime;
}; };
@ -66,12 +65,19 @@ struct NestingArrayBinary : public BinarySerializable<NestingArrayBinary> {
vector<TestObjectBinary> testObjects; vector<TestObjectBinary> testObjects;
}; };
struct ObjectWithVariantsBinary : public BinarySerializable<ObjectWithVariantsBinary> {
variant<int, string, monostate> someVariant;
variant<string, float> anotherVariant;
variant<string, int> yetAnotherVariant;
};
// pretend serialization code for structs has been generated // pretend serialization code for structs has been generated
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
namespace BinaryReflector { 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.number);
deserializer.read(customType.number2); deserializer.read(customType.number2);
deserializer.read(customType.numbers); deserializer.read(customType.numbers);
@ -87,10 +93,12 @@ template <> void readCustomType<TestObjectBinary>(BinaryDeserializer &deserializ
deserializer.read(customType.someEnumClass); deserializer.read(customType.someEnumClass);
deserializer.read(customType.timeSpan); deserializer.read(customType.timeSpan);
deserializer.read(customType.dateTime); 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.number);
serializer.write(customType.number2); serializer.write(customType.number2);
serializer.write(customType.numbers); serializer.write(customType.numbers);
@ -108,18 +116,40 @@ template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer,
serializer.write(customType.dateTime); 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.name);
deserializer.read(customType.testObjects); 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.name);
serializer.write(customType.testObjects); serializer.write(customType.testObjects);
} }
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, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(customType.someVariant);
serializer.write(customType.anotherVariant);
serializer.write(customType.yetAnotherVariant);
}
} // namespace BinaryReflector } // namespace BinaryReflector
// namespace BinaryReflector // namespace BinaryReflector
@ -137,19 +167,28 @@ class BinaryReflectorTests : public TestFixture {
CPPUNIT_TEST(testDeserializeSimpleStruct); CPPUNIT_TEST(testDeserializeSimpleStruct);
CPPUNIT_TEST(testSerializeNestedStruct); CPPUNIT_TEST(testSerializeNestedStruct);
CPPUNIT_TEST(testDeserializeNestedStruct); CPPUNIT_TEST(testDeserializeNestedStruct);
CPPUNIT_TEST(testSmallSharedPointer);
CPPUNIT_TEST(testBigSharedPointer);
CPPUNIT_TEST(testVariant);
CPPUNIT_TEST(testOptional);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
BinaryReflectorTests(); BinaryReflectorTests();
void setUp(); void setUp() override;
void tearDown(); void tearDown() override;
void testSerializeSimpleStruct(); void testSerializeSimpleStruct();
void testDeserializeSimpleStruct(); void testDeserializeSimpleStruct();
void testSerializeNestedStruct(); void testSerializeNestedStruct();
void testDeserializeNestedStruct(); void testDeserializeNestedStruct();
void assertTestObject(const TestObjectBinary &deserialized); void assertTestObject(const TestObjectBinary &deserialized);
void testSharedPointer(std::uintptr_t fakePointer);
void testSmallSharedPointer();
void testBigSharedPointer();
void testVariant();
void testOptional();
private: private:
vector<unsigned char> m_buffer; vector<unsigned char> m_buffer;
@ -220,7 +259,7 @@ void BinaryReflectorTests::setUp()
m_testObj.someSet = { "1", "2", "3", "2" }; m_testObj.someSet = { "1", "2", "3", "2" };
m_testObj.someMultiset = { "1", "2", "3", "2" }; m_testObj.someMultiset = { "1", "2", "3", "2" };
m_testObj.someEnum = SomeEnumItem2; m_testObj.someEnum = SomeEnumItem2;
m_testObj.someEnumClass = SomeEnumClass::Item3; m_testObj.someEnumClass = SomeEnumClassBinary::Item3;
m_testObj.timeSpan = TimeSpan(0xABCD); m_testObj.timeSpan = TimeSpan(0xABCD);
m_testObj.dateTime = DateTime(0xEFAB); m_testObj.dateTime = DateTime(0xEFAB);
m_nestedTestObj.name = "struct with nesting"; m_nestedTestObj.name = "struct with nesting";
@ -234,13 +273,44 @@ 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() void BinaryReflectorTests::testSerializeSimpleStruct()
{ {
stringstream stream(ios_base::out | ios_base::binary); stringstream stream(ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit); stream.exceptions(ios_base::failbit | ios_base::badbit);
m_buffer.resize(m_expectedTestObj.size()); m_buffer.resize(m_expectedTestObj.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_buffer.data()), static_cast<streamsize>(m_buffer.size())); setBuffer(stream, m_buffer.data(), m_buffer.size());
m_testObj.toBinary(stream); m_testObj.toBinary(stream);
readBuffer(stream, m_buffer.data(), m_buffer.size());
CPPUNIT_ASSERT_EQUAL(m_expectedTestObj, m_buffer); CPPUNIT_ASSERT_EQUAL(m_expectedTestObj, m_buffer);
} }
@ -249,7 +319,7 @@ void BinaryReflectorTests::testDeserializeSimpleStruct()
{ {
stringstream stream(ios_base::in | ios_base::binary); stringstream stream(ios_base::in | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit); stream.exceptions(ios_base::failbit | ios_base::badbit);
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_expectedTestObj.data()), static_cast<streamsize>(m_expectedTestObj.size())); writeBuffer(stream, m_expectedTestObj.data(), m_expectedTestObj.size());
const auto deserialized(TestObjectBinary::fromBinary(stream)); const auto deserialized(TestObjectBinary::fromBinary(stream));
assertTestObject(deserialized); assertTestObject(deserialized);
} }
@ -259,8 +329,9 @@ void BinaryReflectorTests::testSerializeNestedStruct()
stringstream stream(ios_base::out | ios_base::binary); stringstream stream(ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit); stream.exceptions(ios_base::failbit | ios_base::badbit);
m_buffer.resize(m_expectedNestedTestObj.size()); m_buffer.resize(m_expectedNestedTestObj.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_buffer.data()), static_cast<streamsize>(m_buffer.size())); setBuffer(stream, m_buffer.data(), m_buffer.size());
m_nestedTestObj.toBinary(stream); m_nestedTestObj.toBinary(stream);
readBuffer(stream, m_buffer.data(), m_buffer.size());
CPPUNIT_ASSERT_EQUAL(m_expectedNestedTestObj, m_buffer); CPPUNIT_ASSERT_EQUAL(m_expectedNestedTestObj, m_buffer);
} }
@ -269,7 +340,7 @@ void BinaryReflectorTests::testDeserializeNestedStruct()
{ {
stringstream stream(ios_base::in | ios_base::binary); stringstream stream(ios_base::in | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit); stream.exceptions(ios_base::failbit | ios_base::badbit);
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_expectedNestedTestObj.data()), static_cast<streamsize>(m_expectedNestedTestObj.size())); writeBuffer(stream, m_expectedNestedTestObj.data(), m_expectedNestedTestObj.size());
const auto deserialized(NestingArrayBinary::fromBinary(stream)); const auto deserialized(NestingArrayBinary::fromBinary(stream));
CPPUNIT_ASSERT_EQUAL(m_nestedTestObj.name, deserialized.name); CPPUNIT_ASSERT_EQUAL(m_nestedTestObj.name, deserialized.name);
@ -292,3 +363,90 @@ void BinaryReflectorTests::assertTestObject(const TestObjectBinary &deserialized
CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedSet, deserialized.someUnorderedSet); CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedSet, deserialized.someUnorderedSet);
CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedMultiset, deserialized.someUnorderedMultiset); CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedMultiset, deserialized.someUnorderedMultiset);
} }
void BinaryReflectorTests::testSharedPointer(uintptr_t fakePointer)
{
// create a shared pointer for the fake pointer ensuring that it is not actually deleted
shared_ptr<int> sharedPointer(reinterpret_cast<int *>(fakePointer), [](int *) {});
// setup stream
stringstream stream(ios_base::in | ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
// serialize the shared pointer assuming its contents have been written before (to prevent actually dereferencing it)
BinaryReflector::BinarySerializer serializer(&stream);
serializer.m_pointer[fakePointer] = true;
serializer.write(sharedPointer);
// deserialize the shared pointer assuming it has already been read and the type does not match
BinaryReflector::BinaryDeserializer deserializer(&stream);
shared_ptr<int> readPtr;
deserializer.m_pointer[fakePointer] = "foo";
CPPUNIT_ASSERT_THROW(deserializer.read(readPtr), CppUtilities::ConversionException);
CPPUNIT_ASSERT(readPtr == nullptr);
// deserialize the shared pointer assuming it has already been read and the type matches
stream.seekg(0);
deserializer.m_pointer[fakePointer] = make_shared<int>(42);
deserializer.read(readPtr);
CPPUNIT_ASSERT(readPtr != nullptr);
CPPUNIT_ASSERT_EQUAL(42, *readPtr);
}
void BinaryReflectorTests::testSmallSharedPointer()
{
testSharedPointer(std::numeric_limits<std::uintptr_t>::min() + 1);
}
void BinaryReflectorTests::testBigSharedPointer()
{
testSharedPointer(std::numeric_limits<std::uintptr_t>::max());
}
void BinaryReflectorTests::testVariant()
{
// create test object
ObjectWithVariantsBinary variants;
variants.someVariant = std::monostate{};
variants.anotherVariant = "foo";
variants.yetAnotherVariant = 42;
// serialize test object
stringstream stream(ios_base::in | ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
variants.toBinary(stream);
// deserialize the object again
const auto deserializedVariants = ObjectWithVariantsBinary::fromBinary(stream);
CPPUNIT_ASSERT_EQUAL(2_st, deserializedVariants.someVariant.index());
CPPUNIT_ASSERT_EQUAL(0_st, deserializedVariants.anotherVariant.index());
CPPUNIT_ASSERT_EQUAL(1_st, deserializedVariants.yetAnotherVariant.index());
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

@ -1 +0,0 @@
#include <c++utilities/tests/cppunit.h>

View File

@ -6,7 +6,7 @@
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
#include <c++utilities/tests/testutils.h> #include <c++utilities/tests/testutils.h>
using TestUtilities::operator<<; // must be visible prior to the call site using CppUtilities::operator<<; // must be visible prior to the call site
#include <cppunit/TestFixture.h> #include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/HelperMacros.h>
@ -21,10 +21,8 @@ using TestUtilities::operator<<; // must be visible prior to the call site
using namespace std; using namespace std;
using namespace CPPUNIT_NS; using namespace CPPUNIT_NS;
using namespace RAPIDJSON_NAMESPACE; using namespace RAPIDJSON_NAMESPACE;
using namespace IoUtilities; using namespace CppUtilities;
using namespace ConversionUtilities; using namespace CppUtilities::Literals;
using namespace TestUtilities;
using namespace TestUtilities::Literals;
using namespace ReflectiveRapidJSON; using namespace ReflectiveRapidJSON;
/// \cond /// \cond
@ -58,8 +56,8 @@ class JsonReflectorBoostHanaTests : public TestFixture {
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
void setUp(); void setUp() override;
void tearDown(); void tearDown() override;
void testSerializePrimitives(); void testSerializePrimitives();
void testSerializeSimpleObjects(); void testSerializeSimpleObjects();
@ -162,11 +160,11 @@ void JsonReflectorBoostHanaTests::testDeserializeNestedObjects()
CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size()); CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size());
CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number); CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number);
CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number); CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number);
for (const TestObjectHana &testObj : testObjects) { for (const TestObjectHana &nestedTestObj : testObjects) {
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2); CPPUNIT_ASSERT_EQUAL(3.141592653589793, nestedTestObj.number2);
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers); CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), nestedTestObj.numbers);
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text); CPPUNIT_ASSERT_EQUAL("test"s, nestedTestObj.text);
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean); CPPUNIT_ASSERT_EQUAL(false, nestedTestObj.boolean);
} }
} }

View File

@ -17,8 +17,8 @@
using namespace std; using namespace std;
using namespace CPPUNIT_NS; using namespace CPPUNIT_NS;
using namespace RAPIDJSON_NAMESPACE; using namespace RAPIDJSON_NAMESPACE;
using namespace ChronoUtilities; using namespace CppUtilities;
using namespace TestUtilities::Literals; using namespace CppUtilities::Literals;
using namespace ReflectiveRapidJSON; using namespace ReflectiveRapidJSON;
/*! /*!

View File

@ -6,7 +6,7 @@
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
#include <c++utilities/tests/testutils.h> #include <c++utilities/tests/testutils.h>
using TestUtilities::operator<<; // must be visible prior to the call site using CppUtilities::operator<<; // must be visible prior to the call site
#include <cppunit/TestFixture.h> #include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/HelperMacros.h>
@ -24,10 +24,8 @@ using TestUtilities::operator<<; // must be visible prior to the call site
using namespace std; using namespace std;
using namespace CPPUNIT_NS; using namespace CPPUNIT_NS;
using namespace RAPIDJSON_NAMESPACE; using namespace RAPIDJSON_NAMESPACE;
using namespace IoUtilities; using namespace CppUtilities;
using namespace ConversionUtilities; using namespace CppUtilities::Literals;
using namespace TestUtilities;
using namespace TestUtilities::Literals;
using namespace ReflectiveRapidJSON; using namespace ReflectiveRapidJSON;
/// \cond /// \cond
@ -54,10 +52,15 @@ struct TestObject : public JsonSerializable<TestObject> {
bool boolean; bool boolean;
map<string, int> someMap; map<string, int> someMap;
unordered_map<string, bool> someHash; unordered_map<string, bool> someHash;
multimap<string, int> someMultimap;
unordered_multimap<string, int> someMultiHash;
set<string> someSet; set<string> someSet;
multiset<string> someMultiset; multiset<string> someMultiset;
unordered_set<string> someUnorderedSet; unordered_set<string> someUnorderedSet;
unordered_multiset<string> someUnorderedMultiset; unordered_multiset<string> someUnorderedMultiset;
variant<monostate, string, int, float> someVariant;
variant<string, int, float> anotherVariant;
variant<string, int, float> yetAnotherVariant;
}; };
struct NestingObject : public JsonSerializable<NestingObject> { struct NestingObject : public JsonSerializable<NestingObject> {
@ -83,10 +86,15 @@ template <> inline void push<TestObject>(const TestObject &reflectable, Value::O
push(reflectable.boolean, "boolean", value, allocator); push(reflectable.boolean, "boolean", value, allocator);
push(reflectable.someMap, "someMap", value, allocator); push(reflectable.someMap, "someMap", value, allocator);
push(reflectable.someHash, "someHash", value, allocator); push(reflectable.someHash, "someHash", value, allocator);
push(reflectable.someMultimap, "someMultimap", value, allocator);
push(reflectable.someMultiHash, "someMultiHash", value, allocator);
push(reflectable.someSet, "someSet", value, allocator); push(reflectable.someSet, "someSet", value, allocator);
push(reflectable.someMultiset, "someMultiset", value, allocator); push(reflectable.someMultiset, "someMultiset", value, allocator);
push(reflectable.someUnorderedSet, "someUnorderedSet", value, allocator); push(reflectable.someUnorderedSet, "someUnorderedSet", value, allocator);
push(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, allocator); push(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, allocator);
push(reflectable.someVariant, "someVariant", value, allocator);
push(reflectable.anotherVariant, "anotherVariant", value, allocator);
push(reflectable.yetAnotherVariant, "yetAnotherVariant", value, allocator);
} }
template <> inline void push<NestingObject>(const NestingObject &reflectable, Value::Object &value, Document::AllocatorType &allocator) template <> inline void push<NestingObject>(const NestingObject &reflectable, Value::Object &value, Document::AllocatorType &allocator)
@ -104,7 +112,7 @@ template <> inline void push<NestingArray>(const NestingArray &reflectable, Valu
template <> template <>
inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors) inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{ {
const char *previousRecord; const char *previousRecord = nullptr;
if (errors) { if (errors) {
previousRecord = errors->currentRecord; previousRecord = errors->currentRecord;
errors->currentRecord = "TestObject"; errors->currentRecord = "TestObject";
@ -116,10 +124,15 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
pull(reflectable.boolean, "boolean", value, errors); pull(reflectable.boolean, "boolean", value, errors);
pull(reflectable.someMap, "someMap", value, errors); pull(reflectable.someMap, "someMap", value, errors);
pull(reflectable.someHash, "someHash", value, errors); pull(reflectable.someHash, "someHash", value, errors);
pull(reflectable.someMultimap, "someMultimap", value, errors);
pull(reflectable.someMultiHash, "someMultiHash", value, errors);
pull(reflectable.someSet, "someSet", value, errors); pull(reflectable.someSet, "someSet", value, errors);
pull(reflectable.someMultiset, "someMultiset", value, errors); pull(reflectable.someMultiset, "someMultiset", value, errors);
pull(reflectable.someUnorderedSet, "someUnorderedSet", value, errors); pull(reflectable.someUnorderedSet, "someUnorderedSet", value, errors);
pull(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, errors); pull(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, errors);
pull(reflectable.someVariant, "someVariant", value, errors);
pull(reflectable.anotherVariant, "anotherVariant", value, errors);
pull(reflectable.yetAnotherVariant, "yetAnotherVariant", value, errors);
if (errors) { if (errors) {
errors->currentRecord = previousRecord; errors->currentRecord = previousRecord;
} }
@ -128,7 +141,7 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
template <> template <>
inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors) inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{ {
const char *previousRecord; const char *previousRecord = nullptr;
if (errors) { if (errors) {
previousRecord = errors->currentRecord; previousRecord = errors->currentRecord;
errors->currentRecord = "NestingObject"; errors->currentRecord = "NestingObject";
@ -143,7 +156,7 @@ inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<U
template <> template <>
inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors) inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{ {
const char *previousRecord; const char *previousRecord = nullptr;
if (errors) { if (errors) {
previousRecord = errors->currentRecord; previousRecord = errors->currentRecord;
errors->currentRecord = "NestingArray"; errors->currentRecord = "NestingArray";
@ -173,18 +186,20 @@ class JsonReflectorTests : public TestFixture {
CPPUNIT_TEST(testSerializeNestedObjects); CPPUNIT_TEST(testSerializeNestedObjects);
CPPUNIT_TEST(testSerializeUniquePtr); CPPUNIT_TEST(testSerializeUniquePtr);
CPPUNIT_TEST(testSerializeSharedPtr); CPPUNIT_TEST(testSerializeSharedPtr);
CPPUNIT_TEST(testSerializeOptional);
CPPUNIT_TEST(testDeserializePrimitives); CPPUNIT_TEST(testDeserializePrimitives);
CPPUNIT_TEST(testDeserializeSimpleObjects); CPPUNIT_TEST(testDeserializeSimpleObjects);
CPPUNIT_TEST(testDeserializeNestedObjects); CPPUNIT_TEST(testDeserializeNestedObjects);
CPPUNIT_TEST(testDeserializeUniquePtr); CPPUNIT_TEST(testDeserializeUniquePtr);
CPPUNIT_TEST(testDeserializeSharedPtr); CPPUNIT_TEST(testDeserializeSharedPtr);
CPPUNIT_TEST(testDeserializeOptional);
CPPUNIT_TEST(testHandlingParseError); CPPUNIT_TEST(testHandlingParseError);
CPPUNIT_TEST(testHandlingTypeMismatch); CPPUNIT_TEST(testHandlingTypeMismatch);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
void setUp(); void setUp() override;
void tearDown(); void tearDown() override;
void experiment(); void experiment();
void testSerializePrimitives(); void testSerializePrimitives();
@ -192,11 +207,13 @@ public:
void testSerializeNestedObjects(); void testSerializeNestedObjects();
void testSerializeUniquePtr(); void testSerializeUniquePtr();
void testSerializeSharedPtr(); void testSerializeSharedPtr();
void testSerializeOptional();
void testDeserializePrimitives(); void testDeserializePrimitives();
void testDeserializeSimpleObjects(); void testDeserializeSimpleObjects();
void testDeserializeNestedObjects(); void testDeserializeNestedObjects();
void testDeserializeUniquePtr(); void testDeserializeUniquePtr();
void testDeserializeSharedPtr(); void testDeserializeSharedPtr();
void testDeserializeOptional();
void testHandlingParseError(); void testHandlingParseError();
void testHandlingTypeMismatch(); void testHandlingTypeMismatch();
@ -224,7 +241,7 @@ void JsonReflectorTests::testSerializePrimitives()
Document::Array array(doc.GetArray()); Document::Array array(doc.GetArray());
// string // string
const string foo("foo"); // musn't be destroyed until JSON is actually written const string foo("foo"); // mustn't be destroyed until JSON is actually written
JsonReflector::push<string>(foo, array, alloc); JsonReflector::push<string>(foo, array, alloc);
JsonReflector::push<const char *>("bar", array, alloc); JsonReflector::push<const char *>("bar", array, alloc);
// number // number
@ -263,12 +280,17 @@ void JsonReflectorTests::testSerializeSimpleObjects()
testObj.boolean = false; testObj.boolean = false;
testObj.someMap = { { "a", 1 }, { "b", 2 } }; testObj.someMap = { { "a", 1 }, { "b", 2 } };
testObj.someHash = { { "c", true }, { "d", false } }; testObj.someHash = { { "c", true }, { "d", false } };
testObj.someMultimap = { { "a", 1 }, { "a", 2 }, { "b", 3 } };
testObj.someMultiHash = { { "a", 1 } };
testObj.someSet = { "a", "b", "c" }; testObj.someSet = { "a", "b", "c" };
testObj.someMultiset = { "a", "b", "b" }; testObj.someMultiset = { "a", "b", "b" };
testObj.someUnorderedSet = { "a" }; testObj.someUnorderedSet = { "a" };
testObj.someUnorderedMultiset = { "b", "b", "b" }; testObj.someUnorderedMultiset = { "b", "b", "b" };
testObj.someVariant = std::monostate{};
testObj.anotherVariant = "foo";
testObj.yetAnotherVariant = 42;
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"d\":false,\"c\":true},\"someSet\":[\"a\",\"b\",\"c\"],\"someMultiset\":[\"a\",\"b\",\"b\"],\"someUnorderedSet\":[\"a\"],\"someUnorderedMultiset\":[\"b\",\"b\",\"b\"]}"s, "{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"d\":false,\"c\":true},\"someMultimap\":{\"a\":[1,2],\"b\":[3]},\"someMultiHash\":{\"a\":[1]},\"someSet\":[\"a\",\"b\",\"c\"],\"someMultiset\":[\"a\",\"b\",\"b\"],\"someUnorderedSet\":[\"a\"],\"someUnorderedMultiset\":[\"b\",\"b\",\"b\"],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}"s,
string(testObj.toJson().GetString())); string(testObj.toJson().GetString()));
} }
@ -286,7 +308,7 @@ void JsonReflectorTests::testSerializeNestedObjects()
testObj.text = "test"; testObj.text = "test";
testObj.boolean = false; testObj.boolean = false;
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}}"s, "{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}}"s,
string(nestingObj.toJson().GetString())); string(nestingObj.toJson().GetString()));
NestingArray nestingArray; NestingArray nestingArray;
@ -295,13 +317,13 @@ void JsonReflectorTests::testSerializeNestedObjects()
nestingArray.testObjects.emplace_back(testObj); nestingArray.testObjects.emplace_back(testObj);
nestingArray.testObjects.back().number = 43; nestingArray.testObjects.back().number = 43;
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]}"s, "{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]}"s,
string(nestingArray.toJson().GetString())); string(nestingArray.toJson().GetString()));
vector<TestObject> nestedInVector; vector<TestObject> nestedInVector;
nestedInVector.emplace_back(testObj); nestedInVector.emplace_back(testObj);
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s, "[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s,
string(JsonReflector::toJson(nestedInVector).GetString())); string(JsonReflector::toJson(nestedInVector).GetString()));
} }
@ -329,7 +351,7 @@ void JsonReflectorTests::testSerializeUniquePtr()
Writer<StringBuffer> jsonWriter(strbuf); Writer<StringBuffer> jsonWriter(strbuf);
doc.Accept(jsonWriter); doc.Accept(jsonWriter);
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s, "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s,
string(strbuf.GetString())); string(strbuf.GetString()));
} }
@ -357,10 +379,32 @@ void JsonReflectorTests::testSerializeSharedPtr()
Writer<StringBuffer> jsonWriter(strbuf); Writer<StringBuffer> jsonWriter(strbuf);
doc.Accept(jsonWriter); doc.Accept(jsonWriter);
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s, "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s,
string(strbuf.GetString())); 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. * \brief Tests deserializing strings, numbers (int, float, double) and boolean.
*/ */
@ -422,10 +466,13 @@ void JsonReflectorTests::testDeserializePrimitives()
*/ */
void JsonReflectorTests::testDeserializeSimpleObjects() void JsonReflectorTests::testDeserializeSimpleObjects()
{ {
const TestObject testObj( const auto testObj
TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":" = TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":"
"false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"c\":true,\"d\":false},\"someSet\":[\"a\",\"b\"],\"someMultiset\":[" "false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"c\":true,\"d\":false},\"someMultimap\":{\"a\":[1,2],\"b\":[3]},"
"\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"]}")); "\"someMultiHash\":{\"a\":[4,5],\"b\":[6]},"
"\"someSet\":[\"a\",\"b\"],\"someMultiset\":["
"\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"],\"someVariant\":{\"index\":0,"
"\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}");
CPPUNIT_ASSERT_EQUAL(42, testObj.number); CPPUNIT_ASSERT_EQUAL(42, testObj.number);
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2); CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
@ -436,10 +483,19 @@ void JsonReflectorTests::testDeserializeSimpleObjects()
CPPUNIT_ASSERT_EQUAL(expectedMap, testObj.someMap); CPPUNIT_ASSERT_EQUAL(expectedMap, testObj.someMap);
const unordered_map<string, bool> expectedHash{ { "c", true }, { "d", false } }; const unordered_map<string, bool> expectedHash{ { "c", true }, { "d", false } };
CPPUNIT_ASSERT_EQUAL(expectedHash, testObj.someHash); CPPUNIT_ASSERT_EQUAL(expectedHash, testObj.someHash);
const multimap<string, int> expectedMultiMap{ { "a", 1 }, { "a", 2 }, { "b", 3 } };
CPPUNIT_ASSERT_EQUAL(expectedMultiMap, testObj.someMultimap);
const unordered_multimap<string, int> expectedUnorderedMultiMap{ { "a", 4 }, { "a", 5 }, { "b", 6 } };
CPPUNIT_ASSERT_EQUAL(expectedUnorderedMultiMap, testObj.someMultiHash);
CPPUNIT_ASSERT_EQUAL(set<string>({ "a", "b" }), testObj.someSet); CPPUNIT_ASSERT_EQUAL(set<string>({ "a", "b" }), testObj.someSet);
CPPUNIT_ASSERT_EQUAL(multiset<string>({ "a", "a" }), testObj.someMultiset); CPPUNIT_ASSERT_EQUAL(multiset<string>({ "a", "a" }), testObj.someMultiset);
CPPUNIT_ASSERT_EQUAL(unordered_set<string>({ "a", "b" }), testObj.someUnorderedSet); CPPUNIT_ASSERT_EQUAL(unordered_set<string>({ "a", "b" }), testObj.someUnorderedSet);
CPPUNIT_ASSERT_EQUAL(unordered_multiset<string>({ "a", "a" }), testObj.someUnorderedMultiset); CPPUNIT_ASSERT_EQUAL(unordered_multiset<string>({ "a", "a" }), testObj.someUnorderedMultiset);
CPPUNIT_ASSERT_EQUAL(0_st, testObj.someVariant.index());
CPPUNIT_ASSERT_EQUAL(0_st, testObj.anotherVariant.index());
CPPUNIT_ASSERT_EQUAL("foo"s, std::get<0>(testObj.anotherVariant));
CPPUNIT_ASSERT_EQUAL(1_st, testObj.yetAnotherVariant.index());
CPPUNIT_ASSERT_EQUAL(42, std::get<1>(testObj.yetAnotherVariant));
} }
/*! /*!
@ -470,11 +526,11 @@ void JsonReflectorTests::testDeserializeNestedObjects()
CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size()); CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size());
CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number); CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number);
CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number); CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number);
for (const TestObject &testObj : testObjects) { for (const TestObject &nestedTestObj : testObjects) {
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2); CPPUNIT_ASSERT_EQUAL(3.141592653589793, nestedTestObj.number2);
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers); CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), nestedTestObj.numbers);
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text); CPPUNIT_ASSERT_EQUAL("test"s, nestedTestObj.text);
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean); CPPUNIT_ASSERT_EQUAL(false, nestedTestObj.boolean);
} }
const auto nestedInVector(JsonReflector::fromJson<vector<TestObject>>( const auto nestedInVector(JsonReflector::fromJson<vector<TestObject>>(
@ -487,6 +543,9 @@ void JsonReflectorTests::testDeserializeNestedObjects()
CPPUNIT_ASSERT_EQUAL("test"s, nestedInVector[0].text); CPPUNIT_ASSERT_EQUAL("test"s, nestedInVector[0].text);
} }
/*!
* \brief Tests deserializing std::optional.
*/
void JsonReflectorTests::testDeserializeUniquePtr() void JsonReflectorTests::testDeserializeUniquePtr()
{ {
Document doc(kArrayType); Document doc(kArrayType);
@ -531,6 +590,22 @@ void JsonReflectorTests::testDeserializeSharedPtr()
CPPUNIT_ASSERT_EQUAL("bar"s, obj->text); 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(). * \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
*/ */

View File

@ -1,13 +1,21 @@
#include "../traits.h" #include "../traits.h"
#include "../versioning.h"
#include "../binary/serializable.h"
#include <list> #include <list>
#include <vector> #include <vector>
// treat some types differently to test Treat... traits // define structs for testing REFLECTIVE_RAPIDJSON_TREAT_AS_…
struct Foo { struct Foo {};
}; struct Bar {};
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 { namespace ReflectiveRapidJSON {
REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(Foo); REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(Foo);
REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(Foo); REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(Foo);
@ -48,3 +56,14 @@ static_assert(IsIteratableExceptString<std::vector<int>>::value, "vector is iter
static_assert(!IsIteratableExceptString<std::string>::value, "string not iteratable"); static_assert(!IsIteratableExceptString<std::string>::value, "string not iteratable");
static_assert(!IsIteratableExceptString<std::wstring>::value, "wstring not iteratable"); static_assert(!IsIteratableExceptString<std::wstring>::value, "wstring not iteratable");
static_assert(!IsIteratableExceptString<const std::string>::value, "string 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

@ -8,35 +8,30 @@
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <variant>
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
namespace Traits = ::CppUtilities::Traits;
// define structs and macros to allow treating custom data types as std::map, std::set, ... // 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. /// \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. /// \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. /// \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. /// \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) \ #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) \ #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) \ #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) \ #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 // define traits to check for arrays, sets and maps
template <typename Type> template <typename Type>
@ -51,14 +46,17 @@ template <typename Type>
using IsMultiSet using IsMultiSet
= Traits::Any<Traits::IsSpecializationOf<Type, std::multiset>, Traits::IsSpecializationOf<Type, std::unordered_multiset>, TreatAsMultiSet<Type>>; = Traits::Any<Traits::IsSpecializationOf<Type, std::multiset>, Traits::IsSpecializationOf<Type, std::unordered_multiset>, TreatAsMultiSet<Type>>;
template <typename Type> template <typename Type>
using IsArrayOrSet = Traits::Any<Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>, using IsArrayOrSet = Traits::Any<
Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>>, Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string_view>>, Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>>,
TreatAsSet<Type>, TreatAsMultiSet<Type>>; TreatAsSet<Type>, TreatAsMultiSet<Type>>;
template <typename Type> template <typename Type>
using IsArray = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>, using IsArray = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsSet<Type>>, Traits::Not<IsMultiSet<Type>>>; Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>, Traits::Not<IsSet<Type>>, Traits::Not<IsMultiSet<Type>>>;
template <typename Type> template <typename Type>
using IsIteratableExceptString = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>; using IsIteratableExceptString = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>;
template <typename Type> using IsVariant = Traits::All<Traits::IsSpecializationOf<Type, std::variant>>;
} // namespace ReflectiveRapidJSON } // namespace ReflectiveRapidJSON

60
lib/versioning.h Normal file
View File

@ -0,0 +1,60 @@
#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