diff --git a/include/trial/protocol/core/detail/lightweight_test.hpp b/include/trial/protocol/core/detail/lightweight_test.hpp index 3bcc57a..1065114 100644 --- a/include/trial/protocol/core/detail/lightweight_test.hpp +++ b/include/trial/protocol/core/detail/lightweight_test.hpp @@ -226,6 +226,7 @@ void test_all_with_impl(FormattedOutputFunction& output, #define TRIAL_PROTOCOL_TEST BOOST_TEST #define TRIAL_PROTOCOL_TEST_EQUAL BOOST_TEST_EQ +#define TRIAL_PROTOCOL_TEST_NE BOOST_TEST_NE #define TRIAL_PROTOCOL_TEST_THROWS BOOST_TEST_THROWS #define TRIAL_PROTOCOL_TEST_THROW_EQUAL(EXPR, EXCEP, MSG) \ diff --git a/include/trial/protocol/json/detail/error.ipp b/include/trial/protocol/json/detail/error.ipp index f5ab29d..6098def 100644 --- a/include/trial/protocol/json/detail/error.ipp +++ b/include/trial/protocol/json/detail/error.ipp @@ -58,6 +58,9 @@ public: case insufficient_tokens: return "algorithm used requires more tokens than available"; + + case field_not_found: + return "request field(s) not found"; } return "trial.protocol.json error"; } diff --git a/include/trial/protocol/json/error.hpp b/include/trial/protocol/json/error.hpp index 77437ad..ea97e91 100644 --- a/include/trial/protocol/json/error.hpp +++ b/include/trial/protocol/json/error.hpp @@ -35,7 +35,8 @@ enum errc expected_end_array, expected_end_object, - insufficient_tokens + insufficient_tokens, + field_not_found }; const std::error_category& error_category(); diff --git a/include/trial/protocol/json/scan.hpp b/include/trial/protocol/json/scan.hpp new file mode 100644 index 0000000..45b5bfc --- /dev/null +++ b/include/trial/protocol/json/scan.hpp @@ -0,0 +1,516 @@ +#ifndef TRIAL_PROTOCOL_JSON_SCAN_HPP +#define TRIAL_PROTOCOL_JSON_SCAN_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2020 Vinícius dos Santos Oliveira +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace trial +{ +namespace protocol +{ +namespace json +{ +namespace partial +{ + +template +void scan(basic_reader& reader, Xs&& xs); + +namespace detail +{ + +template +struct ScanCollector +{ + using view_type = typename basic_reader::view_type; + using size_type = typename basic_reader::size_type; + + void push_back(CharT v) + { + if (size == capacity) return; + + storage[size++] = v; + } + + void append(const CharT *data, size_type size) + { + if (this->size == capacity) return; + + if (size + this->size > capacity) { + this->size = capacity; + return; + } + + std::copy(data, data + size, storage.begin() + this->size); + this->size += size; + } + + void clear() + { + size = 0; + } + + template + bool equals(const Xs& hana_str) const + { + BOOST_HANA_CONSTANT_ASSERT( + boost::hana::size_c > boost::hana::size(hana_str)); + view_type str{hana_str.c_str(), boost::hana::size(hana_str)}; + return as_view() == str; + } + + view_type as_view() const + { + return view_type{storage.data(), size}; + } + + std::array storage; + std::size_t size; +}; + +template +boost::hana::true_ scan_required_arg(const T&) { return {}; } + +inline +boost::hana::false_ scan_required_arg(std::reference_wrapper) +{ return {}; } + +template +boost::hana::false_ +scan_required_arg(std::reference_wrapper>) +{ return {}; } + +template +std::enable_if_t< + boost::hana::contains( + boost::hana::make_tuple(boost::hana::type_c...), + boost::hana::type_c), + boost::hana::false_ +> +scan_required_arg(std::reference_wrapper>) +{ return {}; } + +// scan_action(reference_wrapper) OVERLOAD DECLARATIONS +// These are needed because overloads might reference each other recursively + +template +void scan_action(basic_reader& reader, std::reference_wrapper action); + +template +void scan_action(basic_reader& reader, + std::reference_wrapper>); + +inline void scan_action(reader& reader, + std::reference_wrapper action); + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action); + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action); + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action); + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action); + +// scan_action() OVERLOAD DEFINITIONS + +template +std::enable_if_t> +scan_action(basic_reader& reader, Xs&& xs) +{ + scan(reader, std::forward(xs)); +} + +template +auto scan_action(basic_reader& reader, F&& f) -> decltype(f(reader)) +{ + return f(reader); +} + +template +void scan_action(basic_reader& reader, std::reference_wrapper action) +{ + auto& output = action.get(); + auto ec = reader.value(output); + if (ec) throw json::error(ec); + skip(reader); +} + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action) +{ + action.get() = skip(reader); +} + +inline void scan_action(reader& reader, + std::reference_wrapper action) +{ + action.get() = parse(reader); +} + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action) +{ + if (reader.symbol() == token::symbol::null) { + action.get().reset(); + if (!reader.next()) throw json::error(reader.error()); + return; + } + auto& output = action.get(); + output.emplace(); + scan_action(reader, std::ref(*output)); +} + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action) +{ + namespace hana = boost::hana; + + auto& output = action.get(); + switch (reader.symbol()) { + case token::symbol::null: + hana::eval_if( + !hana::contains( + hana::make_tuple(hana::type_c...), + hana::type_c), + []() { throw json::error(errc::incompatible_type); }, + [&](auto _) { _(output) = boost::none; } + ); + break; + case token::symbol::boolean: + hana::eval_if( + !hana::contains( + hana::make_tuple(hana::type_c...), + hana::type_c), + []() { throw json::error(errc::incompatible_type); }, + [&](auto _) { _(output) = reader.template value(); } + ); + break; + case token::symbol::integer: { + auto target = hana::filter( + hana::make_tuple( + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c), + [](auto&& e) { + return hana::contains( + hana::make_tuple(hana::type_c...), + e); + }); + hana::eval_if( + hana::size(target) == hana::size_c<0>, + []() { throw json::error(errc::incompatible_type); }, + [&](auto _) { + using T = typename decltype(+hana::at_c<0>(_(target)))::type; + _(output) = reader.template value(); + } + ); + break; + } + case token::symbol::real: { + auto target = hana::filter( + hana::make_tuple( + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c, + hana::type_c), + [](auto&& e) { + return hana::contains( + hana::make_tuple(hana::type_c...), + e); + }); + hana::eval_if( + hana::size(target) == hana::size_c<0>, + []() { throw json::error(errc::incompatible_type); }, + [&](auto _) { + using T = typename decltype(+hana::at_c<0>(_(target)))::type; + _(output) = reader.template value(); + } + ); + break; + } + case token::symbol::string: + hana::eval_if( + !hana::contains( + hana::make_tuple(hana::type_c...), + hana::type_c), + []() { throw json::error(errc::incompatible_type); }, + [&](auto _) { _(output) = reader.template value(); } + ); + break; + case token::symbol::begin_array: + case token::symbol::begin_object: + throw json::error(errc::incompatible_type); + default: + assert(false); + } + if (!reader.next()) throw json::error(reader.error()); +} + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action) +{ + if (reader.symbol() != token::symbol::begin_array) + throw json::error(errc::incompatible_type); + + if (!reader.next()) + throw json::error(reader.error()); + + auto& output = action.get(); + + for (;;) { + if (reader.symbol() == token::symbol::end_array) { + if (!reader.next()) + throw json::error(reader.error()); + break; + } + + output.emplace_back(); + scan_action(reader, std::ref(output.back())); + } +} + +template +void scan_action(basic_reader& reader, + std::reference_wrapper> action) +{ + if (reader.symbol() != token::symbol::begin_array) + throw json::error(errc::incompatible_type); + + if (!reader.next()) + throw json::error(reader.error()); + + auto& output = action.get(); + + for (;;) { + if (reader.symbol() == token::symbol::end_array) { + if (!reader.next()) + throw json::error(reader.error()); + break; + } + + output.emplace_back(reader.template value()); + if (!reader.next()) + throw json::error(reader.error()); + } +} + +} // namespace detail + +template +void scan(basic_reader& reader, Xs&& xs) +{ + namespace hana = boost::hana; + using hana::literals::operator""_c; + + // Given this spec: + // + // { (("foo", "bar"), action1), + // (("foo", "baz"), action2), + // (("qux") , action3) } + // + // This spec is returned: + // + // { ("foo", { (("bar"), action1) + // (("baz"), action2) }) + // ("qux", action3 ) } + auto normalize_root = [](auto&& xs) { + return std::forward(xs); + // Not supported. Check + // https://github.com/boostorg/hana/issues/451#issuecomment-643647418 +#if 0 + return hana::fold( + std::forward(xs), + hana::make_map(), + [](auto&& acc, auto&& rule) { + auto& pattern = hana::first(rule); + auto& action = hana::second(rule); + + auto& key = hana::eval_if( + hana::is_a(pattern), + [&pattern]() { return pattern; }, + [&pattern](auto _) { return _(pattern)[0_c]; } + ); + + auto value = hana::eval_if( + (hana::is_a(pattern) || + (hana::is_a(pattern) && + hana::size(pattern) == hana::size_c<1>)), + [&action]() { return action; }, + [&pattern,&action](auto _) { + return hana::make_map(hana::make_pair( + hana::drop_front(_(pattern)), + action + )); + } + ); + + return hana::if_( + hana::contains(acc, key), + hana::transform( + acc, + [&key,&value](auto&& e) { + return hana::eval_if( + hana::first(e) != key, + [&e]() { return std::forward(e); }, + [&e,&value](auto _) { + return hana::union_( + _(value), + _(hana::second(e)) + ); + } + ); + }), + hana::insert(acc, hana::make_pair(key, value)) + ); + } + ); +#endif + }; + + if (reader.symbol() != token::symbol::begin_object) + throw json::error(json::incompatible_type); + + auto spec = hana::insert( + normalize_root(std::forward(xs)), + hana::make_pair( + hana::type_c, + [](basic_reader& reader) { + // Skip key + if (!reader.next()) throw json::error(reader.error()); + // Skip value + skip(reader); + })); + auto wildcard = std::move(spec[hana::type_c]); + auto seen_table = hana::fold( + spec, + hana::make_map(), + [](auto&& acc, const auto& e) { + return hana::if_( + hana::first(e) == hana::type_c || + !detail::scan_required_arg(hana::second(e)), + acc, + hana::insert(acc, hana::make_pair(hana::first(e), false))); + } + ); + auto collector_capacity = hana::size_c<1> + hana::maximum( + hana::transform(hana::remove(hana::keys(spec), hana::type_c), + hana::size)); + detail::ScanCollector current_key; + + if (!reader.next()) + throw json::error(reader.error()); + + auto loop_body = hana::fold_right( + hana::erase_key(std::move(spec), hana::type_c), + std::move(wildcard), + [&seen_table,¤t_key](auto&& rule, auto&& acc) { + return [ + rule=std::forward(rule), + acc=std::forward(acc), + &seen_table,¤t_key + ](basic_reader& reader) { + // Key + if (!current_key.equals(/*pattern=*/hana::first(rule))) + return acc(reader); + + if (!reader.next()) + throw json::error(reader.error()); + + // Value + detail::scan_action(reader, /*action=*/hana::second(rule)); + hana::eval_if( + !detail::scan_required_arg(hana::second(rule)), + []() {}, + [&seen_table,&rule](auto _) { + _(seen_table)[hana::first(rule)] = true; + }); + }; + } + ); + + for (;;) { + if (reader.symbol() == token::symbol::end_object) { + reader.next(); + auto ec = reader.error(); + if (ec) throw json::error(ec); + break; + } + + // Key + assert(reader.symbol() == token::symbol::string); + current_key.clear(); + auto ec = reader.string(current_key); + assert(!ec); (void)ec; + + // Value + loop_body(reader); + } + + if (hana::fold( + seen_table, + hana::false_c, + [](auto&& acc, auto&& e) { return acc || !hana::second(e); } + )) { + throw json::error(errc::field_not_found); + } +} + +} // namespace partial + +template +void scan(boost::basic_string_view input, Args&&... args) +{ + basic_reader reader(input); + partial::scan(reader, std::forward(args)...); + if (reader.symbol() != token::symbol::end) + throw json::error(json::unexpected_token); +} + +template +void scan(const char input[], Args&&... args) +{ + scan(boost::string_view(input), std::forward(args)...); +} + +} // namespace json +} // namespace protocol +} // namespace trial + +#endif // TRIAL_PROTOCOL_JSON_SCAN_HPP diff --git a/test/json/CMakeLists.txt b/test/json/CMakeLists.txt index e0fdc79..d06fb72 100644 --- a/test/json/CMakeLists.txt +++ b/test/json/CMakeLists.txt @@ -18,6 +18,7 @@ trial_add_test(json_writer_suite writer_suite.cpp) trial_add_test(json_iarchive_suite iarchive_suite.cpp) trial_add_test(json_oarchive_suite oarchive_suite.cpp) trial_add_test(json_partial_skip_suite skip_suite.cpp) +trial_add_test(json_partial_scan_suite scan_suite.cpp) # Tree processing trial_add_test(json_parse_suite parse_suite.cpp) diff --git a/test/json/scan_suite.cpp b/test/json/scan_suite.cpp new file mode 100644 index 0000000..62d0117 --- /dev/null +++ b/test/json/scan_suite.cpp @@ -0,0 +1,351 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2020 Vinícius dos Santos Oliveira +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +using namespace trial::protocol; +namespace token = json::token; +namespace hana = boost::hana; + +void test_plain_assignment() +{ + auto input = R"({"foo": 42, "bar": "baz", "else": null, "aboolean": true})"; + int foo = 0; + std::string bar; + bool aboolean = false; + json::scan( + input, + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)), + hana::make_pair(BOOST_HANA_STRING("aboolean"), std::ref(aboolean)) + ) + ); + TRIAL_PROTOCOL_TEST_EQUAL(foo, 42); + TRIAL_PROTOCOL_TEST_EQUAL(bar, std::string{"baz"}); + TRIAL_PROTOCOL_TEST_EQUAL(aboolean, true); +} + +void test_nested_assignment() +{ + auto input = R"({"foo": {"bar": 3, "baz": 4}, "bar": 5})"; + int bar = 0; + int baz = 0; + int qux = 0; + json::scan( + input, + hana::make_map( + hana::make_pair( + BOOST_HANA_STRING("foo"), + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)), + hana::make_pair(BOOST_HANA_STRING("baz"), std::ref(baz)) + )), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(qux)) + ) + ); + TRIAL_PROTOCOL_TEST_EQUAL(bar, 3); + TRIAL_PROTOCOL_TEST_EQUAL(baz, 4); + TRIAL_PROTOCOL_TEST_EQUAL(qux, 5); +} + +void test_nested_assignment2() +{ + // Not supported. Check + // https://github.com/boostorg/hana/issues/451#issuecomment-643647418 +#if 0 + auto input = R"({"foo": {"bar": 3, "baz": 4, "qux": 5}, "foobar": 6})"; + int bar = 0; + int baz = 0; + int qux = 0; + int foobar = 0; + json::scan( + input, + hana::make_map( + hana::make_pair( + hana::make_tuple( + BOOST_HANA_STRING("foo"), BOOST_HANA_STRING("bar")), + std::ref(bar) + ), + hana::make_pair( + hana::make_tuple( + BOOST_HANA_STRING("foo"), BOOST_HANA_STRING("baz")), + std::ref(baz) + ), + hana::make_pair( + BOOST_HANA_STRING("foo"), + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("qux"), std::ref(qux)) + ) + ), + hana::make_pair( + hana::make_tuple(BOOST_HANA_STRING("foobar")), std::ref(foobar) + ) + ) + ); + TRIAL_PROTOCOL_TEST_EQUAL(bar, 3); + TRIAL_PROTOCOL_TEST_EQUAL(baz, 4); + TRIAL_PROTOCOL_TEST_EQUAL(qux, 5); + TRIAL_PROTOCOL_TEST_EQUAL(foobar, 6); +#endif +} + +void test_callback() +{ + auto input = R"({"foo": 42, "bar": "baz", "else": null, "aboolean": true})"; + json::scan( + input, + hana::make_map( + hana::make_pair( + BOOST_HANA_STRING("foo"), + [](json::reader& reader) { + TRIAL_PROTOCOL_TEST_EQUAL(reader.value(), 42); + reader.next(); + }), + hana::make_pair( + BOOST_HANA_STRING("bar"), + [](json::reader& reader) { + TRIAL_PROTOCOL_TEST_EQUAL(reader.value(), + std::string{"baz"}); + reader.next(); + }), + hana::make_pair( + BOOST_HANA_STRING("aboolean"), + [](json::reader& reader) { + TRIAL_PROTOCOL_TEST_EQUAL(reader.value(), true); + reader.next(); + }) + ) + ); +} + +void test_wildcard_match() +{ + auto input = R"({"foo": 42, "bar": "baz", "baz": null})"; + int foo = 0; + bool seen_bar = false; + bool seen_baz = false; + json::scan( + input, + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair( + hana::type_c, + [&seen_bar,&seen_baz](json::reader& reader) { + auto key = reader.value(); + TRIAL_PROTOCOL_TEST_NE(key, std::string{"foo"}); + if (key == "bar") { + auto has_next = reader.next(); + assert(has_next); + TRIAL_PROTOCOL_TEST_EQUAL(reader.literal(), "\"baz\""); + seen_bar = true; + } else if (key == "baz") { + auto has_next = reader.next(); + assert(has_next); + TRIAL_PROTOCOL_TEST_EQUAL(reader.literal(), "null"); + seen_baz = true; + } else { + assert(false); + } + json::partial::skip(reader); + }) + ) + ); + TRIAL_PROTOCOL_TEST_EQUAL(foo, 42); + TRIAL_PROTOCOL_TEST(seen_bar); + TRIAL_PROTOCOL_TEST(seen_baz); +} + +void test_missing_field() +{ + auto input = R"({"foo": 42, "baz": null})"; + int foo = 0; + std::string bar; + auto spec = hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)) + ); + TRIAL_PROTOCOL_TEST_THROW_EQUAL( + json::scan(input, spec), json::error, "request field(s) not found"); +} + +void test_missing_field2() +{ + auto input = R"({"foo": {"bar": 44}, "baz": 22})"; + int foo; + int bar; + int baz; + auto spec = hana::make_map( + hana::make_pair(BOOST_HANA_STRING("baz"), std::ref(baz)), + hana::make_pair( + BOOST_HANA_STRING("foo"), + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)) + ) + ) + ); + TRIAL_PROTOCOL_TEST_THROW_EQUAL( + json::scan(input, spec), json::error, "request field(s) not found"); +} + +void test_literal() +{ + auto input = R"({"foo": 42, "bar": [1, 2]})"; + boost::string_view bar; + json::scan( + input, + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)) + )); + TRIAL_PROTOCOL_TEST_EQUAL(bar, "[1, 2]"); +} + +void test_subtree() +{ + auto input = R"({"foo": null, "bar": [1, 2]})"; + trial::dynamic::variable foo = 33; + trial::dynamic::variable bar; + trial::dynamic::variable baz; + auto spec = hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)), + hana::make_pair(BOOST_HANA_STRING("baz"), std::ref(baz)) + ); + TRIAL_PROTOCOL_TEST_NO_THROW(json::scan(input, spec)); + TRIAL_PROTOCOL_TEST(foo.is()); + TRIAL_PROTOCOL_TEST_EQUAL(bar.size(), 2); + TRIAL_PROTOCOL_TEST(bar[0] == 1); + TRIAL_PROTOCOL_TEST(bar[1] == 2); +} + +void test_optional() +{ + auto input = R"({"foo": 42, "bar": "bar"})"; + boost::optional foo; + boost::optional bar; + boost::optional qux; + json::scan( + input, + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)), + hana::make_pair(BOOST_HANA_STRING("qux"), std::ref(qux)) + )); + TRIAL_PROTOCOL_TEST(foo); + TRIAL_PROTOCOL_TEST_EQUAL(*foo, 42); + TRIAL_PROTOCOL_TEST(bar); + TRIAL_PROTOCOL_TEST_EQUAL(*bar, "bar"); + TRIAL_PROTOCOL_TEST(!qux); +} + +void test_variant() +{ + auto input = R"({"foo": "hello", "baz": null, "foobar": 3.5, + "qux": 3, "qux2": 3.4})"; + boost::variant foo; + boost::variant bar = boost::none; + boost::variant baz = 33; + boost::variant foobar; + boost::variant qux; + boost::variant qux2; + json::scan( + input, + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)), + hana::make_pair(BOOST_HANA_STRING("baz"), std::ref(baz)), + hana::make_pair(BOOST_HANA_STRING("foobar"), std::ref(foobar)), + hana::make_pair(BOOST_HANA_STRING("qux"), std::ref(qux)), + hana::make_pair(BOOST_HANA_STRING("qux2"), std::ref(qux2)) + )); + TRIAL_PROTOCOL_TEST_EQUAL(boost::get(foo), "hello"); + TRIAL_PROTOCOL_TEST_NO_THROW(boost::get(bar)); + TRIAL_PROTOCOL_TEST_NO_THROW(boost::get(baz)); + TRIAL_PROTOCOL_TEST_EQUAL(boost::get(foobar), 3.5); + TRIAL_PROTOCOL_TEST_EQUAL(boost::get(qux), 3); + TRIAL_PROTOCOL_TEST_EQUAL(boost::get(qux2), 3); +} + +void test_variant2() +{ + auto input = R"({"baz": null})"; + boost::variant foo; + auto spec = hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)) + ); + TRIAL_PROTOCOL_TEST_THROW_EQUAL( + json::scan(input, spec), json::error, "request field(s) not found"); +} + +void test_array() +{ + auto input = R"({"foo": [1, 2, 3], + "bar": [[], [15], [16, 17]], + "baz": [null, false, true], + "qux": [false]})"; + std::vector foo; + std::vector> bar; + std::vector> baz; + boost::optional> qux; + json::scan( + input, + hana::make_map( + hana::make_pair(BOOST_HANA_STRING("foo"), std::ref(foo)), + hana::make_pair(BOOST_HANA_STRING("bar"), std::ref(bar)), + hana::make_pair(BOOST_HANA_STRING("baz"), std::ref(baz)), + hana::make_pair(BOOST_HANA_STRING("qux"), std::ref(qux)) + )); + std::vector foo_expected{1, 2, 3}; + std::vector> baz_expected; + baz_expected.push_back(boost::none); + baz_expected.push_back(false); + baz_expected.push_back(true); + TRIAL_PROTOCOL_TEST_ALL_WITH(foo.begin(), foo.end(), + foo_expected.begin(), foo_expected.end(), + std::equal_to()); + TRIAL_PROTOCOL_TEST_EQUAL(bar.size(), 3); + TRIAL_PROTOCOL_TEST_EQUAL(bar[0].size(), 0); + TRIAL_PROTOCOL_TEST_EQUAL(bar[1].size(), 1); + TRIAL_PROTOCOL_TEST_EQUAL(bar[2].size(), 2); + TRIAL_PROTOCOL_TEST_EQUAL(bar[1][0], 15); + TRIAL_PROTOCOL_TEST_EQUAL(bar[2][0], 16); + TRIAL_PROTOCOL_TEST_EQUAL(bar[2][1], 17); + TRIAL_PROTOCOL_TEST_ALL_WITH(baz.begin(), baz.end(), + baz_expected.begin(), baz_expected.end(), + std::equal_to>()); + TRIAL_PROTOCOL_TEST(qux); + TRIAL_PROTOCOL_TEST_EQUAL(qux->size(), 1); + TRIAL_PROTOCOL_TEST_EQUAL((*qux)[0], false); +} + +//----------------------------------------------------------------------------- +// main +//----------------------------------------------------------------------------- + +int main() +{ + test_plain_assignment(); + test_nested_assignment(); + test_nested_assignment2(); + test_callback(); + test_wildcard_match(); + test_missing_field(); + test_missing_field2(); + test_literal(); + test_subtree(); + test_optional(); + test_variant(); + test_variant2(); + test_array(); + + return boost::report_errors(); +}