config_parser: Add type checking to config map

In order to guarantee the output file is consistent, the
constructed values are checked for safety.

Change-Id: Ib70e369471e9f2f47a1cdb5522f4a3bebc37805e
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/config_parser.cpp b/src/config_parser.cpp
index 80f71e7..867d611 100644
--- a/src/config_parser.cpp
+++ b/src/config_parser.cpp
@@ -5,6 +5,7 @@
 
 #include <functional>
 #include <iterator>
+#include <stdexcept>
 #include <stdplus/exception.hpp>
 #include <stdplus/fd/create.hpp>
 #include <stdplus/fd/line.hpp>
@@ -67,7 +68,7 @@
         {
             continue;
         }
-        return &kit->second.back();
+        return &kit->second.back().get();
     }
     return nullptr;
 }
@@ -79,6 +80,42 @@
                      [](const Value& v) { return std::string(v); });
 }
 
+void KeyCheck::operator()(const std::string& s)
+{
+    for (auto c : s)
+    {
+        if (c == '\n' || c == '=')
+        {
+            throw std::invalid_argument(
+                fmt::format(FMT_COMPILE("Invalid Config Key: {}"), s));
+        }
+    }
+}
+
+void SectionCheck::operator()(const std::string& s)
+{
+    for (auto c : s)
+    {
+        if (c == '\n' || c == ']')
+        {
+            throw std::invalid_argument(
+                fmt::format(FMT_COMPILE("Invalid Config Section: {}"), s));
+        }
+    }
+}
+
+void ValueCheck::operator()(const std::string& s)
+{
+    for (auto c : s)
+    {
+        if (c == '\n')
+        {
+            throw std::invalid_argument(
+                fmt::format(FMT_COMPILE("Invalid Config Value: {}"), s));
+        }
+    }
+}
+
 Parser::Parser(const fs::path& filename)
 {
     setFile(filename);
@@ -145,8 +182,8 @@
         auto it = sections.find(s);
         if (it == sections.end())
         {
-            std::tie(it, std::ignore) =
-                sections.emplace(Section(s), KeyValuesMapList{});
+            std::tie(it, std::ignore) = sections.emplace(
+                Section(Section::unchecked(), s), KeyValuesMapList{});
         }
         section = &it->second.emplace_back();
     }
@@ -181,9 +218,10 @@
         auto it = section->find(k);
         if (it == section->end())
         {
-            std::tie(it, std::ignore) = section->emplace(Key(k), ValueList{});
+            std::tie(it, std::ignore) =
+                section->emplace(Key(Key::unchecked(), k), ValueList{});
         }
-        it->second.emplace_back(v);
+        it->second.emplace_back(Value::unchecked(), v);
     }
 
     void pump(std::string_view line)
diff --git a/src/config_parser.hpp b/src/config_parser.hpp
index 9b203c2..6b633e9 100644
--- a/src/config_parser.hpp
+++ b/src/config_parser.hpp
@@ -3,6 +3,7 @@
 #include <filesystem>
 #include <functional>
 #include <optional>
+#include <ostream>
 #include <string>
 #include <string_view>
 #include <unordered_map>
@@ -25,14 +26,98 @@
 fs::path pathForIntfConf(const fs::path& dir, std::string_view intf);
 fs::path pathForIntfDev(const fs::path& dir, std::string_view intf);
 
+template <typename T, typename Check>
+class Checked
+{
+  public:
+    struct unchecked
+    {
+    };
+
+    template <typename... Args>
+    inline constexpr Checked(Args&&... args) :
+        t(conCheck(std::forward<Args>(args)...))
+    {
+    }
+
+    template <typename... Args>
+    inline constexpr Checked(unchecked, Args&&... args) :
+        t(std::forward<Args>(args)...)
+    {
+    }
+
+    inline const T& get() const noexcept
+    {
+        return t;
+    }
+
+    inline constexpr operator const T&() const noexcept
+    {
+        return t;
+    }
+
+    inline constexpr bool operator==(const auto& rhs) const
+    {
+        return t == rhs;
+    }
+
+  private:
+    T t;
+
+    template <typename... Args>
+    inline static constexpr T conCheck(Args&&... args)
+    {
+        T t(std::forward<Args>(args)...);
+        Check{}(t);
+        return t;
+    }
+};
+
+template <typename T, typename Check>
+inline constexpr bool operator==(const auto& lhs, const Checked<T, Check>& rhs)
+{
+    return lhs == rhs.get();
+}
+
+template <typename T, typename Check>
+inline constexpr std::ostream& operator<<(std::ostream& s,
+                                          const Checked<T, Check>& rhs)
+{
+    return s << rhs.get();
+}
+
+struct KeyCheck
+{
+    void operator()(const std::string& s);
+};
+struct SectionCheck
+{
+    void operator()(const std::string& s);
+};
+struct ValueCheck
+{
+    void operator()(const std::string& s);
+};
+
 struct string_hash : public std::hash<std::string_view>
 {
     using is_transparent = void;
+
+    template <typename T>
+    inline size_t operator()(const Checked<std::string, T>& t) const
+    {
+        return static_cast<const std::hash<std::string_view>&>(*this)(t.get());
+    }
+    template <typename T>
+    inline size_t operator()(const T& t) const
+    {
+        return static_cast<const std::hash<std::string_view>&>(*this)(t);
+    }
 };
 
-using Key = std::string;
-using Section = std::string;
-using Value = std::string;
+using Key = Checked<std::string, KeyCheck>;
+using Section = Checked<std::string, SectionCheck>;
+using Value = Checked<std::string, ValueCheck>;
 using ValueList = std::vector<Value>;
 using KeyValuesMap =
     std::unordered_map<Key, ValueList, string_hash, std::equal_to<>>;
diff --git a/test/test_config_parser.cpp b/test/test_config_parser.cpp
index 909ad7f..e7d44ef 100644
--- a/test/test_config_parser.cpp
+++ b/test/test_config_parser.cpp
@@ -49,6 +49,31 @@
     EXPECT_FALSE(parseBool("oFf").value());
 }
 
+TEST(TestTypeChecking, Section)
+{
+    Section("");
+    Section("fds#1!'\"");
+    EXPECT_THROW(Section("fds]sf"), std::invalid_argument);
+    EXPECT_THROW(Section("g\ng"), std::invalid_argument);
+}
+
+TEST(TestTypeChecking, Value)
+{
+    Value("");
+    Value("=fds1!'\"#=");
+    Value("fds]sf'' #");
+    EXPECT_THROW(Value("g\ng"), std::invalid_argument);
+}
+
+TEST(TestTypeChecking, Key)
+{
+    Key("");
+    Key("fds1!'\"#");
+    Key("fds]sf'' #");
+    EXPECT_THROW(Key("fds]sf'='"), std::invalid_argument);
+    EXPECT_THROW(Key("g\ng"), std::invalid_argument);
+}
+
 class TestConfigParser : public stdplus::gtest::TestWithTmp
 {
   public: