Commit 70af2bb9 authored by Dominik Charousset's avatar Dominik Charousset

Improve parsing of int/float from config values

parent 1a35b53f
...@@ -240,8 +240,7 @@ expected<bool> config_value::to_boolean() const { ...@@ -240,8 +240,7 @@ expected<bool> config_value::to_boolean() const {
expected<config_value::integer> config_value::to_integer() const { expected<config_value::integer> config_value::to_integer() const {
using result_type = expected<integer>; using result_type = expected<integer>;
auto f = detail::make_overload( auto f = detail::make_overload(
no_conversions<integer, none_t, bool, timespan, uri, config_value::list, no_conversions<integer, none_t, bool, timespan, uri, config_value::list>(),
config_value::dictionary>(),
[](integer x) { return result_type{x}; }, [](integer x) { return result_type{x}; },
[](real x) { [](real x) {
using limits = std::numeric_limits<config_value::integer>; using limits = std::numeric_limits<config_value::integer>;
...@@ -269,6 +268,37 @@ expected<config_value::integer> config_value::to_integer() const { ...@@ -269,6 +268,37 @@ expected<config_value::integer> config_value::to_integer() const {
detail::print_escaped(msg, x); detail::print_escaped(msg, x);
msg += " to an integer"; msg += " to an integer";
return result_type{make_error(sec::conversion_failed, std::move(msg))}; return result_type{make_error(sec::conversion_failed, std::move(msg))};
},
[](const dictionary& x) {
if (auto i = x.find("@type");
i != x.end() && holds_alternative<std::string>(i->second)) {
const auto& tn = get<std::string>(i->second);
string_view valid_types[]
= {type_name_v<int16_t>, type_name_v<int32_t>,
type_name_v<int64_t>, type_name_v<int8_t>,
type_name_v<uint16_t>, type_name_v<uint32_t>,
type_name_v<uint64_t>, type_name_v<uint8_t>};
auto eq = [&tn](string_view x) { return x == tn; };
if (std::any_of(std::begin(valid_types), std::end(valid_types), eq)) {
if (auto j = x.find("value"); j != x.end()) {
return j->second.to_integer();
} else {
std::string msg = "missing value for object of type ";
msg += tn;
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert ";
msg += tn;
msg += " to an integer";
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert a dictionary to an integer";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
}
}); });
return visit(f, data_); return visit(f, data_);
} }
...@@ -276,8 +306,7 @@ expected<config_value::integer> config_value::to_integer() const { ...@@ -276,8 +306,7 @@ expected<config_value::integer> config_value::to_integer() const {
expected<config_value::real> config_value::to_real() const { expected<config_value::real> config_value::to_real() const {
using result_type = expected<real>; using result_type = expected<real>;
auto f = detail::make_overload( auto f = detail::make_overload(
no_conversions<real, none_t, bool, timespan, uri, config_value::list, no_conversions<real, none_t, bool, timespan, uri, config_value::list>(),
config_value::dictionary>(),
[](integer x) { [](integer x) {
// This cast may lose precision on the value. We could try and check that, // This cast may lose precision on the value. We could try and check that,
// but refusing to convert on loss of precision could also be unexpected // but refusing to convert on loss of precision could also be unexpected
...@@ -293,6 +322,35 @@ expected<config_value::real> config_value::to_real() const { ...@@ -293,6 +322,35 @@ expected<config_value::real> config_value::to_real() const {
detail::print_escaped(msg, x); detail::print_escaped(msg, x);
msg += " to a floating point number"; msg += " to a floating point number";
return result_type{make_error(sec::conversion_failed, std::move(msg))}; return result_type{make_error(sec::conversion_failed, std::move(msg))};
},
[](const dictionary& x) {
if (auto i = x.find("@type");
i != x.end() && holds_alternative<std::string>(i->second)) {
const auto& tn = get<std::string>(i->second);
string_view valid_types[] = {type_name_v<float>, type_name_v<double>,
type_name_v<long double>};
auto eq = [&tn](string_view x) { return x == tn; };
if (std::any_of(std::begin(valid_types), std::end(valid_types), eq)) {
if (auto j = x.find("value"); j != x.end()) {
return j->second.to_real();
} else {
std::string msg = "missing value for object of type ";
msg += tn;
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert ";
msg += tn;
msg += " to a floating point number";
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg
= "cannot convert a dictionary to a floating point number";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
}
}); });
return visit(f, data_); return visit(f, data_);
} }
......
...@@ -381,7 +381,7 @@ bool config_value_reader::begin_associative_array(size_t& size) { ...@@ -381,7 +381,7 @@ bool config_value_reader::begin_associative_array(size_t& size) {
st_.top() = associative_array{dict->begin(), dict->end()}; st_.top() = associative_array{dict->begin(), dict->end()};
return true; return true;
} }
std::string msg = "expected a dictionary, got a "; std::string msg = "begin_associative_array: expected a dictionary, got a ";
msg += top->type_name(); msg += top->type_name();
emplace_error(sec::conversion_failed, std::move(msg)); emplace_error(sec::conversion_failed, std::move(msg));
return false; return false;
...@@ -419,13 +419,10 @@ bool pull(config_value_reader& reader, T& x) { ...@@ -419,13 +419,10 @@ bool pull(config_value_reader& reader, T& x) {
reader.pop(); reader.pop();
return true; return true;
} else { } else {
std::string msg = "expected a dictionary, got a "; reader.set_error(std::move(val.error()));
msg += to_string(type_name_v<T>);
reader.emplace_error(sec::conversion_failed, std::move(msg));
return false; return false;
} }
} } else if (holds_alternative<config_value_reader::sequence>(top)) {
if (holds_alternative<config_value_reader::sequence>(top)) {
auto& seq = get<config_value_reader::sequence>(top); auto& seq = get<config_value_reader::sequence>(top);
if (seq.at_end()) { if (seq.at_end()) {
reader.emplace_error(sec::runtime_error, "value: sequence out of bounds"); reader.emplace_error(sec::runtime_error, "value: sequence out of bounds");
...@@ -437,13 +434,10 @@ bool pull(config_value_reader& reader, T& x) { ...@@ -437,13 +434,10 @@ bool pull(config_value_reader& reader, T& x) {
seq.advance(); seq.advance();
return true; return true;
} else { } else {
std::string msg = "expected a dictionary, got a "; reader.set_error(std::move(val.error()));
msg += to_string(type_name_v<T>);
reader.emplace_error(sec::conversion_failed, std::move(msg));
return false; return false;
} }
} } else if (holds_alternative<config_value_reader::key_ptr>(top)) {
if (holds_alternative<config_value_reader::key_ptr>(top)) {
auto ptr = get<config_value_reader::key_ptr>(top); auto ptr = get<config_value_reader::key_ptr>(top);
if constexpr (std::is_same<std::string, T>::value) { if constexpr (std::is_same<std::string, T>::value) {
x = *ptr; x = *ptr;
...@@ -453,10 +447,11 @@ bool pull(config_value_reader& reader, T& x) { ...@@ -453,10 +447,11 @@ bool pull(config_value_reader& reader, T& x) {
if (auto err = detail::parse(*ptr, x)) { if (auto err = detail::parse(*ptr, x)) {
reader.set_error(std::move(err)); reader.set_error(std::move(err));
return false; return false;
} } else {
return true; return true;
} }
} }
}
reader.emplace_error(sec::conversion_failed, reader.emplace_error(sec::conversion_failed,
"expected a value, sequence, or key"); "expected a value, sequence, or key");
return false; return false;
......
...@@ -161,6 +161,23 @@ SCENARIO("get_as can convert config values to integers") { ...@@ -161,6 +161,23 @@ SCENARIO("get_as can convert config values to integers") {
} }
} }
} }
GIVEN("a config value x with type annotation 'int32_t' and the value 50") {
config_value x;
x.as_dictionary().emplace("@type", "int32_t");
x.as_dictionary().emplace("value", 50);
WHEN("using get_as with integer types") {
THEN("CAF parses the integer and performs a bound check") {
CHECK_EQ(get_as<uint64_t>(x), 50u);
CHECK_EQ(get_as<int64_t>(x), 50);
CHECK_EQ(get_as<uint32_t>(x), 50u);
CHECK_EQ(get_as<int32_t>(x), 50);
CHECK_EQ(get_as<uint16_t>(x), 50u);
CHECK_EQ(get_as<int16_t>(x), 50);
CHECK_EQ(get_as<uint8_t>(x), 50u);
CHECK_EQ(get_as<int8_t>(x), 50);
}
}
}
GIVEN("a config value x with value 50.0") { GIVEN("a config value x with value 50.0") {
auto x = config_value{50.0}; auto x = config_value{50.0};
WHEN("using get_as with integer types") { WHEN("using get_as with integer types") {
...@@ -278,6 +295,18 @@ SCENARIO("get_as can convert config values to floating point numbers") { ...@@ -278,6 +295,18 @@ SCENARIO("get_as can convert config values to floating point numbers") {
} }
} }
} }
GIVEN("a config value x with type annotation 'float' and the value 50") {
config_value x;
x.as_dictionary().emplace("@type", "float");
x.as_dictionary().emplace("value", 123.0);
WHEN("using get_as with floating point types") {
THEN("CAF parses the value and performs a bound check") {
CHECK_EQ(get_as<long double>(x), 123.0);
CHECK_EQ(get_as<double>(x), 123.0);
CHECK_EQ(get_as<float>(x), 123.f);
}
}
}
GIVEN("config_values of null, URI, boolean, list or dictionary") { GIVEN("config_values of null, URI, boolean, list or dictionary") {
WHEN("using get_as with integer types") { WHEN("using get_as with integer types") {
THEN("conversion fails") { THEN("conversion fails") {
......
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