Skip to content

Commit 3d93e71

Browse files
robbat2Codex
andcommitted
feat: URL resolving support for validate & metaschema
Closes: #234 Co-authored-by: Codex <[email protected]> Generated-with: OpenAI Codex CLI (partial) Signed-off-by: Robin H. Johnson <[email protected]>
1 parent 438a11b commit 3d93e71

24 files changed

+1500
-198
lines changed

src/command_fmt.cc

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,22 @@ auto sourcemeta::jsonschema::fmt(const sourcemeta::core::Options &options)
2121
std::vector<std::string> failed_files;
2222
const auto indentation{parse_indentation(options)};
2323
for (const auto &entry : for_each_json(options)) {
24+
const auto &path{entry.local_path_or_throw("fmt")};
2425
if (entry.yaml) {
2526
throw YAMLInputError{"This command does not support YAML input files yet",
26-
entry.first};
27+
path};
2728
}
2829

2930
if (options.contains("check")) {
30-
LOG_VERBOSE(options) << "Checking: " << entry.first.string() << "\n";
31+
LOG_VERBOSE(options) << "Checking: " << path.string() << "\n";
3132
} else {
32-
LOG_VERBOSE(options) << "Formatting: " << entry.first.string() << "\n";
33+
LOG_VERBOSE(options) << "Formatting: " << path.string() << "\n";
3334
}
3435

3536
try {
36-
const auto configuration_path{find_configuration(entry.first)};
37+
const auto configuration_path{find_configuration(path)};
3738
const auto &configuration{
38-
read_configuration(options, configuration_path, entry.first)};
39+
read_configuration(options, configuration_path, path)};
3940
const auto dialect{default_dialect(options, configuration)};
4041
const auto &custom_resolver{
4142
resolver(options, options.contains("http"), dialect, configuration)};
@@ -51,39 +52,37 @@ auto sourcemeta::jsonschema::fmt(const sourcemeta::core::Options &options)
5152
}
5253
expected << "\n";
5354

54-
std::ifstream current_stream{entry.first};
55+
std::ifstream current_stream{path};
5556
std::ostringstream current;
5657
current << current_stream.rdbuf();
5758

5859
if (options.contains("check")) {
5960
if (current.str() == expected.str()) {
60-
LOG_VERBOSE(options) << "ok: " << entry.first.string() << "\n";
61+
LOG_VERBOSE(options) << "ok: " << path.string() << "\n";
6162
} else if (output_json) {
62-
failed_files.push_back(entry.first.string());
63+
failed_files.push_back(path.string());
6364
result = false;
6465
} else {
65-
std::cerr << "fail: " << entry.first.string() << "\n";
66+
std::cerr << "fail: " << path.string() << "\n";
6667
result = false;
6768
}
6869
} else {
6970
if (current.str() != expected.str()) {
70-
std::ofstream output{entry.first};
71+
std::ofstream output{path};
7172
output << expected.str();
7273
}
7374
}
7475
} catch (const sourcemeta::core::SchemaRelativeMetaschemaResolutionError
7576
&error) {
7677
throw FileError<
77-
sourcemeta::core::SchemaRelativeMetaschemaResolutionError>(
78-
entry.first, error);
78+
sourcemeta::core::SchemaRelativeMetaschemaResolutionError>(path,
79+
error);
7980
} catch (const sourcemeta::core::SchemaResolutionError &error) {
80-
throw FileError<sourcemeta::core::SchemaResolutionError>(entry.first,
81-
error);
81+
throw FileError<sourcemeta::core::SchemaResolutionError>(path, error);
8282
} catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) {
83-
throw FileError<sourcemeta::core::SchemaUnknownBaseDialectError>(
84-
entry.first);
83+
throw FileError<sourcemeta::core::SchemaUnknownBaseDialectError>(path);
8584
} catch (const sourcemeta::core::SchemaError &error) {
86-
throw FileError<sourcemeta::core::SchemaError>(entry.first, error.what());
85+
throw FileError<sourcemeta::core::SchemaError>(path, error.what());
8786
}
8887
}
8988

src/command_lint.cc

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static auto get_lint_callback(sourcemeta::core::JSON &errors_array,
7272
if (output_json) {
7373
auto error_obj = sourcemeta::core::JSON::make_object();
7474

75-
error_obj.assign("path", sourcemeta::core::JSON{entry.first.string()});
75+
error_obj.assign("path", sourcemeta::core::JSON{entry.first});
7676
error_obj.assign("id", sourcemeta::core::JSON{name});
7777
error_obj.assign("message", sourcemeta::core::JSON{message});
7878
error_obj.assign("description",
@@ -88,7 +88,11 @@ static auto get_lint_callback(sourcemeta::core::JSON &errors_array,
8888

8989
errors_array.push_back(error_obj);
9090
} else {
91-
std::cout << std::filesystem::relative(entry.first).string();
91+
if (entry.path.has_value()) {
92+
std::cout << std::filesystem::relative(entry.path.value()).string();
93+
} else {
94+
std::cout << entry.first;
95+
}
9296
if (position.has_value()) {
9397
std::cout << ":";
9498
std::cout << std::get<0>(position.value());
@@ -184,18 +188,18 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
184188

185189
if (options.contains("fix")) {
186190
for (const auto &entry : for_each_json(options)) {
187-
const auto configuration_path{find_configuration(entry.first)};
191+
const auto &path{entry.local_path_or_throw("lint --fix")};
192+
const auto configuration_path{find_configuration(path)};
188193
const auto &configuration{
189-
read_configuration(options, configuration_path, entry.first)};
194+
read_configuration(options, configuration_path, path)};
190195
const auto dialect{default_dialect(options, configuration)};
191196

192197
const auto &custom_resolver{
193198
resolver(options, options.contains("http"), dialect, configuration)};
194-
LOG_VERBOSE(options) << "Linting: " << entry.first.string() << "\n";
199+
LOG_VERBOSE(options) << "Linting: " << entry.first << "\n";
195200
if (entry.yaml) {
196201
throw YAMLInputError{
197-
"The --fix option is not supported for YAML input files",
198-
entry.first};
202+
"The --fix option is not supported for YAML input files", path};
199203
}
200204

201205
auto copy = entry.second;
@@ -206,7 +210,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
206210
const auto apply_result = bundle.apply(
207211
copy, sourcemeta::core::schema_walker, custom_resolver,
208212
get_lint_callback(errors_array, entry, output_json), dialect,
209-
sourcemeta::core::URI::from_path(entry.first).recompose());
213+
entry.base_uri());
210214
scores.emplace_back(apply_result.second);
211215
if (!apply_result.first) {
212216
return 2;
@@ -216,20 +220,19 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
216220
} catch (
217221
const sourcemeta::core::SchemaTransformRuleProcessedTwiceError
218222
&error) {
219-
throw LintAutoFixError{error.what(), entry.first,
220-
error.location()};
223+
throw LintAutoFixError{error.what(), path, error.location()};
221224
} catch (
222225
const sourcemeta::core::SchemaBrokenReferenceError &error) {
223226
throw LintAutoFixError{
224227
"Could not autofix the schema without breaking its internal "
225228
"references",
226-
entry.first, error.location()};
229+
path, error.location()};
227230
} catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) {
228231
throw FileError<sourcemeta::core::SchemaUnknownBaseDialectError>(
229-
entry.first);
232+
path);
230233
} catch (const sourcemeta::core::SchemaResolutionError &error) {
231-
throw FileError<sourcemeta::core::SchemaResolutionError>(
232-
entry.first, error);
234+
throw FileError<sourcemeta::core::SchemaResolutionError>(path,
235+
error);
233236
}
234237
});
235238

@@ -239,7 +242,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
239242
}
240243

241244
if (copy != entry.second) {
242-
std::ofstream output{entry.first};
245+
std::ofstream output{path};
243246
sourcemeta::core::prettify(copy, output, indentation);
244247
output << "\n";
245248
}
@@ -250,13 +253,17 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
250253
}
251254
} else {
252255
for (const auto &entry : for_each_json(options)) {
253-
const auto configuration_path{find_configuration(entry.first)};
256+
const bool is_remote{!entry.path.has_value()};
257+
const auto configuration_path{find_configuration(
258+
is_remote ? std::filesystem::current_path() : entry.path.value())};
254259
const auto &configuration{
255-
read_configuration(options, configuration_path, entry.first)};
260+
is_remote ? read_configuration(options, configuration_path)
261+
: read_configuration(options, configuration_path,
262+
entry.path.value())};
256263
const auto dialect{default_dialect(options, configuration)};
257264
const auto &custom_resolver{
258265
resolver(options, options.contains("http"), dialect, configuration)};
259-
LOG_VERBOSE(options) << "Linting: " << entry.first.string() << "\n";
266+
LOG_VERBOSE(options) << "Linting: " << entry.first << "\n";
260267

261268
const auto wrapper_result =
262269
sourcemeta::jsonschema::try_catch(options, [&]() {
@@ -265,7 +272,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
265272
entry.second, sourcemeta::core::schema_walker,
266273
custom_resolver,
267274
get_lint_callback(errors_array, entry, output_json), dialect,
268-
sourcemeta::core::URI::from_path(entry.first).recompose());
275+
entry.base_uri());
269276
scores.emplace_back(subresult.second);
270277
if (subresult.first) {
271278
return EXIT_SUCCESS;
@@ -274,11 +281,20 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
274281
return 2;
275282
}
276283
} catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) {
277-
throw FileError<sourcemeta::core::SchemaUnknownBaseDialectError>(
278-
entry.first);
284+
if (entry.path.has_value()) {
285+
throw FileError<
286+
sourcemeta::core::SchemaUnknownBaseDialectError>(
287+
entry.path.value());
288+
}
289+
290+
throw;
279291
} catch (const sourcemeta::core::SchemaResolutionError &error) {
280-
throw FileError<sourcemeta::core::SchemaResolutionError>(
281-
entry.first, error);
292+
if (entry.path.has_value()) {
293+
throw FileError<sourcemeta::core::SchemaResolutionError>(
294+
entry.path.value(), error);
295+
}
296+
297+
throw;
282298
}
283299
});
284300

src/command_metaschema.cc

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77
#include <sourcemeta/blaze/evaluator.h>
88
#include <sourcemeta/blaze/output.h>
99

10-
#include <cassert> // assert
10+
#include <cassert> // assert
11+
#include <filesystem>
1112
#include <iostream> // std::cerr
1213
#include <map> // std::map
13-
#include <string> // std::string
14+
#include <optional>
15+
#include <sstream>
16+
#include <string> // std::string
17+
#include <string_view>
18+
#include <vector>
1419

1520
#include "command.h"
1621
#include "configuration.h"
@@ -29,31 +34,43 @@ auto sourcemeta::jsonschema::metaschema(
2934
sourcemeta::blaze::Evaluator evaluator;
3035

3136
std::map<std::string, sourcemeta::blaze::Template> cache;
37+
const auto current_path{std::filesystem::current_path()};
38+
const auto remote_configuration_path{find_configuration(current_path)};
39+
const auto &remote_configuration{
40+
read_configuration(options, remote_configuration_path)};
3241

33-
for (const auto &entry : for_each_json(options)) {
34-
if (!sourcemeta::core::is_schema(entry.second)) {
35-
throw NotSchemaError{entry.first};
36-
}
37-
38-
const auto configuration_path{find_configuration(entry.first)};
39-
const auto &configuration{
40-
read_configuration(options, configuration_path, entry.first)};
41-
const auto default_dialect_option{default_dialect(options, configuration)};
42+
const auto process_schema =
43+
[&](const sourcemeta::core::JSON &schema,
44+
const sourcemeta::core::PointerPositionTracker &positions,
45+
const std::optional<std::filesystem::path> &schema_path,
46+
const std::string_view schema_display,
47+
const sourcemeta::jsonschema::CustomResolver &custom_resolver,
48+
const std::string_view default_dialect_option) -> void {
49+
if (!sourcemeta::core::is_schema(schema)) {
50+
if (schema_path.has_value()) {
51+
throw NotSchemaError{schema_path.value()};
52+
}
4253

43-
const auto &custom_resolver{resolver(options, options.contains("http"),
44-
default_dialect_option,
45-
configuration)};
54+
throw RemoteSchemaNotSchemaError{std::string{schema_display}};
55+
}
4656

4757
try {
4858
const auto dialect{
49-
sourcemeta::core::dialect(entry.second, default_dialect_option)};
59+
sourcemeta::core::dialect(schema, default_dialect_option)};
5060
if (dialect.empty()) {
51-
throw FileError<sourcemeta::core::SchemaUnknownBaseDialectError>(
52-
entry.first);
61+
if (schema_path.has_value()) {
62+
throw FileError<sourcemeta::core::SchemaUnknownBaseDialectError>(
63+
schema_path.value());
64+
}
65+
66+
std::ostringstream error;
67+
error << "Could not resolve the metaschema of the schema\n at uri "
68+
<< schema_display;
69+
throw std::runtime_error(error.str());
5370
}
5471

5572
const auto metaschema{sourcemeta::core::metaschema(
56-
entry.second, custom_resolver, default_dialect_option)};
73+
schema, custom_resolver, default_dialect_option)};
5774
const sourcemeta::core::JSON bundled{
5875
sourcemeta::core::bundle(metaschema, sourcemeta::core::schema_walker,
5976
custom_resolver, default_dialect_option)};
@@ -74,16 +91,16 @@ auto sourcemeta::jsonschema::metaschema(
7491
sourcemeta::blaze::TraceOutput output{
7592
sourcemeta::core::schema_walker, custom_resolver,
7693
sourcemeta::core::empty_weak_pointer, frame};
77-
result = evaluator.validate(cache.at(std::string{dialect}),
78-
entry.second, std::ref(output));
79-
print(output, entry.positions, std::cout);
94+
result = evaluator.validate(cache.at(std::string{dialect}), schema,
95+
std::ref(output));
96+
print(output, positions, std::cout);
8097
} else if (json_output) {
8198
// Otherwise its impossible to correlate the output
8299
// when validating i.e. a directory of schemas
83-
std::cerr << entry.first.string() << "\n";
100+
std::cerr << schema_display << "\n";
84101
const auto output{sourcemeta::blaze::standard(
85-
evaluator, cache.at(std::string{dialect}), entry.second,
86-
sourcemeta::blaze::StandardOutput::Basic, entry.positions)};
102+
evaluator, cache.at(std::string{dialect}), schema,
103+
sourcemeta::blaze::StandardOutput::Basic, positions)};
87104
assert(output.is_object());
88105
assert(output.defines("valid"));
89106
assert(output.at("valid").is_boolean());
@@ -94,29 +111,56 @@ auto sourcemeta::jsonschema::metaschema(
94111
sourcemeta::core::prettify(output, std::cout);
95112
std::cout << "\n";
96113
} else {
97-
sourcemeta::blaze::SimpleOutput output{entry.second};
98-
if (evaluator.validate(cache.at(std::string{dialect}), entry.second,
114+
sourcemeta::blaze::SimpleOutput output{schema};
115+
if (evaluator.validate(cache.at(std::string{dialect}), schema,
99116
std::ref(output))) {
100117
LOG_VERBOSE(options)
101-
<< "ok: "
102-
<< sourcemeta::core::weakly_canonical(entry.first).string()
103-
<< "\n matches " << dialect << "\n";
118+
<< "ok: " << schema_display << "\n matches " << dialect << "\n";
104119
} else {
105-
std::cerr << "fail: "
106-
<< sourcemeta::core::weakly_canonical(entry.first).string()
107-
<< "\n";
108-
print(output, entry.positions, std::cerr);
120+
std::cerr << "fail: " << schema_display << "\n";
121+
print(output, positions, std::cerr);
109122
result = false;
110123
}
111124
}
112125
} catch (const sourcemeta::core::SchemaRelativeMetaschemaResolutionError
113126
&error) {
114-
throw FileError<
115-
sourcemeta::core::SchemaRelativeMetaschemaResolutionError>(
116-
entry.first, error);
127+
if (schema_path.has_value()) {
128+
throw FileError<
129+
sourcemeta::core::SchemaRelativeMetaschemaResolutionError>(
130+
schema_path.value(), error);
131+
}
132+
133+
throw;
117134
} catch (const sourcemeta::core::SchemaResolutionError &error) {
118-
throw FileError<sourcemeta::core::SchemaResolutionError>(entry.first,
119-
error);
135+
if (schema_path.has_value()) {
136+
throw FileError<sourcemeta::core::SchemaResolutionError>(
137+
schema_path.value(), error);
138+
}
139+
140+
throw;
141+
}
142+
};
143+
144+
for (const auto &entry : for_each_json(options)) {
145+
if (entry.path.has_value()) {
146+
const auto configuration_path{find_configuration(entry.path.value())};
147+
const auto &configuration{
148+
read_configuration(options, configuration_path, entry.path.value())};
149+
const auto default_dialect_option{
150+
default_dialect(options, configuration)};
151+
const auto &custom_resolver{resolver(options, options.contains("http"),
152+
default_dialect_option,
153+
configuration)};
154+
process_schema(entry.second, entry.positions, entry.path, entry.first,
155+
custom_resolver, default_dialect_option);
156+
} else {
157+
const auto remote_default_dialect_option{
158+
default_dialect(options, remote_configuration)};
159+
const auto &remote_resolver{resolver(options, options.contains("http"),
160+
remote_default_dialect_option,
161+
remote_configuration)};
162+
process_schema(entry.second, entry.positions, std::nullopt, entry.first,
163+
remote_resolver, remote_default_dialect_option);
120164
}
121165
}
122166

0 commit comments

Comments
 (0)