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;
+};