Skip to content

Commit

Permalink
Support bundling schemas with ?bundle=1
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti committed Jan 9, 2025
1 parent b40819d commit 0c1798c
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 19 deletions.
2 changes: 1 addition & 1 deletion DEPENDENCIES
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4
noa https://github.com/sourcemeta/noa 924f5cc8549af7f12227869dcbab4259029ac650
jsontoolkit https://github.com/sourcemeta/jsontoolkit 2ff048e77ed2fc22f25dfd1414358e7be28afb88
jsontoolkit https://github.com/sourcemeta/jsontoolkit 16adce4244a8f21e96b2081bd5f0fd23531cd1c2
blaze https://github.com/sourcemeta/blaze bde17029b8fc8daf38a496d4d27354a4d24b3530
hydra https://github.com/sourcemeta/hydra c31b5b612beb3e6a870deb2581ae437fc5183f47
bootstrap https://github.com/twbs/bootstrap v5.3.3
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ lint:
test:
./test/cli/common/index/invalid-configuration.sh $(PREFIX)/bin/sourcemeta-registry-index
./test/cli/common/index/invalid-schema.sh $(PREFIX)/bin/sourcemeta-registry-index
./test/cli/common/index/external-reference.sh $(PREFIX)/bin/sourcemeta-registry-index
./test/cli/common/index/url-base-trailing-slash.sh $(PREFIX)/bin/sourcemeta-registry-index
ifeq ($(ENTERPRISE), ON)
./test/cli/ee/index/no-options.sh $(PREFIX)/bin/sourcemeta-registry-index
Expand Down
3 changes: 1 addition & 2 deletions src/enterprise/enterprise_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,7 @@ auto generate_toc(
auto attach(const sourcemeta::jsontoolkit::FlatFileSchemaResolver &resolver,
const sourcemeta::jsontoolkit::URI &server_url,
const sourcemeta::jsontoolkit::JSON &configuration,
const std::filesystem::path &, const std::filesystem::path &output)
-> int {
const std::filesystem::path &output) -> int {
std::cerr << "-- Indexing directory: " << output.string() << "\n";
const auto base{std::filesystem::canonical(output / "schemas")};
std::vector<sourcemeta::jsontoolkit::JSON> search_index;
Expand Down
24 changes: 20 additions & 4 deletions src/index/index.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ static auto index(sourcemeta::jsontoolkit::FlatFileSchemaResolver &resolver,
const auto schema_output{std::filesystem::weakly_canonical(
output / "schemas" / schema_uri.recompose())};
std::cerr << "Schema output: " << schema_output.string() << "\n";
std::filesystem::create_directories(schema_output.parent_path());
std::ofstream stream{schema_output};
const auto result{resolver(schema.first)};
if (!result.has_value()) {
std::cout << "Cannot resolve the schema with identifier " << schema.first
Expand Down Expand Up @@ -189,9 +187,25 @@ static auto index(sourcemeta::jsontoolkit::FlatFileSchemaResolver &resolver,
return EXIT_FAILURE;
}

std::filesystem::create_directories(schema_output.parent_path());
std::ofstream stream{schema_output};
sourcemeta::jsontoolkit::prettify(
result.value(), stream, sourcemeta::jsontoolkit::schema_format_compare);
stream << "\n";

auto bundle_path{
output / "bundles" /
std::filesystem::relative(schema_output, output / "schemas")};
std::filesystem::create_directories(bundle_path.parent_path());
std::cerr << "Bundling: " << schema.first << "\n";
const auto bundled_schema{sourcemeta::jsontoolkit::bundle(
result.value(), sourcemeta::jsontoolkit::default_schema_walker,
resolver)};
std::ofstream bundle_stream{bundle_path};
sourcemeta::jsontoolkit::prettify(
bundled_schema, bundle_stream,
sourcemeta::jsontoolkit::schema_format_compare);
bundle_stream << "\n";
}

return EXIT_SUCCESS;
Expand Down Expand Up @@ -278,8 +292,7 @@ static auto index_main(const std::string_view &program,
#ifdef SOURCEMETA_REGISTRY_ENTERPRISE
if (code == EXIT_SUCCESS) {
return sourcemeta::registry::enterprise::attach(
resolver, server_url, configuration_with_defaults,
configuration_path.parent_path(), output);
resolver, server_url, configuration_with_defaults, output);
}
#endif

Expand All @@ -292,6 +305,9 @@ auto main(int argc, char *argv[]) noexcept -> int {
const std::vector<std::string> arguments{argv + std::min(1, argc),
argv + argc};
return index_main(program, arguments);
} catch (const sourcemeta::jsontoolkit::SchemaResolutionError &error) {
std::cerr << "error: " << error.what() << "\n at " << error.id() << "\n";
return EXIT_FAILURE;
} catch (const std::exception &error) {
std::cerr << "unexpected error: " << error.what() << "\n";
return EXIT_FAILURE;
Expand Down
14 changes: 9 additions & 5 deletions src/server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ static auto request_path_to_schema_uri(const std::string &server_base_url,
return schema_identifier.str();
}

static auto resolver(std::string_view identifier)
static auto resolver(std::string_view identifier, const bool bundle)
-> std::optional<sourcemeta::jsontoolkit::JSON> {
static const auto SERVER_BASE_URL{
sourcemeta::jsontoolkit::URI{configuration().at("url").to_string()}
Expand All @@ -76,7 +76,8 @@ static auto resolver(std::string_view identifier)

assert(uri.path().has_value());
const auto schema_path{
path_join(*(__global_data) / "schemas", uri.path().value())};
bundle ? path_join(*(__global_data) / "bundles", uri.path().value())
: path_join(*(__global_data) / "schemas", uri.path().value())};
if (!std::filesystem::exists(schema_path)) {
return std::nullopt;
}
Expand Down Expand Up @@ -198,7 +199,8 @@ static auto on_request(const sourcemeta::hydra::http::ServerLogger &logger,

const auto schema_identifier{request_path_to_schema_uri(
configuration().at("url").to_string(), request_path)};
const auto maybe_schema{resolver(schema_identifier)};
const auto maybe_schema{
resolver(schema_identifier, request.query("bundle").has_value())};
if (!maybe_schema.has_value()) {
json_error(logger, request, response,
sourcemeta::hydra::http::Status::NOT_FOUND, "not-found",
Expand Down Expand Up @@ -258,8 +260,10 @@ static auto on_otherwise(const sourcemeta::hydra::http::ServerLogger &logger,
}
#endif

const auto maybe_schema{resolver(request_path_to_schema_uri(
configuration().at("url").to_string(), request.path()))};
const auto maybe_schema{
resolver(request_path_to_schema_uri(configuration().at("url").to_string(),
request.path()),
false)};

if (maybe_schema.has_value()) {
json_error(logger, request, response,
Expand Down
51 changes: 51 additions & 0 deletions test/cli/common/index/external-reference.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << EOF > "$TMP/configuration.json"
{
"url": "https://sourcemeta.com/",
"port": 8000,
"schemas": {
"example/schemas": {
"base": "https://example.com/",
"path": "./schemas"
}
}
}
EOF

mkdir "$TMP/schemas"

cat << 'EOF' > "$TMP/schemas/test.json"
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/test.json",
"allOf": [ { "$ref": "https://sourcemeta.com/external" } ]
}
EOF

"$1" "$TMP/configuration.json" "$TMP/output" 2> "$TMP/output.txt" && CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
Using configuration: $(realpath "$TMP")/configuration.json
Writing output to: $(realpath "$TMP")/output
Discovering schemas at: $(realpath "$TMP")/schemas
-- Found schema: $(realpath "$TMP")/schemas/test.json
https://example.com/test.json => https://sourcemeta.com/example/schemas/test.json
-- Processing schema: https://sourcemeta.com/example/schemas/test.json
Schema output: $(realpath "$TMP")/output/schemas/example/schemas/test.json
Compiling metaschema: http://json-schema.org/draft-07/schema#
Validating against its metaschema: https://sourcemeta.com/example/schemas/test.json
Bundling: https://sourcemeta.com/example/schemas/test.json
error: Could not resolve the requested schema
at https://sourcemeta.com/external
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"
107 changes: 107 additions & 0 deletions test/e2e/common/bundle.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
GET {{base}}/example/bundling/single.json?bundle=1
HTTP 200
Content-Type: application/schema+json
Link: <http://json-schema.org/draft-07/schema\#>; rel="describedby"
[Asserts]
header "ETag" exists
header "X-Request-id" exists

GET {{base}}/example/bundling/single.json?bundle=1
HTTP 200
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "{{base}}/example/bundling/single.json",
"title": "Bundling",
"description": "A bundling example",
"properties": {
"foo": {
"$ref": "http://localhost:8000/example/v2.0/schema.json"
}
},
"definitions": {
"http://localhost:8000/example/v2.0/schema.json": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://localhost:8000/example/v2.0/schema.json",
"type": "integer"
}
}
}

GET {{base}}/example/bundling/single.json?bundle=0
HTTP 200
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "{{base}}/example/bundling/single.json",
"title": "Bundling",
"description": "A bundling example",
"properties": {
"foo": {
"$ref": "http://localhost:8000/example/v2.0/schema.json"
}
},
"definitions": {
"http://localhost:8000/example/v2.0/schema.json": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://localhost:8000/example/v2.0/schema.json",
"type": "integer"
}
}
}

GET {{base}}/example/bundling/single.json?bundle=foo
HTTP 200
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "{{base}}/example/bundling/single.json",
"title": "Bundling",
"description": "A bundling example",
"properties": {
"foo": {
"$ref": "http://localhost:8000/example/v2.0/schema.json"
}
},
"definitions": {
"http://localhost:8000/example/v2.0/schema.json": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://localhost:8000/example/v2.0/schema.json",
"type": "integer"
}
}
}

GET {{base}}/example/v2.0/schema.json?bundle=1
HTTP 200
Content-Type: application/schema+json
Link: <http://json-schema.org/draft-07/schema\#>; rel="describedby"
[Asserts]
header "ETag" exists
header "X-Request-id" exists

GET {{base}}/example/v2.0/schema.json?bundle=1
HTTP 200
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "{{base}}/example/v2.0/schema.json",
"type": "integer"
}

GET {{base}}/example/bundling/single.json?foo=2&bundle=1
HTTP 200
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "{{base}}/example/bundling/single.json",
"title": "Bundling",
"description": "A bundling example",
"properties": {
"foo": {
"$ref": "http://localhost:8000/example/v2.0/schema.json"
}
},
"definitions": {
"http://localhost:8000/example/v2.0/schema.json": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://localhost:8000/example/v2.0/schema.json",
"type": "integer"
}
}
}
18 changes: 18 additions & 0 deletions test/sandbox/manifest-ce.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
./bundles
./bundles/doc
./bundles/doc/string-1.json
./bundles/example
./bundles/example/bundling
./bundles/example/bundling/single.json
./bundles/example/extension
./bundles/example/extension/with.schema.json
./bundles/example/schemas
./bundles/example/schemas/absolute-refs.json
./bundles/example/schemas/camelcase.json
./bundles/example/schemas/no-id-draft7-ref.json
./bundles/example/schemas/no-id.json
./bundles/example/schemas/no-schema-nor-id.json
./bundles/example/schemas/no-schema.json
./bundles/example/schemas/string.json
./bundles/example/v2.0
./bundles/example/v2.0/schema.json
./configuration.json
./schemas
./schemas/doc
Expand Down
18 changes: 18 additions & 0 deletions test/sandbox/manifest-ee.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
./bundles
./bundles/doc
./bundles/doc/string-1.json
./bundles/example
./bundles/example/bundling
./bundles/example/bundling/single.json
./bundles/example/extension
./bundles/example/extension/with.schema.json
./bundles/example/schemas
./bundles/example/schemas/absolute-refs.json
./bundles/example/schemas/camelcase.json
./bundles/example/schemas/no-id-draft7-ref.json
./bundles/example/schemas/no-id.json
./bundles/example/schemas/no-schema-nor-id.json
./bundles/example/schemas/no-schema.json
./bundles/example/schemas/string.json
./bundles/example/v2.0
./bundles/example/v2.0/schema.json
./configuration.json
./generated
./generated/404.html
Expand Down
30 changes: 23 additions & 7 deletions vendor/jsontoolkit/src/jsonschema/resolver.cc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0c1798c

Please sign in to comment.