fi
AM_CONDITIONAL([CROSS_COMPILING], [test "$cross_compiling" = "yes"])
+# pkg-config can be required.
+AC_PATH_PROG([PKG_CONFIG], [pkg-config])
+
# Enable low-performing debugging facilities? This option optionally
# enables some debugging aids that perform slowly and hence aren't built
# by default.
[cql_config="$withval"])
if test "${cql_config}" = "yes" ; then
- AC_PATH_PROG([PKG_CONFIG], [pkg-config])
CQL_CONFIG="$PKG_CONFIG"
elif test "${cql_config}" != "no" ; then
CQL_CONFIG="${cql_config}"
src/lib/util/threads/Makefile
src/lib/util/threads/tests/Makefile
src/lib/util/unittests/Makefile
+ src/lib/yang/Makefile
+ src/lib/yang/tests/Makefile
src/share/Makefile
src/share/database/Makefile
src/share/database/scripts/Makefile
SUBDIRS += cql
endif
-SUBDIRS += testutils hooks dhcp config stats asiodns dhcp_ddns eval \
- dhcpsrv cfgrpt \
- process http
+SUBDIRS += testutils hooks dhcp config stats
+
+if HAVE_SYSREPO
+SUBDIRS += yang
+endif
+
+SUBDIRS += asiodns dhcp_ddns eval dhcpsrv cfgrpt process http
--- /dev/null
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(SYSREPO_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-yang.la
+libkea_yang_la_SOURCES = adaptor.cc adaptor.h
+
+libkea_yang_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_yang_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(SYSREPO_LIBS)
+
+libkea_yang_la_LDFLAGS = -no-undefined -version-info 0:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_yang_includedir = $(pkgincludedir)/yang
+libkea_yang_include_HEADERS = \
+ adaptor.h
+
+EXTRA_DIST = yang.dox
+
+CLEANFILES = *.gcno *.gcda
--- /dev/null
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <yang/adaptor.h>
+#include <boost/foreach.hpp>
+
+#include <iostream>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace yang {
+
+Adaptor::Adaptor() {
+}
+
+Adaptor::~Adaptor() {
+}
+
+ConstElementPtr
+Adaptor::getContext(ConstElementPtr parent)
+{
+ ConstElementPtr context = parent->get("user-context");
+ ConstElementPtr comment = parent->get("comment");
+ if (!comment) {
+ return (context);
+ }
+ ElementPtr result;
+ if (context) {
+ result = copy(context);
+ } else {
+ result = Element::createMap();
+ }
+ result->set("comment", comment);
+ return (result);
+}
+
+void
+Adaptor::fromParent(const string& name, ConstElementPtr parent,
+ ConstElementPtr list) {
+ ConstElementPtr param = parent->get(name);
+ if (!param) {
+ return;
+ }
+ BOOST_FOREACH(ElementPtr item, list->listValue()) {
+ // don't override?
+ if (item->contains(name)) {
+ continue;
+ }
+ item->set(name, param);
+ }
+}
+
+void
+Adaptor::toParent(const string& name, ElementPtr parent,
+ ConstElementPtr list) {
+ ConstElementPtr param;
+ bool first = true;
+ BOOST_FOREACH(ElementPtr item, list->listValue()) {
+ if (first) {
+ first = false;
+ param = item->get(name);
+ } else if ((!param && item->contains(name)) ||
+ (param && !item->contains(name)) ||
+ (param && item->contains(name) &&
+ !param->equals(*item->get(name)))) {
+ isc_throw(BadValue,
+ "inconsistent value of " << name
+ << " in " << list->str());
+ }
+ }
+ if (!first && param) {
+ BOOST_FOREACH(ElementPtr item, list->listValue()) {
+ if (param) {
+ item->remove(name);
+ }
+ }
+ parent->set(name, param);
+ }
+}
+
+namespace {
+
+/// @brief Apply insert.
+///
+/// @param key The key of the modification.
+/// @param value The value of the modification.
+/// @param scope The place to apply the insert.
+void apply_insert(ConstElementPtr key, ConstElementPtr value,
+ ElementPtr scope) {
+ if (scope->getType() == Element::map) {
+ if (!key || !value || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (!name.empty() && !scope->contains(name)) {
+ scope->set(name, copy(value));
+ }
+ } else if (scope->getType() == Element::list) {
+ if (value) {
+ scope->add(copy(value));
+ }
+ }
+}
+
+/// @brief Apply replace.
+///
+/// For maps same than insert but the new value is set even if the key
+/// already exists.
+///
+/// @param key The key of the modification.
+/// @param value The value of the modification.
+/// @param scope The place to apply the replace.
+void apply_replace(ConstElementPtr key, ConstElementPtr value,
+ ElementPtr scope) {
+ if ((scope->getType() != Element::map) ||
+ !key || !value || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (!name.empty()) {
+ scope->set(name, copy(value));
+ }
+}
+
+/// @brief Apply delete.
+///
+/// @param last The last item of the path.
+/// @param scope The place to apply the delete.
+void apply_delete(ConstElementPtr last, ElementPtr scope) {
+ if (scope->getType() == Element::map) {
+ if (!last || (last->getType() != Element::string)) {
+ return;
+ }
+ string name = last->stringValue();
+ if (!name.empty()) {
+ scope->remove(name);
+ }
+ } else if (scope->getType() == Element::list) {
+ if (!last) {
+ return;
+ } else if (last->getType() == Element::integer) {
+ int index = last->intValue();
+ if ((index >= 0) && (index < scope->size())) {
+ scope->remove(index);
+ }
+ } else if (last->getType() == Element::map) {
+ ConstElementPtr key = last->get("key");
+ ConstElementPtr value = last->get("value");
+ if (!key || !value || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (name.empty()) {
+ return;
+ }
+ for (int i = 0; i < scope->size(); ++i) {
+ ConstElementPtr item = scope->get(i);
+ if (!item || (item->getType() != Element::map)) {
+ continue;
+ }
+ ConstElementPtr compare = item->get(name);
+ if (compare && value->equals(*compare)) {
+ scope->remove(i);
+ return;
+ }
+ }
+ }
+ }
+}
+
+/// @brief Apply action.
+///
+/// @param actions The action list.
+/// @param scope The current scope.
+/// @param next The index of the next action.
+void apply_action(ConstElementPtr actions, ElementPtr scope, size_t next) {
+ if (next == actions->size()) {
+ return;
+ }
+ ConstElementPtr action = actions->get(next);
+ ++next;
+ if (!action || (action->getType() != Element::map) ||
+ !action->contains("action")) {
+ apply_action(actions, scope, next);
+ return;
+ }
+ string name = action->get("action")->stringValue();
+ if (name == "insert") {
+ apply_insert(action->get("key"), action->get("value"), scope);
+ } else if (name == "replace") {
+ apply_replace(action->get("key"), action->get("value"), scope);
+ } else if (name == "delete") {
+ apply_delete(action->get("last"), scope);
+ }
+ apply_action(actions, scope, next);
+}
+
+/// @brief Modify down.
+///
+/// @param path The search list.
+/// @param actions The action list.
+/// @param scope The current scope.
+/// @param next The index of the next item to use in the path.
+void path_down(ConstElementPtr path, ConstElementPtr actions, ElementPtr scope,
+ size_t next) {
+ if (!scope) {
+ return;
+ }
+ if (next == path->size()) {
+ apply_action(actions, scope, 0);
+ return;
+ }
+ ConstElementPtr step = path->get(next);
+ ++next;
+ if (scope->getType() == Element::map) {
+ if (!step || (step->getType() != Element::string)) {
+ return;
+ }
+ string name = step->stringValue();
+ if (name.empty() || !scope->contains(name)) {
+ return;
+ }
+ ElementPtr down = boost::const_pointer_cast<Element>(scope->get(name));
+ if (down) {
+ path_down(path, actions, down, next);
+ }
+ } else if (scope->getType() == Element::list) {
+ if (!step) {
+ return;
+ }
+ auto downs = scope->listValue();
+ if (step->getType() == Element::map) {
+ ConstElementPtr key = step->get("key");
+ ConstElementPtr value = step->get("value");
+ if (!key || !value || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (name.empty()) {
+ return;
+ }
+ for (ElementPtr down : downs) {
+ if (!down || (down->getType() != Element::map)) {
+ continue;
+ }
+ ConstElementPtr compare = down->get(name);
+ if (compare && value->equals(*compare)) {
+ path_down(path, actions, down, next);
+ return;
+ }
+ }
+ } else if (step->getType() != Element::integer) {
+ return;
+ }
+ int index = step->intValue();
+ if (index == -1) {
+ for (ElementPtr down : downs) {
+ path_down(path, actions, down, next);
+ }
+ } else if ((index >= 0) && (index < scope->size())) {
+ path_down(path, actions, scope->getNonConst(index), next);
+ }
+ }
+}
+
+} // end of anonymous namespace
+
+void
+Adaptor::modify(ConstElementPtr path, ConstElementPtr actions,
+ ElementPtr config) {
+ path_down(path, actions, config, 0);
+}
+
+}; // end of namespace isc::yang
+}; // end of namespace isc
--- /dev/null
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ISC_ADAPTOR_H
+#define ISC_ADAPTOR_H 1
+
+#include <cc/data.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief JSON adaptor between canonical Kea and Yang models.
+///
+/// An adaptor slightly modifies a JSON configuration between canonical Kea
+/// what required or rendered by a Yang model, e.g. moving a parameter
+/// to/from a parent.
+/// The basic adaptor provides a set of tools.
+class Adaptor {
+public:
+
+ /// @brief Constructor.
+ Adaptor();
+
+ /// @brief Destructor.
+ virtual ~Adaptor();
+
+ /// @brief Get user context.
+ ///
+ /// Get user-context and/or comment and return it with the comment
+ /// if exists moved inside the user-context (without checking if
+ /// there is already a comment as it should never be the case).
+ static isc::data::ConstElementPtr
+ getContext(isc::data::ConstElementPtr parent);
+
+ /// @brief From parent.
+ ///
+ /// Move a parameter from the parent to each item in a list.
+ ///
+ /// @param name The parameter name.
+ /// @param parent The parent element.
+ /// @param list The children list.
+ static void fromParent(const std::string& name,
+ isc::data::ConstElementPtr parent,
+ isc::data::ConstElementPtr list);
+
+ /// @brief To parent.
+ ///
+ /// Move a parameter from children to the parent.
+ ///
+ /// @param name The parameter name.
+ /// @param parent The parent element.
+ /// @param list The children list.
+ static void toParent(const std::string& name,
+ isc::data::ElementPtr parent,
+ isc::data::ConstElementPtr list);
+
+ /// @brief Modify.
+ ///
+ /// Smart merging tool, e.g. completing a from yang configuration.
+ ///
+ /// A modification is a path and actions:
+ /// - path item can be:
+ /// * a string: current scope is a map, go down following the string
+ /// as a key.
+ /// * a number: current scope is a list, go down the number as an index.
+ /// * special value -1: current scope is a list, apply to all items.
+ /// * map { "<key>": <value> }: current scope is a list, go down to
+ /// the item using the key / value pair.
+ /// - an action can be: insert, replace or delete.
+ ///
+ /// @param path The search list to follow down to the place to
+ /// apply the action list.
+ /// @param actions The action list
+ /// @param config The configuration (JSON map) to modify.
+ static void modify(isc::data::ConstElementPtr path,
+ isc::data::ConstElementPtr actions,
+ isc::data::ElementPtr config);
+
+};
+
+}; // end of namespace isc::yang
+}; // end of namespace isc
+
+#endif // ISC_ADAPTOR_H
--- /dev/null
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(SYSREPO_CPPFLAGS)
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = adaptor_unittests.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/yang/libkea-yang.la
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+run_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+run_unittests_LDADD += $(SYSREPO_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
--- /dev/null
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <yang/adaptor.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+
+namespace {
+
+// Test get context.
+TEST(AdaptorTest, getContext) {
+ // Empty.
+ string config = "{\n"
+ "}\n";
+ ConstElementPtr json = Element::fromJSON(config);
+ ConstElementPtr context;
+ ASSERT_NO_THROW(context = Adaptor::getContext(json));
+ EXPECT_FALSE(context);
+
+ // No relevant.
+ config = "{\n"
+ " \"foo\": 1\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW(context = Adaptor::getContext(json));
+ EXPECT_FALSE(context);
+
+ // User context.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"user-context\": { \"bar\": 2 }\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"bar\": 2 }", context->str());
+
+ // Comment.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"comment\": \"a comment\"\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"comment\": \"a comment\" }", context->str());
+
+ // User context and comment.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"user-context\": { \"bar\": 2 },\n"
+ " \"comment\": \"a comment\"\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"bar\": 2, \"comment\": \"a comment\" }", context->str());
+
+ // User context with conflicting comment and comment.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"user-context\": {\n"
+ " \"bar\": 2,\n"
+ " \"comment\": \"conflicting\"\n"
+ " },\n"
+ " \"comment\": \"a comment\"\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"bar\": 2, \"comment\": \"a comment\" }", context->str());
+}
+
+// Test from parent.
+TEST(AdaptorTest, fromParent) {
+ string config = "{\n"
+ " \"param1\": 123,\n"
+ " \"param2\": \"foo\",\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param1\": 234\n"
+ " },{\n"
+ " \"another\": \"entry\"\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ ConstElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW(Adaptor::fromParent("param1", json, json->get("list")));
+ EXPECT_NO_THROW(Adaptor::fromParent("param2", json, json->get("list")));
+ EXPECT_NO_THROW(Adaptor::fromParent("param3", json, json->get("list")));
+
+ string expected = "{\n"
+ " \"param1\": 123,\n"
+ " \"param2\": \"foo\",\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param1\": 234,\n"
+ " \"param2\": \"foo\"\n"
+ " },{\n"
+ " \"another\": \"entry\",\n"
+ " \"param1\": 123,\n"
+ " \"param2\": \"foo\"\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+ EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
+}
+
+// Test to parent.
+TEST(AdaptorTest, toParent) {
+ string config = "{\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param2\": \"foo\",\n"
+ " \"param3\": 234,\n"
+ " \"param4\": true\n"
+ " },{\n"
+ " \"another\": \"entry\",\n"
+ " \"param2\": \"foo\",\n"
+ " \"param3\": 123,\n"
+ " \"param5\": false\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW(Adaptor::toParent("param1", json, json->get("list")));
+ EXPECT_TRUE(json->equals(*Element::fromJSON(config)));
+
+ string expected = "{\n"
+ " \"param2\": \"foo\",\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param3\": 234,\n"
+ " \"param4\": true\n"
+ " },{\n"
+ " \"another\": \"entry\",\n"
+ " \"param3\": 123,\n"
+ " \"param5\": false\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ EXPECT_NO_THROW(Adaptor::toParent("param2",json, json->get("list")));
+ EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
+
+ // param1 has different value so it should throw.
+ EXPECT_THROW(Adaptor::toParent("param3",json, json->get("list")),
+ BadValue);
+ EXPECT_THROW(Adaptor::toParent("param4",json, json->get("list")),
+ BadValue);
+ EXPECT_THROW(Adaptor::toParent("param5",json, json->get("list")),
+ BadValue);
+ // And not modify the value.
+ EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
+}
+
+// Test for modify (maps & insert).
+TEST(AdaptorTest, modifyMapInsert) {
+ string config = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ "}}}\n";
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ string spath = "[ \"foo\", \"bar\" ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test\",\n"
+ " \"value\": 1234\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
+ string result = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test\": 1234\n"
+ "}}}\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (maps & replace).
+TEST(AdaptorTest, modifyMapReplace) {
+ string config = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test1\": 1234,\n"
+ " \"test2\": 1234\n"
+ "}}}\n";
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ string spath = "[ \"foo\", \"bar\" ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test1\",\n"
+ " \"value\": 5678\n"
+ "},{\n"
+ " \"action\": \"replace\",\n"
+ " \"key\": \"test2\",\n"
+ " \"value\": 5678\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
+ string result = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test1\": 1234,\n"
+ " \"test2\": 5678\n"
+ "}}}\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (maps & delete).
+TEST(AdaptorTest, modifyMapDelete) {
+ string config = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test\": 1234\n"
+ "}}}\n";
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ string spath = "[ \"foo\", \"bar\" ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"delete\",\n"
+ " \"last\": \"test\"\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
+ string result = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ "}}}\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (lists & insert).
+TEST(AdaptorTest, modifyListInsert) {
+ string config = "[\n"
+ "[{\n"
+ " \"foo\": \"bar\"\n"
+ "}]]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ string spath = "[ 0, { \"key\": \"foo\", \"value\": \"bar\" }]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test\",\n"
+ " \"value\": 1234\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
+ string result = "[\n"
+ "[{\n"
+ " \"foo\": \"bar\",\n"
+ " \"test\": 1234\n"
+ "}]]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (list all & insert).
+TEST(AdaptorTest, modifyListAllInsert) {
+ string config = "[\n"
+ "{},\n"
+ "{},\n"
+ "{ \"test\": 1234 },\n"
+ "]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ string spath = "[ -1 ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test\",\n"
+ " \"value\": 5678\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
+ string result = "[\n"
+ "{ \"test\": 5678 },\n"
+ "{ \"test\": 5678 },\n"
+ "{ \"test\": 1234 }\n"
+ "]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+TEST(AdaptorTest, modifyListDelete) {
+ string config = "[[\n"
+ "{\n"
+ " \"foo\": \"bar\"\n"
+ "},{\n"
+ "},[\n"
+ "0, 1, 2, 3\n"
+ "]]]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ string spath = "[ 0 ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW(path = Element::fromJSON(spath));
+ // Put the positional first as it applies after previous actions...
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"delete\",\n"
+ " \"last\": 2\n"
+ "},{\n"
+ " \"action\": \"delete\",\n"
+ " \"last\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
+ string result = "[[{}]]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+TEST(AdaptorTest, modifyListAllDelete) {
+ string config = "[[\n"
+ "{\n"
+ " \"foo\": \"bar\"\n"
+ "},{\n"
+ "},[\n"
+ "0, 1, 2, 3\n"
+ "]]]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ // The only change from the previous unit test is 0 -> -1.
+ string spath = "[ -1 ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW(path = Element::fromJSON(spath));
+ // Put the positional first as it applies after previous actions...
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"delete\",\n"
+ " \"last\": 2\n"
+ "},{\n"
+ " \"action\": \"delete\",\n"
+ " \"last\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
+ string result = "[[{}]]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+}; // end of anonymous namespace
--- /dev/null
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ isc::log::initLogger();
+
+ return (isc::util::unittests::run_all());
+}