json/test/parse_into.cpp
2023-10-09 16:59:39 +03:00

468 lines
15 KiB
C++

//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2021 Peter Dimov
//
// 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/parse_into.hpp>
#include <boost/json/serialize.hpp>
#include <boost/json/value_from.hpp>
#include <boost/describe.hpp>
#include <climits>
#include <map>
#include "test.hpp"
#include "test_suite.hpp"
struct X
{
int a;
float b;
std::string c;
};
BOOST_DESCRIBE_STRUCT(X, (), (a, b, c))
bool operator==( X const& x1, X const& x2 )
{
return x1.a == x2.a && x1.b == x2.b && x1.c == x2.c;
}
struct Y
{
std::vector<X> v;
std::map<std::string, X> m;
};
BOOST_DESCRIBE_STRUCT(Y, (), (v, m))
bool operator==( Y const& y1, Y const& y2 )
{
return y1.v == y2.v && y1.m == y2.m;
}
BOOST_DEFINE_ENUM_CLASS(E, x, y, z)
namespace boost {
namespace json {
class parse_into_test
{
public:
template<class T> void testParseInto( T const& t )
{
#if defined(__GNUC__) && __GNUC__ < 5
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
T t1( t );
std::string json = serialize( value_from( t1 ) );
T t2{};
error_code jec;
parse_into(t2, json, jec);
BOOST_TEST( !jec.failed() ) && BOOST_TEST( t1 == t2 );
T t3{};
std::error_code ec;
parse_into(t3, json, ec);
BOOST_TEST( !ec ) && BOOST_TEST( t1 == t3 );
T t4{};
parse_into(t4, json);
BOOST_TEST( t1 == t4 );
std::istringstream is(json);
T t5{};
jec = {};
parse_into(t5, is, jec);
BOOST_TEST( !jec.failed() ) && BOOST_TEST( t1 == t5 );
is.clear();
is.seekg(0);
T t6{};
ec = {};
parse_into(t6, is, ec);
BOOST_TEST( !ec ) && BOOST_TEST( t1 == t6 );
is.str(json);
is.clear();
T t7{};
parse_into(t7, is);
BOOST_TEST( t1 == t7 );
parse_options opt;
opt.allow_comments = true;
json = "// this is a comment\n" + json;
T t8{};
parser_for<T> p( opt, &t8 );
for( auto& c: json )
{
std::size_t const n = p.write_some( true, &c, 1, jec );
BOOST_TEST( !jec.failed() );
BOOST_TEST( n == 1 );
}
p.write_some(false, nullptr, 0, jec);
BOOST_TEST( !jec.failed() );
BOOST_TEST( t1 == t8 );
#if defined(__GNUC__) && __GNUC__ < 5
# pragma GCC diagnostic pop
#endif
}
template<class T>
void
testParseIntoErrors( error e, value const& sample )
{
#if defined(__GNUC__) && __GNUC__ < 5
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
error_code ec;
T t{};
std::string json = serialize(sample);
parser_for<T> p( parse_options{}, &t );
for( auto& c: json )
{
std::size_t const n = p.write_some( true, &c, 1, ec );
if( ec.failed() )
break;
BOOST_TEST( n == 1 );
}
if( !ec.failed() )
p.write_some(false, nullptr, 0, ec);
BOOST_TEST( ec.failed() );
BOOST_TEST( ec.has_location() );
BOOST_TEST( ec == e );
#if defined(__GNUC__) && __GNUC__ < 5
# pragma GCC diagnostic pop
#endif
}
void testNull()
{
testParseInto( nullptr );
testParseIntoErrors< std::nullptr_t >( error::not_null, 100 );
}
void testBoolean()
{
testParseInto( false );
testParseInto( true );
testParseIntoErrors< bool >( error::not_bool, nullptr );
}
void testIntegral()
{
testParseInto<char>( 'A' ); // ?
testParseInto<signed char>( -127 );
testParseInto<unsigned char>( 255 );
testParseInto<short>( -32767 );
testParseInto<unsigned short>( 65535 );
testParseInto<int>( -32767 );
testParseInto<unsigned int>( 65535 );
testParseInto<long>( LONG_MIN );
testParseInto<unsigned long>( ULONG_MAX );
testParseInto<long long>( LLONG_MIN );
testParseInto<unsigned long long>( ULLONG_MAX );
testParseIntoErrors< int >( error::not_integer, "123" );
testParseIntoErrors< int >( error::not_integer, true );
testParseIntoErrors< int >( error::not_exact, LLONG_MIN );
testParseIntoErrors< int >( error::not_exact, ULONG_MAX );
}
void testFloatingPoint()
{
testParseInto( 0.25f );
testParseInto( 1.125 );
// value_from doesn't support long double
// testParseInto( 2.25L );
{
double d1 = 12;
std::string json = serialize( value_from( 12 ) );
error_code ec;
double d2;
parse_into(d2, json, ec);
BOOST_TEST( !ec.failed() );
BOOST_TEST( d1 == d2 );
d1 = double(UINT64_MAX);
json = serialize( value_from( UINT64_MAX ) );
parse_into(d2, json, ec);
BOOST_TEST( !ec.failed() );
BOOST_TEST( d1 == d2 );
}
testParseIntoErrors< double >( error::not_double, { {"value", 12.1 } } );
}
void testString()
{
testParseInto<std::string>( "" );
testParseInto<std::string>( "12345" );
testParseIntoErrors< string >( error::not_string, UINT64_MAX );
}
void testSequence()
{
testParseInto<std::vector<std::nullptr_t>>( { nullptr, nullptr } );
testParseInto< std::vector<bool> >( {} );
testParseInto< std::vector<bool> >( { true, false } );
testParseInto< std::vector<int> >( {} );
testParseInto< std::vector<int> >( { 1, 2, 3 } );
testParseInto< std::vector<std::uint64_t> >( { 1, 2, UINT64_MAX } );
testParseInto< std::vector<float> >( {} );
testParseInto< std::vector<float> >( { 1.02f, 2.11f, 3.14f } );
testParseInto< std::vector<std::string> >( {} );
testParseInto< std::vector<std::string> >( { "one", "two", "three" } );
testParseInto< std::vector<std::vector<int>> >( {} );
testParseInto< std::vector<std::vector<int>> >( { {}, { 1 }, { 2, 3 }, { 4, 5, 6 } } );
testParseInto< std::vector<std::map<std::string, int>> >(
{ {}, { {"1", 2}, {"3", 4} }, { { "5", 6 } } } );
// clang <= 5 doesn't like when std::array is created from init-list
std::array<int, 4> arr;
arr.fill(17);
testParseInto< std::array<int, 4> >( arr );
testParseIntoErrors< std::vector<int> >( error::not_array, 1 );
testParseIntoErrors< std::vector<char> >( error::not_array, "abcd" );
testParseIntoErrors< std::vector<std::vector<int>> >(
error::not_array, {1, 2, 3} );
testParseIntoErrors< std::array<int, 4> >(
error::size_mismatch, {1, 2, 3} );
testParseInto< std::vector<std::array<int, 4>> >( {arr,arr,arr} );
std::vector<int> v;
parse_into(v, "[1,2,3,4]");
BOOST_TEST( v.size() == 4 );
parse_into(v, "[5,6,7]");
BOOST_TEST( v.size() == 3 );
}
void testMap()
{
testParseInto< std::map<std::string, int> >( {} );
testParseInto< std::map<std::string, int> >( { { "one", 1 }, { "two", 2 } } );
testParseInto< std::map<std::string, int> >( {} );
testParseInto< std::map<std::string, int> >( { { "one", 1 }, { "two", 2 } } );
testParseInto< std::map<std::string, std::vector<int>> >( {} );
testParseInto< std::map<std::string, std::vector<int>> >( { { "one", { 1 } }, { "two", { 2, 3 } } } );
testParseInto< std::map<std::string, std::map<std::string, int>> >( {} );
testParseInto< std::map<std::string, std::map<std::string, int>> >( { { "one", {} }, { "two", { { "1", 1 }, { "2", 2 } } } } );
testParseIntoErrors< std::map<std::string, int> >(
error::not_object, { "1", 1, "2", 2} );
testParseIntoErrors< std::map<std::string, std::map<std::string, int>> >(
error::not_object, { {"1", {}}, {"2", {"3", 4}} } );
std::map<std::string, int> m;
parse_into(m, R"( {"1": 1, "2": 2, "3": 3} )");
BOOST_TEST( m.size() == 3 );
parse_into(m, R"( {"4": 4, "5": 5} )");
BOOST_TEST( m.size() == 2 );
}
void testTuple()
{
testParseInto<std::pair<int, float>>( {} );
testParseInto<std::pair<int, float>>( { 1, 3.14f } );
testParseInto<std::pair<std::nullptr_t, std::uint64_t>>(
{nullptr, UINT64_MAX} );
testParseInto<std::tuple<int, float, std::string>>( {} );
testParseInto<std::tuple<int, float, std::string>>( std::make_tuple(1, 3.14f, "hello") );
testParseInto<std::vector<std::pair<int, int>>>( {} );
testParseInto<std::vector<std::pair<int, int>>>( { { 1, 2 }, { 3, 4 } } );
testParseInto<std::vector<std::vector<std::pair<int, int>>>>( {} );
testParseInto<std::vector<std::vector<std::pair<int, int>>>>( { { { 1, 2 }, { 3, 4 } } } );
testParseInto<std::vector<std::vector<std::pair<int, int>>>>( { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } } );
testParseInto<std::map<std::string, std::vector<std::pair<int, int>>>>( {} );
testParseInto<std::map<std::string, std::vector<std::pair<int, int>>>>( { { "one", {} } } );
testParseInto<std::map<std::string, std::vector<std::pair<int, int>>>>( { { "one", { { 1, 2 }, { 3, 4 } } } } );
testParseInto<std::pair<std::vector<int>, std::map<std::string, std::pair<int, bool>>>>( {} );
testParseInto<std::pair<std::vector<int>, std::map<std::string, std::pair<int, bool>>>>( { { 1, 2, 3 }, { { "one", { 7, true } } } } );
testParseIntoErrors< std::pair<int, int> >( error::not_array, 1 );
testParseIntoErrors< std::pair<int, int> >(
error::size_mismatch, {1, 2, 3} );
testParseIntoErrors< std::tuple<int, int, int> >(
error::size_mismatch, {1, 2} );
}
void testStruct()
{
#if defined(BOOST_DESCRIBE_CXX14)
testParseInto<X>( {} );
testParseInto<X>( { 1, 3.14f, "hello" } );
testParseInto<Y>( {} );
testParseInto<Y>( { { { 1, 1.0f, "one" }, { 2, 2.0f, "two" } }, { { "one", { 1, 1.1f, "1" } }, { "two", { 2, 2.2f, "2" } } } } );
testParseIntoErrors<X>( error::not_object, 1 );
testParseIntoErrors<X>(
error::unknown_name,
{ {"a", 1}, {"b", 3.14f}, {"c", "hello"}, {"d", 0} } );
testParseIntoErrors<X>( error::size_mismatch, { {"a", 1} } );
#endif
}
void testEnum()
{
#ifdef BOOST_DESCRIBE_CXX14
testParseInto<E>( E::x );
testParseInto<E>( E::y );
testParseInto<E>( E::z );
testParseIntoErrors< E >( error::not_string, (int)(E::y) );
#endif // BOOST_DESCRIBE_CXX14
}
template< template<class...> class Variant, class Monostate >
void testVariant()
{
testParseInto< Variant<int> >( 1 );
testParseInto< Variant<int, std::string> >( 1 );
testParseInto< Variant<int, std::string> >( "qwerty" );
testParseInto< Variant<Monostate, int, std::string> >( {} );
testParseInto< Variant<Monostate, int, std::string> >( 1 );
testParseInto< Variant<Monostate, int, std::string> >( "qwerty" );
testParseInto< Variant<bool, std::uint64_t> >( true );
testParseInto< Variant<bool, std::uint64_t> >( UINT64_MAX );
testParseInto< Variant< std::vector<int> > >(
std::vector<int>{1, 2, 3, 4, 5} );
testParseInto< Variant< Monostate, std::vector<int> > >(
std::vector<int>{1, 2, 3, 4, 5} );
testParseInto< std::vector< Variant<int, std::string> > >(
{1, 2, 3, "four", 5, "six", "seven", 8});
using V = Variant<
std::vector< int >,
std::tuple< int, std::string, std::map<std::string, int> >,
std::tuple< int, std::string, std::map<std::string, double> > >;
testParseInto< V >(
std::make_tuple(
5,
"five",
std::map<std::string, double>{ {"one", 1}, {"pi", 3.14} }));
testParseIntoErrors< Variant<Monostate> >(
error::exhausted_variants, "a" );
testParseIntoErrors< Variant<int> >(
error::exhausted_variants, "a" );
testParseIntoErrors< Variant<Monostate, int, bool> >(
error::exhausted_variants, "a" );
}
void testOptional()
{
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
testParseInto< std::optional<int> >( std::nullopt );
testParseInto< std::optional<int> >( 1 );
testParseInto< std::optional<std::vector<std::nullptr_t>> >(
std::nullopt );
testParseInto< std::optional<std::vector<std::nullptr_t>> >(
std::vector<std::nullptr_t>{} );
testParseInto< std::optional<std::vector<std::nullptr_t>> >(
std::vector<std::nullptr_t>{nullptr, nullptr} );
testParseInto< std::optional<std::vector<std::string>> >(
std::vector<std::string>{"1", "2", "3"} );
testParseInto< std::vector< std::optional<int> > >(
{1, 2, 3, std::nullopt, 5, std::nullopt, std::nullopt, 8});
#endif
}
void run()
{
testNull();
testBoolean();
testIntegral();
testFloatingPoint();
testString();
testSequence();
testMap();
testTuple();
testStruct();
testEnum();
testOptional();
testVariant<variant2::variant, variant2::monostate>();
#ifndef BOOST_NO_CXX17_HDR_VARIANT
testVariant<std::variant, std::monostate>();
#endif
{
int n;
BOOST_TEST_THROWS_WITH_LOCATION( parse_into( n, "null" ) );
std::stringstream is("null");
BOOST_TEST_THROWS_WITH_LOCATION( parse_into( n, is ) );
}
{
int n;
error_code ec;
parse_into( n, "12 1", ec);
BOOST_TEST( ec == error::extra_data );
BOOST_TEST( ec.has_location() );
}
{
std::stringstream is("12 1");
int n;
error_code ec;
parse_into( n, is, ec);
BOOST_TEST( ec == error::extra_data );
BOOST_TEST( ec.has_location() );
}
{
int n;
std::stringstream is("1");
is.setstate( std::ios::failbit );
error_code ec;
parse_into(n, is, ec);
BOOST_TEST( ec == error::input_error );
BOOST_TEST( ec.has_location() );
}
}
};
TEST_SUITE(parse_into_test, "boost.json.parse_into");
} // namespace boost
} // namespace json