From cc93ba9747201007c8ff90e7d924152485462fbc Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Tue, 2 Jun 2020 16:40:44 +0100 Subject: [PATCH] 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 --- CMakeLists.txt | 1 + include/dap/serialization.h | 68 +++++++++---------- include/dap/typeof.h | 123 +++++++++++++++++++++++++++++------ src/json_serializer.cpp | 37 +++++------ src/json_serializer.h | 18 +---- src/json_serializer_test.cpp | 2 - src/session.cpp | 61 ++++++++++------- src/typeinfo_test.cpp | 65 ++++++++++++++++++ 8 files changed, 254 insertions(+), 121 deletions(-) create mode 100644 src/typeinfo_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bc668c..3231185 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,7 @@ if(CPPDAP_BUILD_TESTS) ${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 @@ class Deserializer { template inline bool deserialize(dap::variant*) const; - // deserialize() decodes a list of fields and stores them into the object. - inline bool deserialize(void* object, - const std::initializer_list&) const; - // deserialize() decodes the struct field f with the given name. template inline bool field(const std::string& name, T* f) const; @@ -117,20 +113,6 @@ bool Deserializer::deserialize(dap::variant* var) const { return deserialize(&var->value); } -bool Deserializer::deserialize( - void* object, - const std::initializer_list& fields) const { - for (auto const& f : fields) { - if (!field(f.name, [&](Deserializer* d) { - auto ptr = reinterpret_cast(object) + f.offset; - return f.type->deserialize(d, ptr); - })) { - return false; - } - } - return true; -} - template bool Deserializer::field(const std::string& name, T* v) const { return this->field(name, @@ -140,6 +122,7 @@ bool Deserializer::field(const std::string& name, T* v) const { //////////////////////////////////////////////////////////////////////////////// // 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 @@ bool Deserializer::field(const std::string& name, T* v) const { // Methods that return a bool use this to indicate success. class Serializer { public: - using FieldSerializer = std::function; - template - using IsFieldSerializer = std::is_convertible; - // 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 @@ class Serializer { // Serializer that should be used to encode the n'th array element's data. virtual bool array(size_t count, const std::function&) = 0; - // fields() encodes all the provided fields of the given object. - virtual bool fields(const void* object, - const std::initializer_list&) = 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&) = 0; // remove() deletes the object referenced by this Serializer. // remove() can be used to serialize optionals with no value assigned. @@ -198,12 +173,6 @@ class Serializer { // 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::value>::type> - inline bool field(const std::string& name, const T& v); }; template @@ -235,8 +204,31 @@ bool Serializer::serialize(const char* v) { return serialize(std::string(v)); } +//////////////////////////////////////////////////////////////////////////////// +// FieldSerializer +//////////////////////////////////////////////////////////////////////////////// + +// FieldSerializer is the interface used to serialize fields of an object. +class FieldSerializer { + public: + using SerializeFunc = std::function; + template + using IsSerializeFunc = std::is_convertible; + + // 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::value>::type> + inline bool field(const std::string& name, const T& v); +}; + template -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 @@ M member_type(M T::*); // 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::type(), \ } // DAP_DECLARE_STRUCT_TYPEINFO() declares a TypeOf<> specialization for STRUCT. -#define DAP_DECLARE_STRUCT_TYPEINFO(STRUCT) \ - template <> \ - struct TypeOf { \ - 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 { \ + 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::deserializeFields(const Deserializer* d, void* obj) { \ + using StructTy = STRUCT; \ + (void)sizeof(StructTy); /* avoid unused 'using' warning */ \ + for (auto field : std::initializer_list{__VA_ARGS__}) { \ + if (!d->field(field.name, [&](Deserializer* d) { \ + auto ptr = reinterpret_cast(obj) + field.offset; \ + return field.type->deserialize(d, ptr); \ + })) { \ + return false; \ + } \ + } \ + return true; \ + } \ + bool TypeOf::serializeFields(FieldSerializer* s, const void* obj) { \ + using StructTy = STRUCT; \ + (void)sizeof(StructTy); /* avoid unused 'using' warning */ \ + for (auto field : std::initializer_list{__VA_ARGS__}) { \ + if (!s->field(field.name, [&](Serializer* s) { \ + auto ptr = reinterpret_cast(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::type() { \ - using StructTy = STRUCT; \ - struct TI : BasicTypeInfo { \ - TI() : BasicTypeInfo(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::type() { \ + struct TI : BasicTypeInfo { \ + TI() : BasicTypeInfo(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::value, \ + #STRUCT " does not derive from " #BASE); \ + DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION(STRUCT, NAME, __VA_ARGS__) \ + const ::dap::TypeInfo* TypeOf::type() { \ + struct TI : BasicTypeInfo { \ + TI() : BasicTypeInfo(NAME) {} \ + bool deserialize(const Deserializer* d, void* obj) const override { \ + auto derived = static_cast(obj); \ + auto base = static_cast(obj); \ + return TypeOf::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(obj); \ + auto base = static_cast(obj); \ + return TypeOf::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 @@ bool Serializer::array(size_t count, return true; } -bool Serializer::fields(const void* object, - const std::initializer_list& 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(object) + f.offset; - return f.type->serialize(d, ptr); - })) - return false; - } - return true; -} +bool Serializer::object(const std::function& cb) { + struct FS : public FieldSerializer { + nlohmann::json* const json; -bool Serializer::field(const std::string& name, - const std::function& 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 @@ struct Deserializer : public dap::Deserializer { return dap::Deserializer::deserialize(v); } - inline bool deserialize(void* o, - const std::initializer_list& f) const { - return dap::Deserializer::deserialize(o, f); - } - template inline bool field(const std::string& name, T* v) const { return dap::Deserializer::deserialize(name, v); @@ -94,23 +89,14 @@ struct Serializer : public dap::Serializer { 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&) override; - bool fields(const void* object, - const std::initializer_list& fields) override; - bool field(const std::string& name, const FieldSerializer&) override; + bool object(const std::function&) override; void remove() override; // Unhide base overloads - template < - typename T, - typename = typename std::enable_if::value>::type> - inline bool field(const std::string& name, const T& v) { - return dap::Serializer::field(name, v); - } - template ::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 - 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 @@ class Impl : public dap::Session { 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 @@ class Impl : public dap::Session { [&](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 @@ class Impl : public dap::Session { [&](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); +}