//------------------------------------------------------------------------------
/*
    This file is part of rippled: https://github.com/ripple/rippled
    Copyright (c) 2012, 2013 Ripple Labs Inc.

    Permission to use, copy, modify, and/or distribute this software for any
    purpose  with  or without fee is hereby granted, provided that the above
    copyright notice and this permission notice appear in all copies.

    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/json/json_errors.h>
#include <xrpl/json/json_reader.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/json_writer.h>

#include <doctest/doctest.h>

#include <algorithm>
#include <regex>
#include <sstream>
#include <string>

namespace ripple {

TEST_SUITE_BEGIN("json_value");

TEST_CASE("construct and compare Json::StaticString")
{
    static constexpr char sample[]{"Contents of a Json::StaticString"};

    static constexpr Json::StaticString test1(sample);
    char const* addrTest1{test1};

    CHECK(addrTest1 == &sample[0]);
    CHECK(test1.c_str() == &sample[0]);

    static constexpr Json::StaticString test2{
        "Contents of a Json::StaticString"};
    static constexpr Json::StaticString test3{"Another StaticString"};

    CHECK(test1 == test2);
    CHECK(test1 != test3);

    std::string str{sample};
    CHECK(str == test2);
    CHECK(str != test3);
    CHECK(test2 == str);
    CHECK(test3 != str);
}

TEST_CASE("different types")
{
    // Exercise ValueType constructor
    static constexpr Json::StaticString staticStr{"staticStr"};

    auto testCopy = [](Json::ValueType typ) {
        Json::Value val{typ};
        Json::Value cpy{val};
        CHECK(val.type() == typ);
        CHECK(cpy.type() == typ);
        return val;
    };
    {
        Json::Value const nullV{testCopy(Json::nullValue)};
        CHECK(nullV.isNull());
        CHECK(!nullV.isBool());
        CHECK(!nullV.isInt());
        CHECK(!nullV.isUInt());
        CHECK(!nullV.isIntegral());
        CHECK(!nullV.isDouble());
        CHECK(!nullV.isNumeric());
        CHECK(!nullV.isString());
        CHECK(!nullV.isArray());
        CHECK(nullV.isArrayOrNull());
        CHECK(!nullV.isObject());
        CHECK(nullV.isObjectOrNull());
    }
    {
        Json::Value const intV{testCopy(Json::intValue)};
        CHECK(!intV.isNull());
        CHECK(!intV.isBool());
        CHECK(intV.isInt());
        CHECK(!intV.isUInt());
        CHECK(intV.isIntegral());
        CHECK(!intV.isDouble());
        CHECK(intV.isNumeric());
        CHECK(!intV.isString());
        CHECK(!intV.isArray());
        CHECK(!intV.isArrayOrNull());
        CHECK(!intV.isObject());
        CHECK(!intV.isObjectOrNull());
    }
    {
        Json::Value const uintV{testCopy(Json::uintValue)};
        CHECK(!uintV.isNull());
        CHECK(!uintV.isBool());
        CHECK(!uintV.isInt());
        CHECK(uintV.isUInt());
        CHECK(uintV.isIntegral());
        CHECK(!uintV.isDouble());
        CHECK(uintV.isNumeric());
        CHECK(!uintV.isString());
        CHECK(!uintV.isArray());
        CHECK(!uintV.isArrayOrNull());
        CHECK(!uintV.isObject());
        CHECK(!uintV.isObjectOrNull());
    }
    {
        Json::Value const realV{testCopy(Json::realValue)};
        CHECK(!realV.isNull());
        CHECK(!realV.isBool());
        CHECK(!realV.isInt());
        CHECK(!realV.isUInt());
        CHECK(!realV.isIntegral());
        CHECK(realV.isDouble());
        CHECK(realV.isNumeric());
        CHECK(!realV.isString());
        CHECK(!realV.isArray());
        CHECK(!realV.isArrayOrNull());
        CHECK(!realV.isObject());
        CHECK(!realV.isObjectOrNull());
    }
    {
        Json::Value const stringV{testCopy(Json::stringValue)};
        CHECK(!stringV.isNull());
        CHECK(!stringV.isBool());
        CHECK(!stringV.isInt());
        CHECK(!stringV.isUInt());
        CHECK(!stringV.isIntegral());
        CHECK(!stringV.isDouble());
        CHECK(!stringV.isNumeric());
        CHECK(stringV.isString());
        CHECK(!stringV.isArray());
        CHECK(!stringV.isArrayOrNull());
        CHECK(!stringV.isObject());
        CHECK(!stringV.isObjectOrNull());
    }
    {
        Json::Value const staticStrV{staticStr};
        {
            Json::Value cpy{staticStrV};
            CHECK(staticStrV.type() == Json::stringValue);
            CHECK(cpy.type() == Json::stringValue);
        }
        CHECK(!staticStrV.isNull());
        CHECK(!staticStrV.isBool());
        CHECK(!staticStrV.isInt());
        CHECK(!staticStrV.isUInt());
        CHECK(!staticStrV.isIntegral());
        CHECK(!staticStrV.isDouble());
        CHECK(!staticStrV.isNumeric());
        CHECK(staticStrV.isString());
        CHECK(!staticStrV.isArray());
        CHECK(!staticStrV.isArrayOrNull());
        CHECK(!staticStrV.isObject());
        CHECK(!staticStrV.isObjectOrNull());
    }
    {
        Json::Value const boolV{testCopy(Json::booleanValue)};
        CHECK(!boolV.isNull());
        CHECK(boolV.isBool());
        CHECK(!boolV.isInt());
        CHECK(!boolV.isUInt());
        CHECK(boolV.isIntegral());
        CHECK(!boolV.isDouble());
        CHECK(boolV.isNumeric());
        CHECK(!boolV.isString());
        CHECK(!boolV.isArray());
        CHECK(!boolV.isArrayOrNull());
        CHECK(!boolV.isObject());
        CHECK(!boolV.isObjectOrNull());
    }
    {
        Json::Value const arrayV{testCopy(Json::arrayValue)};
        CHECK(!arrayV.isNull());
        CHECK(!arrayV.isBool());
        CHECK(!arrayV.isInt());
        CHECK(!arrayV.isUInt());
        CHECK(!arrayV.isIntegral());
        CHECK(!arrayV.isDouble());
        CHECK(!arrayV.isNumeric());
        CHECK(!arrayV.isString());
        CHECK(arrayV.isArray());
        CHECK(arrayV.isArrayOrNull());
        CHECK(!arrayV.isObject());
        CHECK(!arrayV.isObjectOrNull());
    }
    {
        Json::Value const objectV{testCopy(Json::objectValue)};
        CHECK(!objectV.isNull());
        CHECK(!objectV.isBool());
        CHECK(!objectV.isInt());
        CHECK(!objectV.isUInt());
        CHECK(!objectV.isIntegral());
        CHECK(!objectV.isDouble());
        CHECK(!objectV.isNumeric());
        CHECK(!objectV.isString());
        CHECK(!objectV.isArray());
        CHECK(!objectV.isArrayOrNull());
        CHECK(objectV.isObject());
        CHECK(objectV.isObjectOrNull());
    }
}

TEST_CASE("compare strings")
{
    auto doCompare = [&](Json::Value const& lhs,
                         Json::Value const& rhs,
                         bool lhsEqRhs,
                         bool lhsLtRhs,
                         int line) {
        CAPTURE(line);
        CHECK((lhs == rhs) == lhsEqRhs);
        CHECK((lhs != rhs) != lhsEqRhs);
        CHECK((lhs < rhs) == (!(lhsEqRhs || !lhsLtRhs)));
        CHECK((lhs <= rhs) == (lhsEqRhs || lhsLtRhs));
        CHECK((lhs >= rhs) == (lhsEqRhs || !lhsLtRhs));
        CHECK((lhs > rhs) == (!(lhsEqRhs || lhsLtRhs)));
    };

    Json::Value const null0;
    Json::Value const intNeg1{-1};
    Json::Value const int0{Json::intValue};
    Json::Value const intPos1{1};
    Json::Value const uint0{Json::uintValue};
    Json::Value const uint1{1u};
    Json::Value const realNeg1{-1.0};
    Json::Value const real0{Json::realValue};
    Json::Value const realPos1{1.0};
    Json::Value const str0{Json::stringValue};
    Json::Value const str1{"1"};
    Json::Value const boolF{false};
    Json::Value const boolT{true};
    Json::Value const array0{Json::arrayValue};
    Json::Value const array1{[]() {
        Json::Value array1;
        array1[0u] = 1;
        return array1;
    }()};
    Json::Value const obj0{Json::objectValue};
    Json::Value const obj1{[]() {
        Json::Value obj1;
        obj1["one"] = 1;
        return obj1;
    }()};

#pragma push_macro("DO_COMPARE")
    // DO_COMPARE(lhs, rhs, lhsEqualsToRhs lhsLessThanRhs)
#define DO_COMPARE(lhs, rhs, eq, lt) doCompare(lhs, rhs, eq, lt, __LINE__)
    DO_COMPARE(null0, Json::Value{}, true, false);
    DO_COMPARE(null0, intNeg1, false, true);
    DO_COMPARE(null0, int0, false, true);
    DO_COMPARE(null0, intPos1, false, true);
    DO_COMPARE(null0, uint0, false, true);
    DO_COMPARE(null0, uint1, false, true);
    DO_COMPARE(null0, realNeg1, false, true);
    DO_COMPARE(null0, real0, false, true);
    DO_COMPARE(null0, realPos1, false, true);
    DO_COMPARE(null0, str0, false, true);
    DO_COMPARE(null0, str1, false, true);
    DO_COMPARE(null0, boolF, false, true);
    DO_COMPARE(null0, boolT, false, true);
    DO_COMPARE(null0, array0, false, true);
    DO_COMPARE(null0, array1, false, true);
    DO_COMPARE(null0, obj0, false, true);
    DO_COMPARE(null0, obj1, false, true);

    DO_COMPARE(intNeg1, null0, false, false);
    DO_COMPARE(intNeg1, intNeg1, true, false);
    DO_COMPARE(intNeg1, int0, false, true);
    DO_COMPARE(intNeg1, intPos1, false, true);
    DO_COMPARE(intNeg1, uint0, false, true);
    DO_COMPARE(intNeg1, uint1, false, true);
    DO_COMPARE(intNeg1, realNeg1, false, true);
    DO_COMPARE(intNeg1, real0, false, true);
    DO_COMPARE(intNeg1, realPos1, false, true);
    DO_COMPARE(intNeg1, str0, false, true);
    DO_COMPARE(intNeg1, str1, false, true);
    DO_COMPARE(intNeg1, boolF, false, true);
    DO_COMPARE(intNeg1, boolT, false, true);
    DO_COMPARE(intNeg1, array0, false, true);
    DO_COMPARE(intNeg1, array1, false, true);
    DO_COMPARE(intNeg1, obj0, false, true);
    DO_COMPARE(intNeg1, obj1, false, true);

    DO_COMPARE(int0, null0, false, false);
    DO_COMPARE(int0, intNeg1, false, false);
    DO_COMPARE(int0, int0, true, false);
    DO_COMPARE(int0, intPos1, false, true);
    DO_COMPARE(int0, uint0, true, false);
    DO_COMPARE(int0, uint1, false, true);
    DO_COMPARE(int0, realNeg1, false, true);
    DO_COMPARE(int0, real0, false, true);
    DO_COMPARE(int0, realPos1, false, true);
    DO_COMPARE(int0, str0, false, true);
    DO_COMPARE(int0, str1, false, true);
    DO_COMPARE(int0, boolF, false, true);
    DO_COMPARE(int0, boolT, false, true);
    DO_COMPARE(int0, array0, false, true);
    DO_COMPARE(int0, array1, false, true);
    DO_COMPARE(int0, obj0, false, true);
    DO_COMPARE(int0, obj1, false, true);

    DO_COMPARE(intPos1, null0, false, false);
    DO_COMPARE(intPos1, intNeg1, false, false);
    DO_COMPARE(intPos1, int0, false, false);
    DO_COMPARE(intPos1, intPos1, true, false);
    DO_COMPARE(intPos1, uint0, false, false);
    DO_COMPARE(intPos1, uint1, true, false);
    DO_COMPARE(intPos1, realNeg1, false, true);
    DO_COMPARE(intPos1, real0, false, true);
    DO_COMPARE(intPos1, realPos1, false, true);
    DO_COMPARE(intPos1, str0, false, true);
    DO_COMPARE(intPos1, str1, false, true);
    DO_COMPARE(intPos1, boolF, false, true);
    DO_COMPARE(intPos1, boolT, false, true);
    DO_COMPARE(intPos1, array0, false, true);
    DO_COMPARE(intPos1, array1, false, true);
    DO_COMPARE(intPos1, obj0, false, true);
    DO_COMPARE(intPos1, obj1, false, true);

    DO_COMPARE(uint0, null0, false, false);
    DO_COMPARE(uint0, intNeg1, false, false);
    DO_COMPARE(uint0, int0, true, false);
    DO_COMPARE(uint0, intPos1, false, true);
    DO_COMPARE(uint0, uint0, true, false);
    DO_COMPARE(uint0, uint1, false, true);
    DO_COMPARE(uint0, realNeg1, false, true);
    DO_COMPARE(uint0, real0, false, true);
    DO_COMPARE(uint0, realPos1, false, true);
    DO_COMPARE(uint0, str0, false, true);
    DO_COMPARE(uint0, str1, false, true);
    DO_COMPARE(uint0, boolF, false, true);
    DO_COMPARE(uint0, boolT, false, true);
    DO_COMPARE(uint0, array0, false, true);
    DO_COMPARE(uint0, array1, false, true);
    DO_COMPARE(uint0, obj0, false, true);
    DO_COMPARE(uint0, obj1, false, true);

    DO_COMPARE(uint1, null0, false, false);
    DO_COMPARE(uint1, intNeg1, false, false);
    DO_COMPARE(uint1, int0, false, false);
    DO_COMPARE(uint1, intPos1, true, false);
    DO_COMPARE(uint1, uint0, false, false);
    DO_COMPARE(uint1, uint1, true, false);
    DO_COMPARE(uint1, realNeg1, false, true);
    DO_COMPARE(uint1, real0, false, true);
    DO_COMPARE(uint1, realPos1, false, true);
    DO_COMPARE(uint1, str0, false, true);
    DO_COMPARE(uint1, str1, false, true);
    DO_COMPARE(uint1, boolF, false, true);
    DO_COMPARE(uint1, boolT, false, true);
    DO_COMPARE(uint1, array0, false, true);
    DO_COMPARE(uint1, array1, false, true);
    DO_COMPARE(uint1, obj0, false, true);
    DO_COMPARE(uint1, obj1, false, true);

    DO_COMPARE(realNeg1, null0, false, false);
    DO_COMPARE(realNeg1, intNeg1, false, false);
    DO_COMPARE(realNeg1, int0, false, false);
    DO_COMPARE(realNeg1, intPos1, false, false);
    DO_COMPARE(realNeg1, uint0, false, false);
    DO_COMPARE(realNeg1, uint1, false, false);
    DO_COMPARE(realNeg1, realNeg1, true, false);
    DO_COMPARE(realNeg1, real0, false, true);
    DO_COMPARE(realNeg1, realPos1, false, true);
    DO_COMPARE(realNeg1, str0, false, true);
    DO_COMPARE(realNeg1, str1, false, true);
    DO_COMPARE(realNeg1, boolF, false, true);
    DO_COMPARE(realNeg1, boolT, false, true);
    DO_COMPARE(realNeg1, array0, false, true);
    DO_COMPARE(realNeg1, array1, false, true);
    DO_COMPARE(realNeg1, obj0, false, true);
    DO_COMPARE(realNeg1, obj1, false, true);

    DO_COMPARE(real0, null0, false, false);
    DO_COMPARE(real0, intNeg1, false, false);
    DO_COMPARE(real0, int0, false, false);
    DO_COMPARE(real0, intPos1, false, false);
    DO_COMPARE(real0, uint0, false, false);
    DO_COMPARE(real0, uint1, false, false);
    DO_COMPARE(real0, realNeg1, false, false);
    DO_COMPARE(real0, real0, true, false);
    DO_COMPARE(real0, realPos1, false, true);
    DO_COMPARE(real0, str0, false, true);
    DO_COMPARE(real0, str1, false, true);
    DO_COMPARE(real0, boolF, false, true);
    DO_COMPARE(real0, boolT, false, true);
    DO_COMPARE(real0, array0, false, true);
    DO_COMPARE(real0, array1, false, true);
    DO_COMPARE(real0, obj0, false, true);
    DO_COMPARE(real0, obj1, false, true);

    DO_COMPARE(realPos1, null0, false, false);
    DO_COMPARE(realPos1, intNeg1, false, false);
    DO_COMPARE(realPos1, int0, false, false);
    DO_COMPARE(realPos1, intPos1, false, false);
    DO_COMPARE(realPos1, uint0, false, false);
    DO_COMPARE(realPos1, uint1, false, false);
    DO_COMPARE(realPos1, realNeg1, false, false);
    DO_COMPARE(realPos1, real0, false, false);
    DO_COMPARE(realPos1, realPos1, true, false);
    DO_COMPARE(realPos1, str0, false, true);
    DO_COMPARE(realPos1, str1, false, true);
    DO_COMPARE(realPos1, boolF, false, true);
    DO_COMPARE(realPos1, boolT, false, true);
    DO_COMPARE(realPos1, array0, false, true);
    DO_COMPARE(realPos1, array1, false, true);
    DO_COMPARE(realPos1, obj0, false, true);
    DO_COMPARE(realPos1, obj1, false, true);

    DO_COMPARE(str0, null0, false, false);
    DO_COMPARE(str0, intNeg1, false, false);
    DO_COMPARE(str0, int0, false, false);
    DO_COMPARE(str0, intPos1, false, false);
    DO_COMPARE(str0, uint0, false, false);
    DO_COMPARE(str0, uint1, false, false);
    DO_COMPARE(str0, realNeg1, false, false);
    DO_COMPARE(str0, real0, false, false);
    DO_COMPARE(str0, realPos1, false, false);
    DO_COMPARE(str0, str0, true, false);
    DO_COMPARE(str0, str1, false, true);
    DO_COMPARE(str0, boolF, false, true);
    DO_COMPARE(str0, boolT, false, true);
    DO_COMPARE(str0, array0, false, true);
    DO_COMPARE(str0, array1, false, true);
    DO_COMPARE(str0, obj0, false, true);
    DO_COMPARE(str0, obj1, false, true);

    DO_COMPARE(str1, null0, false, false);
    DO_COMPARE(str1, intNeg1, false, false);
    DO_COMPARE(str1, int0, false, false);
    DO_COMPARE(str1, intPos1, false, false);
    DO_COMPARE(str1, uint0, false, false);
    DO_COMPARE(str1, uint1, false, false);
    DO_COMPARE(str1, realNeg1, false, false);
    DO_COMPARE(str1, real0, false, false);
    DO_COMPARE(str1, realPos1, false, false);
    DO_COMPARE(str1, str0, false, false);
    DO_COMPARE(str1, str1, true, false);
    DO_COMPARE(str1, boolF, false, true);
    DO_COMPARE(str1, boolT, false, true);
    DO_COMPARE(str1, array0, false, true);
    DO_COMPARE(str1, array1, false, true);
    DO_COMPARE(str1, obj0, false, true);
    DO_COMPARE(str1, obj1, false, true);

    DO_COMPARE(boolF, null0, false, false);
    DO_COMPARE(boolF, intNeg1, false, false);
    DO_COMPARE(boolF, int0, false, false);
    DO_COMPARE(boolF, intPos1, false, false);
    DO_COMPARE(boolF, uint0, false, false);
    DO_COMPARE(boolF, uint1, false, false);
    DO_COMPARE(boolF, realNeg1, false, false);
    DO_COMPARE(boolF, real0, false, false);
    DO_COMPARE(boolF, realPos1, false, false);
    DO_COMPARE(boolF, str0, false, false);
    DO_COMPARE(boolF, str1, false, false);
    DO_COMPARE(boolF, boolF, true, false);
    DO_COMPARE(boolF, boolT, false, true);
    DO_COMPARE(boolF, array0, false, true);
    DO_COMPARE(boolF, array1, false, true);
    DO_COMPARE(boolF, obj0, false, true);
    DO_COMPARE(boolF, obj1, false, true);

    DO_COMPARE(boolT, null0, false, false);
    DO_COMPARE(boolT, intNeg1, false, false);
    DO_COMPARE(boolT, int0, false, false);
    DO_COMPARE(boolT, intPos1, false, false);
    DO_COMPARE(boolT, uint0, false, false);
    DO_COMPARE(boolT, uint1, false, false);
    DO_COMPARE(boolT, realNeg1, false, false);
    DO_COMPARE(boolT, real0, false, false);
    DO_COMPARE(boolT, realPos1, false, false);
    DO_COMPARE(boolT, str0, false, false);
    DO_COMPARE(boolT, str1, false, false);
    DO_COMPARE(boolT, boolF, false, false);
    DO_COMPARE(boolT, boolT, true, false);
    DO_COMPARE(boolT, array0, false, true);
    DO_COMPARE(boolT, array1, false, true);
    DO_COMPARE(boolT, obj0, false, true);
    DO_COMPARE(boolT, obj1, false, true);

    DO_COMPARE(array0, null0, false, false);
    DO_COMPARE(array0, intNeg1, false, false);
    DO_COMPARE(array0, int0, false, false);
    DO_COMPARE(array0, intPos1, false, false);
    DO_COMPARE(array0, uint0, false, false);
    DO_COMPARE(array0, uint1, false, false);
    DO_COMPARE(array0, realNeg1, false, false);
    DO_COMPARE(array0, real0, false, false);
    DO_COMPARE(array0, realPos1, false, false);
    DO_COMPARE(array0, str0, false, false);
    DO_COMPARE(array0, str1, false, false);
    DO_COMPARE(array0, boolF, false, false);
    DO_COMPARE(array0, boolT, false, false);
    DO_COMPARE(array0, array0, true, false);
    DO_COMPARE(array0, array1, false, true);
    DO_COMPARE(array0, obj0, false, true);
    DO_COMPARE(array0, obj1, false, true);

    DO_COMPARE(array1, null0, false, false);
    DO_COMPARE(array1, intNeg1, false, false);
    DO_COMPARE(array1, int0, false, false);
    DO_COMPARE(array1, intPos1, false, false);
    DO_COMPARE(array1, uint0, false, false);
    DO_COMPARE(array1, uint1, false, false);
    DO_COMPARE(array1, realNeg1, false, false);
    DO_COMPARE(array1, real0, false, false);
    DO_COMPARE(array1, realPos1, false, false);
    DO_COMPARE(array1, str0, false, false);
    DO_COMPARE(array1, str1, false, false);
    DO_COMPARE(array1, boolF, false, false);
    DO_COMPARE(array1, boolT, false, false);
    DO_COMPARE(array1, array0, false, false);
    DO_COMPARE(array1, array1, true, false);
    DO_COMPARE(array1, obj0, false, true);
    DO_COMPARE(array1, obj1, false, true);

    DO_COMPARE(obj0, null0, false, false);
    DO_COMPARE(obj0, intNeg1, false, false);
    DO_COMPARE(obj0, int0, false, false);
    DO_COMPARE(obj0, intPos1, false, false);
    DO_COMPARE(obj0, uint0, false, false);
    DO_COMPARE(obj0, uint1, false, false);
    DO_COMPARE(obj0, realNeg1, false, false);
    DO_COMPARE(obj0, real0, false, false);
    DO_COMPARE(obj0, realPos1, false, false);
    DO_COMPARE(obj0, str0, false, false);
    DO_COMPARE(obj0, str1, false, false);
    DO_COMPARE(obj0, boolF, false, false);
    DO_COMPARE(obj0, boolT, false, false);
    DO_COMPARE(obj0, array0, false, false);
    DO_COMPARE(obj0, array1, false, false);
    DO_COMPARE(obj0, obj0, true, false);
    DO_COMPARE(obj0, obj1, false, true);

    DO_COMPARE(obj1, null0, false, false);
    DO_COMPARE(obj1, intNeg1, false, false);
    DO_COMPARE(obj1, int0, false, false);
    DO_COMPARE(obj1, intPos1, false, false);
    DO_COMPARE(obj1, uint0, false, false);
    DO_COMPARE(obj1, uint1, false, false);
    DO_COMPARE(obj1, realNeg1, false, false);
    DO_COMPARE(obj1, real0, false, false);
    DO_COMPARE(obj1, realPos1, false, false);
    DO_COMPARE(obj1, str0, false, false);
    DO_COMPARE(obj1, str1, false, false);
    DO_COMPARE(obj1, boolF, false, false);
    DO_COMPARE(obj1, boolT, false, false);
    DO_COMPARE(obj1, array0, false, false);
    DO_COMPARE(obj1, array1, false, false);
    DO_COMPARE(obj1, obj0, false, false);
    DO_COMPARE(obj1, obj1, true, false);
#undef DO_COMPARE
#pragma pop_macro("DO_COMPARE")
}

TEST_CASE("bool")
{
    CHECK(!Json::Value());

    CHECK(!Json::Value(""));

    CHECK(bool(Json::Value("empty")));
    CHECK(bool(Json::Value(false)));
    CHECK(bool(Json::Value(true)));
    CHECK(bool(Json::Value(0)));
    CHECK(bool(Json::Value(1)));

    Json::Value array(Json::arrayValue);
    CHECK(!array);
    array.append(0);
    CHECK(bool(array));

    Json::Value object(Json::objectValue);
    CHECK(!object);
    object[""] = false;
    CHECK(bool(object));
}

TEST_CASE("bad json")
{
    char const* s(R"({"method":"ledger","params":[{"ledger_index":1e300}]})");

    Json::Value j;
    Json::Reader r;

    CHECK(r.parse(s, j));
}

TEST_CASE("edge cases")
{
    std::string json;

    std::uint32_t max_uint = std::numeric_limits<std::uint32_t>::max();
    std::int32_t max_int = std::numeric_limits<std::int32_t>::max();
    std::int32_t min_int = std::numeric_limits<std::int32_t>::min();

    std::uint32_t a_uint = max_uint - 1978;
    std::int32_t a_large_int = max_int - 1978;
    std::int32_t a_small_int = min_int + 1978;

    json = "{\"max_uint\":" + std::to_string(max_uint);
    json += ",\"max_int\":" + std::to_string(max_int);
    json += ",\"min_int\":" + std::to_string(min_int);
    json += ",\"a_uint\":" + std::to_string(a_uint);
    json += ",\"a_large_int\":" + std::to_string(a_large_int);
    json += ",\"a_small_int\":" + std::to_string(a_small_int);
    json += "}";

    Json::Value j1;
    Json::Reader r1;

    CHECK(r1.parse(json, j1));
    CHECK(j1["max_uint"].asUInt() == max_uint);
    CHECK(j1["max_int"].asInt() == max_int);
    CHECK(j1["min_int"].asInt() == min_int);
    CHECK(j1["a_uint"].asUInt() == a_uint);
    CHECK(j1["a_uint"] > a_large_int);
    CHECK(j1["a_uint"] > a_small_int);
    CHECK(j1["a_large_int"].asInt() == a_large_int);
    CHECK(j1["a_large_int"].asUInt() == a_large_int);
    CHECK(j1["a_large_int"] < a_uint);
    CHECK(j1["a_small_int"].asInt() == a_small_int);
    CHECK(j1["a_small_int"] < a_uint);

    json = "{\"overflow\":";
    json += std::to_string(std::uint64_t(max_uint) + 1);
    json += "}";

    Json::Value j2;
    Json::Reader r2;

    CHECK(!r2.parse(json, j2));

    json = "{\"underflow\":";
    json += std::to_string(std::int64_t(min_int) - 1);
    json += "}";

    Json::Value j3;
    Json::Reader r3;

    CHECK(!r3.parse(json, j3));

    Json::Value intString{"4294967296"};
    CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast);

    intString = "4294967295";
    CHECK(intString.asUInt() == 4294967295u);

    intString = "0";
    CHECK(intString.asUInt() == 0);

    intString = "-1";
    CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast);

    intString = "2147483648";
    CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast);

    intString = "2147483647";
    CHECK(intString.asInt() == 2147483647);

    intString = "-2147483648";
    CHECK(intString.asInt() == -2147483648LL);  // MSVC wants the LL

    intString = "-2147483649";
    CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast);
}

TEST_CASE("copy")
{
    Json::Value v1{2.5};
    CHECK(v1.isDouble());
    CHECK(v1.asDouble() == 2.5);

    Json::Value v2 = v1;
    CHECK(v1.isDouble());
    CHECK(v1.asDouble() == 2.5);
    CHECK(v2.isDouble());
    CHECK(v2.asDouble() == 2.5);
    CHECK(v1 == v2);

    v1 = v2;
    CHECK(v1.isDouble());
    CHECK(v1.asDouble() == 2.5);
    CHECK(v2.isDouble());
    CHECK(v2.asDouble() == 2.5);
    CHECK(v1 == v2);
}

TEST_CASE("move")
{
    Json::Value v1{2.5};
    CHECK(v1.isDouble());
    CHECK(v1.asDouble() == 2.5);

    Json::Value v2 = std::move(v1);
    CHECK(!v1);
    CHECK(v2.isDouble());
    CHECK(v2.asDouble() == 2.5);
    CHECK(v1 != v2);

    v1 = std::move(v2);
    CHECK(v1.isDouble());
    CHECK(v1.asDouble() == 2.5);
    CHECK(!v2);
    CHECK(v1 != v2);
}

TEST_CASE("comparisons")
{
    Json::Value a, b;
    auto testEquals = [&](std::string const& name) {
        CHECK(a == b);
        CHECK(a <= b);
        CHECK(a >= b);

        CHECK(!(a != b));
        CHECK(!(a < b));
        CHECK(!(a > b));

        CHECK(b == a);
        CHECK(b <= a);
        CHECK(b >= a);

        CHECK(!(b != a));
        CHECK(!(b < a));
        CHECK(!(b > a));
    };

    auto testGreaterThan = [&](std::string const& name) {
        CHECK(!(a == b));
        CHECK(!(a <= b));
        CHECK(a >= b);

        CHECK(a != b);
        CHECK(!(a < b));
        CHECK(a > b);

        CHECK(!(b == a));
        CHECK(b <= a);
        CHECK(!(b >= a));

        CHECK(b != a);
        CHECK(b < a);
        CHECK(!(b > a));
    };

    a["a"] = Json::UInt(0);
    b["a"] = Json::Int(0);
    testEquals("zero");

    b["a"] = Json::Int(-1);
    testGreaterThan("negative");

    Json::Int big = std::numeric_limits<int>::max();
    Json::UInt bigger = big;
    bigger++;

    a["a"] = bigger;
    b["a"] = big;
    testGreaterThan("big");
}

TEST_CASE("compact")
{
    Json::Value j;
    Json::Reader r;
    char const* s("{\"array\":[{\"12\":23},{},null,false,0.5]}");

    auto countLines = [](std::string const& str) {
        return 1 + std::count_if(str.begin(), str.end(), [](char c) {
                   return c == '\n';
               });
    };

    CHECK(r.parse(s, j));
    {
        std::stringstream ss;
        ss << j;
        CHECK(countLines(ss.str()) > 1);
    }
    {
        std::stringstream ss;
        ss << Json::Compact(std::move(j));
        CHECK(countLines(ss.str()) == 1);
    }
}

TEST_CASE("conversions")
{
    // We have Json::Int, but not Json::Double or Json::Real.
    // We have Json::Int, Json::Value::Int, and Json::ValueType::intValue.
    // We have Json::ValueType::realValue but Json::Value::asDouble.
    // TODO: What's the thinking here?
    {
        // null
        Json::Value val;
        CHECK(val.isNull());
        // val.asCString() should trigger an assertion failure
        CHECK(val.asString() == "");
        CHECK(val.asInt() == 0);
        CHECK(val.asUInt() == 0);
        CHECK(val.asDouble() == 0.0);
        CHECK(val.asBool() == false);

        CHECK(val.isConvertibleTo(Json::nullValue));
        CHECK(val.isConvertibleTo(Json::intValue));
        CHECK(val.isConvertibleTo(Json::uintValue));
        CHECK(val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(val.isConvertibleTo(Json::booleanValue));
        CHECK(val.isConvertibleTo(Json::arrayValue));
        CHECK(val.isConvertibleTo(Json::objectValue));
    }
    {
        // int
        Json::Value val = -1234;
        CHECK(val.isInt());
        // val.asCString() should trigger an assertion failure
        CHECK(val.asString() == "-1234");
        CHECK(val.asInt() == -1234);
        CHECK_THROWS_AS(val.asUInt(), Json::error);
        CHECK(val.asDouble() == -1234.0);
        CHECK(val.asBool() == true);

        CHECK(!val.isConvertibleTo(Json::nullValue));
        CHECK(val.isConvertibleTo(Json::intValue));
        CHECK(!val.isConvertibleTo(Json::uintValue));
        CHECK(val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // uint
        Json::Value val = 1234U;
        CHECK(val.isUInt());
        // val.asCString() should trigger an assertion failure
        CHECK(val.asString() == "1234");
        CHECK(val.asInt() == 1234);
        CHECK(val.asUInt() == 1234u);
        CHECK(val.asDouble() == 1234.0);
        CHECK(val.asBool() == true);

        CHECK(!val.isConvertibleTo(Json::nullValue));
        CHECK(val.isConvertibleTo(Json::intValue));
        CHECK(val.isConvertibleTo(Json::uintValue));
        CHECK(val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // real
        Json::Value val = 2.0;
        CHECK(val.isDouble());
        // val.asCString() should trigger an assertion failure
        CHECK(std::regex_match(val.asString(), std::regex("^2\\.0*$")));
        CHECK(val.asInt() == 2);
        CHECK(val.asUInt() == 2u);
        CHECK(val.asDouble() == 2.0);
        CHECK(val.asBool() == true);

        CHECK(!val.isConvertibleTo(Json::nullValue));
        CHECK(val.isConvertibleTo(Json::intValue));
        CHECK(val.isConvertibleTo(Json::uintValue));
        CHECK(val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // numeric string
        Json::Value val = "54321";
        CHECK(val.isString());
        CHECK(strcmp(val.asCString(), "54321") == 0);
        CHECK(val.asString() == "54321");
        CHECK(val.asInt() == 54321);
        CHECK(val.asUInt() == 54321u);
        CHECK_THROWS_AS(val.asDouble(), Json::error);
        CHECK(val.asBool() == true);

        CHECK(!val.isConvertibleTo(Json::nullValue));
        CHECK(!val.isConvertibleTo(Json::intValue));
        CHECK(!val.isConvertibleTo(Json::uintValue));
        CHECK(!val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(!val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // non-numeric string
        Json::Value val(Json::stringValue);
        CHECK(val.isString());
        CHECK(val.asCString() == nullptr);
        CHECK(val.asString() == "");
        CHECK_THROWS_AS(val.asInt(), std::exception);
        CHECK_THROWS_AS(val.asUInt(), std::exception);
        CHECK_THROWS_AS(val.asDouble(), std::exception);
        CHECK(val.asBool() == false);

        CHECK(val.isConvertibleTo(Json::nullValue));
        CHECK(!val.isConvertibleTo(Json::intValue));
        CHECK(!val.isConvertibleTo(Json::uintValue));
        CHECK(!val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(!val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // bool false
        Json::Value val = false;
        CHECK(val.isBool());
        // val.asCString() should trigger an assertion failure
        CHECK(val.asString() == "false");
        CHECK(val.asInt() == 0);
        CHECK(val.asUInt() == 0);
        CHECK(val.asDouble() == 0.0);
        CHECK(val.asBool() == false);

        CHECK(val.isConvertibleTo(Json::nullValue));
        CHECK(val.isConvertibleTo(Json::intValue));
        CHECK(val.isConvertibleTo(Json::uintValue));
        CHECK(val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // bool true
        Json::Value val = true;
        CHECK(val.isBool());
        // val.asCString() should trigger an assertion failure
        CHECK(val.asString() == "true");
        CHECK(val.asInt() == 1);
        CHECK(val.asUInt() == 1);
        CHECK(val.asDouble() == 1.0);
        CHECK(val.asBool() == true);

        CHECK(!val.isConvertibleTo(Json::nullValue));
        CHECK(val.isConvertibleTo(Json::intValue));
        CHECK(val.isConvertibleTo(Json::uintValue));
        CHECK(val.isConvertibleTo(Json::realValue));
        CHECK(val.isConvertibleTo(Json::stringValue));
        CHECK(val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // array type
        Json::Value val(Json::arrayValue);
        CHECK(val.isArray());
        // val.asCString should trigger an assertion failure
        CHECK_THROWS_AS(val.asString(), Json::error);
        CHECK_THROWS_AS(val.asInt(), Json::error);
        CHECK_THROWS_AS(val.asUInt(), Json::error);
        CHECK_THROWS_AS(val.asDouble(), Json::error);
        CHECK(val.asBool() == false);  // empty or not

        CHECK(val.isConvertibleTo(Json::nullValue));
        CHECK(!val.isConvertibleTo(Json::intValue));
        CHECK(!val.isConvertibleTo(Json::uintValue));
        CHECK(!val.isConvertibleTo(Json::realValue));
        CHECK(!val.isConvertibleTo(Json::stringValue));
        CHECK(!val.isConvertibleTo(Json::booleanValue));
        CHECK(val.isConvertibleTo(Json::arrayValue));
        CHECK(!val.isConvertibleTo(Json::objectValue));
    }
    {
        // object type
        Json::Value val(Json::objectValue);
        CHECK(val.isObject());
        // val.asCString should trigger an assertion failure
        CHECK_THROWS_AS(val.asString(), Json::error);
        CHECK_THROWS_AS(val.asInt(), Json::error);
        CHECK_THROWS_AS(val.asUInt(), Json::error);
        CHECK_THROWS_AS(val.asDouble(), Json::error);
        CHECK(val.asBool() == false);  // empty or not

        CHECK(val.isConvertibleTo(Json::nullValue));
        CHECK(!val.isConvertibleTo(Json::intValue));
        CHECK(!val.isConvertibleTo(Json::uintValue));
        CHECK(!val.isConvertibleTo(Json::realValue));
        CHECK(!val.isConvertibleTo(Json::stringValue));
        CHECK(!val.isConvertibleTo(Json::booleanValue));
        CHECK(!val.isConvertibleTo(Json::arrayValue));
        CHECK(val.isConvertibleTo(Json::objectValue));
    }
}

TEST_CASE("access members")
{
    Json::Value val;
    CHECK(val.type() == Json::nullValue);
    CHECK(val.size() == 0);
    CHECK(!val.isValidIndex(0));
    CHECK(!val.isMember("key"));
    {
        Json::Value const constVal = val;
        CHECK(constVal[7u].type() == Json::nullValue);
        CHECK(!constVal.isMember("key"));
        CHECK(constVal["key"].type() == Json::nullValue);
        CHECK(constVal.getMemberNames().empty());
        CHECK(constVal.get(1u, "default0") == "default0");
        CHECK(constVal.get(std::string("not"), "oh") == "oh");
        CHECK(constVal.get("missing", "default2") == "default2");
    }

    val = -7;
    CHECK(val.type() == Json::intValue);
    CHECK(val.size() == 0);
    CHECK(!val.isValidIndex(0));
    CHECK(!val.isMember("key"));

    val = 42u;
    CHECK(val.type() == Json::uintValue);
    CHECK(val.size() == 0);
    CHECK(!val.isValidIndex(0));
    CHECK(!val.isMember("key"));

    val = 3.14159;
    CHECK(val.type() == Json::realValue);
    CHECK(val.size() == 0);
    CHECK(!val.isValidIndex(0));
    CHECK(!val.isMember("key"));

    val = true;
    CHECK(val.type() == Json::booleanValue);
    CHECK(val.size() == 0);
    CHECK(!val.isValidIndex(0));
    CHECK(!val.isMember("key"));

    val = "string";
    CHECK(val.type() == Json::stringValue);
    CHECK(val.size() == 0);
    CHECK(!val.isValidIndex(0));
    CHECK(!val.isMember("key"));

    val = Json::Value(Json::objectValue);
    CHECK(val.type() == Json::objectValue);
    CHECK(val.size() == 0);
    static Json::StaticString const staticThree("three");
    val[staticThree] = 3;
    val["two"] = 2;
    CHECK(val.size() == 2);
    CHECK(val.isValidIndex(1));
    CHECK(!val.isValidIndex(2));
    CHECK(val[staticThree] == 3);
    CHECK(val.isMember("two"));
    CHECK(val.isMember(staticThree));
    CHECK(!val.isMember("key"));
    {
        Json::Value const constVal = val;
        CHECK(constVal["two"] == 2);
        CHECK(constVal["four"].type() == Json::nullValue);
        CHECK(constVal[staticThree] == 3);
        CHECK(constVal.isMember("two"));
        CHECK(constVal.isMember(staticThree));
        CHECK(!constVal.isMember("key"));
        CHECK(val.get(std::string("two"), "backup") == 2);
        CHECK(val.get("missing", "default2") == "default2");
    }

    val = Json::Value(Json::arrayValue);
    CHECK(val.type() == Json::arrayValue);
    CHECK(val.size() == 0);
    val[0u] = "zero";
    val[1u] = "one";
    CHECK(val.size() == 2);
    CHECK(val.isValidIndex(1));
    CHECK(!val.isValidIndex(2));
    CHECK(val[20u].type() == Json::nullValue);
    CHECK(!val.isMember("key"));
    {
        Json::Value const constVal = val;
        CHECK(constVal[0u] == "zero");
        CHECK(constVal[2u].type() == Json::nullValue);
        CHECK(!constVal.isMember("key"));
        CHECK(val.get(1u, "default0") == "one");
        CHECK(val.get(3u, "default1") == "default1");
    }
}

TEST_CASE("remove members")
{
    Json::Value val;
    CHECK(val.removeMember(std::string("member")).type() == Json::nullValue);

    val = Json::Value(Json::objectValue);
    static Json::StaticString const staticThree("three");
    val[staticThree] = 3;
    val["two"] = 2;
    CHECK(val.size() == 2);

    CHECK(val.removeMember(std::string("six")).type() == Json::nullValue);
    CHECK(val.size() == 2);

    CHECK(val.removeMember(staticThree) == 3);
    CHECK(val.size() == 1);

    CHECK(val.removeMember(staticThree).type() == Json::nullValue);
    CHECK(val.size() == 1);

    CHECK(val.removeMember(std::string("two")) == 2);
    CHECK(val.size() == 0);

    CHECK(val.removeMember(std::string("two")).type() == Json::nullValue);
    CHECK(val.size() == 0);
}

TEST_CASE("iterator")
{
    {
        // Iterating an array.
        Json::Value arr{Json::arrayValue};
        arr[0u] = "zero";
        arr[1u] = "one";
        arr[2u] = "two";
        arr[3u] = "three";

        Json::ValueIterator const b{arr.begin()};
        Json::ValueIterator const e{arr.end()};

        Json::ValueIterator i1 = b;
        Json::ValueIterator i2 = e;
        --i2;

        // key(), index(), and memberName() on an object iterator.
        CHECK(b != e);
        CHECK(!(b == e));
        CHECK(i1.key() == 0);
        CHECK(i2.key() == 3);
        CHECK(i1.index() == 0);
        CHECK(i2.index() == 3);
        CHECK(std::strcmp(i1.memberName(), "") == 0);
        CHECK(std::strcmp(i2.memberName(), "") == 0);

        // Pre and post increment and decrement.
        *i1++ = "0";
        CHECK(*i1 == "one");
        *i1 = "1";
        ++i1;

        *i2-- = "3";
        CHECK(*i2 == "two");
        CHECK(i1 == i2);
        *i2 = "2";
        CHECK(*i1 == "2");
    }
    {
        // Iterating a const object.
        Json::Value const obj{[]() {
            Json::Value obj{Json::objectValue};
            obj["0"] = 0;
            obj["1"] = 1;
            obj["2"] = 2;
            obj["3"] = 3;
            return obj;
        }()};

        Json::ValueConstIterator i1{obj.begin()};
        Json::ValueConstIterator i2{obj.end()};
        --i2;

        // key(), index(), and memberName() on an object iterator.
        CHECK(i1 != i2);
        CHECK(!(i1 == i2));
        CHECK(i1.key() == "0");
        CHECK(i2.key() == "3");
        CHECK(i1.index() == -1);
        CHECK(i2.index() == -1);
        CHECK(std::strcmp(i1.memberName(), "0") == 0);
        CHECK(std::strcmp(i2.memberName(), "3") == 0);

        // Pre and post increment and decrement.
        CHECK(*i1++ == 0);
        CHECK(*i1 == 1);
        ++i1;

        CHECK(*i2-- == 3);
        CHECK(*i2 == 2);
        CHECK(i1 == i2);
        CHECK(*i1 == 2);
    }
    {
        // Iterating a non-const null object.
        Json::Value nul{};
        CHECK(nul.begin() == nul.end());
    }
    {
        // Iterating a const Int.
        Json::Value const i{-3};
        CHECK(i.begin() == i.end());
    }
}

TEST_CASE("nest limits")
{
    Json::Reader r;
    {
        auto nest = [](std::uint32_t depth) -> std::string {
            std::string s = "{";
            for (std::uint32_t i{1}; i <= depth; ++i)
                s += "\"obj\":{";
            for (std::uint32_t i{1}; i <= depth; ++i)
                s += "}";
            s += "}";
            return s;
        };

        {
            // Within object nest limit
            auto json{nest(std::min(10u, Json::Reader::nest_limit))};
            Json::Value j;
            CHECK(r.parse(json, j));
        }

        {
            // Exceed object nest limit
            auto json{nest(Json::Reader::nest_limit + 1)};
            Json::Value j;
            CHECK(!r.parse(json, j));
        }
    }

    auto nest = [](std::uint32_t depth) -> std::string {
        std::string s = "{";
        for (std::uint32_t i{1}; i <= depth; ++i)
            s += "\"array\":[{";
        for (std::uint32_t i{1}; i <= depth; ++i)
            s += "]}";
        s += "}";
        return s;
    };
    {
        // Exceed array nest limit
        auto json{nest(Json::Reader::nest_limit + 1)};
        Json::Value j;
        CHECK(!r.parse(json, j));
    }
}

TEST_CASE("memory leak")
{
    // When run with the address sanitizer, this test confirms there is no
    // memory leak with the scenarios below.
    {
        Json::Value a;
        a[0u] = 1;
        CHECK(a.type() == Json::arrayValue);
        CHECK(a[0u].type() == Json::intValue);
        a = std::move(a[0u]);
        CHECK(a.type() == Json::intValue);
    }
    {
        Json::Value b;
        Json::Value temp;
        temp["a"] = "Probably avoids the small string optimization";
        temp["b"] = "Also probably avoids the small string optimization";
        CHECK(temp.type() == Json::objectValue);
        b.append(temp);
        CHECK(temp.type() == Json::objectValue);
        CHECK(b.size() == 1);

        b.append(std::move(temp));
        CHECK(b.size() == 2);

        // Note that the type() == nullValue check is implementation
        // specific and not guaranteed to be valid in the future.
        CHECK(temp.type() == Json::nullValue);
    }
}

TEST_SUITE_END();

}  // namespace ripple
