2025-01-12 20:40:48 +08:00

623 lines
18 KiB
C++

//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
//
// 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)
//
// Official repository: https://github.com/boostorg/json
//
// Test that header file is self-contained.
#include <boost/json/value_to.hpp>
#include <boost/json/value_from.hpp>
#include <boost/describe/class.hpp>
#include <boost/describe/enum.hpp>
#include "test_suite.hpp"
#include <array>
#include <map>
#include <unordered_map>
#include <vector>
namespace value_to_test_ns
{
struct T1 { };
//----------------------------------------------------------
struct T2 { };
boost::json::result<T2>
tag_invoke(
boost::json::try_value_to_tag<T2>,
boost::json::value const& jv)
{
boost::json::string const* str = jv.if_string();
if( str && *str == "T2" )
return T2{};
return make_error_code(boost::json::error::syntax);
}
//----------------------------------------------------------
struct T3 { };
T3
tag_invoke(
boost::json::value_to_tag<T3>,
boost::json::value const& jv)
{
boost::json::string const* str = jv.if_string();
if( !str )
throw boost::json::system_error(
make_error_code(boost::json::error::not_string));
if ( *str != "T3" )
throw std::invalid_argument("");
return T3{};
}
// map-like type with fixed size
struct T4 {
using value_type = std::pair<std::string, int>;
value_type*
begin()
{
return data;
}
value_type*
end()
{
return data + sizeof(data);
}
std::pair< value_type*, bool >
emplace(value_type);
value_type data[2];
};
struct T5 { };
T5
tag_invoke(
boost::json::value_to_tag<T5>,
boost::json::value const&)
{
throw std::bad_alloc();
}
//----------------------------------------------------------
struct T6
{
int n;
double d;
};
BOOST_DESCRIBE_STRUCT(T6, (), (n, d))
//----------------------------------------------------------
struct T7 : T6
{
std::string s;
};
BOOST_DESCRIBE_STRUCT(T7, (T6), (s))
//----------------------------------------------------------
BOOST_DEFINE_ENUM_CLASS(E1, a, b, c)
//----------------------------------------------------------
struct T8
{
int n;
double d;
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
std::optional<std::string> opt_s;
#else
std::string opt_s;
#endif // BOOST_NO_CXX17_HDR_OPTIONAL
};
BOOST_DESCRIBE_STRUCT(T8, (), (n, d, opt_s))
} // namespace value_to_test_ns
namespace std
{
// some versions of libstdc++ forward-declare tuple_size as class
#if defined(__clang__) || ( defined(__GNUC__) && __GNUC__ >= 10 )
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmismatched-tags"
#endif
template<>
struct tuple_size<value_to_test_ns::T4>
: std::integral_constant<std::size_t, 2>
{ };
#if defined(__clang__) || ( defined(__GNUC__) && __GNUC__ >= 10 )
# pragma GCC diagnostic pop
#endif
} // namespace std
namespace boost {
namespace json {
template<>
struct is_null_like<::value_to_test_ns::T1> : std::true_type { };
template<>
struct is_described_class<::value_to_test_ns::T7> : std::true_type { };
template <class T, class = void>
struct can_apply_value_to
: std::false_type
{
};
template <class T>
struct can_apply_value_to<T, detail::void_t<decltype(
value_to<int>(std::declval<T>()))
>>
: std::true_type
{
};
BOOST_STATIC_ASSERT(!can_apply_value_to<int>::value);
class value_to_test
{
public:
template<class T>
void
check(T t)
{
BOOST_TEST(value_to<T>(value_from(t)) == t);
}
void
testNumberCast()
{
check((short)-1);
check((int)-2);
check((long)-3);
check((long long)-4);
check((unsigned short)1);
check((unsigned int)2);
check((unsigned long)3);
check((unsigned long long)4);
check((float)1.5);
check((double)2.5);
check(true);
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<bool>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<int>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<double>(value()) );
}
void
testJsonTypes()
{
value_to<object>(value(object_kind));
value_to<array>(value(array_kind));
value_to<string>(value(string_kind));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<object>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<array>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<string>(value()) );
}
void
testGenerics()
{
check(std::string("test"));
check(std::map<std::string, int>
{
{"a", 1}, {"b", 2}, {"c", 3}
});
check(std::multimap<std::string, int>
{
{"2", 4}, {"3", 9}, {"5", 25}
});
check(std::unordered_map<std::string, int>
{
{ "a", 1 }, {"b", 2}, {"c", 3}
});
check(std::vector<int>{1, 2, 3, 4});
check(std::vector<bool>{true, false, false, true});
check(std::make_pair(std::string("test"), 5));
check(std::make_tuple(std::string("outer"),
std::make_pair(std::string("test"), 5)));
check(std::map<int, int>
{
{2, 4}, {3, 9}, {5, 25}
});
{
std::array<int, 1000> arr;
arr.fill(0);
check(arr);
}
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::string>(value()) );
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::map<std::string, int>>(value()) ));
// element fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::map<std::string, int>>(
value{{"1", 1}, {"2", true}, {"3", false}}) ));
// reserve fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<value_to_test_ns::T4>(
value{{"1", 1}, {"2", true}, {"3", false}}) ));
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::vector<int>>(value()) );
// element fails
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::vector<int>>(value{1, 2, false, 3}) );
// reserve fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::array<int, 4>>(value{1, 2, 3}) ));
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::array<int, 4>>(value{1, 2, 3, 4, 5}) ));
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::tuple<int, int, int, int>>(value()) ));
// element fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::tuple<int, int, int>>(value{1, 2, false}) ));
// reserve fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::tuple<int, int, int, int>>(value{1, 2, 3}) ));
}
void
testContainerHelpers()
{
{
std::vector<int> v;
detail::try_reserve(
v, 10, detail::reserve_implementation<decltype(v)>());
BOOST_TEST(v.capacity() >= 10);
BOOST_STATIC_ASSERT(std::is_same<
decltype(detail::inserter(
v, detail::inserter_implementation<decltype(v)>())),
decltype(std::back_inserter(v)) >::value);
}
{
std::array<int, 2> arr;
detail::try_reserve(
arr, 2, detail::reserve_implementation<decltype(arr)>());
}
{
int n;
detail::try_reserve(
n, 5, detail::reserve_implementation<decltype(n)>());
}
}
void testNullptr()
{
(void)value_to<std::nullptr_t>(value());
(void)value_to<::value_to_test_ns::T1>(value());
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::nullptr_t>(value(1)) ));
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<::value_to_test_ns::T1>(value(1)) ));
}
void testDescribed()
{
#ifdef BOOST_DESCRIBE_CXX14
{
value jv = {{"n", -78}, {"d", 0.125}};
auto res = try_value_to<::value_to_test_ns::T6>(jv);
BOOST_TEST( res );
BOOST_TEST( res->n == -78 );
BOOST_TEST( res->d == 0.125 );
jv.as_object()["x"] = 0;
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T6>( jv ));
}
{
value jv = {{"n", 1}, {"d", 2}, {"s", "xyz"}};
auto res = try_value_to<::value_to_test_ns::T7>(jv);
BOOST_TEST( res );
BOOST_TEST( res->n == 1 );
BOOST_TEST( res->d == 2 );
BOOST_TEST( res->s == "xyz" );
}
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T6>( value(1) ));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T6>( value{{"x", 0}} ));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T6>( (value{{"n", 0}, {"x", 0}}) ));
{
value jv = "a";
auto e1 = value_to<::value_to_test_ns::E1>(jv);
BOOST_TEST( e1 == ::value_to_test_ns::E1::a );
jv = "b";
e1 = value_to<::value_to_test_ns::E1>(jv);
BOOST_TEST( e1 == ::value_to_test_ns::E1::b );
}
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::E1>( value(1) ));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::E1>( value("x") ));
{
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
value jv = {{"n", -78}, {"d", 0.125}};
auto res = try_value_to<::value_to_test_ns::T8>(jv);
BOOST_TEST( res );
BOOST_TEST( res->n == -78 );
BOOST_TEST( res->d == 0.125 );
BOOST_TEST( std::nullopt == res->opt_s );
jv.as_object()["x"] = 0;
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T8>( jv ));
#endif // BOOST_NO_CXX17_HDR_OPTIONAL
}
#endif // BOOST_DESCRIBE_CXX14
}
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
void testOptional()
{
using Opts = std::vector<std::optional<int>>;
value jv = value{1, nullptr, 3, nullptr, 5};
auto opts = value_to<Opts>(jv);
BOOST_TEST( opts == (Opts{1, {}, 3, {}, 5}) );
value_to< std::nullopt_t >(value());
BOOST_TEST_THROWS_WITH_LOCATION( value_to< std::nullopt_t >(jv) );
}
#endif
#ifndef BOOST_NO_CXX17_HDR_VARIANT
void
testVariant()
{
using Var = std::variant<int, ::value_to_test_ns::T2, std::string>;
value jv(4);
auto v = value_to<Var>(jv);
BOOST_TEST( v.index() == 0 );
BOOST_TEST( std::get<0>(v) == 4 );
jv = "foobar";
v = value_to<Var>(jv);
BOOST_TEST( v.index() == 2 );
BOOST_TEST( std::get<2>(v) == "foobar" );
jv = "T2";
v = value_to<Var>(jv);
BOOST_TEST( v.index() == 1 );
jv = 3.5;
BOOST_TEST_THROWS_WITH_LOCATION( value_to<Var>(jv) );
value_to<std::monostate>( value() );
BOOST_TEST_THROWS_WITH_LOCATION( value_to<std::monostate>(jv) );
}
#endif // BOOST_NO_CXX17_HDR_VARIANT
void
testNonThrowing()
{
// using result
{
auto res = try_value_to<::value_to_test_ns::T2>(value());
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::syntax );
res = try_value_to<::value_to_test_ns::T2>(value("T2"));
BOOST_TEST( res.has_value() );
}
// throwing overload falls back to nonthrowing customization
{
BOOST_TEST_THROWS(
value_to<::value_to_test_ns::T2>(value()),
system_error);
}
// nonthrowing overload falls back to throwing customization
{
auto res = try_value_to<::value_to_test_ns::T3>(value());
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::not_string );
res = try_value_to<::value_to_test_ns::T3>(value(""));
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::exception );
res = try_value_to<::value_to_test_ns::T3>(value("T3"));
BOOST_TEST( res.has_value() );
}
// sequence
{
// wrong input type
{
auto res = try_value_to< std::vector<::value_to_test_ns::T2> >(
value("not an array"));
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::not_array );
}
// wrong input type
{
auto res = try_value_to< std::array<int, 4> >(
value{1, 2});
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::size_mismatch );
}
// element error
{
auto res = try_value_to< std::vector<::value_to_test_ns::T2> >(
value{"T2", "T2", nullptr});
BOOST_TEST( res.error() == error::syntax );
}
// success
auto res = try_value_to< std::vector<::value_to_test_ns::T2> >(
value{"T2", "T2", "T2"});
BOOST_TEST( res.has_value() );
BOOST_TEST( res->size() == 3 );
}
// map
{
// wrong input type
{
auto res = try_value_to<
std::map<std::string, ::value_to_test_ns::T2> >(
value("not a map"));
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::not_object );
}
// reserve fails
{
auto res = try_value_to< ::value_to_test_ns::T4 >(
value{{"1", 1}, {"2", 2}, {"3", 3}});
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::size_mismatch );
}
// element error
{
auto res = try_value_to<
std::map<std::string, ::value_to_test_ns::T2> >(
value{{"1", "T2"}, {"2", "T2"}, {"3", nullptr}});
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::syntax );
}
// success
auto res = try_value_to<
std::map<std::string, ::value_to_test_ns::T2> >(
value{{"1", "T2"}, {"2", "T2"}, {"3", "T2"}});
BOOST_TEST( res.has_value() );
BOOST_TEST( res->size() == 3 );
}
// tuple
{
// wrong input type
{
auto res = try_value_to<std::tuple<
int,
std::string,
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value("not an array"));
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::not_array );
}
// size mismatch
{
auto res = try_value_to<std::tuple<
int,
std::string,
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value{1, 2});
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::size_mismatch );
}
// element error
{
auto res = try_value_to<std::tuple<
int,
std::string,
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value{1, "foobar", false, nullptr, ""});
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::syntax );
}
// success
auto res = try_value_to<std::tuple<
int,
std::string,
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value{1, "foobar", false, nullptr, "T2"});
BOOST_TEST( res.has_value() );
BOOST_TEST( std::get<0>(*res) == 1 );
BOOST_TEST( std::get<1>(*res) == "foobar" );
BOOST_TEST_NOT( std::get<2>(*res) );
}
// rethrowing bad_alloc
BOOST_TEST_THROWS(
try_value_to<value_to_test_ns::T5>(value()),
std::bad_alloc);
}
void
testUserConversion()
{
value_to<value_to_test_ns::T2>(value("T2"));
}
void
run()
{
testNumberCast();
testJsonTypes();
testGenerics();
testContainerHelpers();
testNullptr();
testDescribed();
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
testOptional();
#endif
#ifndef BOOST_NO_CXX17_HDR_VARIANT
testVariant();
#endif // BOOST_NO_CXX17_HDR_VARIANT
testUserConversion();
testNonThrowing();
}
};
TEST_SUITE(value_to_test, "boost.json.value_to");
} // namespace json
} // namespace boost