Commit cad4baef authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/neverlord/json-unsigned'

parents 6bbcebf8 7271c19d
......@@ -5,6 +5,21 @@ is based on [Keep a Changelog](https://keepachangelog.com).
## [Unreleased]
### Added
- The class `json_value` can now hold unsigned 64-bit integer values. This
allows it to store values that would otherwise overflow a signed integer.
Values that can be represented in both integer types will return `true` for
`is_integer()` as well as for the new `is_unsigned()` function. Users can
obtain the stored value as `uint64_t` via `to_unsigned()`.
### Changed
- With the addition of the unsigned type to `json_value`, there is now a new
edge case where `is_number()` returns `true` but neither `is_integer()` nor
`is_double()` return `true`: integer values larger than `INT64_MAX` will only
return true for `is_unsigned()`.
### Fixed
- Fix flow setup for servers that use `web_socket::with`. This bug caused
......
......@@ -10,26 +10,28 @@
namespace caf::detail {
template <class To, bool LargeUnsigned = sizeof(To) >= sizeof(int64_t)
&& std::is_unsigned<To>::value>
struct bounds_checker {
static constexpr bool check(int64_t x) noexcept {
return x >= std::numeric_limits<To>::min()
&& x <= std::numeric_limits<To>::max();
}
};
template <>
struct bounds_checker<int64_t, false> {
static constexpr bool check(int64_t) noexcept {
return true;
}
};
template <class To>
struct bounds_checker<To, true> {
static constexpr bool check(int64_t x) noexcept {
return x >= 0;
struct bounds_checker {
template <class From>
static constexpr bool check(From x) noexcept {
auto converted = static_cast<To>(x);
if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
// If the source and target types have the same signedness, we can simply
// check whether the value is in the range of the target type.
return static_cast<From>(converted) == x;
} else if constexpr (std::is_signed_v<From>) {
// If the source type is signed and the target type is unsigned, we need
// to check that the value is not negative. Otherwise, the conversion
// could yield a positive value, which is out of range for the target
// type.
return x >= 0 && static_cast<From>(converted) == x;
} else {
static_assert(std::is_signed_v<To>);
// If the source type is unsigned and the target type is signed, we need
// to check whether the conversion produced a non-negative value. If the
// value is negative, the conversion is out of range.
return converted >= 0 && static_cast<From>(converted) == x;
}
}
};
......
......@@ -102,7 +102,15 @@ public:
template <class T>
pec value(T&& x) {
return value_impl(config_value{std::forward<T>(x)});
using val_t = std::decay_t<T>;
if constexpr (std::is_same_v<val_t, uint64_t>) {
if (x <= INT64_MAX)
return value_impl(config_value{static_cast<int64_t>(x)});
else
return pec::integer_overflow;
} else {
return value_impl(config_value{std::forward<T>(x)});
}
}
const std::string& current_key() {
......
......@@ -4,10 +4,15 @@
#pragma once
#include "caf/pec.hpp"
#include <optional>
#include <type_traits>
#include <utility>
namespace caf::detail {
/// Consumes a single value.
template <class T>
class consumer {
public:
......@@ -17,17 +22,117 @@ public:
// nop
}
void value(T&& y) {
x_ = std::move(y);
template <class U>
void value(U&& y) {
x_ = std::forward<U>(y);
}
private:
T& x_;
};
/// Specializes `consumer` for `int64_t` with a safe conversion from `uint64_t`.
template <>
class consumer<int64_t> {
public:
using value_type = int64_t;
explicit consumer(int64_t& x) : x_(x) {
// nop
}
void value(int64_t y) noexcept {
x_ = y;
}
pec value(uint64_t y) noexcept {
if (y < INT64_MAX) {
value(static_cast<int64_t>(y));
return pec::success;
}
return pec::integer_overflow;
}
private:
int64_t& x_;
};
/// Specializes `consumer` for `uint64_t` with a safe conversion from `int64_t`.
template <>
class consumer<uint64_t> {
public:
using value_type = uint64_t;
explicit consumer(uint64_t& x) : x_(x) {
// nop
}
void value(uint64_t y) noexcept {
x_ = y;
}
pec value(int64_t y) noexcept {
if (y >= 0) {
value(static_cast<uint64_t>(y));
return pec::success;
}
return pec::integer_underflow;
}
private:
uint64_t& x_;
};
template <class... Ts>
class consumer<std::variant<Ts...>> {
public:
explicit consumer(std::variant<Ts...>& x) : x_(x) {
// nop
}
template <class T>
void value(T&& y) {
x_ = std::forward<T>(y);
}
private:
std::variant<Ts...>& x_;
};
template <class T>
class consumer<std::optional<T>> {
public:
using value_type = T;
explicit consumer(std::optional<T>& x) : x_(x) {
// nop
}
template <class U>
void value(U&& y) {
x_ = std::forward<U>(y);
}
private:
std::optional<T>& x_;
};
template <class T>
consumer<T> make_consumer(T& x) {
return consumer<T>{x};
}
/// Applies a consumer to a value and updates the error code if necessary.
template <class Consumer, class T>
static void apply_consumer(Consumer&& consumer, T&& value, pec& code) {
using res_t = decltype(consumer.value(std::forward<T>(value)));
if constexpr (std::is_same_v<res_t, void>) {
consumer.value(std::forward<T>(value));
} else {
auto res = consumer.value(std::forward<T>(value));
if (res != pec::success)
code = res;
}
}
} // namespace caf::detail
......@@ -333,24 +333,26 @@ public:
using object_allocator = object::allocator_type;
using data_type = std::variant<null_t, int64_t, double, bool,
using data_type = std::variant<null_t, int64_t, uint64_t, double, bool,
std::string_view, array, object, undefined_t>;
static constexpr size_t null_index = 0;
static constexpr size_t integer_index = 1;
static constexpr size_t double_index = 2;
static constexpr size_t unsigned_index = 2;
static constexpr size_t bool_index = 3;
static constexpr size_t double_index = 3;
static constexpr size_t string_index = 4;
static constexpr size_t bool_index = 4;
static constexpr size_t array_index = 5;
static constexpr size_t string_index = 5;
static constexpr size_t object_index = 6;
static constexpr size_t array_index = 6;
static constexpr size_t undefined_index = 7;
static constexpr size_t object_index = 7;
static constexpr size_t undefined_index = 8;
data_type data;
......@@ -362,6 +364,10 @@ public:
return data.index() == integer_index;
}
bool is_unsigned() const noexcept {
return data.index() == unsigned_index;
}
bool is_double() const noexcept {
return data.index() == double_index;
}
......@@ -470,10 +476,10 @@ bool save(Serializer& sink, const value& val) {
if (!sink.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>,
type_id_v<double>, type_id_v<bool>,
type_id_v<std::string>, type_id_v<json_array>,
type_id_v<json_object>, type_id_v<none_t>};
type_id_t mapping[]
= {type_id_v<unit_t>, type_id_v<int64_t>, type_id_v<uint64_t>,
type_id_v<double>, type_id_v<bool>, type_id_v<std::string>,
type_id_v<json_array>, type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types.
auto type_index = val.data.index();
if (!sink.begin_field("value", make_span(mapping), type_index))
......@@ -484,6 +490,10 @@ bool save(Serializer& sink, const value& val) {
if (!sink.apply(std::get<int64_t>(val.data)))
return false;
break;
case value::unsigned_index:
if (!sink.apply(std::get<uint64_t>(val.data)))
return false;
break;
case value::double_index:
if (!sink.apply(std::get<double>(val.data)))
return false;
......@@ -552,10 +562,10 @@ bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
if (!source.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>,
type_id_v<double>, type_id_v<bool>,
type_id_v<std::string>, type_id_v<json_array>,
type_id_v<json_object>, type_id_v<none_t>};
type_id_t mapping[]
= {type_id_v<unit_t>, type_id_v<int64_t>, type_id_v<uint64_t>,
type_id_v<double>, type_id_v<bool>, type_id_v<std::string>,
type_id_v<json_array>, type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types.
auto type_index = size_t{0};
if (!source.begin_field("value", make_span(mapping), type_index))
......@@ -572,6 +582,13 @@ bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
val.data = tmp;
break;
}
case value::unsigned_index: {
auto tmp = uint64_t{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::double_index: {
auto tmp = double{0};
if (!source.apply(tmp))
......@@ -738,6 +755,9 @@ void print_to(Buffer& buf, const value& val, size_t indentation_factor,
case value::integer_index:
print(buf, std::get<int64_t>(val.data));
break;
case value::unsigned_index:
print(buf, std::get<uint64_t>(val.data));
break;
case value::double_index:
print(buf, std::get<double>(val.data));
break;
......
......@@ -35,6 +35,8 @@ void read_number_or_timespan(State& ps, Consumer& consumer,
Consumer* outer = nullptr;
std::variant<none_t, int64_t, double> interim;
void value(int64_t x) {
// If we see a second integer, we have a range of integers and forward all
// calls to the outer consumer.
switch (++invocations) {
case 1:
interim = x;
......@@ -48,6 +50,13 @@ void read_number_or_timespan(State& ps, Consumer& consumer,
outer->value(x);
}
}
pec value(uint64_t x) {
if (x <= INT64_MAX) {
value(static_cast<int64_t>(x));
return pec::success;
}
return pec::integer_overflow;
}
void value(double x) {
interim = x;
}
......
......@@ -1056,6 +1056,10 @@ constexpr bool is_string_or_cstring_v
= std::is_convertible_v<T, const char*>
|| std::is_same_v<std::string, std::decay_t<T>>;
template <class T>
constexpr bool is_64bit_integer_v = std::is_same_v<T, int64_t>
|| std::is_same_v<T, uint64_t>;
} // namespace caf::detail
#undef CAF_HAS_MEMBER_TRAIT
......
......@@ -19,6 +19,7 @@ class CAF_CORE_EXPORT json_value {
public:
// -- constructors, destructors, and assignment operators --------------------
/// Creates a @c null JSON value.
json_value() noexcept : val_(detail::json::null_value()) {
// nop
}
......@@ -39,6 +40,8 @@ public:
// -- factories --------------------------------------------------------------
/// Creates an undefined JSON value. This special state usually indicates that
/// a previous key lookup failed.
static json_value undefined() noexcept {
return json_value{detail::json::undefined_value(), nullptr};
}
......@@ -57,19 +60,19 @@ public:
}
/// Checks whether the value is an @c int64_t.
bool is_integer() const noexcept {
return val_->is_integer();
}
bool is_integer() const noexcept;
/// Checks whether the value is an @c uint64_t.
bool is_unsigned() const noexcept;
/// Checks whether the value is a @c double.
bool is_double() const noexcept {
return val_->is_double();
}
/// Checks whether the value is a number, i.e., an @c int64_t or a @c double.
bool is_number() const noexcept {
return is_integer() || is_double();
}
/// Checks whether the value is a number, i.e., an @c int64_t, a @c uint64_t
/// or a @c double.
bool is_number() const noexcept;
/// Checks whether the value is a @c bool.
bool is_bool() const noexcept {
......@@ -93,26 +96,49 @@ public:
// -- conversion -------------------------------------------------------------
/// Converts the value to an @c int64_t or returns @p fallback if the value
/// is not a valid number.
int64_t to_integer(int64_t fallback = 0) const;
/// Converts the value to an @c uint64_t or returns @p fallback if the value
/// is not a valid number.
uint64_t to_unsigned(uint64_t fallback = 0) const;
/// Converts the value to a @c double or returns @p fallback if the value is
/// not a valid number.
double to_double(double fallback = 0.0) const;
/// Converts the value to a @c bool or returns @p fallback if the value is
/// not a valid boolean.
bool to_bool(bool fallback = false) const;
/// Returns the value as a JSON string or an empty string if the value is not
/// a string.
std::string_view to_string() const;
/// Returns the value as a JSON string or @p fallback if the value is not a
/// string.
std::string_view to_string(std::string_view fallback) const;
/// Returns the value as a JSON array or an empty array if the value is not
/// an array.
json_array to_array() const;
/// Returns the value as a JSON array or @p fallback if the value is not an
/// array.
json_array to_array(json_array fallback) const;
/// Returns the value as a JSON object or an empty object if the value is not
/// an object.
json_object to_object() const;
/// Returns the value as a JSON object or @p fallback if the value is not an
/// object.
json_object to_object(json_object fallback) const;
// -- comparison -------------------------------------------------------------
/// Compares two JSON values for equality.
bool equal_to(const json_value& other) const noexcept;
// -- parsing ----------------------------------------------------------------
......@@ -143,6 +169,7 @@ public:
// -- printing ---------------------------------------------------------------
/// Prints the JSON value to @p buf.
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *val_, indentation_factor);
......@@ -150,6 +177,7 @@ public:
// -- serialization ----------------------------------------------------------
/// Applies the `Inspector` to the JSON value `val`.
template <class Inspector>
friend bool inspect(Inspector& inspector, json_value& val) {
if constexpr (Inspector::is_loading) {
......
......@@ -12,7 +12,6 @@
#include "caf/detail/parser/read_floating_point.hpp"
#include "caf/detail/parser/read_ipv4_address.hpp"
#include "caf/detail/parser/read_ipv6_address.hpp"
#include "caf/detail/parser/read_signed_integer.hpp"
#include "caf/detail/parser/read_string.hpp"
#include "caf/detail/parser/read_timespan.hpp"
#include "caf/detail/parser/read_unsigned_integer.hpp"
......
......@@ -56,6 +56,7 @@ std::string type_clash(std::string_view want,
using caf::detail::json::value;
switch (got.data.index()) {
case value::integer_index:
case value::unsigned_index:
return type_clash(want, "json::integer"sv);
case value::double_index:
return type_clash(want, "json::real"sv);
......@@ -534,11 +535,19 @@ bool json_reader::integer(T& x) {
if (detail::bounds_checker<T>::check(i64)) {
x = static_cast<T>(i64);
return true;
} else {
emplace_error(sec::runtime_error, class_name, fn,
"integer out of bounds");
return false;
}
emplace_error(sec::runtime_error, class_name, fn,
"integer out of bounds");
return false;
} else if (val.data.index() == detail::json::value::unsigned_index) {
auto u64 = std::get<uint64_t>(val.data);
if (detail::bounds_checker<T>::check(u64)) {
x = static_cast<T>(u64);
return true;
}
emplace_error(sec::runtime_error, class_name, fn,
"integer out of bounds");
return false;
} else {
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::integer", val));
......@@ -592,16 +601,20 @@ bool json_reader::value(float& x) {
bool json_reader::value(double& x) {
FN_DECL;
return consume<true>(fn, [this, &x](const detail::json::value& val) {
if (val.data.index() == detail::json::value::double_index) {
x = std::get<double>(val.data);
return true;
} else if (val.data.index() == detail::json::value::integer_index) {
x = std::get<int64_t>(val.data);
return true;
} else {
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::real", val));
return false;
switch (val.data.index()) {
case detail::json::value::double_index:
x = std::get<double>(val.data);
return true;
case detail::json::value::integer_index:
x = std::get<int64_t>(val.data);
return true;
case detail::json::value::unsigned_index:
x = std::get<uint64_t>(val.data);
return true;
default:
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::real", val));
return false;
}
});
}
......
......@@ -10,30 +10,82 @@
#include "caf/make_counted.hpp"
#include "caf/parser_state.hpp"
#include <cstdint>
#include <fstream>
namespace caf {
// -- properties ---------------------------------------------------------------
bool json_value::is_integer() const noexcept {
return val_->is_integer()
|| (val_->is_unsigned()
&& std::get<uint64_t>(val_->data) <= INT64_MAX);
}
bool json_value::is_unsigned() const noexcept {
return val_->is_unsigned()
|| (val_->is_integer() && std::get<int64_t>(val_->data) >= 0);
}
bool json_value::is_number() const noexcept {
switch (val_->data.index()) {
default:
return false;
case detail::json::value::integer_index:
case detail::json::value::unsigned_index:
case detail::json::value::double_index:
return true;
}
}
// -- conversion ---------------------------------------------------------------
int64_t json_value::to_integer(int64_t fallback) const {
if (is_integer()) {
return std::get<int64_t>(val_->data);
switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index:
return std::get<int64_t>(val_->data);
case detail::json::value::unsigned_index: {
auto val = std::get<uint64_t>(val_->data);
if (val <= INT64_MAX)
return static_cast<int64_t>(val);
return fallback;
}
case detail::json::value::double_index:
return static_cast<int64_t>(std::get<double>(val_->data));
}
if (is_double()) {
return static_cast<int64_t>(std::get<double>(val_->data));
}
uint64_t json_value::to_unsigned(uint64_t fallback) const {
switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index: {
auto val = std::get<int64_t>(val_->data);
if (val >= 0)
return static_cast<uint64_t>(val);
return fallback;
}
case detail::json::value::unsigned_index:
return std::get<uint64_t>(val_->data);
case detail::json::value::double_index:
return static_cast<int64_t>(std::get<double>(val_->data));
}
return fallback;
}
double json_value::to_double(double fallback) const {
if (is_double()) {
return std::get<double>(val_->data);
}
if (is_integer()) {
return static_cast<double>(std::get<int64_t>(val_->data));
}
return fallback;
switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index:
return static_cast<double>(std::get<int64_t>(val_->data));
case detail::json::value::unsigned_index:
return static_cast<double>(std::get<uint64_t>(val_->data));
case detail::json::value::double_index:
return std::get<double>(val_->data);
};
}
bool json_value::to_bool(bool fallback) const {
......
......@@ -10,8 +10,8 @@
namespace {
template <class T>
bool check(int64_t x) {
template <class T, class U>
bool check(U x) {
return caf::detail::bounds_checker<T>::check(x);
}
......@@ -39,5 +39,7 @@ CAF_TEST(small integers) {
CAF_TEST(large unsigned integers) {
CHECK_EQ(check<uint64_t>(-1), false);
CHECK_EQ(check<uint64_t>(0), true);
CHECK_EQ(check<uint64_t>(0u), true);
CHECK_EQ(check<uint64_t>(std::numeric_limits<int64_t>::max()), true);
CHECK_EQ(check<uint64_t>(std::numeric_limits<uint64_t>::max()), true);
}
......@@ -85,6 +85,10 @@ void stringify(std::string& str, size_t, int64_t val) {
str += std::to_string(val);
}
void stringify(std::string& str, size_t, uint64_t val) {
str += std::to_string(val);
}
void stringify(std::string& str, size_t, double val) {
str += std::to_string(val);
}
......
......@@ -30,6 +30,12 @@ struct number_consumer {
void value(int64_t y) {
x = y;
}
void value(uint64_t y) {
if (y < INT64_MAX)
value(static_cast<int64_t>(y));
else
CAF_FAIL("number consumer called with a uint64_t larger than INT64_MAX");
}
};
struct range_consumer {
......@@ -40,6 +46,12 @@ struct range_consumer {
void value(int64_t y) {
xs.emplace_back(y);
}
void value(uint64_t y) {
if (y < INT64_MAX)
value(static_cast<int64_t>(y));
else
CAF_FAIL("range consumer called with a uint64_t larger than INT64_MAX");
}
};
struct res_t {
......@@ -328,6 +340,7 @@ CAF_TEST(ranges can use signed integers) {
CAF_TEST(the parser rejects invalid step values) {
CHECK_ERR("-2..+2..-2", pec::invalid_range_expression);
CHECK_ERR("+2..-2..+2", pec::invalid_range_expression);
CHECK_ERR("+2..-2..0", pec::invalid_range_expression);
}
END_FIXTURE_SCOPE()
......@@ -17,6 +17,7 @@ using namespace std::literals;
namespace {
struct fixture {
// Adds a test case for a given input and expected output.
template <class T>
void add_test_case(std::string_view input, T val) {
auto f = [this, input, obj{std::move(val)}]() -> bool {
......@@ -36,6 +37,20 @@ struct fixture {
test_cases.emplace_back(std::move(f));
}
// Adds a test case that should fail.
template <class T>
void add_neg_test_case(std::string_view input) {
auto f = [this, input]() -> bool {
auto tmp = T{};
auto res = reader.load(input) // parse JSON
&& reader.apply(tmp); // deserialize object
if (res)
MESSAGE("got unexpected output: " << tmp);
return !res;
};
test_cases.emplace_back(std::move(f));
}
template <class T, class... Ts>
std::vector<T> ls(Ts... xs) {
std::vector<T> result;
......@@ -67,7 +82,6 @@ fixture::fixture() {
add_test_case(R"_(true)_", true);
add_test_case(R"_(false)_", false);
add_test_case(R"_([true, false])_", ls<bool>(true, false));
add_test_case(R"_(42)_", int32_t{42});
add_test_case(R"_([1, 2, 3])_", ls<int32_t>(1, 2, 3));
add_test_case(R"_([[1, 2], [3], []])_",
ls<i32_list>(ls<int32_t>(1, 2), ls<int32_t>(3), ls<int32_t>()));
......@@ -109,6 +123,42 @@ fixture::fixture() {
R"({"top-left": {"x": 10, "y": 10}, )"
R"("bottom-right": {"x": 20, "y": 20}}})",
widget{"blue", rectangle{{10, 10}, {20, 20}}});
// Test cases for integers that are in bound.
add_test_case(R"_(-128)_", int8_t{INT8_MIN});
add_test_case(R"_(127)_", int8_t{INT8_MAX});
add_test_case(R"_(-32768)_", int16_t{INT16_MIN});
add_test_case(R"_(32767)_", int16_t{INT16_MAX});
add_test_case(R"_(-2147483648)_", int32_t{INT32_MIN});
add_test_case(R"_(2147483647)_", int32_t{INT32_MAX});
add_test_case(R"_(-9223372036854775808)_", int64_t{INT64_MIN});
add_test_case(R"_(9223372036854775807)_", int64_t{INT64_MAX});
// Test cases for unsigned integers that are in bound.
add_test_case(R"_(0)_", uint8_t{0});
add_test_case(R"_(255)_", uint8_t{UINT8_MAX});
add_test_case(R"_(0)_", uint16_t{0});
add_test_case(R"_(65535)_", uint16_t{UINT16_MAX});
add_test_case(R"_(0)_", uint32_t{0});
add_test_case(R"_(4294967295)_", uint32_t{UINT32_MAX});
add_test_case(R"_(0)_", uint64_t{0});
add_test_case(R"_(18446744073709551615)_", uint64_t{UINT64_MAX});
// Test cases for integers that are out of bound.
add_neg_test_case<int8_t>(R"_(-129)_");
add_neg_test_case<int8_t>(R"_(128)_");
add_neg_test_case<int16_t>(R"_(-32769)_");
add_neg_test_case<int16_t>(R"_(32768)_");
add_neg_test_case<int32_t>(R"_(-2147483649)_");
add_neg_test_case<int32_t>(R"_(2147483648)_");
add_neg_test_case<int64_t>(R"_(-9223372036854775809)_");
add_neg_test_case<int64_t>(R"_(9223372036854775808)_");
// Test cases for unsigned integers that are out of bound.
add_neg_test_case<uint8_t>(R"_(-1)_");
add_neg_test_case<uint8_t>(R"_(256)_");
add_neg_test_case<uint16_t>(R"_(-1)_");
add_neg_test_case<uint16_t>(R"_(65536)_");
add_neg_test_case<uint32_t>(R"_(-1)_");
add_neg_test_case<uint32_t>(R"_(4294967296)_");
add_neg_test_case<uint64_t>(R"_(-1)_");
add_neg_test_case<uint64_t>(R"_(18446744073709551616)_");
}
} // namespace
......
......@@ -39,6 +39,7 @@ TEST_CASE("default-constructed") {
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -61,6 +62,7 @@ TEST_CASE("from undefined") {
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -70,11 +72,36 @@ TEST_CASE("from undefined") {
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from integer") {
TEST_CASE("from negative integer") {
auto val = unbox(json_value::parse("-1"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(val.is_integer());
CHECK(!val.is_unsigned());
CHECK(!val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), -1);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), -1.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "-1");
CHECK_EQ(printed(val), "-1");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from small integer") {
// A small integer can be represented as both int64_t and uint64_t.
auto val = unbox(json_value::parse("42"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(val.is_integer());
CHECK(val.is_unsigned());
CHECK(!val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
......@@ -82,6 +109,7 @@ TEST_CASE("from integer") {
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_unsigned(), 42u);
CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -91,6 +119,29 @@ TEST_CASE("from integer") {
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from UINT64_MAX") {
auto val = unbox(json_value::parse("18446744073709551615"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(val.is_unsigned());
CHECK(!val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), UINT64_MAX);
CHECK_EQ(val.to_double(), static_cast<double>(UINT64_MAX));
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "18446744073709551615");
CHECK_EQ(printed(val), "18446744073709551615");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from double") {
auto val = unbox(json_value::parse("42.0"));
CHECK(!val.is_null());
......@@ -103,6 +154,7 @@ TEST_CASE("from double") {
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_unsigned(), 42u);
CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -124,6 +176,7 @@ TEST_CASE("from bool") {
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), true);
CHECK_EQ(val.to_string(), ""sv);
......@@ -145,6 +198,7 @@ TEST_CASE("from string") {
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), "Hello, world!"sv);
......@@ -166,6 +220,7 @@ TEST_CASE("from empty array") {
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -188,6 +243,7 @@ TEST_CASE("from array of size 1") {
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -210,6 +266,7 @@ TEST_CASE("from array of size 3") {
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -232,6 +289,7 @@ TEST_CASE("from empty object") {
CHECK(!val.is_array());
CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -253,6 +311,7 @@ TEST_CASE("from non-empty object") {
CHECK(!val.is_array());
CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
......@@ -261,3 +320,24 @@ TEST_CASE("from non-empty object") {
CHECK_EQ(printed(val), "{\n \"foo\": \"bar\"\n}");
CHECK_EQ(deep_copy(val), val);
}
namespace {
std::string_view json_with_boundary_values = R"_({
"min-int64": -9223372036854775808,
"max-int64": 9223372036854775807,
"min-uint64": 0,
"max-uint64": 18446744073709551615
})_";
} // namespace
TEST_CASE("non-empty object with nested values") {
auto val = unbox(json_value::parse(json_with_boundary_values));
CHECK(val.is_object());
auto obj = val.to_object();
CHECK_EQ(obj.value("min-int64").to_integer(), INT64_MIN);
CHECK_EQ(obj.value("max-int64").to_integer(), INT64_MAX);
CHECK_EQ(obj.value("min-uint64").to_unsigned(), 0u);
CHECK_EQ(obj.value("max-uint64").to_unsigned(), UINT64_MAX);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment