parent->set(name, param);
}
}
-
+
namespace {
/// @brief Apply insert.
///
+/// This function applies an insert action at the given scope:
+/// - in a map it adds the value at the key when the key does not exist
+/// - in a list it appends the value
+/// - in other scope type it does nothing
+/// @note a copy of the value is used to avoid unwanted sharing/side effects.
+///
/// @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) {
+void applyInsert(ConstElementPtr key, ConstElementPtr value,
+ ElementPtr scope) {
if (scope->getType() == Element::map) {
if (!key || !value || (key->getType() != Element::string)) {
return;
/// @brief Apply replace.
///
-/// For maps same than insert but the new value is set even if the key
-/// already exists.
+/// This function works only on map scopes and acts as insert at the
+/// exception 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,
+void applyReplace(ConstElementPtr key, ConstElementPtr value,
ElementPtr scope) {
if ((scope->getType() != Element::map) ||
!key || !value || (key->getType() != Element::string)) {
/// @brief Apply delete.
///
-/// @param last The last item of the path.
+/// This function deletes the value designed by the key from the given scope:
+/// - in a map the key is the key of the value to remove
+/// - in a list the key is:
+/// * when it is an integer the index of the value to remove
+/// * when it is a map the key / value pair which belongs to the value
+/// to remove
+/// - in other scope type it does nothing
+///
+/// @param key The key item of the path.
/// @param scope The place to apply the delete.
-void apply_delete(ConstElementPtr last, ElementPtr scope) {
+void applyDelete(ConstElementPtr key, ElementPtr scope) {
if (scope->getType() == Element::map) {
- if (!last || (last->getType() != Element::string)) {
+ if (!key || (key->getType() != Element::string)) {
return;
}
- string name = last->stringValue();
+ string name = key->stringValue();
if (!name.empty()) {
scope->remove(name);
}
} else if (scope->getType() == Element::list) {
- if (!last) {
+ if (!key) {
return;
- } else if (last->getType() == Element::integer) {
- int index = last->intValue();
+ } else if (key->getType() == Element::integer) {
+ int index = key->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)) {
+ } else if (key->getType() == Element::map) {
+ ConstElementPtr entry = key->get("key");
+ ConstElementPtr value = key->get("value");
+ if (!entry || !value || (entry->getType() != Element::string)) {
return;
}
- string name = key->stringValue();
+ string name = entry->stringValue();
if (name.empty()) {
return;
}
/// @brief Apply action.
///
+/// This function applies one action (insert, replace or delete) using
+/// applyInsert, applyReplace or applyDelete.
+///
/// @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) {
+void applyAction(ConstElementPtr actions, ElementPtr scope, size_t next) {
if (next == actions->size()) {
return;
}
++next;
if (!action || (action->getType() != Element::map) ||
!action->contains("action")) {
- apply_action(actions, scope, next);
+ applyAction(actions, scope, next);
return;
}
string name = action->get("action")->stringValue();
if (name == "insert") {
- apply_insert(action->get("key"), action->get("value"), scope);
+ applyInsert(action->get("key"), action->get("value"), scope);
} else if (name == "replace") {
- apply_replace(action->get("key"), action->get("value"), scope);
+ applyReplace(action->get("key"), action->get("value"), scope);
} else if (name == "delete") {
- apply_delete(action->get("last"), scope);
+ applyDelete(action->get("key"), scope);
}
- apply_action(actions, scope, next);
+ applyAction(actions, scope, next);
}
-/// @brief Modify down.
+/// @brief Apply recursively actions following the path and going down
+/// in the to modify structure.
+///
+/// The recursive call when the end of the path is not reached is done:
+/// - in a map on the value at the key
+/// - in a list the next path item is:
+/// * if it is a positive integer on the value at the index
+/// * if it is -1 on all elements of the list
+/// * if a map on the element where the key / value pair belongs to
///
/// @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,
+void applyDown(ConstElementPtr path, ConstElementPtr actions, ElementPtr scope,
size_t next) {
if (!scope) {
return;
}
if (next == path->size()) {
- apply_action(actions, scope, 0);
+ applyAction(actions, scope, 0);
return;
}
ConstElementPtr step = path->get(next);
}
ElementPtr down = boost::const_pointer_cast<Element>(scope->get(name));
if (down) {
- path_down(path, actions, down, next);
+ applyDown(path, actions, down, next);
}
} else if (scope->getType() == Element::list) {
if (!step) {
}
ConstElementPtr compare = down->get(name);
if (compare && value->equals(*compare)) {
- path_down(path, actions, down, next);
+ applyDown(path, actions, down, next);
return;
}
}
int index = step->intValue();
if (index == -1) {
for (ElementPtr down : downs) {
- path_down(path, actions, down, next);
+ applyDown(path, actions, down, next);
}
} else if ((index >= 0) && (index < scope->size())) {
- path_down(path, actions, scope->getNonConst(index), next);
+ applyDown(path, actions, scope->getNonConst(index), next);
}
}
}
} // end of anonymous namespace
+/// Apply recursively starting at the beginning of the path.
void
Adaptor::modify(ConstElementPtr path, ConstElementPtr actions,
ElementPtr config) {
- path_down(path, actions, config, 0);
+ applyDown(path, actions, config, 0);
}
}; // end of namespace isc::yang
isc::data::ElementPtr parent,
isc::data::ConstElementPtr list);
- /// @brief Modify.
+ /// @brief Modify a configuration in its JSON element format.
///
/// Smart merging tool, e.g. completing a from yang configuration.
///
/// * 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.
+ /// - an action can be: insert, replace or delete:
+ /// * insert adds a value at a key:
+ /// . in a map the key is the new entry key
+ /// . in a list an integer key is the new element index
+ /// . in a list a map key is the key / value pair the to modify
+ /// element contains
+ /// * replace adds or replaces if it already exists a value at
+ /// an entry key in a map.
+ /// * delete removes a value designed by a key
+ ///
+ /// For instance to add a control-socket entry in a configuration
+ /// from a generic (vs Kea) model:
+ /// @code
+ /// path := [ ]
+ /// actions := [ {
+ /// "action": "insert",
+ /// "key": "Dhcp4",
+ /// "value":
+ /// {
+ /// "control-socket":
+ /// {
+ /// "socket-type": "unix",
+ /// "socket-name": "/tmp/kea4-ctrl-socket"
+ /// }
+ /// }
+ /// }
+ /// @endcode
///
/// @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.
+ /// @note modify does not throw but returns on unexpected conditions.
static void modify(isc::data::ConstElementPtr path,
isc::data::ConstElementPtr actions,
isc::data::ElementPtr config);
namespace {
-// Test get context.
+// Test that get context works as expected i.e. moves a comment into
+// the user context creating it if not exists.
TEST(AdaptorTest, getContext) {
// Empty.
string config = "{\n"
EXPECT_EQ("{ \"bar\": 2, \"comment\": \"a comment\" }", context->str());
}
-// Test from parent.
+// Test that fromParent works as expected i.e. moves parameters from the
+// parent to children and not overwrite them.
TEST(AdaptorTest, fromParent) {
string config = "{\n"
" \"param1\": 123,\n"
EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
}
-// Test to parent.
+// Test that toParent works as expected i.e. moves parameters from children
+// to the parent throwing when a value differs between two children.
TEST(AdaptorTest, toParent) {
string config = "{\n"
" \"list\": [\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.
+ // param[345] have different values so it should throw.
EXPECT_THROW(Adaptor::toParent("param3",json, json->get("list")),
BadValue);
EXPECT_THROW(Adaptor::toParent("param4",json, json->get("list")),
string sactions = "[\n"
"{\n"
" \"action\": \"delete\",\n"
- " \"last\": \"test\"\n"
+ " \"key\": \"test\"\n"
"}]\n";
ConstElementPtr actions;
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
string sactions = "[\n"
"{\n"
" \"action\": \"delete\",\n"
- " \"last\": 2\n"
+ " \"key\": 2\n"
"},{\n"
" \"action\": \"delete\",\n"
- " \"last\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
+ " \"key\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
"}]\n";
ConstElementPtr actions;
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
"]]]\n";
ElementPtr json;
ASSERT_NO_THROW(json = Element::fromJSON(config));
- // The only change from the previous unit test is 0 -> -1.
+ // The main change from the previous unit test is key 0 -> -1 so
+ // modify() applies the delete to all elements vs only the first one.
string spath = "[ -1 ]";
ConstElementPtr path;
ASSERT_NO_THROW(path = Element::fromJSON(spath));
string sactions = "[\n"
"{\n"
" \"action\": \"delete\",\n"
- " \"last\": 2\n"
+ " \"key\": 2\n"
"},{\n"
" \"action\": \"delete\",\n"
- " \"last\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
+ " \"key\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
"}]\n";
ConstElementPtr actions;
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));