@@ -9,7 +9,7 @@ namespace {
99auto embed_schema (sourcemeta::core::JSON &root,
1010 const sourcemeta::core::Pointer &container,
1111 const std::string &identifier,
12- const sourcemeta::core::JSON &target) -> void {
12+ sourcemeta::core::JSON && target) -> std::string {
1313 auto *current{&root};
1414 for (const auto &token : container) {
1515 if (token.is_property ()) {
@@ -34,13 +34,15 @@ auto embed_schema(sourcemeta::core::JSON &root,
3434 key << " /x" ;
3535 }
3636
37- current->assign (key.str (), target);
37+ current->assign (key.str (), std::move (target));
38+ return key.str ();
3839}
3940
4041auto is_official_metaschema_reference (const sourcemeta::core::Pointer &pointer,
4142 const std::string &destination) -> bool {
42- return !pointer.empty () && pointer.back ().is_property () &&
43- pointer.back ().to_property () == " $schema" &&
43+ assert (!pointer.empty ());
44+ assert (pointer.back ().is_property ());
45+ return pointer.back ().to_property () == " $schema" &&
4446 sourcemeta::core::schema_official_resolver (destination).has_value ();
4547}
4648
@@ -53,6 +55,7 @@ auto bundle_schema(sourcemeta::core::JSON &root,
5355 const std::optional<std::string> &default_dialect,
5456 const std::optional<std::string> &default_id,
5557 const sourcemeta::core::SchemaFrame::Paths &paths,
58+ const sourcemeta::core::BundleCallback &callback,
5659 const std::size_t depth = 0 ) -> void {
5760 // Keep in mind that the resulting frame does miss some information. For
5861 // example, when we recurse to framing embedded schemas, we will frame them
@@ -65,12 +68,12 @@ auto bundle_schema(sourcemeta::core::JSON &root,
6568 // We only want to frame in "wrapper" mode for the top level object
6669 paths);
6770 } else {
68- // Note that we only apply the default identifier to the top-level frame
69- frame.analyse (subschema, walker, resolver, default_dialect);
71+ frame.analyse (subschema, walker, resolver, default_dialect, default_id);
7072 }
7173
7274 // Otherwise, given recursion, we would be modifying the
7375 // references list *while* looping on it
76+ // TODO: How can we avoid this very expensive copy?
7477 const auto references_copy = frame.references ();
7578 for (const auto &[key, reference] : references_copy) {
7679 if (frame.traverse (reference.destination ).has_value () ||
@@ -97,8 +100,8 @@ auto bundle_schema(sourcemeta::core::JSON &root,
97100 }
98101
99102 assert (reference.base .has_value ());
100- const auto identifier{reference.base .value ()};
101- const auto remote{resolver (identifier)};
103+ const auto & identifier{reference.base .value ()};
104+ auto remote{resolver (identifier)};
102105 if (!remote.has_value ()) {
103106 if (frame.traverse (identifier).has_value ()) {
104107 throw sourcemeta::core::SchemaReferenceError (
@@ -110,53 +113,60 @@ auto bundle_schema(sourcemeta::core::JSON &root,
110113 identifier, " Could not resolve the reference to an external schema" );
111114 }
112115
113- // Otherwise, if the target schema does not declare an inline identifier,
114- // references to that identifier from the outer schema won't resolve.
115- sourcemeta::core::JSON copy{remote.value ()};
116-
117- if (!sourcemeta::core::is_schema (copy)) {
116+ if (!sourcemeta::core::is_schema (remote.value ())) {
118117 throw sourcemeta::core::SchemaReferenceError (
119118 identifier, key.second ,
120119 " The JSON document is not a valid JSON Schema" );
121120 }
122121
123- const auto dialect{sourcemeta::core::dialect (copy, default_dialect)};
124- if (!dialect.has_value ()) {
122+ const auto base_dialect{sourcemeta::core::base_dialect (
123+ remote.value (), resolver, default_dialect)};
124+ if (!base_dialect.has_value ()) {
125125 throw sourcemeta::core::SchemaReferenceError (
126126 identifier, key.second ,
127127 " The JSON document is not a valid JSON Schema" );
128128 }
129129
130- if (copy .is_object ()) {
130+ if (remote. value () .is_object ()) {
131131 // Always insert an identifier, as a schema might refer to another schema
132132 // using another URI (i.e. due to relying on HTTP re-directions, etc)
133- sourcemeta::core::reidentify (copy, identifier, resolver, default_dialect);
133+ sourcemeta::core::reidentify (remote.value (), identifier,
134+ base_dialect.value ());
134135 }
135136
136- embed_schema (root, container, identifier, copy);
137- bundle_schema (root, container, copy, frame, walker, resolver,
138- default_dialect, default_id, paths, depth + 1 );
137+ bundle_schema (root, container, remote.value (), frame, walker, resolver,
138+ default_dialect, identifier, paths, callback, depth + 1 );
139+ auto embed_key{
140+ embed_schema (root, container, identifier, std::move (remote).value ())};
141+
142+ if (callback) {
143+ const auto origin{sourcemeta::core::identify (
144+ subschema, resolver,
145+ sourcemeta::core::SchemaIdentificationStrategy::Strict,
146+ default_dialect, default_id)};
147+ callback (origin, key.second , identifier, container.concat ({embed_key}));
148+ }
139149 }
140150}
141151
142152} // namespace
143153
144154namespace sourcemeta ::core {
145155
146- auto bundle (sourcemeta::core:: JSON &schema, const SchemaWalker &walker,
156+ auto bundle (JSON &schema, const SchemaWalker &walker,
147157 const SchemaResolver &resolver,
148158 const std::optional<std::string> &default_dialect,
149159 const std::optional<std::string> &default_id,
150160 const std::optional<Pointer> &default_container,
151- const SchemaFrame::Paths &paths) -> void {
152- sourcemeta::core::SchemaFrame frame {
153- sourcemeta::core:: SchemaFrame::Mode::References};
161+ const SchemaFrame::Paths &paths, const BundleCallback &callback)
162+ -> void {
163+ SchemaFrame frame{ SchemaFrame::Mode::References};
154164
155165 if (default_container.has_value ()) {
156166 // This is undefined behavior
157167 assert (!default_container.value ().empty ());
158168 bundle_schema (schema, default_container.value (), schema, frame, walker,
159- resolver, default_dialect, default_id, paths);
169+ resolver, default_dialect, default_id, paths, callback );
160170 return ;
161171 }
162172
@@ -167,7 +177,7 @@ auto bundle(sourcemeta::core::JSON &schema, const SchemaWalker &walker,
167177 vocabularies.contains (
168178 " https://json-schema.org/draft/2019-09/vocab/core" )) {
169179 bundle_schema (schema, {" $defs" }, schema, frame, walker, resolver,
170- default_dialect, default_id, paths);
180+ default_dialect, default_id, paths, callback );
171181 return ;
172182 } else if (vocabularies.contains (" http://json-schema.org/draft-07/schema#" ) ||
173183 vocabularies.contains (
@@ -179,7 +189,7 @@ auto bundle(sourcemeta::core::JSON &schema, const SchemaWalker &walker,
179189 vocabularies.contains (
180190 " http://json-schema.org/draft-04/hyper-schema#" )) {
181191 bundle_schema (schema, {" definitions" }, schema, frame, walker, resolver,
182- default_dialect, default_id, paths);
192+ default_dialect, default_id, paths, callback );
183193 return ;
184194 } else if (vocabularies.contains (
185195 " http://json-schema.org/draft-03/hyper-schema#" ) ||
@@ -201,19 +211,20 @@ auto bundle(sourcemeta::core::JSON &schema, const SchemaWalker &walker,
201211
202212 // We don't attempt to bundle on dialects where we
203213 // don't know where to put the embedded schemas
204- throw sourcemeta::core:: SchemaError (
214+ throw SchemaError (
205215 " Could not determine how to perform bundling in this dialect" );
206216}
207217
208- auto bundle (const sourcemeta::core:: JSON &schema, const SchemaWalker &walker,
218+ auto bundle (const JSON &schema, const SchemaWalker &walker,
209219 const SchemaResolver &resolver,
210220 const std::optional<std::string> &default_dialect,
211221 const std::optional<std::string> &default_id,
212222 const std::optional<Pointer> &default_container,
213- const SchemaFrame::Paths &paths) -> sourcemeta::core::JSON {
214- sourcemeta::core::JSON copy = schema;
223+ const SchemaFrame::Paths &paths, const BundleCallback &callback)
224+ -> JSON {
225+ JSON copy = schema;
215226 bundle (copy, walker, resolver, default_dialect, default_id, default_container,
216- paths);
227+ paths, callback );
217228 return copy;
218229}
219230
0 commit comments