Implement YangSchema subclass
While we do not necessarily want to use `-isystem` for libyang, we
definitely need to use it for Boost itself. We are only using SPirit X3
directly, but that one still brings in a great deal of other supporting
libraries, including variant and a metaprogramming library.
In our CI environment, these are installed into the same prefix. Boost
itself is OK as we're including it via cmake imported target which uses
-isystem behind the scene. However, if we then add libyang via a regular
target_include_directories, the same path gets pushed into the include
paths, this time without -isystem, but rather as a regular -I. This in
turn starts affecting Boost, and we're screwed.
Fix this by ensuring that we consider libyang to be a system library,
too. We won't get annoyed by some extra warnings and therefore we won't
be able to improve that library as much as the CI would force us
otherwise :).
Change-Id: I7ec9b28501a46f8b2219f4920cbf5c1e4df59d7e
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
new file mode 100644
index 0000000..8cccf23
--- /dev/null
+++ b/src/yang_schema.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <libyang/Libyang.hpp>
+#include <libyang/Tree_Schema.hpp>
+#include <string_view>
+#include "utils.hpp"
+#include "yang_schema.hpp"
+
+class YangLoadError : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+ ~YangLoadError() override = default;
+};
+
+class UnsupportedYangTypeException : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+ ~UnsupportedYangTypeException() override = default;
+};
+
+class InvalidSchemaQueryException : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+ ~InvalidSchemaQueryException() override = default;
+};
+
+std::string pathToYangAbsSchemPath(const path_& path)
+{
+ std::string res = "/";
+ std::string currentModule;
+
+ for (const auto& it : path.m_nodes) {
+ const auto name = nodeToSchemaString(it);
+
+ if (it.m_suffix.type() == typeid(module_)) {
+ currentModule = name;
+ continue;
+ } else {
+ res += currentModule + ":";
+ res += name + "/";
+ }
+ }
+
+ return res;
+}
+
+YangSchema::YangSchema()
+{
+ m_context = std::make_shared<Context>();
+}
+
+YangSchema::~YangSchema() = default;
+
+void YangSchema::addSchemaString(const char* schema)
+{
+ if (!m_context->parse_module_mem(schema, LYS_IN_YANG)) {
+ throw YangLoadError("Couldn't load schema");
+ }
+}
+
+void YangSchema::addSchemaDirectory(const char* directoryName)
+{
+ if (m_context->set_searchdir(directoryName)) {
+ throw YangLoadError("Couldn't add schema search directory");
+ }
+}
+
+void YangSchema::addSchemaFile(const char* filename)
+{
+ if (!m_context->parse_module_path(filename, LYS_IN_YANG)) {
+ throw YangLoadError("Couldn't load schema");
+ }
+}
+
+bool YangSchema::isModule(const path_&, const std::string& name) const
+{
+ const auto set = modules();
+ return set.find(name) != set.end();
+}
+
+bool YangSchema::isContainer(const path_& location, const ModuleNodePair& node) const
+{
+ const auto schemaNode = getSchemaNode(location, node);
+ return schemaNode && schemaNode->nodetype() == LYS_CONTAINER;
+}
+
+bool YangSchema::isLeaf(const path_& location, const ModuleNodePair& node) const
+{
+ const auto schemaNode = getSchemaNode(location, node);
+ return schemaNode && schemaNode->nodetype() == LYS_LEAF;
+}
+
+bool YangSchema::isList(const path_& location, const ModuleNodePair& node) const
+{
+ const auto schemaNode = getSchemaNode(location, node);
+ return schemaNode && schemaNode->nodetype() == LYS_LIST;
+}
+
+bool YangSchema::isPresenceContainer(const path_& location, const ModuleNodePair& node) const
+{
+ if (!isContainer(location, node))
+ return false;
+ return Schema_Node_Container(getSchemaNode(location, node)).presence();
+}
+
+bool YangSchema::leafEnumHasValue(const path_& location, const ModuleNodePair& node, const std::string& value) const
+{
+ if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::Enum)
+ return false;
+
+ Schema_Node_Leaf leaf(getSchemaNode(location, node));
+ const auto enm = std::unique_ptr<std::vector<S_Type_Enum>>(leaf.type()->info()->enums()->enm());
+
+ return std::any_of(enm->begin(), enm->end(), [=](const auto& x) { return x->name() == value; });
+}
+
+bool YangSchema::listHasKey(const path_& location, const ModuleNodePair& node, const std::string& key) const
+{
+ if (!isList(location, node))
+ return false;
+ const auto keys = listKeys(location, node);
+ return keys.find(key) != keys.end();
+}
+
+bool YangSchema::nodeExists(const std::string& location, const std::string& node) const
+{
+ const auto absPath = location + "/" + node;
+ const auto set = m_context->find_path(absPath.c_str());
+ return set->number() == 1;
+}
+
+S_Set YangSchema::getNodeSet(const path_& location, const ModuleNodePair& node) const
+{
+ std::string absPath = location.m_nodes.empty() ? "" : "/";
+ absPath += pathToAbsoluteSchemaString(location) + "/" + fullNodeName(location, node);
+ return m_context->find_path(absPath.c_str());
+}
+
+std::shared_ptr<Schema_Node> YangSchema::getSchemaNode(const path_& location, const ModuleNodePair& node) const
+{
+ const auto set = getNodeSet(location, node);
+ if (!set)
+ return nullptr;
+ const auto schemaSet = std::unique_ptr<std::vector<std::shared_ptr<Schema_Node>>>(set->schema());
+ if (set->number() != 1)
+ return nullptr;
+ return (*(schemaSet->begin()));
+}
+
+const std::set<std::string> YangSchema::listKeys(const path_& location, const ModuleNodePair& node) const
+{
+ std::set<std::string> keys;
+ if (!isList(location, node))
+ return keys;
+ Schema_Node_List list(getSchemaNode(location, node));
+ const auto keysVec = std::unique_ptr<std::vector<S_Schema_Node_Leaf>>(list.keys());
+
+ std::transform(keysVec->begin(), keysVec->end(), std::inserter(keys, keys.begin()),
+ [] (const auto& it) {return it->name();});
+ return keys;
+}
+
+yang::LeafDataTypes YangSchema::leafType(const path_& location, const ModuleNodePair& node) const
+{
+ using namespace std::string_literals;
+ if (!isLeaf(location, node))
+ throw InvalidSchemaQueryException(fullNodeName(location, node) + " is not a leaf");
+
+ Schema_Node_Leaf leaf(getSchemaNode(location, node));
+ switch (leaf.type()->base()) {
+ case LY_TYPE_STRING:
+ return yang::LeafDataTypes::String;
+ case LY_TYPE_DEC64:
+ return yang::LeafDataTypes::Decimal;
+ case LY_TYPE_BOOL:
+ return yang::LeafDataTypes::Bool;
+ case LY_TYPE_INT32:
+ return yang::LeafDataTypes::Int;
+ case LY_TYPE_UINT32:
+ return yang::LeafDataTypes::Uint;
+ case LY_TYPE_ENUM:
+ return yang::LeafDataTypes::Enum;
+ default:
+ throw UnsupportedYangTypeException("the type of "s + fullNodeName(location, node) + " is not supported");
+ }
+}
+
+std::set<std::string> YangSchema::modules() const
+{
+ const auto modules = std::unique_ptr<std::vector<S_Module>>(m_context->get_module_iter());
+
+ std::set<std::string> res;
+ std::transform(modules->begin(), modules->end(),
+ std::inserter(res, res.end()),
+ [] (const auto module) { return module->name(); });
+ return res;
+}
+
+std::set<std::string> YangSchema::childNodes(const path_& path) const
+{
+ using namespace std::string_view_literals;
+ std::set<std::string> res;
+ if (path.m_nodes.empty()) {
+ const auto nodeVec = std::unique_ptr<std::vector<S_Schema_Node>>(m_context->data_instantiables(0));
+ for (const auto it : *nodeVec) {
+ if (it->module()->name() == "ietf-yang-library"sv)
+ continue;
+ res.insert(std::string(it->module()->name()) + ":" + it->name());
+ }
+ } else {
+ const auto absolutePath = "/" + pathToAbsoluteSchemaString(path);
+ const auto set = m_context->find_path(absolutePath.c_str());
+ const auto schemaSet = std::unique_ptr<std::vector<std::shared_ptr<Schema_Node>>>(set->schema());
+ for (auto it = (*(schemaSet->begin()))->child(); it; it = it->next()) {
+ res.insert(std::string(it->module()->name()) + ":" + it->name());
+ }
+ }
+ return res;
+}
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
new file mode 100644
index 0000000..e30c701
--- /dev/null
+++ b/src/yang_schema.hpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#pragma once
+
+#include <set>
+#include <stdexcept>
+#include <unordered_map>
+#include "ast_path.hpp"
+#include "schema.hpp"
+
+class Context;
+class Set;
+class Schema_Node;
+
+/*! \class YangSchema
+ * \brief A schema class, which uses libyang for queries.
+ * */
+class YangSchema : public Schema {
+public:
+ YangSchema();
+ ~YangSchema() override;
+
+ bool isContainer(const path_& location, const ModuleNodePair& node) const override;
+ bool isLeaf(const path_& location, const ModuleNodePair& node) const override;
+ bool isModule(const path_& location, const std::string& name) const override;
+ bool isList(const path_& location, const ModuleNodePair& node) const override;
+ bool isPresenceContainer(const path_& location, const ModuleNodePair& node) const override;
+ bool leafEnumHasValue(const path_& location, const ModuleNodePair& node, const std::string& value) const override;
+ bool listHasKey(const path_& location, const ModuleNodePair& node, const std::string& key) const override;
+ bool nodeExists(const std::string& location, const std::string& node) const override;
+ const std::set<std::string> listKeys(const path_& location, const ModuleNodePair& node) const override;
+ yang::LeafDataTypes leafType(const path_& location, const ModuleNodePair& node) const override;
+ std::set<std::string> childNodes(const path_& path) const override;
+
+ /** @short Adds a new module passed as a YANG string. */
+ void addSchemaString(const char* schema);
+
+ /** @short Adds a new module from a file. */
+ void addSchemaFile(const char* filename);
+
+ /** @short Adds a new directory for schema lookup. */
+ void addSchemaDirectory(const char* directoryName);
+
+private:
+ std::set<std::string> modules() const;
+ bool nodeExists(const path_& location, const ModuleNodePair& node) const;
+
+ /** @short Returns a set of nodes, that match the location and name criteria. */
+ std::shared_ptr<Set> getNodeSet(const path_& location, const ModuleNodePair& node) const;
+
+ /** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullptr. */
+ std::shared_ptr<Schema_Node> getSchemaNode(const path_& location, const ModuleNodePair& node) const;
+ std::shared_ptr<Context> m_context;
+};