Add the ability to derive message types from one another.
`DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT` and `DAP_STRUCT_TYPEINFO_EXT` are two new flavors of `DAP_IMPLEMENT_STRUCT_TYPEINFO` and `DAP_STRUCT_TYPEINFO` that allow you to derive message types.
This involved a bit of reworking on the serializer interfaces.
Added test.
Issue: #32
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2bc668c..3231185 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -194,6 +194,7 @@
${CPPDAP_SRC_DIR}/network_test.cpp
${CPPDAP_SRC_DIR}/optional_test.cpp
${CPPDAP_SRC_DIR}/session_test.cpp
+ ${CPPDAP_SRC_DIR}/typeinfo_test.cpp
${CPPDAP_SRC_DIR}/variant_test.cpp
${CPPDAP_GOOGLETEST_DIR}/googletest/src/gtest-all.cc
)
diff --git a/include/dap/serialization.h b/include/dap/serialization.h
index 25f774e..998cb37 100644
--- a/include/dap/serialization.h
+++ b/include/dap/serialization.h
@@ -78,10 +78,6 @@
template <typename T0, typename... Types>
inline bool deserialize(dap::variant<T0, Types...>*) const;
- // deserialize() decodes a list of fields and stores them into the object.
- inline bool deserialize(void* object,
- const std::initializer_list<Field>&) const;
-
// deserialize() decodes the struct field f with the given name.
template <typename T>
inline bool field(const std::string& name, T* f) const;
@@ -117,20 +113,6 @@
return deserialize(&var->value);
}
-bool Deserializer::deserialize(
- void* object,
- const std::initializer_list<Field>& fields) const {
- for (auto const& f : fields) {
- if (!field(f.name, [&](Deserializer* d) {
- auto ptr = reinterpret_cast<uint8_t*>(object) + f.offset;
- return f.type->deserialize(d, ptr);
- })) {
- return false;
- }
- }
- return true;
-}
-
template <typename T>
bool Deserializer::field(const std::string& name, T* v) const {
return this->field(name,
@@ -140,6 +122,7 @@
////////////////////////////////////////////////////////////////////////////////
// Serializer
////////////////////////////////////////////////////////////////////////////////
+class FieldSerializer;
// Serializer is the interface used to encode data to structured storage.
// A Serializer is associated with a single storage object, whos type and value
@@ -149,16 +132,12 @@
// Methods that return a bool use this to indicate success.
class Serializer {
public:
- using FieldSerializer = std::function<bool(Serializer*)>;
- template <typename T>
- using IsFieldSerializer = std::is_convertible<T, FieldSerializer>;
-
// serialization methods for simple data types.
virtual bool serialize(boolean) = 0;
virtual bool serialize(integer) = 0;
virtual bool serialize(number) = 0;
virtual bool serialize(const string&) = 0;
- virtual bool serialize(const object&) = 0;
+ virtual bool serialize(const dap::object&) = 0;
virtual bool serialize(const any&) = 0;
// array() encodes count array elements to the array object referenced by this
@@ -166,14 +145,10 @@
// Serializer that should be used to encode the n'th array element's data.
virtual bool array(size_t count, const std::function<bool(Serializer*)>&) = 0;
- // fields() encodes all the provided fields of the given object.
- virtual bool fields(const void* object,
- const std::initializer_list<Field>&) = 0;
-
- // field() encodes a field to the struct object referenced by this Serializer.
- // The FieldSerializer will be called with a Serializer used to encode the
- // field's data.
- virtual bool field(const std::string& name, const FieldSerializer&) = 0;
+ // object() begins encoding the object referenced by this Serializer.
+ // The std::function will be called with a FieldSerializer to serialize the
+ // object's fields.
+ virtual bool object(const std::function<bool(dap::FieldSerializer*)>&) = 0;
// remove() deletes the object referenced by this Serializer.
// remove() can be used to serialize optionals with no value assigned.
@@ -198,12 +173,6 @@
// deserialize() encodes the given string.
inline bool serialize(const char* v);
-
- // field() encodes the field with the given name and value.
- template <
- typename T,
- typename = typename std::enable_if<!IsFieldSerializer<T>::value>::type>
- inline bool field(const std::string& name, const T& v);
};
template <typename T, typename>
@@ -235,8 +204,31 @@
return serialize(std::string(v));
}
+////////////////////////////////////////////////////////////////////////////////
+// FieldSerializer
+////////////////////////////////////////////////////////////////////////////////
+
+// FieldSerializer is the interface used to serialize fields of an object.
+class FieldSerializer {
+ public:
+ using SerializeFunc = std::function<bool(Serializer*)>;
+ template <typename T>
+ using IsSerializeFunc = std::is_convertible<T, SerializeFunc>;
+
+ // field() encodes a field to the struct object referenced by this Serializer.
+ // The SerializeFunc will be called with a Serializer used to encode the
+ // field's data.
+ virtual bool field(const std::string& name, const SerializeFunc&) = 0;
+
+ // field() encodes the field with the given name and value.
+ template <
+ typename T,
+ typename = typename std::enable_if<!IsSerializeFunc<T>::value>::type>
+ inline bool field(const std::string& name, const T& v);
+};
+
template <typename T, typename>
-bool Serializer::field(const std::string& name, const T& v) {
+bool FieldSerializer::field(const std::string& name, const T& v) {
return this->field(name, [&](Serializer* s) { return s->serialize(v); });
}
diff --git a/include/dap/typeof.h b/include/dap/typeof.h
index e2d3cc8..e682ab5 100644
--- a/include/dap/typeof.h
+++ b/include/dap/typeof.h
@@ -136,46 +136,127 @@
// NAME is the serialized name of the field, as described by the DAP
// specification.
#define DAP_FIELD(FIELD, NAME) \
- dap::Field { \
+ ::dap::Field { \
NAME, DAP_OFFSETOF(StructTy, FIELD), \
TypeOf<DAP_TYPEOF(StructTy, FIELD)>::type(), \
}
// DAP_DECLARE_STRUCT_TYPEINFO() declares a TypeOf<> specialization for STRUCT.
-#define DAP_DECLARE_STRUCT_TYPEINFO(STRUCT) \
- template <> \
- struct TypeOf<STRUCT> { \
- static constexpr bool has_custom_serialization = true; \
- static const TypeInfo* type(); \
+// Must be used within the 'dap' namespace.
+#define DAP_DECLARE_STRUCT_TYPEINFO(STRUCT) \
+ template <> \
+ struct TypeOf<STRUCT> { \
+ static constexpr bool has_custom_serialization = true; \
+ static const TypeInfo* type(); \
+ static bool deserializeFields(const Deserializer*, void* obj); \
+ static bool serializeFields(FieldSerializer*, const void* obj); \
}
-// DAP_DECLARE_STRUCT_TYPEINFO() implements the type() member function for the
+// DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION() implements the deserializeFields()
+// and serializeFields() static methods of a TypeOf<> specialization. Used
+// internally by DAP_IMPLEMENT_STRUCT_TYPEINFO() and
+// DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT().
+// You probably do not want to use this directly.
+#define DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION(STRUCT, NAME, ...) \
+ bool TypeOf<STRUCT>::deserializeFields(const Deserializer* d, void* obj) { \
+ using StructTy = STRUCT; \
+ (void)sizeof(StructTy); /* avoid unused 'using' warning */ \
+ for (auto field : std::initializer_list<Field>{__VA_ARGS__}) { \
+ if (!d->field(field.name, [&](Deserializer* d) { \
+ auto ptr = reinterpret_cast<uint8_t*>(obj) + field.offset; \
+ return field.type->deserialize(d, ptr); \
+ })) { \
+ return false; \
+ } \
+ } \
+ return true; \
+ } \
+ bool TypeOf<STRUCT>::serializeFields(FieldSerializer* s, const void* obj) { \
+ using StructTy = STRUCT; \
+ (void)sizeof(StructTy); /* avoid unused 'using' warning */ \
+ for (auto field : std::initializer_list<Field>{__VA_ARGS__}) { \
+ if (!s->field(field.name, [&](Serializer* s) { \
+ auto ptr = reinterpret_cast<const uint8_t*>(obj) + field.offset; \
+ return field.type->serialize(s, ptr); \
+ })) { \
+ return false; \
+ } \
+ } \
+ return true; \
+ }
+
+// DAP_IMPLEMENT_STRUCT_TYPEINFO() implements the type() member function for the
// TypeOf<> specialization for STRUCT.
// STRUCT is the structure typename.
// NAME is the serialized name of the structure, as described by the DAP
// specification. The variadic (...) parameters should be a repeated list of
// DAP_FIELD()s, one for each field of the struct.
-#define DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, ...) \
- const TypeInfo* TypeOf<STRUCT>::type() { \
- using StructTy = STRUCT; \
- struct TI : BasicTypeInfo<StructTy> { \
- TI() : BasicTypeInfo<StructTy>(NAME) {} \
- bool deserialize(const Deserializer* d, void* ptr) const override { \
- return d->deserialize(ptr, {__VA_ARGS__}); \
+// Must be used within the 'dap' namespace.
+#define DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, ...) \
+ DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION(STRUCT, NAME, __VA_ARGS__) \
+ const ::dap::TypeInfo* TypeOf<STRUCT>::type() { \
+ struct TI : BasicTypeInfo<STRUCT> { \
+ TI() : BasicTypeInfo<STRUCT>(NAME) {} \
+ bool deserialize(const Deserializer* d, void* obj) const override { \
+ return deserializeFields(d, obj); \
+ } \
+ bool serialize(Serializer* s, const void* obj) const override { \
+ return s->object( \
+ [&](FieldSerializer* fs) { return serializeFields(fs, obj); }); \
+ } \
+ }; \
+ static TI typeinfo; \
+ return &typeinfo; \
+ }
+
+// DAP_STRUCT_TYPEINFO() is a helper for declaring and implementing a TypeOf<>
+// specialization for STRUCT in a single statement.
+// Must be used within the 'dap' namespace.
+#define DAP_STRUCT_TYPEINFO(STRUCT, NAME, ...) \
+ DAP_DECLARE_STRUCT_TYPEINFO(STRUCT); \
+ DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, __VA_ARGS__)
+
+// DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT() implements the type() member function for
+// the TypeOf<> specialization for STRUCT that derives from BASE.
+// STRUCT is the structure typename.
+// BASE is the base structure typename.
+// NAME is the serialized name of the structure, as described by the DAP
+// specification. The variadic (...) parameters should be a repeated list of
+// DAP_FIELD()s, one for each field of the struct.
+// Must be used within the 'dap' namespace.
+#define DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT(STRUCT, BASE, NAME, ...) \
+ static_assert(std::is_base_of<BASE, STRUCT>::value, \
+ #STRUCT " does not derive from " #BASE); \
+ DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION(STRUCT, NAME, __VA_ARGS__) \
+ const ::dap::TypeInfo* TypeOf<STRUCT>::type() { \
+ struct TI : BasicTypeInfo<STRUCT> { \
+ TI() : BasicTypeInfo<STRUCT>(NAME) {} \
+ bool deserialize(const Deserializer* d, void* obj) const override { \
+ auto derived = static_cast<STRUCT*>(obj); \
+ auto base = static_cast<BASE*>(obj); \
+ return TypeOf<BASE>::deserializeFields(d, base) && \
+ deserializeFields(d, derived); \
} \
- bool serialize(Serializer* s, const void* ptr) const override { \
- return s->fields(ptr, {__VA_ARGS__}); \
+ bool serialize(Serializer* s, const void* obj) const override { \
+ return s->object([&](FieldSerializer* fs) { \
+ auto derived = static_cast<const STRUCT*>(obj); \
+ auto base = static_cast<const BASE*>(obj); \
+ return TypeOf<BASE>::serializeFields(fs, base) && \
+ serializeFields(fs, derived); \
+ }); \
} \
}; \
static TI typeinfo; \
return &typeinfo; \
}
-// DAP_STRUCT_TYPEINFO() is a helper for declaring and implementing a TypeOf<>
-// specialization for STRUCT in a single statement.
-#define DAP_STRUCT_TYPEINFO(STRUCT, NAME, ...) \
- DAP_DECLARE_STRUCT_TYPEINFO(STRUCT); \
- DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, __VA_ARGS__)
+// DAP_STRUCT_TYPEINFO_EXT() is a helper for declaring and implementing a
+// TypeOf<> specialization for STRUCT that derives from BASE in a single
+// statement.
+// Must be used within the 'dap' namespace.
+#define DAP_STRUCT_TYPEINFO_EXT(STRUCT, BASE, NAME, ...) \
+ DAP_DECLARE_STRUCT_TYPEINFO(STRUCT); \
+ DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT(STRUCT, BASE, NAME, __VA_ARGS__)
} // namespace dap
diff --git a/src/json_serializer.cpp b/src/json_serializer.cpp
index 12fdf9f..16ada79 100644
--- a/src/json_serializer.cpp
+++ b/src/json_serializer.cpp
@@ -224,27 +224,24 @@
return true;
}
-bool Serializer::fields(const void* object,
- const std::initializer_list<Field>& fields) {
- *json = nlohmann::json({}, false, nlohmann::json::value_t::object);
- for (auto const& f : fields) {
- if (!field(f.name, [&](dap::Serializer* d) {
- auto ptr = reinterpret_cast<const uint8_t*>(object) + f.offset;
- return f.type->serialize(d, ptr);
- }))
- return false;
- }
- return true;
-}
+bool Serializer::object(const std::function<bool(dap::FieldSerializer*)>& cb) {
+ struct FS : public FieldSerializer {
+ nlohmann::json* const json;
-bool Serializer::field(const std::string& name,
- const std::function<bool(dap::Serializer*)>& cb) {
- Serializer s(&(*json)[name]);
- auto res = cb(&s);
- if (s.removed) {
- json->erase(name);
- }
- return res;
+ FS(nlohmann::json* json) : json(json) {}
+ bool field(const std::string& name, const SerializeFunc& cb) override {
+ Serializer s(&(*json)[name]);
+ auto res = cb(&s);
+ if (s.removed) {
+ json->erase(name);
+ }
+ return res;
+ }
+ };
+
+ *json = nlohmann::json({}, false, nlohmann::json::value_t::object);
+ FS fs{json};
+ return cb(&fs);
}
void Serializer::remove() {
diff --git a/src/json_serializer.h b/src/json_serializer.h
index 41afe1e..4ae4e11 100644
--- a/src/json_serializer.h
+++ b/src/json_serializer.h
@@ -67,11 +67,6 @@
return dap::Deserializer::deserialize(v);
}
- inline bool deserialize(void* o,
- const std::initializer_list<Field>& f) const {
- return dap::Deserializer::deserialize(o, f);
- }
-
template <typename T>
inline bool field(const std::string& name, T* v) const {
return dap::Deserializer::deserialize(name, v);
@@ -94,23 +89,14 @@
bool serialize(integer v) override;
bool serialize(number v) override;
bool serialize(const string& v) override;
- bool serialize(const object& v) override;
+ bool serialize(const dap::object& v) override;
bool serialize(const any& v) override;
bool array(size_t count,
const std::function<bool(dap::Serializer*)>&) override;
- bool fields(const void* object,
- const std::initializer_list<Field>& fields) override;
- bool field(const std::string& name, const FieldSerializer&) override;
+ bool object(const std::function<bool(dap::FieldSerializer*)>&) override;
void remove() override;
// Unhide base overloads
- template <
- typename T,
- typename = typename std::enable_if<!IsFieldSerializer<T>::value>::type>
- inline bool field(const std::string& name, const T& v) {
- return dap::Serializer::field(name, v);
- }
-
template <typename T,
typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
inline bool serialize(const T& v) {
diff --git a/src/json_serializer_test.cpp b/src/json_serializer_test.cpp
index 53cc9a2..d26c63e 100644
--- a/src/json_serializer_test.cpp
+++ b/src/json_serializer_test.cpp
@@ -20,8 +20,6 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include <nlohmann/json.hpp>
-
namespace dap {
struct JSONInnerTestObject {
diff --git a/src/session.cpp b/src/session.cpp
index ce8f63f..6344e5a 100644
--- a/src/session.cpp
+++ b/src/session.cpp
@@ -89,22 +89,31 @@
handlers.put(seq, responseTypeInfo, responseHandler);
dap::json::Serializer s;
- s.field("seq", dap::integer(seq));
- s.field("type", "request");
- s.field("command", requestTypeInfo->name());
- s.field("arguments", [&](dap::Serializer* s) {
- return requestTypeInfo->serialize(s, request);
- });
+ if (!s.object([&](dap::FieldSerializer* fs) {
+ return fs->field("seq", dap::integer(seq)) &&
+ fs->field("type", "request") &&
+ fs->field("command", requestTypeInfo->name()) &&
+ fs->field("arguments", [&](dap::Serializer* s) {
+ return requestTypeInfo->serialize(s, request);
+ });
+ })) {
+ return false;
+ }
return send(s.dump());
}
bool send(const dap::TypeInfo* typeinfo, const void* event) override {
dap::json::Serializer s;
- s.field("seq", dap::integer(nextSeq++));
- s.field("type", "event");
- s.field("event", typeinfo->name());
- s.field("body",
- [&](dap::Serializer* s) { return typeinfo->serialize(s, event); });
+ if (!s.object([&](dap::FieldSerializer* fs) {
+ return fs->field("seq", dap::integer(nextSeq++)) &&
+ fs->field("type", "event") &&
+ fs->field("event", typeinfo->name()) &&
+ fs->field("body", [&](dap::Serializer* s) {
+ return typeinfo->serialize(s, event);
+ });
+ })) {
+ return false;
+ }
return send(s.dump());
}
@@ -319,13 +328,15 @@
[&](const dap::TypeInfo* typeinfo, const void* data) {
// onSuccess
dap::json::Serializer s;
- s.field("seq", dap::integer(nextSeq++));
- s.field("type", "response");
- s.field("request_seq", sequence);
- s.field("success", dap::boolean(true));
- s.field("command", command);
- s.field("body", [&](dap::Serializer* s) {
- return typeinfo->serialize(s, data);
+ s.object([&](dap::FieldSerializer* fs) {
+ return fs->field("seq", dap::integer(nextSeq++)) &&
+ fs->field("type", "response") &&
+ fs->field("request_seq", sequence) &&
+ fs->field("success", dap::boolean(true)) &&
+ fs->field("command", command) &&
+ fs->field("body", [&](dap::Serializer* s) {
+ return typeinfo->serialize(s, data);
+ });
});
send(s.dump());
@@ -336,12 +347,14 @@
[&](const dap::TypeInfo* typeinfo, const dap::Error& error) {
// onError
dap::json::Serializer s;
- s.field("seq", dap::integer(nextSeq++));
- s.field("type", "response");
- s.field("request_seq", sequence);
- s.field("success", dap::boolean(false));
- s.field("command", command);
- s.field("message", error.message);
+ s.object([&](dap::FieldSerializer* fs) {
+ return fs->field("seq", dap::integer(nextSeq++)) &&
+ fs->field("type", "response") &&
+ fs->field("request_seq", sequence) &&
+ fs->field("success", dap::boolean(false)) &&
+ fs->field("command", command) &&
+ fs->field("message", error.message);
+ });
send(s.dump());
if (auto handler = handlers.responseSent(typeinfo)) {
diff --git a/src/typeinfo_test.cpp b/src/typeinfo_test.cpp
new file mode 100644
index 0000000..23d5793
--- /dev/null
+++ b/src/typeinfo_test.cpp
@@ -0,0 +1,65 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/typeof.h"
+#include "dap/types.h"
+#include "json_serializer.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace dap {
+
+struct BaseStruct {
+ dap::integer i;
+ dap::number n;
+};
+
+DAP_STRUCT_TYPEINFO(BaseStruct,
+ "BaseStruct",
+ DAP_FIELD(i, "i"),
+ DAP_FIELD(n, "n"));
+
+struct DerivedStruct : public BaseStruct {
+ dap::string s;
+ dap::boolean b;
+};
+
+DAP_STRUCT_TYPEINFO_EXT(DerivedStruct,
+ BaseStruct,
+ "DerivedStruct",
+ DAP_FIELD(s, "s"),
+ DAP_FIELD(b, "b"));
+
+} // namespace dap
+
+TEST(TypeInfo, Derived) {
+ dap::DerivedStruct in;
+ in.s = "hello world";
+ in.b = true;
+ in.i = 42;
+ in.n = 3.14;
+
+ dap::json::Serializer s;
+ ASSERT_TRUE(s.serialize(in));
+
+ dap::DerivedStruct out;
+ dap::json::Deserializer d(s.dump());
+ ASSERT_TRUE(d.deserialize(&out)) << "Failed to deserialize\n" << s.dump();
+
+ ASSERT_EQ(out.s, "hello world");
+ ASSERT_EQ(out.b, true);
+ ASSERT_EQ(out.i, 42);
+ ASSERT_EQ(out.n, 3.14);
+}