From aa6e445a2c42ba193852b112c1dd2f0b0707e0d6 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Wed, 22 Apr 2026 11:24:16 -0400 Subject: [PATCH] Extend `src/documentation` to Draft 4 Signed-off-by: Juan Cruz Viotti --- src/documentation/documentation.cc | 47 +- test/documentation/CMakeLists.txt | 1 + .../documentation_draft4_test.cc | 989 ++++++++++++++++++ 3 files changed, 1031 insertions(+), 6 deletions(-) create mode 100644 test/documentation/documentation_draft4_test.cc diff --git a/src/documentation/documentation.cc b/src/documentation/documentation.cc index c6674e00c..c2fc426c2 100644 --- a/src/documentation/documentation.cc +++ b/src/documentation/documentation.cc @@ -150,6 +150,20 @@ auto type_expression_of(const sourcemeta::core::JSON &schema, type_expression_of(schema.at("unevaluatedItems"), frame, root, visited, ref_chain)); } + } else if (schema.defines("items") && schema.at("items").is_array()) { + result.assign("kind", sourcemeta::core::JSON{"tuple"}); + auto items{sourcemeta::core::JSON::make_array()}; + for (const auto &item : schema.at("items").as_array()) { + items.push_back( + type_expression_of(item, frame, root, visited, ref_chain)); + } + result.assign("items", std::move(items)); + if (schema.defines("additionalItems") && + schema.at("additionalItems").is_object()) { + result.assign("additional", + type_expression_of(schema.at("additionalItems"), frame, + root, visited, ref_chain)); + } } else { result.assign("kind", sourcemeta::core::JSON{"array"}); if (schema.defines("items") && schema.at("items").is_object()) { @@ -274,12 +288,18 @@ auto constraints_of(const sourcemeta::core::JSON &schema) } if (schema.defines("minimum") && schema.at("minimum").is_number()) { + const auto exclusive{schema.defines("exclusiveMinimum") && + schema.at("exclusiveMinimum").is_boolean() && + schema.at("exclusiveMinimum").to_boolean()}; constraints.push_back(sourcemeta::core::JSON{ - ">= " + format_json_number(schema.at("minimum"))}); + (exclusive ? "> " : ">= ") + format_json_number(schema.at("minimum"))}); } if (schema.defines("maximum") && schema.at("maximum").is_number()) { + const auto exclusive{schema.defines("exclusiveMaximum") && + schema.at("exclusiveMaximum").is_boolean() && + schema.at("exclusiveMaximum").to_boolean()}; constraints.push_back(sourcemeta::core::JSON{ - "<= " + format_json_number(schema.at("maximum"))}); + (exclusive ? "< " : "<= ") + format_json_number(schema.at("maximum"))}); } if (schema.defines("exclusiveMinimum") && schema.at("exclusiveMinimum").is_number()) { @@ -774,11 +794,19 @@ auto walk_prefix_items(const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &root, VisitedSchemas &visited, std::size_t &next_identifier) -> void { - if (!schema.is_object() || !schema.defines("prefixItems") || - !schema.at("prefixItems").is_array()) { + const auto has_prefix_items{schema.is_object() && + schema.defines("prefixItems") && + schema.at("prefixItems").is_array()}; + const auto has_draft4_tuple{!has_prefix_items && schema.is_object() && + schema.defines("items") && + schema.at("items").is_array()}; + if (!has_prefix_items && !has_draft4_tuple) { return; } + const auto &tuple_items{has_prefix_items ? schema.at("prefixItems") + : schema.at("items")}; + std::size_t min_items{0}; if (schema.defines("minItems") && schema.at("minItems").is_integer() && schema.at("minItems").to_integer() > 0) { @@ -786,7 +814,7 @@ auto walk_prefix_items(const sourcemeta::core::JSON &schema, } std::size_t index{0}; - for (const auto &item : schema.at("prefixItems").as_array()) { + for (const auto &item : tuple_items.as_array()) { if (is_complex_schema(item)) { auto section_children{sourcemeta::core::JSON::make_array()}; section_children.push_back( @@ -847,11 +875,18 @@ auto walk_prefix_items(const sourcemeta::core::JSON &schema, ++index; } - if (schema.defines("items") && schema.at("items").is_object()) { + if (has_prefix_items && schema.defines("items") && + schema.at("items").is_object()) { auto path{base_path}; path.push_back(make_path_segment("wildcard", "*")); emit_row(schema.at("items"), std::move(path), rows, frame, root, visited, next_identifier); + } else if (has_draft4_tuple && schema.defines("additionalItems") && + schema.at("additionalItems").is_object()) { + auto path{base_path}; + path.push_back(make_path_segment("wildcard", "*")); + emit_row(schema.at("additionalItems"), std::move(path), rows, frame, root, + visited, next_identifier); } } diff --git a/test/documentation/CMakeLists.txt b/test/documentation/CMakeLists.txt index 26e31fb27..fd16ef33e 100644 --- a/test/documentation/CMakeLists.txt +++ b/test/documentation/CMakeLists.txt @@ -3,6 +3,7 @@ sourcemeta_googletest(NAMESPACE sourcemeta PROJECT blaze NAME documentation SOURCES documentation_test_utils.h documentation_2020_12_test.cc + documentation_draft4_test.cc documentation_html_test.cc) target_link_libraries(sourcemeta_blaze_documentation_unit diff --git a/test/documentation/documentation_draft4_test.cc b/test/documentation/documentation_draft4_test.cc new file mode 100644 index 000000000..b6d1c3477 --- /dev/null +++ b/test/documentation/documentation_draft4_test.cc @@ -0,0 +1,989 @@ +#include + +#include +#include +#include + +#include +#include + +#include // std::filesystem::path +#include // std::unique_ptr + +#include "documentation_test_utils.h" + +class DocumentationDraft4Test : public testing::Test { +protected: + static auto SetUpTestSuite() -> void { + const auto meta_schema = sourcemeta::core::read_json( + std::filesystem::path{SCHEMAS_PATH} / "documentation.json"); + compiled_schema_ = std::make_unique( + sourcemeta::blaze::compile(meta_schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, + sourcemeta::blaze::default_schema_compiler)); + } + + static std::unique_ptr compiled_schema_; +}; + +std::unique_ptr + DocumentationDraft4Test::compiled_schema_ = nullptr; + +TEST_F(DocumentationDraft4Test, type_string) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "string" } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, type_integer) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "integer" } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, type_number) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "number" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "number" } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, type_boolean) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "boolean" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "enum", "values": [ false, true ] } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, type_null) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "null" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "enum", "values": [ null ] } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, string_min_max_length) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string", + "minLength": 2, + "maxLength": 10 + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "string" }, + "constraints": [ ">= 2 chars", "<= 10 chars" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, string_pattern) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string", + "pattern": "^[a-z]+$" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "string" }, + "constraints": [ "pattern: ^[a-z]+$" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, string_format) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string", + "format": "email" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "string" }, + "badges": [ { "kind": "format", "value": "email" } ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, number_minimum_maximum) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "number", + "minimum": 0, + "maximum": 100 + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "number" }, + "constraints": [ ">= 0", "<= 100" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, number_multiple_of) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "number", + "multipleOf": 5 + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "number" }, + "constraints": [ "multiple of 5" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, number_exclusive_minimum) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "number" }, + "constraints": [ "> 0" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, number_exclusive_maximum) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "number", + "maximum": 100, + "exclusiveMaximum": true + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "number" }, + "constraints": [ "< 100" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, number_both_exclusive) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "number", + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 100, + "exclusiveMaximum": true + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "number" }, + "constraints": [ "> 0", "< 100" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, integer_minimum) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "minimum": 1 + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "integer" }, + "constraints": [ ">= 1" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, object_properties) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "integer" } + } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" } + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "name" } ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + }, + { + "identifier": 3, + "path": [ { "type": "literal", "value": "age" } ], + "type": { "kind": "primitive", "name": "integer" }, + "required": false + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, object_required) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": [ "name" ] + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" }, + "constraints": [ ">= 1 properties" ] + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "name" } ], + "type": { "kind": "primitive", "name": "string" }, + "required": true + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, object_additional_properties_typed) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "additionalProperties": { "type": "integer" } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" } + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "name" } ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + }, + { + "identifier": 3, + "path": [ { "type": "wildcard", "value": "*" } ], + "type": { "kind": "primitive", "name": "integer" } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, object_pattern_properties) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "patternProperties": { + "^S_": { "type": "string" } + } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" } + }, + { + "identifier": 2, + "path": [ { "type": "pattern", "value": "^S_" } ], + "type": { "kind": "primitive", "name": "string" } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, object_min_max_properties) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "minProperties": 1, + "maxProperties": 5 + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" }, + "constraints": [ ">= 1 properties", "<= 5 properties" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, array_items_string) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { "type": "string" } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "array", "items": { "kind": "primitive", "name": "string" } } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, array_min_max_items) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "maxItems": 10 + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "array", "items": { "kind": "primitive", "name": "string" } }, + "constraints": [ ">= 1 items", "<= 10 items" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, array_unique_items) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { "type": "integer" }, + "uniqueItems": true + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "array", "items": { "kind": "primitive", "name": "integer" } }, + "constraints": [ "unique" ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, tuple_items_array) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": [ + { "type": "string" }, + { "type": "integer" } + ] + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { + "kind": "tuple", + "items": [ + { "kind": "primitive", "name": "string" }, + { "kind": "primitive", "name": "integer" } + ] + } + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "0" } ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + }, + { + "identifier": 3, + "path": [ { "type": "literal", "value": "1" } ], + "type": { "kind": "primitive", "name": "integer" }, + "required": false + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, tuple_with_additional_items) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": [ + { "type": "string" }, + { "type": "integer" } + ], + "additionalItems": { "type": "boolean" } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { + "kind": "tuple", + "items": [ + { "kind": "primitive", "name": "string" }, + { "kind": "primitive", "name": "integer" } + ], + "additional": { "kind": "enum", "values": [ false, true ] } + } + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "0" } ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + }, + { + "identifier": 3, + "path": [ { "type": "literal", "value": "1" } ], + "type": { "kind": "primitive", "name": "integer" }, + "required": false + }, + { + "identifier": 4, + "path": [ { "type": "wildcard", "value": "*" } ], + "type": { "kind": "enum", "values": [ false, true ] } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, array_of_objects_with_properties) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" } + } + } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "array", "items": { "kind": "object" } } + }, + { + "identifier": 2, + "path": [ { "type": "wildcard", "value": "*" } ], + "type": { "kind": "object" } + }, + { + "identifier": 3, + "path": [ + { "type": "wildcard", "value": "*" }, + { "type": "literal", "value": "name" } + ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, enum_values) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "enum": [ "red", "green", "blue" ] + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "enum", "values": [ "red", "green", "blue" ] } + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, title_and_description) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string", + "title": "Name", + "description": "The full name" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "string" }, + "title": "Name", + "description": "The full name" + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, default_value) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string", + "default": "unknown" + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "string" }, + "default": "unknown" + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, ref_to_definition) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { "$ref": "#/definitions/string_type" } + }, + "definitions": { + "string_type": { "type": "string" } + } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" } + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "name" } ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, ref_recursive) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://example.com/tree", + "type": "object", + "properties": { + "value": { "type": "string" }, + "child": { "$ref": "#" } + } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" } + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "value" } ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + }, + { + "identifier": 3, + "path": [ { "type": "literal", "value": "child" } ], + "type": { "kind": "recursiveRef", "identifier": 1 }, + "required": false + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, anyof) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "anyOf": [ + { "type": "string" }, + { "type": "integer" } + ] + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "any" } + } + ], + "children": [ + { + "label": "Any of", + "children": [ + { + "identifier": 2, + "rows": [ + { + "identifier": 3, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "string" } + } + ] + }, + { + "identifier": 4, + "rows": [ + { + "identifier": 5, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "primitive", "name": "integer" } + } + ] + } + ] + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +} + +TEST_F(DocumentationDraft4Test, nested_object_in_property) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "address": { + "type": "object", + "properties": { + "street": { "type": "string" }, + "city": { "type": "string" } + } + } + } + })JSON")}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "identifier": 0, + "rows": [ + { + "identifier": 1, + "path": [ { "type": "synthetic", "value": "root" } ], + "type": { "kind": "object" } + }, + { + "identifier": 2, + "path": [ { "type": "literal", "value": "address" } ], + "type": { "kind": "object" }, + "required": false + }, + { + "identifier": 3, + "path": [ + { "type": "literal", "value": "address" }, + { "type": "literal", "value": "street" } + ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + }, + { + "identifier": 4, + "path": [ + { "type": "literal", "value": "address" }, + { "type": "literal", "value": "city" } + ], + "type": { "kind": "primitive", "name": "string" }, + "required": false + } + ] + })JSON")}; + + EXPECT_DOCUMENTATION(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, *compiled_schema_, + expected); +}