11#include < sourcemeta/one/configuration.h>
22
3- #include < algorithm> // std::ranges
4- #include < cassert> // assert
5- #include < iterator> // std::back_inserter
6- #include < utility> // std::move
7- #include < vector> // std::vector
3+ #include < algorithm> // std::ranges
4+ #include < cassert> // assert
5+ #include < iterator> // std::back_inserter
6+ #include < string> // std::string
7+ #include < unordered_set> // std::unordered_set
8+ #include < utility> // std::move
9+ #include < vector> // std::vector
810
911namespace {
1012
@@ -49,7 +51,8 @@ auto maybe_suffix(const std::filesystem::path &path,
4951auto dereference (const std::filesystem::path &collections_path,
5052 const std::filesystem::path &base,
5153 sourcemeta::core::JSON &input,
52- const sourcemeta::core::Pointer &location) -> void {
54+ const sourcemeta::core::Pointer &location,
55+ std::unordered_set<std::string> &visited) -> void {
5356 assert (base.is_absolute ());
5457 if (!input.is_object ()) {
5558 return ;
@@ -64,20 +67,27 @@ auto dereference(const std::filesystem::path &collections_path,
6467 entry.to_string ()),
6568 " one.json" )};
6669 const auto new_location{location.concat ({" extends" })};
70+ if (!visited.emplace (target_path.native ()).second ) {
71+ throw sourcemeta::one::ConfigurationCyclicReferenceError (
72+ base, new_location, target_path);
73+ }
6774 auto extension{
6875 read_file (base, new_location, target_path, entry.to_string ())};
6976 if (extension.is_object ()) {
70- dereference (collections_path, target_path, extension, new_location);
77+ dereference (collections_path, target_path, extension, new_location,
78+ visited);
7179 accumulator.merge (std::move (extension).as_object ());
7280 }
81+
82+ visited.erase (target_path.native ());
7383 }
7484 }
7585
7686 input.erase (" extends" );
7787 accumulator.merge (input.as_object ());
7888 input = std::move (accumulator);
7989 assert (!input.defines (" extends" ));
80- dereference (collections_path, base, input, location);
90+ dereference (collections_path, base, input, location, visited );
8191
8292 // Read included files
8393 } else if (!location.empty () && input.defines (" include" ) &&
@@ -89,10 +99,14 @@ auto dereference(const std::filesystem::path &collections_path,
8999 input.at (" include" ).to_string ()),
90100 " jsonschema.json" )};
91101 const auto new_location{location.concat ({" include" })};
102+ if (!visited.emplace (target_path.native ()).second ) {
103+ throw sourcemeta::one::ConfigurationCyclicReferenceError (
104+ base, new_location, target_path);
105+ }
92106 input.into (read_file (base, new_location, target_path,
93107 input.at (" include" ).to_string ()));
94- assert (! input. defines ( " include " ) );
95- dereference (collections_path, target_path, input, new_location );
108+ dereference (collections_path, target_path, input, new_location, visited );
109+ visited. erase ( target_path. native () );
96110
97111 // Revisit and relativize paths
98112 } else if (input.defines (" path" ) && input.at (" path" ).is_string ()) {
@@ -116,7 +130,7 @@ auto dereference(const std::filesystem::path &collections_path,
116130 [](const auto &entry) { return entry.first ; });
117131 for (const auto &key : keys) {
118132 dereference (collections_path, base, input.at (" contents" ).at (key),
119- location.concat ({" contents" , key}));
133+ location.concat ({" contents" , key}), visited );
120134 }
121135 }
122136}
@@ -168,7 +182,10 @@ auto Configuration::read(const std::filesystem::path &configuration_path,
168182 sourcemeta::core::JSON {" The next-generation JSON Schema platform" });
169183 }
170184
171- dereference (collections_path, configuration_path, data, {});
185+ std::unordered_set<std::string> visited;
186+ visited.emplace (
187+ std::filesystem::weakly_canonical (configuration_path).native ());
188+ dereference (collections_path, configuration_path, data, {}, visited);
172189
173190 if (data.is_object () && data.defines (" url" ) && data.defines (" contents" ) &&
174191 data.at (" contents" ).is_object ()) {
0 commit comments