From: Joel Rosdahl Date: Tue, 13 Jul 2021 11:04:49 +0000 (+0200) Subject: Move parse_signed and parse_unsigned to util X-Git-Tag: v4.4~119 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=556039bc3d95dd875898419e0e93ffd815af35d8;p=thirdparty%2Fccache.git Move parse_signed and parse_unsigned to util --- diff --git a/src/Config.cpp b/src/Config.cpp index 110c04995..931d24760 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -28,6 +28,7 @@ #include "fmtmacros.hpp" #include +#include #include #include @@ -859,11 +860,10 @@ Config::set_item(const std::string& key, m_compression = parse_bool(value, env_var_key, negate); break; - case ConfigItem::compression_level: { - m_compression_level = - Util::parse_signed(value, INT8_MIN, INT8_MAX, "compression_level"); + case ConfigItem::compression_level: + m_compression_level = util::value_or_throw( + util::parse_signed(value, INT8_MIN, INT8_MAX, "compression_level")); break; - } case ConfigItem::cpp_extension: m_cpp_extension = value; @@ -930,7 +930,8 @@ Config::set_item(const std::string& key, break; case ConfigItem::max_files: - m_max_files = Util::parse_unsigned(value, nullopt, nullopt, "max_files"); + m_max_files = util::value_or_throw( + util::parse_unsigned(value, nullopt, nullopt, "max_files")); break; case ConfigItem::max_size: diff --git a/src/Util.cpp b/src/Util.cpp index 36a6bf4e3..cce67c8b0 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -1027,36 +1027,13 @@ parse_duration(const std::string& duration) duration); } - return factor * parse_unsigned(duration.substr(0, duration.length() - 1)); -} - -int64_t -parse_signed(const std::string& value, - optional min_value, - optional max_value, - string_view description) -{ - std::string stripped_value = util::strip_whitespace(value); - - size_t end = 0; - long long result = 0; - bool failed = false; - try { - // Note: sizeof(long long) is guaranteed to be >= sizeof(int64_t) - result = std::stoll(stripped_value, &end, 10); - } catch (std::exception&) { - failed = true; - } - if (failed || end != stripped_value.size()) { - throw Error("invalid integer: \"{}\"", stripped_value); - } - - int64_t min = min_value ? *min_value : INT64_MIN; - int64_t max = max_value ? *max_value : INT64_MAX; - if (result < min || result > max) { - throw Error("{} must be between {} and {}", description, min, max); + const auto value = + util::parse_unsigned(duration.substr(0, duration.length() - 1)); + if (value) { + return factor * *value; + } else { + throw Error(value.error()); } - return result; } uint64_t @@ -1100,43 +1077,6 @@ parse_size(const std::string& value) return static_cast(result); } -uint64_t -parse_unsigned(const std::string& value, - optional min_value, - optional max_value, - string_view description, - int base) -{ - std::string stripped_value = util::strip_whitespace(value); - - size_t end = 0; - unsigned long long result = 0; - bool failed = false; - if (util::starts_with(stripped_value, "-")) { - failed = true; - } else { - try { - // Note: sizeof(unsigned long long) is guaranteed to be >= - // sizeof(uint64_t) - result = std::stoull(stripped_value, &end, base); - } catch (std::exception&) { - failed = true; - } - } - if (failed || end != stripped_value.size()) { - const auto base_info = base == 8 ? "octal " : ""; - throw Error( - "invalid unsigned {}integer: \"{}\"", base_info, stripped_value); - } - - uint64_t min = min_value ? *min_value : 0; - uint64_t max = max_value ? *max_value : UINT64_MAX; - if (result < min || result > max) { - throw Error("{} must be between {} and {}", description, min, max); - } - return result; -} - bool read_fd(int fd, DataReceiver data_receiver) { diff --git a/src/Util.hpp b/src/Util.hpp index 853891d8d..f41707bf1 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -315,34 +315,11 @@ std::string normalize_absolute_path(nonstd::string_view path); // into seconds. Throws `Error` on error. uint64_t parse_duration(const std::string& duration); -// Parse a string into a signed integer. -// -// Throws `Error` if `value` cannot be parsed as an int64_t or if the value -// falls out of the range [`min_value`, `max_value`]. `min_value` and -// `max_value` default to min and max values of int64_t. `description` is -// included in the error message for range violations. -int64_t parse_signed(const std::string& value, - nonstd::optional min_value = nonstd::nullopt, - nonstd::optional max_value = nonstd::nullopt, - nonstd::string_view description = "integer"); - // Parse a "size value", i.e. a string that can end in k, M, G, T (10-based // suffixes) or Ki, Mi, Gi, Ti (2-based suffixes). For backward compatibility, K // is also recognized as a synonym of k. Throws `Error` on parse error. uint64_t parse_size(const std::string& value); -// Parse a string into an unsigned integer. -// -// Throws `Error` if `value` cannot be parsed as an uint64_t with base `base`, -// or if the value falls out of the range [`min_value`, `max_value`]. -// `min_value` and `max_value` default to min and max values of uint64_t. -// `description` is included in the error message for range violations. -uint64_t parse_unsigned(const std::string& value, - nonstd::optional min_value = nonstd::nullopt, - nonstd::optional max_value = nonstd::nullopt, - nonstd::string_view description = "integer", - int base = 10); - // Read data from `fd` until end of file and call `data_receiver` with the read // data. Returns whether reading was successful, i.e. whether the read(2) call // did not return -1. diff --git a/src/ccache.cpp b/src/ccache.cpp index c9f9253b8..7b942ce8f 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -57,6 +57,7 @@ #include #include +#include #include #include @@ -2397,7 +2398,7 @@ handle_main_options(int argc, const char* const* argv) break; case 'F': { // --max-files - auto files = Util::parse_unsigned(arg); + auto files = util::value_or_throw(util::parse_unsigned(arg)); Config::set_value_in_file( ctx.config.primary_config_path(), "max_files", arg); if (files == 0) { @@ -2482,8 +2483,8 @@ handle_main_options(int argc, const char* const* argv) if (arg == "uncompressed") { wanted_level = nullopt; } else { - wanted_level = - Util::parse_signed(arg, INT8_MIN, INT8_MAX, "compression level"); + wanted_level = util::value_or_throw( + util::parse_signed(arg, INT8_MIN, INT8_MAX, "compression level")); } ProgressBar progress_bar("Recompressing..."); diff --git a/src/storage/secondary/HttpStorage.cpp b/src/storage/secondary/HttpStorage.cpp index a9cb39bc3..c5c4a6220 100644 --- a/src/storage/secondary/HttpStorage.cpp +++ b/src/storage/secondary/HttpStorage.cpp @@ -20,10 +20,10 @@ #include #include -#include #include #include #include +#include #include #include @@ -142,8 +142,8 @@ parse_timeout_attribute(const AttributeMap& attributes, if (it == attributes.end()) { return default_value; } else { - auto timeout_in_ms = - Util::parse_unsigned(it->second, 1, 1000 * 3600, "timeout"); + const auto timeout_in_ms = util::value_or_throw( + util::parse_unsigned(it->second, 1, 1000 * 3600, "timeout")); return std::chrono::milliseconds{timeout_in_ms}; } } diff --git a/src/storage/secondary/RedisStorage.cpp b/src/storage/secondary/RedisStorage.cpp index a05dc1b46..c181817f2 100644 --- a/src/storage/secondary/RedisStorage.cpp +++ b/src/storage/secondary/RedisStorage.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -65,7 +66,8 @@ parse_timeout_attribute(const AttributeMap& attributes, if (it == attributes.end()) { return default_value; } else { - return Util::parse_unsigned(it->second, 1, 1000 * 3600, "timeout"); + return util::value_or_throw( + util::parse_unsigned(it->second, 1, 1000 * 3600, "timeout")); } } @@ -129,17 +131,18 @@ RedisStorage::connect() ASSERT(m_url.scheme() == "redis"); const std::string host = m_url.host().empty() ? "localhost" : m_url.host(); - const uint32_t port = - m_url.port().empty() ? DEFAULT_PORT - : Util::parse_unsigned(m_url.port(), 1, 65535, "port"); + const uint32_t port = m_url.port().empty() + ? DEFAULT_PORT + : util::value_or_throw<::Error>(util::parse_unsigned( + m_url.port(), 1, 65535, "port")); ASSERT(m_url.path().empty() || m_url.path()[0] == '/'); const uint32_t db_number = - m_url.path().empty() - ? 0 - : Util::parse_unsigned(m_url.path().substr(1), - 0, - std::numeric_limits::max(), - "db number"); + m_url.path().empty() ? 0 + : util::value_or_throw<::Error>(util::parse_unsigned( + m_url.path().substr(1), + 0, + std::numeric_limits::max(), + "db number")); const auto connect_timeout = milliseconds_to_timeval(m_connect_timeout); diff --git a/src/util/string_utils.cpp b/src/util/string_utils.cpp index 0e4397796..237c61a01 100644 --- a/src/util/string_utils.cpp +++ b/src/util/string_utils.cpp @@ -19,20 +19,86 @@ #include "string_utils.hpp" #include -#include #include #include namespace util { +nonstd::expected +parse_signed(const std::string& value, + const nonstd::optional min_value, + const nonstd::optional max_value, + const nonstd::string_view description) +{ + const std::string stripped_value = strip_whitespace(value); + + size_t end = 0; + long long result = 0; + bool failed = false; + try { + // Note: sizeof(long long) is guaranteed to be >= sizeof(int64_t) + result = std::stoll(stripped_value, &end, 10); + } catch (std::exception&) { + failed = true; + } + if (failed || end != stripped_value.size()) { + return nonstd::make_unexpected( + FMT("invalid integer: \"{}\"", stripped_value)); + } + + const int64_t min = min_value ? *min_value : INT64_MIN; + const int64_t max = max_value ? *max_value : INT64_MAX; + if (result < min || result > max) { + return nonstd::make_unexpected( + FMT("{} must be between {} and {}", description, min, max)); + } else { + return result; + } +} + nonstd::expected parse_umask(const std::string& value) { - try { - return Util::parse_unsigned(value, 0, 0777, "umask", 8); - } catch (const Error& e) { - return nonstd::make_unexpected(e.what()); + return util::parse_unsigned(value, 0, 0777, "umask", 8); +} + +nonstd::expected +parse_unsigned(const std::string& value, + const nonstd::optional min_value, + const nonstd::optional max_value, + const nonstd::string_view description, + const int base) +{ + const std::string stripped_value = strip_whitespace(value); + + size_t end = 0; + unsigned long long result = 0; + bool failed = false; + if (starts_with(stripped_value, "-")) { + failed = true; + } else { + try { + // Note: sizeof(unsigned long long) is guaranteed to be >= + // sizeof(uint64_t) + result = std::stoull(stripped_value, &end, base); + } catch (std::exception&) { + failed = true; + } + } + if (failed || end != stripped_value.size()) { + const auto base_info = base == 8 ? "octal " : ""; + return nonstd::make_unexpected( + FMT("invalid unsigned {}integer: \"{}\"", base_info, stripped_value)); + } + + const uint64_t min = min_value ? *min_value : 0; + const uint64_t max = max_value ? *max_value : UINT64_MAX; + if (result < min || result > max) { + return nonstd::make_unexpected( + FMT("{} must be between {} and {}", description, min, max)); + } else { + return result; } } diff --git a/src/util/string_utils.hpp b/src/util/string_utils.hpp index e6aa19b20..ac27704e1 100644 --- a/src/util/string_utils.hpp +++ b/src/util/string_utils.hpp @@ -37,9 +37,34 @@ ends_with(const nonstd::string_view string, const nonstd::string_view suffix) return string.ends_with(suffix); } +// Parse a string into a signed integer. +// +// Return an error string if `value` cannot be parsed as an int64_t or if the +// value falls out of the range [`min_value`, `max_value`]. `min_value` and +// `max_value` default to min and max values of int64_t. `description` is +// included in the error message for range violations. +nonstd::expected +parse_signed(const std::string& value, + nonstd::optional min_value = nonstd::nullopt, + nonstd::optional max_value = nonstd::nullopt, + nonstd::string_view description = "integer"); + // Parse `value` (an octal integer). nonstd::expected parse_umask(const std::string& value); +// Parse a string into an unsigned integer. +// +// Returns an error string if `value` cannot be parsed as an uint64_t with base +// `base`, or if the value falls out of the range [`min_value`, `max_value`]. +// `min_value` and `max_value` default to min and max values of uint64_t. +// `description` is included in the error message for range violations. +nonstd::expected +parse_unsigned(const std::string& value, + nonstd::optional min_value = nonstd::nullopt, + nonstd::optional max_value = nonstd::nullopt, + nonstd::string_view description = "integer", + int base = 10); + // Percent-decode[1] `string`. // // [1]: https://en.wikipedia.org/wiki/Percent-encoding diff --git a/unittest/test_Util.cpp b/unittest/test_Util.cpp index f34bcbb29..32e14687a 100644 --- a/unittest/test_Util.cpp +++ b/unittest/test_Util.cpp @@ -671,46 +671,6 @@ TEST_CASE("Util::parse_duration") "invalid suffix (supported: d (day) and s (second)): \"2\""); } -TEST_CASE("Util::parse_signed") -{ - CHECK(Util::parse_signed("0") == 0); - CHECK(Util::parse_signed("2") == 2); - CHECK(Util::parse_signed("-17") == -17); - CHECK(Util::parse_signed("42") == 42); - CHECK(Util::parse_signed("0666") == 666); - CHECK(Util::parse_signed(" 777 ") == 777); - - CHECK_THROWS_WITH(Util::parse_signed(""), "invalid integer: \"\""); - CHECK_THROWS_WITH(Util::parse_signed("x"), "invalid integer: \"x\""); - CHECK_THROWS_WITH(Util::parse_signed("0x"), "invalid integer: \"0x\""); - CHECK_THROWS_WITH(Util::parse_signed("0x4"), "invalid integer: \"0x4\""); - - // Custom description not used for invalid value. - CHECK_THROWS_WITH(Util::parse_signed("apple", nullopt, nullopt, "banana"), - "invalid integer: \"apple\""); - - // Boundary values. - CHECK_THROWS_WITH(Util::parse_signed("-9223372036854775809"), - "invalid integer: \"-9223372036854775809\""); - CHECK(Util::parse_signed("-9223372036854775808") == INT64_MIN); - CHECK(Util::parse_signed("9223372036854775807") == INT64_MAX); - CHECK_THROWS_WITH(Util::parse_signed("9223372036854775808"), - "invalid integer: \"9223372036854775808\""); - - // Min and max values. - CHECK_THROWS_WITH(Util::parse_signed("-2", -1, 1), - "integer must be between -1 and 1"); - CHECK(Util::parse_signed("-1", -1, 1) == -1); - CHECK(Util::parse_signed("0", -1, 1) == 0); - CHECK(Util::parse_signed("1", -1, 1) == 1); - CHECK_THROWS_WITH(Util::parse_signed("2", -1, 1), - "integer must be between -1 and 1"); - - // Custom description used for boundary violation. - CHECK_THROWS_WITH(Util::parse_signed("0", 1, 2, "banana"), - "banana must be between 1 and 2"); -} - TEST_CASE("Util::parse_size") { CHECK(Util::parse_size("0") == 0); @@ -735,40 +695,6 @@ TEST_CASE("Util::parse_size") CHECK_THROWS_WITH(Util::parse_size("10x"), "invalid size: \"10x\""); } -TEST_CASE("Util::parse_unsigned") -{ - CHECK(Util::parse_unsigned("0") == 0); - CHECK(Util::parse_unsigned("2") == 2); - CHECK(Util::parse_unsigned("42") == 42); - CHECK(Util::parse_unsigned("0666") == 666); - CHECK(Util::parse_unsigned(" 777 ") == 777); - - CHECK_THROWS_WITH(Util::parse_unsigned(""), "invalid unsigned integer: \"\""); - CHECK_THROWS_WITH(Util::parse_unsigned("x"), - "invalid unsigned integer: \"x\""); - CHECK_THROWS_WITH(Util::parse_unsigned("0x"), - "invalid unsigned integer: \"0x\""); - CHECK_THROWS_WITH(Util::parse_unsigned("0x4"), - "invalid unsigned integer: \"0x4\""); - - // Custom description not used for invalid value. - CHECK_THROWS_WITH(Util::parse_unsigned("apple", nullopt, nullopt, "banana"), - "invalid unsigned integer: \"apple\""); - - // Boundary values. - CHECK_THROWS_WITH(Util::parse_unsigned("-1"), - "invalid unsigned integer: \"-1\""); - CHECK(Util::parse_unsigned("0") == 0); - CHECK(Util::parse_unsigned("18446744073709551615") == UINT64_MAX); - CHECK_THROWS_WITH(Util::parse_unsigned("18446744073709551616"), - "invalid unsigned integer: \"18446744073709551616\""); - - // Base - CHECK(Util::parse_unsigned("0666", nullopt, nullopt, "", 8) == 0666); - CHECK(Util::parse_unsigned("0666", nullopt, nullopt, "", 10) == 666); - CHECK(Util::parse_unsigned("0666", nullopt, nullopt, "", 16) == 0x666); -} - TEST_CASE("Util::read_file and Util::write_file") { TestContext test_context; diff --git a/unittest/test_util_string_utils.cpp b/unittest/test_util_string_utils.cpp index f4bef5b49..25d195656 100644 --- a/unittest/test_util_string_utils.cpp +++ b/unittest/test_util_string_utils.cpp @@ -48,6 +48,47 @@ TEST_CASE("util::ends_with") CHECK_FALSE(util::ends_with("x", "xy")); } +TEST_CASE("util::parse_signed") +{ + CHECK(*util::parse_signed("0") == 0); + CHECK(*util::parse_signed("2") == 2); + CHECK(*util::parse_signed("-17") == -17); + CHECK(*util::parse_signed("42") == 42); + CHECK(*util::parse_signed("0666") == 666); + CHECK(*util::parse_signed(" 777 ") == 777); + + CHECK(util::parse_signed("").error() == "invalid integer: \"\""); + CHECK(util::parse_signed("x").error() == "invalid integer: \"x\""); + CHECK(util::parse_signed("0x").error() == "invalid integer: \"0x\""); + CHECK(util::parse_signed("0x4").error() == "invalid integer: \"0x4\""); + + // Custom description not used for invalid value. + CHECK(util::parse_signed("apple", nonstd::nullopt, nonstd::nullopt, "banana") + .error() + == "invalid integer: \"apple\""); + + // Boundary values. + CHECK(util::parse_signed("-9223372036854775809").error() + == "invalid integer: \"-9223372036854775809\""); + CHECK(*util::parse_signed("-9223372036854775808") == INT64_MIN); + CHECK(*util::parse_signed("9223372036854775807") == INT64_MAX); + CHECK(util::parse_signed("9223372036854775808").error() + == "invalid integer: \"9223372036854775808\""); + + // Min and max values. + CHECK(util::parse_signed("-2", -1, 1).error() + == "integer must be between -1 and 1"); + CHECK(*util::parse_signed("-1", -1, 1) == -1); + CHECK(*util::parse_signed("0", -1, 1) == 0); + CHECK(*util::parse_signed("1", -1, 1) == 1); + CHECK(util::parse_signed("2", -1, 1).error() + == "integer must be between -1 and 1"); + + // Custom description used for boundary violation. + CHECK(util::parse_signed("0", 1, 2, "banana").error() + == "banana must be between 1 and 2"); +} + TEST_CASE("util::parse_umask") { CHECK(util::parse_umask("1") == 01u); @@ -63,6 +104,44 @@ TEST_CASE("util::parse_umask") == "invalid unsigned octal integer: \"088\""); } +TEST_CASE("util::parse_unsigned") +{ + CHECK(*util::parse_unsigned("0") == 0); + CHECK(*util::parse_unsigned("2") == 2); + CHECK(*util::parse_unsigned("42") == 42); + CHECK(*util::parse_unsigned("0666") == 666); + CHECK(*util::parse_unsigned(" 777 ") == 777); + + CHECK(util::parse_unsigned("").error() == "invalid unsigned integer: \"\""); + CHECK(util::parse_unsigned("x").error() == "invalid unsigned integer: \"x\""); + CHECK(util::parse_unsigned("0x").error() + == "invalid unsigned integer: \"0x\""); + CHECK(util::parse_unsigned("0x4").error() + == "invalid unsigned integer: \"0x4\""); + + // Custom description not used for invalid value. + CHECK( + util::parse_unsigned("apple", nonstd::nullopt, nonstd::nullopt, "banana") + .error() + == "invalid unsigned integer: \"apple\""); + + // Boundary values. + CHECK(util::parse_unsigned("-1").error() + == "invalid unsigned integer: \"-1\""); + CHECK(*util::parse_unsigned("0") == 0); + CHECK(*util::parse_unsigned("18446744073709551615") == UINT64_MAX); + CHECK(util::parse_unsigned("18446744073709551616").error() + == "invalid unsigned integer: \"18446744073709551616\""); + + // Base + CHECK(*util::parse_unsigned("0666", nonstd::nullopt, nonstd::nullopt, "", 8) + == 0666); + CHECK(*util::parse_unsigned("0666", nonstd::nullopt, nonstd::nullopt, "", 10) + == 666); + CHECK(*util::parse_unsigned("0666", nonstd::nullopt, nonstd::nullopt, "", 16) + == 0x666); +} + TEST_CASE("util::percent_decode") { CHECK(util::percent_decode("") == "");