Pārlūkot izejas kodu

refactor/fix: make integer parsing for Pointer require all chars in the string

Sam Jaffe 3 mēneši atpakaļ
vecāks
revīzija
ac689794ed

+ 19 - 0
include/jvalidate/detail/number.h

@@ -8,8 +8,12 @@
  */
 #pragma once
 
+#include <charconv>
 #include <cmath>
 #include <limits>
+#include <stdexcept>
+#include <string_view>
+#include <system_error>
 
 namespace jvalidate::detail {
 /**
@@ -32,4 +36,19 @@ inline bool fits_in_integer(double number) {
  * @brief Determine if an unsigned integer fits into a signed integer
  */
 inline bool fits_in_integer(uint64_t number) { return (number & 0x8000'0000'0000'0000) == 0; }
+
+template <std::integral I> I from_str(std::string_view str, int base = 10) {
+  I rval;
+  auto [end, ec] = std::from_chars(str.begin(), str.end(), rval, base);
+
+  if (ec != std::errc{}) {
+    throw std::runtime_error(std::make_error_code(ec).message());
+  }
+
+  if (end != str.end()) {
+    throw std::runtime_error("NaN: " + std::string(str));
+  }
+
+  return rval;
+}
 }

+ 4 - 3
include/jvalidate/detail/pointer.h

@@ -3,6 +3,7 @@
 #include <algorithm>
 #include <cassert>
 #include <iostream>
+#include <jvalidate/detail/number.h>
 #include <string>
 #include <string_view>
 #include <variant>
@@ -53,7 +54,7 @@ public:
       // assume that we are not going to use those kinds of paths in a reference
       // field. Therefore we don't need to include any clever tricks for storage
       if (not in.empty() && in.find_first_not_of("0123456789") == std::string::npos) {
-        return tokens_.push_back(std::stoull(in));
+        return tokens_.push_back(from_str<size_t>(in));
       }
 
       for (size_t i = 0; i < in.size(); ++i) {
@@ -62,8 +63,8 @@ public:
         // than '/' and '~' to be handled in all contexts.
         // TODO(samjaffe): Only do this if enc is hex-like (currently throws?)
         if (in[i] == '%') {
-          char const enc[3] = {in[i + 1], in[i + 2]};
-          in.replace(i, 3, 1, char(std::stoi(enc, nullptr, 16)));
+          char const enc[3] = {in[i + 1], in[i + 2], '\0'};
+          in.replace(i, 3, 1, from_str<char>(enc, 16));
         } else if (in[i] != '~') {
           // Not a special char-sequence, does not need massaging
           continue;

+ 3 - 4
include/jvalidate/detail/relative_pointer.h

@@ -5,6 +5,7 @@
 #include <string_view>
 
 #include <jvalidate/detail/expect.h>
+#include <jvalidate/detail/number.h>
 #include <jvalidate/detail/pointer.h>
 #include <jvalidate/forward.h>
 
@@ -18,13 +19,11 @@ public:
     if (auto pos = path.find('/'); pos != path.npos) {
       pointer_ = Pointer(path.substr(pos));
       path.remove_suffix(path.size() - pos);
-    } else {
-      EXPECT_M(not path.empty() && path.back() == '#',
-               "RelativePointer must end in a pointer, or a '#'");
+    } else if (path.ends_with('#')) {
       requests_key_ = true;
       path.remove_suffix(1);
     }
-    parent_steps_ = std::stoull(std::string(path));
+    parent_steps_ = from_str<size_t>(path);
   }
 
   template <Adapter A>