From 97ceef119c900c37bbaa27860c3b43cfa6d69f95 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
Date: Mon, 13 Jan 2025 13:01:07 +0100
Subject: [PATCH 27/44] restconf: refactor uri parser for stream URIs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires

This is a preparation for the upcoming patch that will add support for
subscribed streams. I will use boost spirit x3 parser to parse such
URIs so I need to refactor the existing parser, which is not really a
parser, but we only match for the two known URIs.

Change-Id: Ib76973cd967fc3e9558ceb6be8f51239ce4c5ef3
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
 src/restconf/Server.cpp | 15 +--------------
 src/restconf/uri.cpp    | 36 +++++++++++++++++++++++++++---------
 src/restconf/uri.h      | 11 ++++++-----
 src/restconf/uri_impl.h |  2 ++
 tests/uri-parser.cpp    |  4 ++--
 5 files changed, 38 insertions(+), 30 deletions(-)

diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
index daf23ed..d356f7e 100644
--- a/src/restconf/Server.cpp
+++ b/src/restconf/Server.cpp
@@ -874,7 +874,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
 
     server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
         auto sess = conn.sessionStart();
-        libyang::DataFormat dataFormat;
         std::optional<std::string> xpathFilter;
         std::optional<sysrepo::NotificationTimeStamp> startTime;
         std::optional<sysrepo::NotificationTimeStamp> stopTime;
@@ -890,18 +889,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
 
             auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query);
 
-            switch(streamRequest.type) {
-            case RestconfStreamRequest::Type::NetconfNotificationJSON:
-                dataFormat = libyang::DataFormat::JSON;
-                break;
-            case RestconfStreamRequest::Type::NetconfNotificationXML:
-                dataFormat = libyang::DataFormat::XML;
-                break;
-            default:
-                // GCC 14 complains about uninitialized variable, but asRestconfStreamRequest() would have thrown
-                __builtin_unreachable();
-            }
-
             if (auto it = streamRequest.queryParams.find("filter"); it != streamRequest.queryParams.end()) {
                 xpathFilter = std::get<std::string>(it->second);
             }
@@ -916,7 +903,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
             // The signal is constructed outside NotificationStream class because it is required to be passed to
             // NotificationStream's parent (EventStream) constructor where it already must be constructed
             // Yes, this is a hack.
-            auto client = std::make_shared<NotificationStream>(req, res, std::make_shared<rousette::http::EventStream::Signal>(), sess, dataFormat, xpathFilter, startTime, stopTime);
+            auto client = std::make_shared<NotificationStream>(req, res, std::make_shared<rousette::http::EventStream::Signal>(), sess, streamRequest.type.encoding, xpathFilter, startTime, stopTime);
             client->activate();
         } catch (const auth::Error& e) {
             processAuthError(req, res, e, [&res]() {
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
index da1e3a5..e95d70e 100644
--- a/src/restconf/uri.cpp
+++ b/src/restconf/uri.cpp
@@ -57,6 +57,13 @@ const auto resources = x3::rule<class resources, URIPath>{"resources"} =
     ((x3::lit("/") | x3::eps) /* /restconf/ and /restconf */ >> x3::attr(URIPrefix{URIPrefix::Type::RestconfRoot}) >> x3::attr(std::vector<PathSegment>{}));
 const auto uriGrammar = x3::rule<class grammar, URIPath>{"grammar"} = x3::lit("/restconf") >> resources;
 
+
+const auto netconfStream = x3::rule<class netconfStream, RestconfStreamRequest::NetconfStream>{"netconfStream"} =
+    x3::lit("/NETCONF") >>
+    ((x3::lit("/XML") >> x3::attr(libyang::DataFormat::XML)) |
+     (x3::lit("/JSON") >> x3::attr(libyang::DataFormat::JSON)));
+const auto streamUriGrammar = x3::rule<class grammar, RestconfStreamRequest::NetconfStream>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
+
 // clang-format on
 }
 
@@ -197,6 +204,17 @@ std::optional<queryParams::QueryParams> parseQueryParams(const std::string& quer
     return ret;
 }
 
+std::optional<RestconfStreamRequest::NetconfStream> parseStreamUri(const std::string& uriPath)
+{
+    std::optional<RestconfStreamRequest::NetconfStream> ret;
+
+    if (!x3::parse(std::begin(uriPath), std::end(uriPath), streamUriGrammar >> x3::eoi, ret)) {
+        return std::nullopt;
+    }
+
+    return ret;
+}
+
 URIPrefix::URIPrefix()
     : resourceType(URIPrefix::Type::BasicRestconfData)
 {
@@ -637,20 +655,20 @@ std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> asYangMod
     return std::nullopt;
 }
 
-RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
+RestconfStreamRequest::NetconfStream::NetconfStream() = default;
+RestconfStreamRequest::NetconfStream::NetconfStream(const libyang::DataFormat& encoding)
+    : encoding(encoding)
 {
-    static const auto netconfStreamRoot = "/streams/NETCONF/";
-    RestconfStreamRequest::Type type;
+}
 
+RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
+{
     if (httpMethod != "GET" && httpMethod != "HEAD") {
         throw ErrorResponse(405, "application", "operation-not-supported", "Method not allowed.");
     }
 
-    if (uriPath == netconfStreamRoot + "XML"s) {
-        type = RestconfStreamRequest::Type::NetconfNotificationXML;
-    } else if (uriPath == netconfStreamRoot + "JSON"s) {
-        type = RestconfStreamRequest::Type::NetconfNotificationJSON;
-    } else {
+    auto type = impl::parseStreamUri(uriPath);
+    if (!type) {
         throw ErrorResponse(404, "application", "invalid-value", "Invalid stream");
     }
 
@@ -661,7 +679,7 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con
 
     validateQueryParametersForStream(*queryParameters);
 
-    return {type, *queryParameters};
+    return {*type, *queryParameters};
 }
 
 /** @brief Returns a set of allowed HTTP methods for given URI. Usable for the 'allow' header */
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
index f6df724..6f1f69f 100644
--- a/src/restconf/uri.h
+++ b/src/restconf/uri.h
@@ -197,11 +197,12 @@ struct RestconfRequest {
 };
 
 struct RestconfStreamRequest {
-    enum class Type {
-        NetconfNotificationJSON,
-        NetconfNotificationXML,
-    };
-    Type type;
+    struct NetconfStream {
+        libyang::DataFormat encoding;
+
+        NetconfStream();
+        NetconfStream(const libyang::DataFormat& encoding);
+    } type;
     queryParams::QueryParams queryParams;
 };
 
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
index 2bcdb3f..00b22f6 100644
--- a/src/restconf/uri_impl.h
+++ b/src/restconf/uri_impl.h
@@ -66,6 +66,8 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::impl::YangModule, name, revision);
 BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys);
 BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier);
 
+BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::RestconfStreamRequest::NetconfStream, encoding);
+
 BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs);
 BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs);
 BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SemiExpr, lhs, rhs);
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
index fd5dd8b..53d1fbb 100644
--- a/tests/uri-parser.cpp
+++ b/tests/uri-parser.cpp
@@ -1072,13 +1072,13 @@ TEST_CASE("URI path parser")
 
         {
             auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
-            REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationXML);
+            REQUIRE(type.encoding == libyang::DataFormat::XML);
             REQUIRE(queryParams.empty());
         }
 
         {
             auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
-            REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationJSON);
+            REQUIRE(type.encoding == libyang::DataFormat::JSON);
             REQUIRE(queryParams.empty());
         }
 
-- 
2.43.0

