浏览代码

refactor: move parse_integer into number.h

Sam Jaffe 3 月之前
父节点
当前提交
c2da67ffeb

+ 101 - 1
include/jvalidate/compat/expected.h

@@ -3,7 +3,7 @@
 #include <type_traits>
 
 #ifdef __cpp_lib_expected
-#if __cpp_lib_expected >= 202202L
+#if __cpp_lib_expected >= 202211L
 #include <expected>
 
 namespace jvalidate::detail {
@@ -171,6 +171,54 @@ public:
   constexpr E & error() & noexcept { return *std::get_if<1>(&value_); }
   constexpr E && error() && noexcept { return std::move(*std::get_if<1>(&value_)); }
 
+  template <typename F> constexpr auto transform(F && f) & {
+    using G = std::invoke_result_t<F, value_type &>;
+    if (has_value()) {
+      return expected<G, error_type>(std::forward<F>(f)(**this));
+    }
+    return expected<G, error_type>(unexpect, error());
+  }
+
+  template <typename F> constexpr auto transform(F && f) const & {
+    using G = std::invoke_result_t<F, value_type const &>;
+    if (has_value()) {
+      return expected<G, error_type>(std::forward<F>(f)(**this));
+    }
+    return expected<G, error_type>(unexpect, error());
+  }
+
+  template <typename F> constexpr auto transform(F && f) && {
+    using G = std::invoke_result_t<F, value_type>;
+    if (has_value()) {
+      return expected<G, error_type>(std::forward<F>(f)(*std::move(*this)));
+    }
+    return expected<G, error_type>(unexpect, std::move(*this).error());
+  }
+
+  template <typename F> constexpr auto transform_error(F && f) & {
+    using G = std::invoke_result_t<F, E &>;
+    if (has_value()) {
+      return expected<value_type, G>(**this);
+    }
+    return expected<value_type, G>(unexpect, std::forward<F>(f)(error()));
+  }
+
+  template <typename F> constexpr auto transform_error(F && f) const & {
+    using G = std::invoke_result_t<F, E const &>;
+    if (has_value()) {
+      return expected<value_type, G>(**this);
+    }
+    return expected<value_type, G>(unexpect, std::forward<F>(f)(error()));
+  }
+
+  template <typename F> constexpr auto transform_error(F && f) && {
+    using G = std::invoke_result_t<F, E>;
+    if (has_value()) {
+      return expected<value_type, G>(*std::move(*this));
+    }
+    return expected<value_type, G>(unexpect, std::forward<F>(f)(std::move(*this).error()));
+  }
+
 private:
   std::variant<T, E> value_;
 };
@@ -241,8 +289,60 @@ public:
   constexpr E & error() & noexcept { return *value_; }
   constexpr E && error() && noexcept { return std::move(*value_); }
 
+  template <typename F> constexpr auto transform(F && f) & {
+    using G = std::invoke_result_t<F>;
+    if (has_value()) {
+      return expected<G, error_type>(std::forward<F>(f)());
+    }
+    return expected<G, error_type>(unexpect, error());
+  }
+
+  template <typename F> constexpr auto transform(F && f) const & {
+    using G = std::invoke_result_t<F>;
+    if (has_value()) {
+      return expected<G, error_type>(std::forward<F>(f)());
+    }
+    return expected<G, error_type>(unexpect, error());
+  }
+
+  template <typename F> constexpr auto transform(F && f) && {
+    using G = std::invoke_result_t<F>;
+    if (has_value()) {
+      return expected<G, error_type>(std::forward<F>(f)());
+    }
+    return expected<G, error_type>(unexpect, std::move(*this).error());
+  }
+
+  template <typename F> constexpr auto transform_error(F && f) & {
+    using G = std::invoke_result_t<F, E &>;
+    if (has_value()) {
+      return expected<value_type, G>(**this);
+    }
+    return expected<value_type, G>(unexpect, std::forward<F>(f)(error()));
+  }
+
+  template <typename F> constexpr auto transform_error(F && f) const & {
+    using G = std::invoke_result_t<F, E const &>;
+    if (has_value()) {
+      return expected<value_type, G>(**this);
+    }
+    return expected<value_type, G>(unexpect, std::forward<F>(f)(error()));
+  }
+
+  template <typename F> constexpr auto transform_error(F && f) && {
+    using G = std::invoke_result_t<F, E>;
+    if (has_value()) {
+      return expected<value_type, G>(*std::move(*this));
+    }
+    return expected<value_type, G>(unexpect, std::forward<F>(f)(std::move(*this).error()));
+  }
+
 private:
   std::optional<E> value_;
 };
 }
 #endif
+
+namespace jvalidate::detail {
+inline std::string to_message(std::errc ec) { return std::make_error_code(ec).message(); }
+}

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

@@ -8,8 +8,14 @@
  */
 #pragma once
 
+#include <charconv>
 #include <cmath>
 #include <limits>
+#include <string_view>
+#include <system_error>
+
+#include <jvalidate/compat/expected.h>
+#include <jvalidate/detail/out.h>
 
 namespace jvalidate::detail {
 /**
@@ -32,4 +38,20 @@ 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; }
+
+struct parse_integer_args {
+  int base = 10;
+  out<size_t> read = discard_out;
+};
+
+template <std::integral T>
+static expected<T, std::errc> parse_integer(std::string_view in, parse_integer_args args = {}) {
+  T rval = 0;
+  auto [ptr, ec] = std::from_chars(in.begin(), in.end(), rval, args.base);
+  args.read = ptr - in.begin();
+  if (ec != std::errc{}) {
+    return unexpected(ec);
+  }
+  return rval;
+}
 }

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

@@ -12,6 +12,7 @@
 
 #include <jvalidate/compat/compare.h>
 #include <jvalidate/compat/expected.h>
+#include <jvalidate/detail/number.h>
 #include <jvalidate/forward.h>
 
 namespace jvalidate::detail {
@@ -75,16 +76,6 @@ public:
     return rval;
   }
 
-  template <typename T>
-  static expected<T, std::string> parse_integer(std::string_view in, int base = 10) {
-    T rval = 0;
-    auto [ptr, ec] = std::from_chars(in.begin(), in.end(), rval, base);
-    if (ec != std::errc{}) {
-      return unexpected(std::make_error_code(ec).message());
-    }
-    return rval;
-  }
-
   static expected<Token, std::string> parse_token(std::string in) {
     // Best-guess that the input token text represents a numeric value.
     // Technically - this could mean that we have an object key that is also
@@ -92,19 +83,18 @@ 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 parse_integer<size_t>(in);
+      return parse_integer<size_t>(in).transform_error(to_message);
     }
 
     for (size_t i = 0; i < in.size(); ++i) {
       // Allow URL-Escaped characters (%\x\x) to be turned into their
       // matching ASCII characters. This allows passing abnormal chars other
       // than '/' and '~' to be handled in all contexts.
-      // TODO(samjaffe): Only do this if enc is hex-like (currently throws?)
       if (in[i] == '%') {
-        if (auto code = parse_integer<char>(std::string_view(in).substr(i + 1, 2), 16)) {
+        if (expected code = parse_integer<char>(in.substr(i + 1, 2), {.base = 16})) {
           in.replace(i, 3, 1, *code);
         } else {
-          return code.error();
+          return unexpected(to_message(code.error()));
         }
       } else if (in[i] != '~') {
         // Not a special char-sequence, does not need massaging

+ 7 - 2
include/jvalidate/detail/relative_pointer.h

@@ -6,6 +6,7 @@
 
 #include <jvalidate/compat/expected.h>
 #include <jvalidate/detail/expect.h>
+#include <jvalidate/detail/number.h>
 #include <jvalidate/detail/pointer.h>
 #include <jvalidate/forward.h>
 
@@ -32,8 +33,12 @@ public:
       return unexpected("RelativePointer must end in a pointer, or a '#'");
     }
 
-    expected parent_steps = Pointer::parse_integer<size_t>(path);
-    JVALIDATE_PROPIGATE_UNEXPECTED(parent_steps);
+    size_t read = 0;
+    expected parent_steps = parse_integer<size_t>(path, {.read = read});
+
+    JVALIDATE_PROPIGATE_UNEXPECTED(parent_steps.transform_error(to_message));
+    EXPECT_M(read == path.size(), "Extra chars in RelativePointer");
+
     rval.parent_steps_ = *parent_steps;
     return rval;
   }