Disable readonly data paths in set

The first solution to this was to just filter operational nodes directly
inside NodeParser. That however can't work, because we might want to
parse operational data paths (for `get` and also `ls`). That means I
have to define, what gets filtered out, outside of NodeParser. The first
thing that came to mind is using a template argument, however, that
can't be used with lambdas (at least not easily). Instead I just use the
constructor and std::function.

Change-Id: Ic9e503ff721970066ef53d7b5626ff153ad44e18
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 4c5bdaa..33715a4 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -242,7 +242,7 @@
     }
 };
 
-struct leaf_path_class {
+struct writable_leaf_path_class {
     template <typename T, typename Iterator, typename Context>
     void on_success(Iterator const&, Iterator const&, T&, Context const& context)
     {
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 859d5b8..1b4b49d 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -62,7 +62,7 @@
     get_::name >> -(space_separator >> ((dataPathListEnd | dataPath) | (module >> "*")));
 
 auto const set_def =
-    set_::name >> space_separator > leafPath > space_separator > leaf_data;
+    set_::name >> space_separator > writableLeafPath > space_separator > leaf_data;
 
 auto const commit_def =
     commit_::name >> x3::attr(commit_());
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 6b894d8..15655d7 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -14,7 +14,7 @@
 
 namespace x3 = boost::spirit::x3;
 
-x3::rule<leaf_path_class, dataPath_> const leafPath = "leafPath";
+x3::rule<writable_leaf_path_class, dataPath_> const writableLeafPath = "writableLeafPath";
 x3::rule<presenceContainerPath_class, dataPath_> const presenceContainerPath = "presenceContainerPath";
 x3::rule<listInstancePath_class, dataPath_> const listInstancePath = "listInstancePath";
 x3::rule<initializePath_class, x3::unused_type> const initializePath = "initializePath";
@@ -59,6 +59,14 @@
 template <NodeParserMode PARSER_MODE>
 struct NodeParser : x3::parser<NodeParser<PARSER_MODE>> {
     using attribute_type = typename ModeToAttribute<PARSER_MODE>::type;
+
+    std::function<bool(const Schema&, const std::string& path)> m_filterFunction;
+
+    NodeParser(const std::function<bool(const Schema&, const std::string& path)>& filterFunction)
+        : m_filterFunction(filterFunction)
+    {
+    }
+
     // GCC complains that `end` isn't used when doing completions only
     // FIXME: GCC 10.1 doesn't emit a warning here. Remove [[maybe_unused]] when GCC 10 is available
     template <typename It, typename Ctx, typename RCtx, typename Attr>
@@ -82,6 +90,11 @@
                 parseString = *child.first + ":";
             }
             parseString += child.second;
+
+            if (!m_filterFunction(parserContext.m_schema, joinPaths(pathToSchemaString(parserContext.currentSchemaPath(), Prefixes::Always), parseString))) {
+                continue;
+            }
+
             switch (parserContext.m_schema.nodeType(parserContext.currentSchemaPath(), child)) {
                 case yang::NodeTypes::Container:
                 case yang::NodeTypes::PresenceContainer:
@@ -174,10 +187,10 @@
     }
 };
 
-NodeParser<NodeParserMode::SchemaNode> schemaNode;
-NodeParser<NodeParserMode::CompleteDataNode> dataNode;
-NodeParser<NodeParserMode::IncompleteDataNode> dataNodeAllowList;
-NodeParser<NodeParserMode::CompletionsOnly> pathCompletions;
+using schemaNode = NodeParser<NodeParserMode::SchemaNode>;
+using dataNode = NodeParser<NodeParserMode::CompleteDataNode>;
+using dataNodeAllowList = NodeParser<NodeParserMode::IncompleteDataNode>;
+using pathCompletions = NodeParser<NodeParserMode::CompletionsOnly>;
 
 using AnyPath = boost::variant<schemaPath_, dataPath_>;
 
@@ -205,6 +218,13 @@
 template <PathParserMode PARSER_MODE>
 struct PathParser : x3::parser<PathParser<PARSER_MODE>> {
     using attribute_type = ModeToAttribute<PARSER_MODE>;
+    std::function<bool(const Schema&, const std::string& path)> m_filterFunction;
+
+    PathParser(const std::function<bool(const Schema&, const std::string& path)>& filterFunction = [] (const auto&, const auto&) {return true;})
+        : m_filterFunction(filterFunction)
+    {
+    }
+
     template <typename It, typename Ctx, typename RCtx, typename Attr>
     bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr) const
     {
@@ -217,7 +237,7 @@
         // gets reverted to before the starting slash.
         auto res = (-absoluteStart).parse(begin, end, ctx, rctx, attrData.m_scope);
         auto dataPath = x3::attr(attrData.m_scope)
-            >> (dataNode % '/' | pathEnd >> x3::attr(std::vector<dataNode_>{}))
+            >> (dataNode{m_filterFunction} % '/' | pathEnd >> x3::attr(std::vector<dataNode_>{}))
             >> -trailingSlash;
         res = dataPath.parse(begin, end, ctx, rctx, attrData);
 
@@ -225,13 +245,13 @@
         if constexpr (PARSER_MODE == PathParserMode::DataPathListEnd || PARSER_MODE == PathParserMode::AnyPath) {
             if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
                 dataNode_ attrNodeList;
-                res = dataNodeAllowList.parse(begin, end, ctx, rctx, attrNodeList);
+                res = dataNodeAllowList{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
                 if (res) {
                     attrData.m_nodes.push_back(attrNodeList);
                     // If the trailing slash matches, no more nodes are parsed.
                     // That means no more completion. So, I generate them
                     // manually.
-                    res = (-(trailingSlash >> x3::omit[pathCompletions])).parse(begin, end, ctx, rctx, attrData.m_trailingSlash);
+                    res = (-(trailingSlash >> x3::omit[pathCompletions{m_filterFunction}])).parse(begin, end, ctx, rctx, attrData.m_trailingSlash);
                 }
             }
         }
@@ -246,7 +266,7 @@
                 if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
                     // If dataPath parsed some nodes, they will be saved in `attrData`. We have to keep these.
                     schemaPath_ attrSchema = dataPathToSchemaPath(attrData);
-                    auto schemaPath = schemaNode % '/';
+                    auto schemaPath = schemaNode{m_filterFunction} % '/';
                     // The schemaPath parser continues where the dataPath parser ended.
                     res = schemaPath.parse(begin, end, ctx, rctx, attrSchema.m_nodes);
                     auto trailing = -trailingSlash >> pathEnd;
@@ -311,8 +331,12 @@
 auto const trailingSlash_def =
     x3::omit['/'] >> x3::attr(TrailingSlash::Present);
 
-auto const leafPath_def =
-    dataPath;
+auto const filterConfigFalse = [] (const Schema& schema, const std::string& path) {
+    return schema.isConfig(path);
+};
+
+auto const writableLeafPath_def =
+    PathParser<PathParserMode::DataPath>{filterConfigFalse};
 
 auto const presenceContainerPath_def =
     dataPath;
@@ -333,7 +357,7 @@
 BOOST_SPIRIT_DEFINE(keyValue)
 BOOST_SPIRIT_DEFINE(key_identifier)
 BOOST_SPIRIT_DEFINE(listSuffix)
-BOOST_SPIRIT_DEFINE(leafPath)
+BOOST_SPIRIT_DEFINE(writableLeafPath)
 BOOST_SPIRIT_DEFINE(presenceContainerPath)
 BOOST_SPIRIT_DEFINE(listInstancePath)
 BOOST_SPIRIT_DEFINE(initializePath)
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index aecad5a..738ce49 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -12,10 +12,10 @@
 
 StaticSchema::StaticSchema()
 {
-    m_nodes.emplace("/", std::unordered_map<std::string, NodeType>());
+    m_nodes.emplace("/", std::unordered_map<std::string, NodeInfo>());
 }
 
-const std::unordered_map<std::string, NodeType>& StaticSchema::children(const std::string& name) const
+const std::unordered_map<std::string, NodeInfo>& StaticSchema::children(const std::string& name) const
 {
     return m_nodes.at(name);
 }
@@ -36,11 +36,11 @@
 
 void StaticSchema::addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence)
 {
-    m_nodes.at(location).emplace(name, yang::container{isPresence});
+    m_nodes.at(location).emplace(name, NodeInfo{yang::container{isPresence}, yang::AccessType::Writable});
 
     //create a new set of children for the new node
     std::string key = joinPaths(location, name);
-    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
 }
 
 bool StaticSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
@@ -49,7 +49,7 @@
     assert(isList(location, node));
 
     const auto& child = children(locationString).at(fullNodeName(location, node));
-    const auto& list = boost::get<yang::list>(child);
+    const auto& list = boost::get<yang::list>(child.m_nodeType);
     return list.m_keys.find(key) != list.m_keys.end();
 }
 
@@ -59,16 +59,16 @@
     assert(isList(location, node));
 
     const auto& child = children(locationString).at(fullNodeName(location, node));
-    const auto& list = boost::get<yang::list>(child);
+    const auto& list = boost::get<yang::list>(child.m_nodeType);
     return list.m_keys;
 }
 
 void StaticSchema::addList(const std::string& location, const std::string& name, const std::set<std::string>& keys)
 {
-    m_nodes.at(location).emplace(name, yang::list{keys});
+    m_nodes.at(location).emplace(name, NodeInfo{yang::list{keys}, yang::AccessType::Writable});
 
     std::string key = joinPaths(location, name);
-    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
 }
 
 std::set<identityRef_> StaticSchema::validIdentities(std::string_view module, std::string_view value)
@@ -79,11 +79,11 @@
     return identities;
 }
 
-void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type)
+void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type, const yang::AccessType accessType)
 {
-    m_nodes.at(location).emplace(name, yang::leaf{yang::TypeInfo{type, std::nullopt}});
+    m_nodes.at(location).emplace(name, NodeInfo{yang::leaf{yang::TypeInfo{type, std::nullopt}}, accessType});
     std::string key = joinPaths(location, name);
-    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
 }
 
 void StaticSchema::addModule(const std::string& name)
@@ -120,14 +120,14 @@
 yang::TypeInfo StaticSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
 {
     std::string locationString = pathToSchemaString(location, Prefixes::Always);
-    return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node))).m_type;
+    return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node)).m_nodeType).m_type;
 }
 
 yang::TypeInfo StaticSchema::leafType(const std::string& path) const
 {
     auto locationString = stripLastNodeFromPath(path);
     auto node = lastNodeOfSchemaPath(path);
-    return boost::get<yang::leaf>(children(locationString).at(node)).m_type;
+    return boost::get<yang::leaf>(children(locationString).at(node).m_nodeType).m_type;
 }
 
 std::set<ModuleNodePair> StaticSchema::availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const
@@ -185,18 +185,18 @@
     try {
         auto targetNode = children(locationString).at(fullName);
 
-        if (targetNode.type() == typeid(yang::container)) {
-            if (boost::get<yang::container>(targetNode).m_presence == yang::ContainerTraits::Presence) {
+        if (targetNode.m_nodeType.type() == typeid(yang::container)) {
+            if (boost::get<yang::container>(targetNode.m_nodeType).m_presence == yang::ContainerTraits::Presence) {
                 return yang::NodeTypes::PresenceContainer;
             }
             return yang::NodeTypes::Container;
         }
 
-        if (targetNode.type() == typeid(yang::list)) {
+        if (targetNode.m_nodeType.type() == typeid(yang::list)) {
             return yang::NodeTypes::List;
         }
 
-        if (targetNode.type() == typeid(yang::leaf)) {
+        if (targetNode.m_nodeType.type() == typeid(yang::leaf)) {
             return yang::NodeTypes::Leaf;
         }
 
@@ -207,6 +207,25 @@
     }
 }
 
+std::string fullNodeName(const std::string& location, const std::string& node)
+{
+    // If the node already contains a module name, just return it.
+    if (node.find_first_of(':') != std::string::npos) {
+        return node;
+    }
+
+    // Otherwise take the module name from the first node of location.
+    return location.substr(location.find_first_not_of('/'), location.find_first_of(':') - 1) + ":" + node;
+}
+
+bool StaticSchema::isConfig(const std::string& leafPath) const
+{
+    auto locationString = stripLastNodeFromPath(leafPath);
+
+    auto node = fullNodeName(locationString, lastNodeOfSchemaPath(leafPath));
+    return children(locationString).at(node).m_configType == yang::AccessType::Writable;
+}
+
 std::optional<std::string> StaticSchema::description([[maybe_unused]] const std::string& path) const
 {
     throw std::runtime_error{"StaticSchema::description not implemented"};
@@ -237,11 +256,6 @@
     throw std::runtime_error{"Internal error: StaticSchema::leafTypeName(std::string) not implemented. The tests should not have called this overload."};
 }
 
-bool StaticSchema::isConfig([[maybe_unused]] const std::string& leafPath) const
-{
-    throw std::runtime_error{"Internal error: StaticSchema::isConfigLeaf(std::string) not implemented. The tests should not have called this overload."};
-}
-
 std::optional<std::string> StaticSchema::defaultValue([[maybe_unused]] const std::string& leafPath) const
 {
     throw std::runtime_error{"Internal error: StaticSchema::defaultValue(std::string) not implemented. The tests should not have called this overload."};
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 5184ae6..b94dfa3 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -32,10 +32,20 @@
 
 struct module {
 };
+
+enum class AccessType {
+    Writable,
+    ReadOnly
+};
 }
 
 using NodeType = boost::variant<yang::container, yang::list, yang::leaf, yang::module>;
 
+struct NodeInfo {
+    NodeType m_nodeType;
+    yang::AccessType m_configType;
+};
+
 
 /*! \class StaticSchema
  *     \brief Static schema, used mainly for testing
@@ -66,17 +76,17 @@
      * used in addLeaf for the `type` argument */
     std::set<identityRef_> validIdentities(std::string_view module, std::string_view value);
     void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
-    void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type);
+    void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type, const yang::AccessType accessType = yang::AccessType::Writable);
     void addList(const std::string& location, const std::string& name, const std::set<std::string>& keys);
     void addModule(const std::string& name);
     void addIdentity(const std::optional<identityRef_>& base, const identityRef_& name);
 
 private:
-    const std::unordered_map<std::string, NodeType>& children(const std::string& name) const;
+    const std::unordered_map<std::string, NodeInfo>& children(const std::string& name) const;
     void getIdentSet(const identityRef_& ident, std::set<identityRef_>& res) const;
     bool nodeExists(const std::string& location, const std::string& node) const;
 
-    std::unordered_map<std::string, std::unordered_map<std::string, NodeType>> m_nodes;
+    std::unordered_map<std::string, std::unordered_map<std::string, NodeInfo>> m_nodes;
     std::set<std::string> m_modules;
 
     std::map<identityRef_, std::set<identityRef_>> m_identities;