Validate data paths and schema paths

- a leaf can be only present as a last item
- a leaf-list can be only present as a last item
- a list (without keys) cannot be present in a dataPath unless it's the
last node

Change-Id: I7c28d5b235fd050e218aa2152e3a863d8db78680
diff --git a/src/ast_path.cpp b/src/ast_path.cpp
index 318aada..0d8a606 100644
--- a/src/ast_path.cpp
+++ b/src/ast_path.cpp
@@ -115,6 +115,62 @@
 {
 }
 
+namespace {
+template <typename T, typename U>
+auto findFirstOf(const std::vector<U>& nodes)
+{
+    return std::find_if(nodes.begin(), nodes.end(), [](const auto& e) {
+        return std::holds_alternative<T>(e.m_suffix);
+    });
+}
+
+template <typename T>
+void validatePathNodes(const std::vector<T>& nodes)
+{
+    static_assert(std::is_same<T, dataNode_>() || std::is_same<T, schemaNode_>());
+
+    if (nodes.empty()) {
+        // there are default ctors, so it makes sense to specify the same thing via explicit args and not fail
+        return;
+    }
+
+    if (auto firstLeaf = findFirstOf<leaf_>(nodes);
+            firstLeaf != nodes.end() && firstLeaf != nodes.end() - 1) {
+        throw std::logic_error{"Cannot put any extra nodes after a leaf"};
+    }
+
+    if (auto firstLeafList = findFirstOf<leafList_>(nodes);
+            firstLeafList != nodes.end() && firstLeafList != nodes.end() - 1) {
+        throw std::logic_error{"Cannot put any extra nodes after a leaf-list"};
+    }
+
+    if constexpr (std::is_same<T, dataNode_>()) {
+        if (auto firstLeafListElements = findFirstOf<leafListElement_>(nodes);
+                firstLeafListElements != nodes.end() && firstLeafListElements != nodes.end() - 1) {
+            throw std::logic_error{"Cannot put any extra nodes after a leaf-list with element specification"};
+        }
+        if (auto firstList = findFirstOf<list_>(nodes);
+                firstList != nodes.end() && firstList != nodes.end() - 1) {
+            throw std::logic_error{
+                "A list with no key specification can be present only as a last item in a dataPath. Did you mean to use a schemaPath?"
+            };
+        }
+    }
+}
+}
+
+schemaPath_::schemaPath_()
+{
+}
+
+schemaPath_::schemaPath_(const Scope scope, const std::vector<schemaNode_>& nodes, const TrailingSlash trailingSlash)
+    : m_scope(scope)
+    , m_nodes(nodes)
+    , m_trailingSlash(trailingSlash)
+{
+    validatePathNodes(m_nodes);
+}
+
 bool schemaPath_::operator==(const schemaPath_& b) const
 {
     if (this->m_nodes.size() != b.m_nodes.size())
@@ -122,6 +178,18 @@
     return this->m_nodes == b.m_nodes;
 }
 
+dataPath_::dataPath_()
+{
+}
+
+dataPath_::dataPath_(const Scope scope, const std::vector<dataNode_>& nodes, const TrailingSlash trailingSlash)
+    : m_scope(scope)
+    , m_nodes(nodes)
+    , m_trailingSlash(trailingSlash)
+{
+    validatePathNodes(m_nodes);
+}
+
 bool dataPath_::operator==(const dataPath_& b) const
 {
     if (this->m_nodes.size() != b.m_nodes.size())
@@ -276,9 +344,11 @@
 void schemaPath_::pushFragment(const schemaNode_& fragment)
 {
     impl_pushFragment(m_nodes, fragment);
+    validatePathNodes(m_nodes);
 }
 
 void dataPath_::pushFragment(const dataNode_& fragment)
 {
     impl_pushFragment(m_nodes, fragment);
+    validatePathNodes(m_nodes);
 }
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index 91268e6..83727ff 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -113,6 +113,8 @@
 };
 
 struct schemaPath_ {
+    schemaPath_();
+    schemaPath_(const Scope scope, const std::vector<schemaNode_>& nodes, const TrailingSlash trailingSlash = TrailingSlash::NonPresent);
     bool operator==(const schemaPath_& b) const;
     Scope m_scope = Scope::Relative;
     std::vector<schemaNode_> m_nodes;
@@ -122,6 +124,8 @@
 };
 
 struct dataPath_ {
+    dataPath_();
+    dataPath_(const Scope scope, const std::vector<dataNode_>& nodes, const TrailingSlash trailingSlash = TrailingSlash::NonPresent);
     bool operator==(const dataPath_& b) const;
     Scope m_scope = Scope::Relative;
     std::vector<dataNode_> m_nodes;