Support of nested type in schemer json formatter (#5547)

Summary:
Pull Request resolved: https://github.com/facebook/osquery/pull/5547

Since this diff an object of a class with defined schema (see type trait
schemer::has_schema) are allowed as memebers of anoter class with schema.

Example. C++ classes:
```
class Simple {
  int alpha = 1;

 public:
  template <typename Archive, typename ValueType>
  static void discloseSchema(Archive& a, ValueType& value) {
    schemer::record(a, "alpha", value.alpha);
  }
};

class Nested {
  Frist beta;
  int gama = 2;

 public:
  template <typename Archive, typename ValueType>
  static void discloseSchema(Archive& a, ValueType& value) {
    schemer::record(a, "beta", value.beta);
    schemer::record(a, "gama", value.gama);
  }
};
```

Json representation of `Nested`:
```
{
  "beta": {
    "alpha": 1
  },
  "gama": 2
}
```

Reviewed By: SAlexandru

Differential Revision: D14683589

fbshipit-source-id: 1f9e2f862d2bf64be166a717e49cf0f470f8ee36
This commit is contained in:
Alexander Kindyakov 2019-04-01 09:22:44 -07:00 committed by Facebook Github Bot
parent eca9296a88
commit 5ec514e714
3 changed files with 106 additions and 13 deletions

View File

@ -32,10 +32,10 @@ namespace schemer {
* - integral types (int, short, long, long long, unsigned etc);
* - floating point nubmers (float, double);
* - std::string;
* - C-string - only for serialisation.
* - C-string - only for serialisation;
* - types with defined schema, @see schemer::has_schema type trait.
*
* Not implemented yet, but comming soon:
* - Nested types support
* - Standard sontainers support
*/
@ -72,8 +72,6 @@ Expected<std::string, JsonError> toJson(Type const& value) {
*/
template <typename Type, typename RapidJsonInStream>
ExpectedSuccess<JsonError> fromJson(Type& value, RapidJsonInStream& is) {
static_assert(!std::is_const<Type>::value,
"schemer can read only to non-const ref");
auto dom = rapidjson::Document{};
dom.ParseStream(is);
if (dom.HasParseError()) {
@ -83,14 +81,10 @@ ExpectedSuccess<JsonError> fromJson(Type& value, RapidJsonInStream& is) {
<< GetParseError_En(dom.GetParseError())
<< " Offset: " << dom.GetErrorOffset();
}
if (!dom.IsObject()) {
return createError(JsonError::TypeMismatch)
<< "Can not parse value of type "
<< boost::core::demangle(typeid(Type).name())
<< " from JSON. Incorrect type, expected object";
}
auto reader = impl::JsonReader{dom};
Type::discloseSchema(reader, value);
if (reader.status.isValue()) {
Type::discloseSchema(reader, value);
}
if (reader.status.isError()) {
return createError(JsonError::IncorrectFormat, reader.status.takeError())
<< "Can not parse value of type "

View File

@ -14,6 +14,7 @@
#include <osquery/utils/json/json.h>
#include <osquery/utils/schemer/schemer.h>
#include <algorithm>
#include <string>
namespace osquery {
@ -82,6 +83,17 @@ void writeValue(WriterType& writer, ValueType const& value) {
writer.String(value);
}
template <typename WriterType>
class JsonWriter;
template <typename WriterType,
typename ValueType,
typename std::enable_if<has_schema<ValueType>::value, int>::type = 0>
void writeValue(WriterType& writer, ValueType const& value) {
auto next_writer = impl::JsonWriter<WriterType>(writer);
ValueType::discloseSchema(next_writer, value);
}
template <typename WriterType>
class JsonWriter final {
public:
@ -107,6 +119,12 @@ class JsonReader final {
public:
explicit JsonReader(rapidjson::Value const& jObject) : jObject_(jObject) {
status.ignoreResult();
if (!jObject_.IsObject()) {
status =
createError(JsonError::TypeMismatch)
<< "Wrong type of value: " << jValueToStringForErrorMessage(jObject_)
<< ", expected object";
}
}
template <typename KeyType, typename ValueType>
@ -189,6 +207,24 @@ class JsonReader final {
}
}
template <
typename KeyType,
typename ValueType,
typename std::enable_if<has_schema<ValueType>::value, int>::type = 0>
void copyValueFromJValue(const KeyType& key,
ValueType& value,
rapidjson::Value const& jValue) {
auto next_reader = impl::JsonReader{jValue};
if (next_reader.status.isError()) {
status = std::move(next_reader.status);
} else {
ValueType::discloseSchema(next_reader, value);
if (next_reader.status.isError()) {
status = std::move(next_reader.status);
}
}
}
public:
ExpectedSuccess<JsonError> status = Success{};

View File

@ -31,7 +31,7 @@ class TestClass {
schemer::record(a, "Sierra", value.str_v_);
}
private:
public:
bool b_v_ = true;
int i_v_ = -92374;
int ui_v_ = 64774;
@ -57,6 +57,28 @@ TEST_F(SchemerJsonTests, writer_to_string) {
R"json({"Bravo":true,"India":-92374,"Uniform":64774,"Sierra":"What is Architecture?"})json");
}
class NestedTestClass {
public:
template <typename Archive, typename ValueType>
static void discloseSchema(Archive& a, ValueType& value) {
schemer::record(a, "First", value.first_);
schemer::record(a, "test_class", value.second_);
}
public:
int first_ = -273;
TestClass second_;
};
TEST_F(SchemerJsonTests, writer_nested_to_string) {
auto const v = NestedTestClass{};
auto const exp = schemer::toJson(v);
EXPECT_TRUE(exp) << exp.getError().getMessage();
EXPECT_EQ(
exp.get(),
R"json({"First":-273,"test_class":{"Bravo":true,"India":-92374,"Uniform":64774,"Sierra":"What is Architecture?"}})json");
}
class SecondTestClass {
public:
template <typename Archive, typename ValueType>
@ -128,7 +150,7 @@ TEST_F(SchemerJsonTests, read_from_stream_object_type_error) {
])json"};
auto const retcode = schemer::fromJson(v, buf);
ASSERT_TRUE(retcode.isError());
ASSERT_EQ(retcode.getErrorCode(), schemer::JsonError::TypeMismatch);
ASSERT_EQ(retcode.getErrorCode(), schemer::JsonError::IncorrectFormat);
}
TEST_F(SchemerJsonTests, read_from_stream_member_type_error) {
@ -196,5 +218,46 @@ TEST_F(SchemerJsonTests, read_write) {
EXPECT_EQ(fromValue.fourth, toValue.fourth);
}
TEST_F(SchemerJsonTests, read_nested_from_string) {
auto const str =
R"json({"First":-459,"test_class":{"Bravo":false,"India":31,"Uniform":145,"Sierra":"I have no clue"}})json";
auto value = NestedTestClass{};
auto const retcode = schemer::fromJson(value, str);
ASSERT_TRUE(retcode.isValue()) << retcode.getError().getMessage();
EXPECT_EQ(value.first_, -459);
EXPECT_EQ(value.second_.b_v_, false);
EXPECT_EQ(value.second_.i_v_, 31);
EXPECT_EQ(value.second_.ui_v_, 145);
EXPECT_EQ(value.second_.str_v_, "I have no clue");
}
TEST_F(SchemerJsonTests,
read_nested_from_string_fails_because_value_is_not_an_object) {
auto const str = R"json({"First":-459,"test_class":false})json";
auto value = NestedTestClass{};
auto const retcode = schemer::fromJson(value, str);
ASSERT_TRUE(retcode.isError());
EXPECT_EQ(retcode.getErrorCode(), schemer::JsonError::IncorrectFormat);
}
TEST_F(SchemerJsonTests,
read_nested_from_string_fails_because_of_incomplete_json) {
// here is JSON with missed last '}'
auto const str = R"json({
"First":-459,
"test_class":{
"Bravo":false,
"India":31,
"Uniform":145,
"Sierra":"I have no clue"
}
)json";
auto value = NestedTestClass{};
auto const retcode = schemer::fromJson(value, str);
ASSERT_TRUE(retcode.isError());
EXPECT_EQ(retcode.getErrorCode(), schemer::JsonError::Syntax);
}
} // namespace
} // namespace osquery