1 // Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <testutils/user_context_utils.h>
10 
11 using namespace isc::data;
12 
13 namespace {
14 
15 /// @brief Encapsulate either a modified copy or a unmodified value
16 /// @tparam EP ElementPtr or ConstElementPtr (compiler can't infer which one)
17 template<typename EP>
18 class Value {
19 public:
20     /// @brief Factory for modified copy
mkCopy(EP value)21     static Value mkCopy(EP value) { return (Value(value, false)); }
22 
23     /// @brief Factory for unmodified original
mkShare(EP value)24     static Value mkShare(EP value) { return (Value(value, true)); }
25 
26     /// @brief Get the value
27     /// @return the value
get() const28     EP get() const { return (value_); }
29 
30     /// @brief Get the shared status
31     /// @return true if original, false if copy
isShared() const32     bool isShared() const { return (shared_); }
33 
34 private:
35     /// @brief Constructor
36     /// @param value the modified copy or unmodified value
37     /// @param shared true if original, false if copy
Value(EP value,bool shared)38     Value(EP value, bool shared) : value_(value), shared_(shared) { }
39 
40     /// @brief the value
41     EP value_;
42 
43     /// @brief the shared status
44     bool shared_;
45 };
46 
47 /// @brief Recursive helper
48 ///
49 /// @tparam EP ElementPtr or ConstElementPtr (compiler will infer which one)
50 /// @param element the element to traverse
51 /// @return a modified copy where comment entries were moved to user-context
52 /// or the unmodified original argument encapsulated into a Value
53 template<typename EP>
moveComments1(EP element)54 Value<EP> moveComments1(EP element) {
55     bool modified = false;
56 
57     // On lists recurse on items
58     if (element->getType() == Element::list) {
59         ElementPtr result = ElementPtr(new ListElement());
60         typedef std::vector<ElementPtr> ListType;
61         const ListType& list = element->listValue();
62         for (ListType::const_iterator it = list.cbegin();
63              it != list.cend(); ++it) {
64             Value<ElementPtr> item = moveComments1(*it);
65             result->add(item.get());
66             if (!item.isShared()) {
67                 modified = true;
68             }
69         }
70         if (!modified) {
71             return (Value<EP>::mkShare(element));
72         } else {
73             return (Value<EP>::mkCopy(result));
74         }
75     } else if (element->getType() != Element::map) {
76         return (Value<EP>::mkShare(element));
77     }
78 
79     // Process maps: recurse on items
80     ElementPtr result = ElementPtr(new MapElement());
81     bool has_comment = false;
82     typedef std::map<std::string, ConstElementPtr> map_type;
83     const map_type& map = element->mapValue();
84     for (map_type::const_iterator it = map.cbegin(); it != map.cend(); ++it) {
85         if (it->first == "comment") {
86             // Note there is a comment entry to move
87             has_comment = true;
88         } else if (it->first == "user-context") {
89             // Do not traverse user-context entries
90             result->set("user-context", it->second);
91         } else {
92             // Not comment or user-context
93             Value<ConstElementPtr> item = moveComments1(it->second);
94             result->set(it->first, item.get());
95             if (!item.isShared()) {
96                 modified = true;
97             }
98         }
99     }
100     // Check if the value should be not modified
101     if (!has_comment && !modified) {
102         return (Value<EP>::mkShare(element));
103     }
104 
105     if (has_comment) {
106         // Move the comment entry
107         ConstElementPtr comment = element->get("comment");
108         ElementPtr moved = Element::createMap();
109         moved->set("comment", comment);
110         ConstElementPtr previous = element->get("user-context");
111         // If there is already a user context merge it
112         if (previous) {
113                 merge(moved, previous);
114         }
115         result->set("user-context", moved);
116     }
117 
118     return (Value<EP>::mkCopy(result));
119 }
120 
121 } // anonymous namespace
122 
123 namespace isc {
124 namespace test {
125 
moveComments(ElementPtr element)126 ElementPtr moveComments(ElementPtr element) {
127     Value<ElementPtr> result = moveComments1(element);
128     return (result.get());
129 }
130 
moveComments(ConstElementPtr element)131 ConstElementPtr moveComments(ConstElementPtr element) {
132     Value<ConstElementPtr> result = moveComments1(element);
133     return (result.get());
134 }
135 
136 } // end of isc::test namespace
137 } // end of isc namespace
138