99#include < charconv>
1010#include < cstdint>
1111#include < cstdlib>
12- #include < limits>
1312#include < string>
1413#include < string_view>
1514#include < type_traits>
16- #include < typeindex>
1715#include < typeinfo>
1816
17+ #include < userver/utils/expected.hpp>
1918#include < userver/utils/zstring_view.hpp>
2019
2120USERVER_NAMESPACE_BEGIN
2221
2322namespace utils {
2423
24+ // / @brief Conversion error code.
25+ enum class FromStringErrorCode {
26+ // / @brief String contains leading whitespace characters.
27+ kLeadingSpaces = 1 ,
28+
29+ // / @brief String contains invalid (non-digit) characters at the end.
30+ kTrailingJunk = 2 ,
31+
32+ // / @brief String does not contain a number.
33+ kNoNumber = 3 ,
34+
35+ // / @brief Conversion result is out of the valid range of the specified type.
36+ kOverflow = 4
37+ };
38+
39+ // / @brief Converts @a code to string representation.
40+ constexpr inline std::string_view ToString (FromStringErrorCode code) noexcept {
41+ switch (code) {
42+ case FromStringErrorCode::kLeadingSpaces :
43+ return " leading spaces are not allowed" ;
44+ case FromStringErrorCode::kTrailingJunk :
45+ return " extra junk at the end of the string is not allowed" ;
46+ case FromStringErrorCode::kNoNumber :
47+ return " no number found" ;
48+ case FromStringErrorCode::kOverflow :
49+ return " overflow" ;
50+ default :
51+ return " unknown" ;
52+ }
53+ }
54+
55+ // / @brief Function `utils::FromString` exception type.
2556class FromStringException : public std ::runtime_error {
2657public:
27- using std::runtime_error::runtime_error;
58+ // / @brief Creates exception for @a code .
59+ FromStringException (FromStringErrorCode code, const std::string& what);
60+
61+ // / @brief Returns conversion error code.
62+ FromStringErrorCode GetCode () const noexcept { return code_; }
63+
64+ private:
65+ FromStringErrorCode code_;
2866};
2967
3068namespace impl {
@@ -48,24 +86,25 @@ template <class T>
4886inline constexpr bool kIsFromCharsConvertible = IsFromCharsConvertible<T>::value;
4987
5088[[noreturn]] void ThrowFromStringException (
51- std::string_view message ,
89+ FromStringErrorCode code ,
5290 std::string_view input,
53- std::type_index result_type
91+ const std::type_info& result_type
5492);
5593
5694template <typename T>
57- std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, T> FromString (utils::zstring_view str) {
95+ std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, expected<T, FromStringErrorCode>>
96+ FromString (utils::zstring_view str) noexcept {
5897 static_assert (!std::is_const_v<T> && !std::is_volatile_v<T>);
5998 static_assert (!std::is_reference_v<T>);
6099
61100 if (str.empty ()) {
62- impl::ThrowFromStringException ( " empty string " , str, typeid (T)) ;
101+ return unexpected{FromStringErrorCode:: kNoNumber } ;
63102 }
64103 if (std::isspace (str.front ())) {
65- impl::ThrowFromStringException ( " leading spaces are not allowed " , str, typeid (T)) ;
104+ return unexpected{FromStringErrorCode:: kLeadingSpaces } ;
66105 }
67106 if (str.size () > 2 && str[0 ] == ' 0' && (str[1 ] == ' x' || str[1 ] == ' X' )) {
68- impl::ThrowFromStringException ( " extra junk at the end of the string is not allowed " , str, typeid (T)) ;
107+ return unexpected{FromStringErrorCode:: kTrailingJunk } ;
69108 }
70109
71110 errno = 0 ;
@@ -82,36 +121,35 @@ std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, T>
82121 }();
83122
84123 if (errno == ERANGE && !(result < 1 && result > 0.0 )) {
85- impl::ThrowFromStringException ( " overflow " , str, typeid (T)) ;
124+ return unexpected{FromStringErrorCode:: kOverflow } ;
86125 }
87126
88127 if (end == str.c_str ()) {
89- impl::ThrowFromStringException ( " no number found " , str, typeid (T)) ;
128+ return unexpected{FromStringErrorCode:: kNoNumber } ;
90129 }
91130
92131 if (end != str.data () + str.size ()) {
93- if (std::isspace (*end)) {
94- impl::ThrowFromStringException (" trailing spaces are not allowed" , str, typeid (T));
95- } else {
96- impl::ThrowFromStringException (" extra junk at the end of the string is not allowed" , str, typeid (T));
97- }
132+ return unexpected{FromStringErrorCode::kTrailingJunk };
98133 }
99134
100135 return result;
101136}
102137
103138template <typename T>
104- std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, T> FromString (const std::string& str) {
139+ std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, expected<T, FromStringErrorCode>>
140+ FromString (const std::string& str) noexcept {
105141 return impl::FromString<T>(utils::zstring_view{str});
106142}
107143
108144template <typename T>
109- std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, T> FromString (const char * str) {
145+ std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, expected<T, FromStringErrorCode>>
146+ FromString (const char * str) noexcept {
110147 return impl::FromString<T>(utils::zstring_view{str});
111148}
112149
113150template <typename T>
114- std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, T> FromString (std::string_view str) {
151+ std::enable_if_t <std::is_floating_point_v<T> && !kIsFromCharsConvertible <T>, expected<T, FromStringErrorCode>>
152+ FromString (std::string_view str) noexcept {
115153 static constexpr std::size_t kSmallBufferSize = 32 ;
116154
117155 if (str.size () >= kSmallBufferSize ) {
@@ -127,22 +165,23 @@ std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, T>
127165}
128166
129167template <typename T>
130- std::enable_if_t <kIsFromCharsConvertible <T>, T> FromString (std::string_view str) {
168+ std::enable_if_t <kIsFromCharsConvertible <T>, expected<T, FromStringErrorCode>> FromString (std::string_view str
169+ ) noexcept {
131170 static_assert (!std::is_const_v<T> && !std::is_volatile_v<T>);
132171 static_assert (!std::is_reference_v<T>);
133172
134173 if (str.empty ()) {
135- impl::ThrowFromStringException ( " empty string " , str, typeid (T)) ;
174+ return unexpected{FromStringErrorCode:: kNoNumber } ;
136175 }
137176 if (std::isspace (str[0 ])) {
138- impl::ThrowFromStringException ( " leading spaces are not allowed " , str, typeid (T)) ;
177+ return unexpected{FromStringErrorCode:: kLeadingSpaces } ;
139178 }
140179
141180 std::size_t offset = 0 ;
142181
143182 // to allow leading plus
144183 if (str.size () > 1 && str[0 ] == ' +' && str[1 ] == ' -' ) {
145- impl::ThrowFromStringException ( " no number found " , str, typeid (T)) ;
184+ return unexpected{FromStringErrorCode:: kNoNumber } ;
146185 }
147186 if (str[0 ] == ' +' ) {
148187 offset = 1 ;
@@ -157,22 +196,18 @@ std::enable_if_t<kIsFromCharsConvertible<T>, T> FromString(std::string_view str)
157196 const auto [end, error_code] = std::from_chars (str.data () + offset, str.data () + str.size (), result);
158197
159198 if (error_code == std::errc::result_out_of_range) {
160- impl::ThrowFromStringException ( " overflow " , str, typeid (T)) ;
199+ return unexpected{FromStringErrorCode:: kOverflow } ;
161200 }
162201 if (error_code == std::errc::invalid_argument) {
163- impl::ThrowFromStringException ( " no number found " , str, typeid (T)) ;
202+ return unexpected{FromStringErrorCode:: kNoNumber } ;
164203 }
165204
166205 if (std::is_unsigned_v<T> && str[0 ] == ' -' && result != 0 ) {
167- impl::ThrowFromStringException ( " overflow " , str, typeid (T)) ;
206+ return unexpected{FromStringErrorCode:: kOverflow } ;
168207 }
169208
170209 if (end != str.data () + str.size ()) {
171- if (std::isspace (*end)) {
172- impl::ThrowFromStringException (" trailing spaces are not allowed" , str, typeid (T));
173- } else {
174- impl::ThrowFromStringException (" extra junk at the end of the string is not allowed" , str, typeid (T));
175- }
210+ return unexpected{FromStringErrorCode::kTrailingJunk };
176211 }
177212
178213 return result;
@@ -199,6 +234,31 @@ template <
199234 typename StringType,
200235 typename = std::enable_if_t <std::is_convertible_v<StringType, std::string_view>>>
201236T FromString (const StringType& str) {
237+ const auto result = impl::FromString<T>(str);
238+
239+ if (result) {
240+ return result.value ();
241+ } else {
242+ impl::ThrowFromStringException (result.error (), str, typeid (T));
243+ }
244+ }
245+
246+ // / @brief Extract the number contained in the string. No space characters or
247+ // / other extra characters allowed. Supported types:
248+ // /
249+ // / - Integer types. Leading plus or minus is allowed. The number is always
250+ // / base-10.
251+ // / - Floating-point types. The accepted number format is identical to
252+ // / `std::strtod`.
253+ // /
254+ // / @tparam T The type of the number to be parsed
255+ // / @param str The string that contains the number
256+ // / @return `utils::expected` with the conversion result or error code
257+ template <
258+ typename T,
259+ typename StringType,
260+ typename = std::enable_if_t <std::is_convertible_v<StringType, std::string_view>>>
261+ expected<T, FromStringErrorCode> FromStringNoThrow (const StringType& str) noexcept {
202262 return impl::FromString<T>(str);
203263}
204264
0 commit comments