net/addr/subnet: Add FromStr conversion

Change-Id: I41ef5adda1e8784e10259989bea3316b94668b9b
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/net/addr/subnet.hpp b/include/stdplus/net/addr/subnet.hpp
index 7f898c5..8a783ff 100644
--- a/include/stdplus/net/addr/subnet.hpp
+++ b/include/stdplus/net/addr/subnet.hpp
@@ -208,6 +208,22 @@
 {
 
 template <typename Subnet>
+struct SubnetFromStr
+{
+    template <typename CharT>
+    constexpr Subnet operator()(std::basic_string_view<CharT> sv) const
+    {
+        const auto pos = sv.rfind('/');
+        if (pos == sv.npos)
+        {
+            throw std::invalid_argument("Invalid subnet");
+        }
+        return {FromStr<typename Subnet::Addr>{}(sv.substr(0, pos)),
+                StrToInt<10, typename Subnet::Pfx>{}(sv.substr(pos + 1))};
+    }
+};
+
+template <typename Subnet>
 struct SubnetToStr
 {
     using type = Subnet;
@@ -228,6 +244,15 @@
 } // namespace detail
 
 template <typename Addr, typename Pfx>
+struct FromStr<detail::Subnet46<Addr, Pfx>> :
+    detail::SubnetFromStr<detail::Subnet46<Addr, Pfx>>
+{};
+
+template <>
+struct FromStr<SubnetAny> : detail::SubnetFromStr<SubnetAny>
+{};
+
+template <typename Addr, typename Pfx>
 struct ToStr<detail::Subnet46<Addr, Pfx>> :
     detail::SubnetToStr<detail::Subnet46<Addr, Pfx>>
 {};
diff --git a/src/net/addr/subnet.cpp b/src/net/addr/subnet.cpp
index d7cb24f..47f028f 100644
--- a/src/net/addr/subnet.cpp
+++ b/src/net/addr/subnet.cpp
@@ -15,6 +15,10 @@
 template class Subnet46<In4Addr, uint8_t>;
 template class Subnet46<In6Addr, uint8_t>;
 
+template Subnet4 SubnetFromStr<Subnet4>::operator()(std::string_view) const;
+template Subnet6 SubnetFromStr<Subnet6>::operator()(std::string_view) const;
+template SubnetAny SubnetFromStr<SubnetAny>::operator()(std::string_view) const;
+
 template char* SubnetToStr<Subnet4>::operator()(char*, Subnet4) const noexcept;
 template char* SubnetToStr<Subnet6>::operator()(char*, Subnet6) const noexcept;
 template char* SubnetToStr<SubnetAny>::operator()(char*,
diff --git a/test/net/addr/subnet.cpp b/test/net/addr/subnet.cpp
index 5024928..319a514 100644
--- a/test/net/addr/subnet.cpp
+++ b/test/net/addr/subnet.cpp
@@ -2,8 +2,12 @@
 
 #include <stdplus/net/addr/subnet.hpp>
 
+#include <string_view>
+
 #include <gtest/gtest.h>
 
+using std::literals::string_view_literals::operator""sv;
+
 namespace stdplus
 {
 
@@ -51,6 +55,17 @@
     EXPECT_TRUE(Subnet4(addr4Full, 0).contains(In4Addr{}));
 }
 
+TEST(Subnet4, FromStr)
+{
+    constexpr FromStr<Subnet4> fs;
+    EXPECT_THROW(fs("10"sv), std::invalid_argument);
+    EXPECT_THROW(fs("/10"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0.0.0.0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0.0.0.0/"sv), std::invalid_argument);
+    EXPECT_THROW(fs("::/80"sv), std::invalid_argument);
+    EXPECT_EQ((SubnetAny{in_addr{}, 30}), fs("0.0.0.0/30"sv));
+}
+
 TEST(Subnet4, ToStr)
 {
     ToStrHandle<ToStr<Subnet4>> tsh;
@@ -111,6 +126,17 @@
     EXPECT_TRUE(Subnet6(addr6Full, 0).contains(In6Addr{}));
 }
 
+TEST(Subnet6, FromStr)
+{
+    constexpr FromStr<Subnet6> fs;
+    EXPECT_THROW(fs("10"sv), std::invalid_argument);
+    EXPECT_THROW(fs("/10"sv), std::invalid_argument);
+    EXPECT_THROW(fs("ff::"sv), std::invalid_argument);
+    EXPECT_THROW(fs("::/"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0.0.0.0/0"sv), std::invalid_argument);
+    EXPECT_EQ((Subnet6{in6_addr{}, 80}), fs("::/80"sv));
+}
+
 TEST(Subnet6, ToStr)
 {
     ToStrHandle<ToStr<Subnet6>> tsh;
@@ -175,6 +201,18 @@
     EXPECT_FALSE(SubnetAny(addr4Full, 32).contains(InAnyAddr{In4Addr{}}));
 }
 
+TEST(SubnetAny, FromStr)
+{
+    constexpr FromStr<SubnetAny> fs;
+    EXPECT_THROW(fs("10"sv), std::invalid_argument);
+    EXPECT_THROW(fs("/10"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0.0.0.0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0.0.0.0/"sv), std::invalid_argument);
+    EXPECT_EQ((SubnetAny{in_addr{}, 0}), fs("0.0.0.0/0"sv));
+    EXPECT_EQ((SubnetAny{in_addr{}, 30}), fs("0.0.0.0/30"sv));
+    EXPECT_EQ((SubnetAny{in6_addr{}, 80}), fs("::/80"sv));
+}
+
 TEST(SubnetAny, ToStr)
 {
     ToStrHandle<ToStr<SubnetAny>> tsh;