11#include < userver/formats/yaml/serialize.hpp>
22
3+ #include < algorithm>
34#include < array>
45#include < fstream>
6+ #include < functional>
57#include < memory>
68#include < sstream>
9+ #include < string_view>
10+ #include < type_traits>
711
812#include < fmt/format.h>
13+ #include < yaml-cpp/yaml.h>
14+ #include < boost/container/small_vector.hpp>
915
16+ #include < userver/formats/common/path.hpp>
1017#include < userver/formats/yaml/exception.hpp>
11-
12- #include < yaml-cpp/yaml.h>
18+ #include < userver/formats/yaml/value.hpp>
19+ #include < userver/utils/enumerate.hpp>
20+ #include < userver/utils/not_null.hpp>
1321
1422USERVER_NAMESPACE_BEGIN
1523
1624namespace formats ::yaml {
1725
26+ namespace {
27+
28+ constexpr std::size_t kInitialStackDepth = 16 ;
29+ constexpr std::size_t kInitialSortedKeysSize = 32 ;
30+
31+ struct PathElement {
32+ explicit PathElement (YAML::Node& parent)
33+ : current(parent.begin()),
34+ end(parent.end())
35+ {}
36+
37+ // Cannot store Node[&], because it has broken copy/move semantics for iteration.
38+ // https://github.com/jbeder/yaml-cpp/issues/928
39+ //
40+ // Cannot store const_iterator, because incurs extra copies.
41+ YAML::Node::iterator current;
42+ YAML::Node::iterator end;
43+ };
44+
45+ class RawYamlVisitor final {
46+ public:
47+ explicit RawYamlVisitor (YAML::Node& root)
48+ : root_(root)
49+ {}
50+
51+ template <typename Visitor, typename = std::enable_if_t <std::is_invocable_v<Visitor&, YAML::Node&>>>
52+ void VisitPreOrder (Visitor visitor) {
53+ if (!root_.IsSequence () && !root_.IsMap ()) {
54+ return ;
55+ }
56+
57+ visitor (root_);
58+ path_stack_.emplace_back (root_);
59+
60+ while (true ) {
61+ if (path_stack_.back ().current == path_stack_.back ().end ) {
62+ path_stack_.pop_back ();
63+ if (path_stack_.empty ()) {
64+ break ;
65+ }
66+ ++path_stack_.back ().current ;
67+ continue ;
68+ }
69+
70+ auto item = *path_stack_.back ().current ;
71+ auto & node = item.IsDefined () ? item : item.second ;
72+
73+ visitor (node);
74+ if (node.IsSequence () || node.IsMap ()) {
75+ path_stack_.emplace_back (node);
76+ } else {
77+ ++path_stack_.back ().current ;
78+ }
79+ }
80+ }
81+
82+ // Complexity: O(whole-yaml-size)
83+ std::string ComputeCurrentPath () const {
84+ if (path_stack_.empty ()) {
85+ return common::kPathRoot ;
86+ }
87+
88+ std::string path;
89+ for (const auto [idx, element] : utils::enumerate (path_stack_)) {
90+ if (element.current ->IsDefined ()) {
91+ // Sequence iterator.
92+ const auto begin = idx == 0 ? root_.begin () : path_stack_[idx - 1 ].current ->begin ();
93+ common::AppendPath (path, std::distance (begin, element.current ));
94+ } else {
95+ // Map iterator.
96+ const auto map_item = *element.current ;
97+ const auto & key = map_item.first ;
98+ switch (key.Type ()) {
99+ case YAML::NodeType::Null:
100+ common::AppendPath (path, " null" );
101+ break ;
102+ case YAML::NodeType::Scalar:
103+ common::AppendPath (path, key.Scalar ());
104+ break ;
105+ default : {
106+ // YAML can have non-scalar keys:
107+ // https://yaml.org/spec/1.2.2/#mapping
108+ std::ostringstream stream;
109+ stream << key;
110+ common::AppendPath (path, stream.str ());
111+ break ;
112+ }
113+ }
114+ }
115+ }
116+ return path;
117+ }
118+
119+ private:
120+ YAML::Node& root_;
121+ boost::container::small_vector<PathElement, kInitialStackDepth > path_stack_;
122+ };
123+
124+ // Non-unique keys in mappings are not allowed, but yaml-cpp does not check for them:
125+ // https://github.com/jbeder/yaml-cpp/issues/60
126+ class UniquenessChecker final {
127+ public:
128+ explicit UniquenessChecker (YAML::Node& root)
129+ : visitor_(root)
130+ {}
131+
132+ void CheckKeyUniquenessIterative () {
133+ visitor_.VisitPreOrder ([this ](const YAML::Node& node) {
134+ if (node.IsMap ()) {
135+ CheckSingleMapKeyUniqueness (node);
136+ }
137+ });
138+ }
139+
140+ private:
141+ void CheckSingleMapKeyUniqueness (const YAML::Node& node) {
142+ UASSERT (node.IsMap ());
143+ sorted_keys_.clear ();
144+ sorted_keys_.reserve (node.size ());
145+ for (const auto item : node) {
146+ const auto & key = item.first ;
147+ switch (key.Type ()) {
148+ case YAML::NodeType::Null:
149+ sorted_keys_.push_back (" null" );
150+ break ;
151+ case YAML::NodeType::Scalar:
152+ sorted_keys_.push_back (key.Scalar ());
153+ break ;
154+ default :
155+ // This is a valid key, but too complex to check. YAML nodes may be recursive and infinitely deep.
156+ break ;
157+ }
158+ }
159+
160+ std::sort (sorted_keys_.begin (), sorted_keys_.end ());
161+ if (const auto duplicate = std::adjacent_find (sorted_keys_.begin (), sorted_keys_.end ());
162+ duplicate != sorted_keys_.end ())
163+ {
164+ throw ParseException (
165+ fmt::format (" Duplicate mapping key '{}' at path '{}'" , *duplicate, visitor_.ComputeCurrentPath ())
166+ );
167+ }
168+ }
169+
170+ RawYamlVisitor visitor_;
171+ boost::container::small_vector<std::string_view, kInitialSortedKeysSize > sorted_keys_;
172+ };
173+
174+ } // namespace
175+
18176formats::yaml::Value FromString (const std::string& doc) {
19177 if (doc.empty ()) {
20178 throw ParseException (" YAML document is empty" );
21179 }
22180
23181 try {
24- return YAML::Load (doc);
182+ auto node = YAML::Load (doc);
183+ UniquenessChecker{node}.CheckKeyUniquenessIterative ();
184+ return formats::yaml::Value (node);
25185 } catch (const YAML::ParserException& e) {
26186 throw ParseException (e.what ());
27187 }
@@ -33,7 +193,9 @@ formats::yaml::Value FromStream(std::istream& is) {
33193 }
34194
35195 try {
36- return YAML::Load (is);
196+ auto node = YAML::Load (is);
197+ UniquenessChecker{node}.CheckKeyUniquenessIterative ();
198+ return formats::yaml::Value (node);
37199 } catch (const YAML::ParserException& e) {
38200 throw ParseException (e.what ());
39201 }
@@ -63,6 +225,22 @@ formats::yaml::Value FromFile(const std::string& path) {
63225}
64226} // namespace blocking
65227
228+ namespace impl {
229+
230+ formats::yaml::Value FromStringAllowRepeatedKeys (const std::string& doc) {
231+ if (doc.empty ()) {
232+ throw ParseException (" YAML document is empty" );
233+ }
234+
235+ try {
236+ return formats::yaml::Value (YAML::Load (doc));
237+ } catch (const YAML::ParserException& e) {
238+ throw ParseException (e.what ());
239+ }
240+ }
241+
242+ } // namespace impl
243+
66244} // namespace formats::yaml
67245
68246USERVER_NAMESPACE_END
0 commit comments