diff options
Diffstat (limited to 'src/lib/yang/adaptor.cc')
-rw-r--r-- | src/lib/yang/adaptor.cc | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/src/lib/yang/adaptor.cc b/src/lib/yang/adaptor.cc new file mode 100644 index 0000000..5e2de98 --- /dev/null +++ b/src/lib/yang/adaptor.cc @@ -0,0 +1,302 @@ +// Copyright (C) 2018-2022 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 <iostream> + +using namespace std; +using namespace isc::data; + +namespace isc { +namespace yang { + +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; + } + for (ElementPtr const& item : list->listValue()) { + // don't override. Skip this entry if it already has the parameter. + if (item->contains(name)) { + continue; + } + item->set(name, param); + } +} + +void +Adaptor::toParent(const string& name, ElementPtr parent, + ConstElementPtr list) { + ConstElementPtr param; + bool first = true; + for (ElementPtr const& 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) { + for (ElementPtr const& item : list->listValue()) { + if (param) { + item->remove(name); + } + } + 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 applyInsert(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. +/// +/// 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 applyReplace(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. +/// +/// 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 applyDelete(ConstElementPtr key, ElementPtr scope) { + if (scope->getType() == Element::map) { + if (!key || (key->getType() != Element::string)) { + return; + } + string name = key->stringValue(); + if (!name.empty()) { + scope->remove(name); + } + } else if (scope->getType() == Element::list) { + if (!key) { + return; + } else if (key->getType() == Element::integer) { + int index = key->intValue(); + if ((index >= 0) && (index < scope->size())) { + scope->remove(index); + } + } 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 = entry->stringValue(); + if (name.empty()) { + return; + } + for (int i = 0; i < scope->size(); ++i) { + ElementPtr item = scope->getNonConst(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. +/// +/// 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 applyAction(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")) { + applyAction(actions, scope, next); + return; + } + string name = action->get("action")->stringValue(); + if (name == "insert") { + applyInsert(action->get("key"), action->get("value"), scope); + } else if (name == "replace") { + applyReplace(action->get("key"), action->get("value"), scope); + } else if (name == "delete") { + applyDelete(action->get("key"), scope); + } + applyAction(actions, scope, next); +} + +/// @brief Apply recursively actions following the path and going down +/// in the to-be-modified 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 applyDown(ConstElementPtr path, ConstElementPtr actions, ElementPtr scope, + size_t next) { + if (!scope) { + return; + } + if (next == path->size()) { + applyAction(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; + } + ConstElementPtr down(scope->get(name)); + if (down) { + ElementPtr mutable_down(copy(down, 0)); + applyDown(path, actions, mutable_down, next); + scope->set(name, mutable_down); + } + } 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)) { + applyDown(path, actions, down, next); + return; + } + } + } else if (step->getType() != Element::integer) { + return; + } + int index = step->intValue(); + if (index == -1) { + for (ElementPtr down : downs) { + applyDown(path, actions, down, next); + } + } else if ((index >= 0) && (index < scope->size())) { + applyDown(path, actions, scope->getNonConst(index), next); + } + } +} + +} //anonymous namespace + +/// Apply recursively starting at the beginning of the path. +void +Adaptor::modify(ConstElementPtr path, ConstElementPtr actions, + ElementPtr config) { + applyDown(path, actions, config, 0); +} + +} // namespace yang +} // namespace isc |