// 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 #include #include 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