Add common url segments parser

This change is adding helper template function, which can be used both
to validate and read segments from segments_view returned by boost_url
parser. Number of segments is also validated - in case when argument
count differs from them, false will be returned. In case when we want to
validate only existence of a segment, special argument can be passed in
its place: 'anySegment'.

Reasoning why url_view  was chosen instead of strings:
- This way code generation is kept minimal.
- There are multiple parse functions in boost_url with different rules,
  but all of them return url_view. This solution should accommodate
  every use case.

Testing done:
- Unit tests are added, passing.
- Refactored part of telemetry to use this new approach, no regression
  spotted during simple POST/GET tests.

Change-Id: I677a34e1ee570d33f2322a80dc1629f88273e0d5
Signed-off-by: Szymon Dompke <szymon.dompke@intel.com>
diff --git a/http/utility.hpp b/http/utility.hpp
index 52a72b6..f7e456b 100644
--- a/http/utility.hpp
+++ b/http/utility.hpp
@@ -18,6 +18,7 @@
 #include <tuple>
 #include <type_traits>
 #include <utility>
+#include <variant>
 
 namespace crow
 {
@@ -669,6 +670,69 @@
     return details::urlFromPiecesDetail({args...});
 }
 
+namespace details
+{
+
+// std::reference_wrapper<std::string> - extracts segment to variable
+//                    std::string_view - checks if segment is equal to variable
+using UrlSegment =
+    std::variant<std::reference_wrapper<std::string>, std::string_view>;
+
+class UrlSegmentMatcherVisitor
+{
+  public:
+    bool operator()(std::string& output)
+    {
+        output = std::string_view(segment.data(), segment.size());
+        return true;
+    }
+
+    bool operator()(std::string_view expected)
+    {
+        return std::string_view(segment.data(), segment.size()) == expected;
+    }
+
+    UrlSegmentMatcherVisitor(const boost::urls::string_value& segmentIn) :
+        segment(segmentIn)
+    {}
+
+  private:
+    const boost::urls::string_value& segment;
+};
+
+inline bool readUrlSegments(const boost::urls::url_view& urlView,
+                            std::initializer_list<UrlSegment>&& segments)
+{
+    const boost::urls::segments_view& urlSegments = urlView.segments();
+
+    if (!urlSegments.is_absolute() || segments.size() != urlSegments.size())
+    {
+        return false;
+    }
+
+    boost::urls::segments_view::iterator it = urlSegments.begin();
+    boost::urls::segments_view::iterator end = urlSegments.end();
+
+    for (const auto& segment : segments)
+    {
+        if (!std::visit(UrlSegmentMatcherVisitor(*it), segment))
+        {
+            return false;
+        }
+        it++;
+    }
+    return true;
+}
+
+} // namespace details
+
+template <typename... Args>
+inline bool readUrlSegments(const boost::urls::url_view& urlView,
+                            Args&&... args)
+{
+    return details::readUrlSegments(urlView, {std::forward<Args>(args)...});
+}
+
 inline bool validateAndSplitUrl(std::string_view destUrl, std::string& urlProto,
                                 std::string& host, std::string& port,
                                 std::string& path)