/*
 * Copyright 2011-present Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/json.h>

#include <iterator>
#include <limits>

#include <folly/portability/GTest.h>

using folly::dynamic;
using folly::parseJson;
using folly::toJson;

TEST(Json, Unicode) {
  auto val = parseJson(u8"\"I \u2665 UTF-8\"");
  EXPECT_EQ(u8"I \u2665 UTF-8", val.asString());
  val = parseJson("\"I \\u2665 UTF-8\"");
  EXPECT_EQ(u8"I \u2665 UTF-8", val.asString());
  val = parseJson(u8"\"I \U0001D11E playing in G-clef\"");
  EXPECT_EQ(u8"I \U0001D11E playing in G-clef", val.asString());

  val = parseJson("\"I \\uD834\\uDD1E playing in G-clef\"");
  EXPECT_EQ(u8"I \U0001D11E playing in G-clef", val.asString());
}

TEST(Json, Parse) {
  auto num = parseJson("12");
  EXPECT_TRUE(num.isInt());
  EXPECT_EQ(num, 12);
  num = parseJson("12e5");
  EXPECT_TRUE(num.isDouble());
  EXPECT_EQ(num, 12e5);
  auto numAs1 = num.asDouble();
  EXPECT_EQ(numAs1, 12e5);
  EXPECT_EQ(num, 12e5);
  EXPECT_EQ(num, 1200000);

  auto largeNumber = parseJson("4611686018427387904");
  EXPECT_TRUE(largeNumber.isInt());
  EXPECT_EQ(largeNumber, 4611686018427387904L);

  auto negative = parseJson("-123");
  EXPECT_EQ(negative, -123);

  auto bfalse = parseJson("false");
  auto btrue = parseJson("true");
  EXPECT_EQ(bfalse, false);
  EXPECT_EQ(btrue, true);

  auto null = parseJson("null");
  EXPECT_TRUE(null == nullptr);

  auto doub1 = parseJson("12.0");
  auto doub2 = parseJson("12e2");
  EXPECT_EQ(doub1, 12.0);
  EXPECT_EQ(doub2, 12e2);
  EXPECT_EQ(
      std::numeric_limits<double>::infinity(),
      parseJson("Infinity").asDouble());
  EXPECT_EQ(
      -std::numeric_limits<double>::infinity(),
      parseJson("-Infinity").asDouble());
  EXPECT_TRUE(std::isnan(parseJson("NaN").asDouble()));

  // case matters
  EXPECT_THROW(parseJson("infinity"), std::runtime_error);
  EXPECT_THROW(parseJson("inf"), std::runtime_error);
  EXPECT_THROW(parseJson("Inf"), std::runtime_error);
  EXPECT_THROW(parseJson("INF"), std::runtime_error);
  EXPECT_THROW(parseJson("nan"), std::runtime_error);
  EXPECT_THROW(parseJson("NAN"), std::runtime_error);

  auto array = parseJson("[12,false, false  , null , [12e4,32, [], 12]]");
  EXPECT_EQ(array.size(), 5);
  if (array.size() == 5) {
    EXPECT_EQ(std::prev(array.end())->size(), 4);
  }

  EXPECT_THROW(parseJson("\n[12,\n\nnotvalidjson"), std::runtime_error);

  EXPECT_THROW(parseJson("12e2e2"), std::runtime_error);

  EXPECT_THROW(
      parseJson("{\"foo\":12,\"bar\":42} \"something\""), std::runtime_error);

  // clang-format off
  dynamic value = dynamic::object
    ("foo", "bar")
    ("junk", 12)
    ("another", 32.2)
    ("a",
      dynamic::array(
          dynamic::object("a", "b")("c", "d"),
          12.5,
          "Yo Dawg",
          dynamic::array("heh"),
          nullptr));
  // clang-format on

  // Print then parse and get the same thing, hopefully.
  EXPECT_EQ(value, parseJson(toJson(value)));

  // Test an object with non-string values.
  dynamic something =
      parseJson("{\"old_value\":40,\"changed\":true,\"opened\":false}");
  dynamic expected =
      dynamic::object("old_value", 40)("changed", true)("opened", false);
  EXPECT_EQ(something, expected);
}

TEST(Json, ParseTrailingComma) {
  folly::json::serialization_opts on, off;
  on.allow_trailing_comma = true;
  off.allow_trailing_comma = false;

  dynamic arr = dynamic::array(1, 2);
  EXPECT_EQ(arr, parseJson("[1, 2]", on));
  EXPECT_EQ(arr, parseJson("[1, 2,]", on));
  EXPECT_EQ(arr, parseJson("[1, 2, ]", on));
  EXPECT_EQ(arr, parseJson("[1, 2 , ]", on));
  EXPECT_EQ(arr, parseJson("[1, 2 ,]", on));
  EXPECT_THROW(parseJson("[1, 2,]", off), std::runtime_error);

  dynamic obj = dynamic::object("a", 1);
  EXPECT_EQ(obj, parseJson("{\"a\": 1}", on));
  EXPECT_EQ(obj, parseJson("{\"a\": 1,}", on));
  EXPECT_EQ(obj, parseJson("{\"a\": 1, }", on));
  EXPECT_EQ(obj, parseJson("{\"a\": 1 , }", on));
  EXPECT_EQ(obj, parseJson("{\"a\": 1 ,}", on));
  EXPECT_THROW(parseJson("{\"a\":1,}", off), std::runtime_error);
}

TEST(Json, BoolConversion) {
  EXPECT_TRUE(parseJson("42").asBool());
}

TEST(Json, JavascriptSafe) {
  auto badDouble = int64_t((1ULL << 63ULL) + 1);
  dynamic badDyn = badDouble;
  EXPECT_EQ(folly::toJson(badDouble), folly::to<std::string>(badDouble));
  folly::json::serialization_opts opts;
  opts.javascript_safe = true;
  EXPECT_ANY_THROW(folly::json::serialize(badDouble, opts));

  auto okDouble = int64_t(1ULL << 63ULL);
  dynamic okDyn = okDouble;
  EXPECT_EQ(folly::toJson(okDouble), folly::to<std::string>(okDouble));
}

TEST(Json, Produce) {
  auto value = parseJson(R"( "f\"oo" )");
  EXPECT_EQ(toJson(value), R"("f\"oo")");
  value = parseJson("\"Control code: \001 \002 \x1f\"");
  EXPECT_EQ(toJson(value), R"("Control code: \u0001 \u0002 \u001f")");

  // We're not allowed to have non-string keys in json.
  EXPECT_THROW(
      toJson(dynamic::object("abc", "xyz")(42.33, "asd")), std::runtime_error);

  // Check Infinity/Nan
  folly::json::serialization_opts opts;
  opts.allow_nan_inf = true;
  EXPECT_EQ("Infinity", folly::json::serialize(parseJson("Infinity"), opts));
  EXPECT_EQ("NaN", folly::json::serialize(parseJson("NaN"), opts));
}

TEST(Json, JsonEscape) {
  folly::json::serialization_opts opts;
  EXPECT_EQ(
      folly::json::serialize("\b\f\n\r\x01\t\\\"/\v\a", opts),
      R"("\b\f\n\r\u0001\t\\\"/\u000b\u0007")");
}

TEST(Json, EscapeCornerCases) {
  // The escaping logic uses some bitwise operations to determine
  // which bytes need escaping 8 bytes at a time. Test that this logic
  // is correct regardless of positions by planting 2 characters that
  // may need escaping at each possible position and checking the
  // result, for varying string lengths.

  folly::json::serialization_opts opts;
  opts.validate_utf8 = true;

  std::string s;
  std::string expected;
  for (bool ascii : {true, false}) {
    opts.encode_non_ascii = ascii;

    for (size_t len = 2; len < 32; ++len) {
      for (size_t i = 0; i < len; ++i) {
        for (size_t j = 0; j < len; ++j) {
          if (i == j) {
            continue;
          }

          s.clear();
          expected.clear();

          expected.push_back('"');
          for (size_t pos = 0; pos < len; ++pos) {
            if (pos == i) {
              s.push_back('\\');
              expected.append("\\\\");
            } else if (pos == j) {
              s.append("\xe2\x82\xac");
              expected.append(ascii ? "\\u20ac" : "\xe2\x82\xac");
            } else {
              s.push_back('x');
              expected.push_back('x');
            }
          }
          expected.push_back('"');

          EXPECT_EQ(folly::json::serialize(s, opts), expected) << ascii;
        }
      }
    }
  }
}

TEST(Json, JsonNonAsciiEncoding) {
  folly::json::serialization_opts opts;
  opts.encode_non_ascii = true;

  // simple tests
  EXPECT_EQ(folly::json::serialize("\x1f", opts), R"("\u001f")");
  EXPECT_EQ(folly::json::serialize("\xc2\xa2", opts), R"("\u00a2")");
  EXPECT_EQ(folly::json::serialize("\xe2\x82\xac", opts), R"("\u20ac")");

  // multiple unicode encodings
  EXPECT_EQ(
      folly::json::serialize("\x1f\xe2\x82\xac", opts), R"("\u001f\u20ac")");
  EXPECT_EQ(
      folly::json::serialize("\x1f\xc2\xa2\xe2\x82\xac", opts),
      R"("\u001f\u00a2\u20ac")");
  EXPECT_EQ(
      folly::json::serialize("\xc2\x80\xef\xbf\xbf", opts),
      R"("\u0080\uffff")");
  EXPECT_EQ(
      folly::json::serialize("\xe0\xa0\x80\xdf\xbf", opts),
      R"("\u0800\u07ff")");

  // first possible sequence of a certain length
  EXPECT_EQ(folly::json::serialize("\xc2\x80", opts), R"("\u0080")");
  EXPECT_EQ(folly::json::serialize("\xe0\xa0\x80", opts), R"("\u0800")");

  // last possible sequence of a certain length
  EXPECT_EQ(folly::json::serialize("\xdf\xbf", opts), R"("\u07ff")");
  EXPECT_EQ(folly::json::serialize("\xef\xbf\xbf", opts), R"("\uffff")");

  // other boundary conditions
  EXPECT_EQ(folly::json::serialize("\xed\x9f\xbf", opts), R"("\ud7ff")");
  EXPECT_EQ(folly::json::serialize("\xee\x80\x80", opts), R"("\ue000")");
  EXPECT_EQ(folly::json::serialize("\xef\xbf\xbd", opts), R"("\ufffd")");

  // incomplete sequences
  EXPECT_ANY_THROW(folly::json::serialize("a\xed\x9f", opts));
  EXPECT_ANY_THROW(folly::json::serialize("b\xee\x80", opts));
  EXPECT_ANY_THROW(folly::json::serialize("c\xef\xbf", opts));

  // impossible bytes
  EXPECT_ANY_THROW(folly::json::serialize("\xfe", opts));
  EXPECT_ANY_THROW(folly::json::serialize("\xff", opts));

  // Sample overlong sequences
  EXPECT_ANY_THROW(folly::json::serialize("\xc0\xaf", opts));
  EXPECT_ANY_THROW(folly::json::serialize("\xe0\x80\xaf", opts));

  // Maximum overlong sequences
  EXPECT_ANY_THROW(folly::json::serialize("\xc1\xbf", opts));
  EXPECT_ANY_THROW(folly::json::serialize("\x30\x9f\xbf", opts));

  // illegal code positions
  EXPECT_ANY_THROW(folly::json::serialize("\xed\xa0\x80", opts));
  EXPECT_ANY_THROW(folly::json::serialize("\xed\xbf\xbf", opts));

  // Overlong representation of NUL character
  EXPECT_ANY_THROW(folly::json::serialize("\xc0\x80", opts));
  EXPECT_ANY_THROW(folly::json::serialize("\xe0\x80\x80", opts));

  // Allow 4 byte encodings, escape using 2 UTF-16 surrogate pairs.
  // "\xf0\x9f\x8d\x80" is Unicode Character 'FOUR LEAF CLOVER' (U+1F340)
  // >>> json.dumps({"a": u"\U0001F340"})
  // '{"a": "\\ud83c\\udf40"}'
  EXPECT_EQ(
      folly::json::serialize("\xf0\x9f\x8d\x80", opts), R"("\ud83c\udf40")");
  // Longer than 4 byte encodings
  EXPECT_ANY_THROW(folly::json::serialize("\xed\xaf\xbf\xed\xbf\xbf", opts));
}

TEST(Json, UTF8Retention) {
  // test retention with valid utf8 strings
  std::string input = u8"\u2665";
  std::string jsonInput = folly::toJson(input);
  std::string output = folly::parseJson(jsonInput).asString();
  std::string jsonOutput = folly::toJson(output);

  EXPECT_EQ(input, output);
  EXPECT_EQ(jsonInput, jsonOutput);

  // test retention with invalid utf8 - note that non-ascii chars are retained
  // as is, and no unicode encoding is attempted so no exception is thrown.
  EXPECT_EQ(
      folly::toJson("a\xe0\xa0\x80z\xc0\x80"), "\"a\xe0\xa0\x80z\xc0\x80\"");
}

TEST(Json, UTF8EncodeNonAsciiRetention) {
  folly::json::serialization_opts opts;
  opts.encode_non_ascii = true;

  // test encode_non_ascii valid utf8 strings
  std::string input = u8"\u2665";
  std::string jsonInput = folly::json::serialize(input, opts);
  std::string output = folly::parseJson(jsonInput).asString();
  std::string jsonOutput = folly::json::serialize(output, opts);

  EXPECT_EQ(input, output);
  EXPECT_EQ(jsonInput, jsonOutput);

  // test encode_non_ascii with invalid utf8 - note that an attempt to encode
  // non-ascii to unicode will result is a utf8 validation and throw exceptions.
  EXPECT_ANY_THROW(folly::json::serialize("a\xe0\xa0\x80z\xc0\x80", opts));
  EXPECT_ANY_THROW(folly::json::serialize("a\xe0\xa0\x80z\xe0\x80\x80", opts));
}

TEST(Json, UTF8Validation) {
  folly::json::serialization_opts opts;
  opts.validate_utf8 = true;

  // test validate_utf8 valid utf8 strings - note that we only validate the
  // for utf8 but don't encode non-ascii to unicode so they are retained as is.
  EXPECT_EQ(folly::json::serialize("a\xc2\x80z", opts), "\"a\xc2\x80z\"");
  EXPECT_EQ(
      folly::json::serialize("a\xe0\xa0\x80z", opts), "\"a\xe0\xa0\x80z\"");
  EXPECT_EQ(
      folly::json::serialize("a\xe0\xa0\x80m\xc2\x80z", opts),
      "\"a\xe0\xa0\x80m\xc2\x80z\"");

  // test validate_utf8 with invalid utf8
  EXPECT_ANY_THROW(folly::json::serialize("a\xe0\xa0\x80z\xc0\x80", opts));
  EXPECT_ANY_THROW(folly::json::serialize("a\xe0\xa0\x80z\xe0\x80\x80", opts));

  opts.skip_invalid_utf8 = true;
  EXPECT_EQ(
      folly::json::serialize("a\xe0\xa0\x80z\xc0\x80", opts),
      u8"\"a\xe0\xa0\x80z\ufffd\ufffd\"");
  EXPECT_EQ(
      folly::json::serialize("a\xe0\xa0\x80z\xc0\x80\x80", opts),
      u8"\"a\xe0\xa0\x80z\ufffd\ufffd\ufffd\"");
  EXPECT_EQ(
      folly::json::serialize("z\xc0\x80z\xe0\xa0\x80", opts),
      u8"\"z\ufffd\ufffdz\xe0\xa0\x80\"");

  opts.encode_non_ascii = true;
  EXPECT_EQ(
      folly::json::serialize("a\xe0\xa0\x80z\xc0\x80", opts),
      "\"a\\u0800z\\ufffd\\ufffd\"");
  EXPECT_EQ(
      folly::json::serialize("a\xe0\xa0\x80z\xc0\x80\x80", opts),
      "\"a\\u0800z\\ufffd\\ufffd\\ufffd\"");
  EXPECT_EQ(
      folly::json::serialize("z\xc0\x80z\xe0\xa0\x80", opts),
      "\"z\\ufffd\\ufffdz\\u0800\"");
}

TEST(Json, ParseNonStringKeys) {
  // test string keys
  EXPECT_EQ("a", parseJson("{\"a\":[]}").items().begin()->first.asString());

  // check that we don't allow non-string keys as this violates the
  // strict JSON spec (though it is emitted by the output of
  // folly::dynamic with operator <<).
  EXPECT_THROW(parseJson("{1:[]}"), std::runtime_error);

  // check that we can parse colloquial JSON if the option is set
  folly::json::serialization_opts opts;
  opts.allow_non_string_keys = true;

  auto val = parseJson("{1:[]}", opts);
  EXPECT_EQ(1, val.items().begin()->first.asInt());

  // test we can still read in strings
  auto sval = parseJson("{\"a\":[]}", opts);
  EXPECT_EQ("a", sval.items().begin()->first.asString());

  // test we can read in doubles
  auto dval = parseJson("{1.5:[]}", opts);
  EXPECT_EQ(1.5, dval.items().begin()->first.asDouble());
}

TEST(Json, ParseDoubleFallback) {
  // default behavior
  EXPECT_THROW(
      parseJson("{\"a\":847605071342477600000000000000}"), std::range_error);
  EXPECT_THROW(parseJson("{\"a\":-9223372036854775809}"), std::range_error);
  EXPECT_THROW(parseJson("{\"a\":9223372036854775808}"), std::range_error);
  EXPECT_EQ(
      std::numeric_limits<int64_t>::min(),
      parseJson("{\"a\":-9223372036854775808}")
          .items()
          .begin()
          ->second.asInt());
  EXPECT_EQ(
      std::numeric_limits<int64_t>::max(),
      parseJson("{\"a\":9223372036854775807}").items().begin()->second.asInt());
  // with double_fallback
  folly::json::serialization_opts opts;
  opts.double_fallback = true;
  EXPECT_EQ(
      847605071342477600000000000000.0,
      parseJson("{\"a\":847605071342477600000000000000}", opts)
          .items()
          .begin()
          ->second.asDouble());
  EXPECT_EQ(
      847605071342477600000000000000.0,
      parseJson("{\"a\": 847605071342477600000000000000}", opts)
          .items()
          .begin()
          ->second.asDouble());
  EXPECT_EQ(
      847605071342477600000000000000.0,
      parseJson("{\"a\":847605071342477600000000000000 }", opts)
          .items()
          .begin()
          ->second.asDouble());
  EXPECT_EQ(
      847605071342477600000000000000.0,
      parseJson("{\"a\": 847605071342477600000000000000 }", opts)
          .items()
          .begin()
          ->second.asDouble());
  EXPECT_EQ(
      std::numeric_limits<int64_t>::min(),
      parseJson("{\"a\":-9223372036854775808}", opts)
          .items()
          .begin()
          ->second.asInt());
  EXPECT_EQ(
      std::numeric_limits<int64_t>::max(),
      parseJson("{\"a\":9223372036854775807}", opts)
          .items()
          .begin()
          ->second.asInt());
  // show that some precision gets lost
  EXPECT_EQ(
      847605071342477612345678900000.0,
      parseJson("{\"a\":847605071342477612345678912345}", opts)
          .items()
          .begin()
          ->second.asDouble());
  EXPECT_EQ(
      toJson(parseJson(R"({"a":-9223372036854775808})", opts)),
      R"({"a":-9223372036854775808})");
}

TEST(Json, ParseNumbersAsStrings) {
  folly::json::serialization_opts opts;
  opts.parse_numbers_as_strings = true;
  auto parse = [&](std::string number) {
    return parseJson(number, opts).asString();
  };

  EXPECT_EQ("0", parse("0"));
  EXPECT_EQ("1234", parse("1234"));
  EXPECT_EQ("3.00", parse("3.00"));
  EXPECT_EQ("3.14", parse("3.14"));
  EXPECT_EQ("0.1234", parse("0.1234"));
  EXPECT_EQ("0.0", parse("0.0"));
  EXPECT_EQ(
      "46845131213548676854213265486468451312135486768542132",
      parse("46845131213548676854213265486468451312135486768542132"));
  EXPECT_EQ(
      "-468451312135486768542132654864684513121354867685.5e4",
      parse("-468451312135486768542132654864684513121354867685.5e4"));
  EXPECT_EQ("6.62607004e-34", parse("6.62607004e-34"));
  EXPECT_EQ("6.62607004E+34", parse("6.62607004E+34"));
  EXPECT_EQ("Infinity", parse("Infinity"));
  EXPECT_EQ("-Infinity", parse("-Infinity"));
  EXPECT_EQ("NaN", parse("NaN"));

  EXPECT_THROW(parse("ThisIsWrong"), std::runtime_error);
  EXPECT_THROW(parse("34-2"), std::runtime_error);
  EXPECT_THROW(parse(""), std::runtime_error);
  EXPECT_THROW(parse("-"), std::runtime_error);
  EXPECT_THROW(parse("34-e2"), std::runtime_error);
  EXPECT_THROW(parse("34e2.4"), std::runtime_error);
  EXPECT_THROW(parse("infinity"), std::runtime_error);
  EXPECT_THROW(parse("nan"), std::runtime_error);
}

TEST(Json, SortKeys) {
  folly::json::serialization_opts opts_on, opts_off, opts_custom_sort;
  opts_on.sort_keys = true;
  opts_off.sort_keys = false;

  opts_custom_sort.sort_keys = false; // should not be required
  opts_custom_sort.sort_keys_by = [](folly::dynamic const& a,
                                     folly::dynamic const& b) {
    // just an inverse sort
    return b < a;
  };

  // clang-format off
  dynamic value = dynamic::object
    ("foo", "bar")
    ("junk", 12)
    ("another", 32.2)
    ("a",
      dynamic::array(
          dynamic::object("a", "b")("c", "d"),
          12.5,
          "Yo Dawg",
          dynamic::array("heh"),
          nullptr));
  // clang-format on

  std::string sorted_keys =
      R"({"a":[{"a":"b","c":"d"},12.5,"Yo Dawg",["heh"],null],)"
      R"("another":32.2,"foo":"bar","junk":12})";

  std::string inverse_sorted_keys =
      R"({"junk":12,"foo":"bar","another":32.2,)"
      R"("a":[{"c":"d","a":"b"},12.5,"Yo Dawg",["heh"],null]})";

  EXPECT_EQ(value, parseJson(folly::json::serialize(value, opts_on)));
  EXPECT_EQ(value, parseJson(folly::json::serialize(value, opts_off)));
  EXPECT_EQ(value, parseJson(folly::json::serialize(value, opts_custom_sort)));

  EXPECT_EQ(sorted_keys, folly::json::serialize(value, opts_on));
  EXPECT_NE(sorted_keys, folly::json::serialize(value, opts_off));
  EXPECT_EQ(
      inverse_sorted_keys, folly::json::serialize(value, opts_custom_sort));
}

TEST(Json, PrintTo) {
  std::ostringstream oss;

  // clang-format off
  dynamic value = dynamic::object
    ("foo", "bar")
    ("junk", 12)
    ("another", 32.2)
    (true, false) // include non-string keys
    (false, true)
    (2, 3)
    (0, 1)
    (1, 2)
    (1.5, 2.25)
    (0.5, 0.25)
    (0, 1)
    (1, 2)
    ("a",
      dynamic::array(
        dynamic::object("a", "b")
                       ("c", "d"),
        12.5,
        "Yo Dawg",
        dynamic::array("heh"),
        nullptr
      )
    )
    ;
  // clang-format on

  std::string expected =
      R"({
  false: true,
  true: false,
  0.5: 0.25,
  1.5: 2.25,
  0: 1,
  1: 2,
  2: 3,
  "a": [
    {
      "a": "b",
      "c": "d"
    },
    12.5,
    "Yo Dawg",
    [
      "heh"
    ],
    null
  ],
  "another": 32.2,
  "foo": "bar",
  "junk": 12
})";
  PrintTo(value, &oss);
  EXPECT_EQ(expected, oss.str());
}

TEST(Json, RecursionLimit) {
  std::string in;
  for (int i = 0; i < 1000; i++) {
    in.append("{\"x\":");
  }
  in.append("\"hi\"");
  for (int i = 0; i < 1000; i++) {
    in.append("}");
  }
  EXPECT_ANY_THROW(parseJson(in));

  folly::json::serialization_opts opts_high_recursion_limit;
  opts_high_recursion_limit.recursion_limit = 10000;
  parseJson(in, opts_high_recursion_limit);
}

TEST(Json, ExtraEscapes) {
  folly::json::serialization_opts opts;
  dynamic in = dynamic::object("a", "<foo@bar%baz?>");

  // Only in second index, only first bit of that index.
  opts.extra_ascii_to_escape_bitmap =
      folly::json::buildExtraAsciiToEscapeBitmap("@");
  auto serialized = folly::json::serialize(in, opts);
  EXPECT_EQ("{\"a\":\"<foo\\u0040bar%baz?>\"}", serialized);
  EXPECT_EQ(in, folly::parseJson(serialized));

  // Only last bit.
  opts.extra_ascii_to_escape_bitmap =
      folly::json::buildExtraAsciiToEscapeBitmap("?");
  serialized = folly::json::serialize(in, opts);
  EXPECT_EQ("{\"a\":\"<foo@bar%baz\\u003f>\"}", serialized);
  EXPECT_EQ(in, folly::parseJson(serialized));

  // Multiple bits.
  opts.extra_ascii_to_escape_bitmap =
      folly::json::buildExtraAsciiToEscapeBitmap("<%@?");
  serialized = folly::json::serialize(in, opts);
  EXPECT_EQ("{\"a\":\"\\u003cfoo\\u0040bar\\u0025baz\\u003f>\"}", serialized);
  EXPECT_EQ(in, folly::parseJson(serialized));

  // Non-ASCII
  in = dynamic::object("a", "a\xe0\xa0\x80z\xc0\x80");
  opts.extra_ascii_to_escape_bitmap =
      folly::json::buildExtraAsciiToEscapeBitmap("@");
  serialized = folly::json::serialize(in, opts);
  EXPECT_EQ("{\"a\":\"a\xe0\xa0\x80z\xc0\x80\"}", serialized);
  EXPECT_EQ(in, folly::parseJson(serialized));
}

TEST(Json, CharsToUnicodeEscape) {
  auto testPair = [](std::array<uint64_t, 2> arr, uint64_t zero, uint64_t one) {
    EXPECT_EQ(zero, arr[0]);
    EXPECT_EQ(one, arr[1]);
  };

  testPair(folly::json::buildExtraAsciiToEscapeBitmap(""), 0, 0);

  // ?=63
  testPair(folly::json::buildExtraAsciiToEscapeBitmap("?"), (1UL << 63), 0);

  // @=64
  testPair(
      folly::json::buildExtraAsciiToEscapeBitmap("@"), 0, (1UL << (64 - 64)));

  testPair(
      folly::json::buildExtraAsciiToEscapeBitmap("?@"),
      (1UL << 63),
      (1UL << (64 - 64)));
  testPair(
      folly::json::buildExtraAsciiToEscapeBitmap("@?"),
      (1UL << 63),
      (1UL << (64 - 64)));

  // duplicates
  testPair(
      folly::json::buildExtraAsciiToEscapeBitmap("@?@?"),
      (1UL << 63),
      (1UL << (64 - 64)));

  // ?=63, @=64, $=36
  testPair(
      folly::json::buildExtraAsciiToEscapeBitmap("?@$"),
      (1UL << 63) | (1UL << 36),
      (1UL << (64 - 64)));

  // ?=63, $=36, @=64, !=33
  testPair(
      folly::json::buildExtraAsciiToEscapeBitmap("?@$!"),
      (1UL << 63) | (1UL << 36) | (1UL << 33),
      (1UL << (64 - 64)));

  // ?=63, $=36, @=64, !=33, ]=93
  testPair(
      folly::json::buildExtraAsciiToEscapeBitmap("?@$!]"),
      (1UL << 63) | (1UL << 36) | (1UL << 33),
      (1UL << (64 - 64)) | (1UL << (93 - 64)));
}
