diff --git a/src/core/jsonschema/known_resolver.cmake b/src/core/jsonschema/known_resolver.cmake index 991dda43f..4b1197b6c 100644 --- a/src/core/jsonschema/known_resolver.cmake +++ b/src/core/jsonschema/known_resolver.cmake @@ -91,6 +91,11 @@ set(OPENAPI_3_1_DIR "${PROJECT_SOURCE_DIR}/vendor/openapi/oas/3.1") file(READ "${OPENAPI_3_1_DIR}/meta/base" OPENAPI_OAS_3_1_META_BASE) file(READ "${OPENAPI_3_1_DIR}/dialect/base" OPENAPI_OAS_3_1_DIALECT_BASE) +# OpenAPI v3.0 +set(OPENAPI_3_0_DIR "${PROJECT_SOURCE_DIR}/vendor/openapi/oas/3.0") +file(READ "${OPENAPI_3_0_DIR}/schema/2024-10-18" OPENAPI_OAS_3_0_SCHEMA_2024_10_18) +file(READ "${OPENAPI_3_0_DIR}/schema/2021-09-28" OPENAPI_OAS_3_0_SCHEMA_2021_09_28) + if(NOT KNOWN_RESOLVER_INPUT) message(FATAL_ERROR "Missing KNOWN_RESOLVER_INPUT") endif() diff --git a/src/core/jsonschema/known_resolver.in.cc b/src/core/jsonschema/known_resolver.in.cc index e4af4a56e..fa2096c64 100644 --- a/src/core/jsonschema/known_resolver.in.cc +++ b/src/core/jsonschema/known_resolver.in.cc @@ -69,6 +69,9 @@ enum class KnownSchema : std::uint8_t { OAS_3_1_DIALECT_BASE, OAS_3_1_META_BASE, + OAS_3_0_DIALECT_2024_10_18, + OAS_3_0_DIALECT_2021_09_28, + UNKNOWN }; @@ -419,6 +422,19 @@ static auto parse_identifier(const std::string_view identifier) -> KnownSchema { return KnownSchema::OAS_3_1_META_BASE; } + // OpenAPI v3.0 + // + // The OpenAPI Initiative never assigned a dialect identifier to the v3.0 + // Schema Object, unlike v3.1 and later, so we mint one per published release + // of the document schema that embeds it, as described in RFC 4151 + if (identifier == "tag:spec.openapis.org,2024-10-18:oas/3.0/dialect") { + return KnownSchema::OAS_3_0_DIALECT_2024_10_18; + } + + if (identifier == "tag:spec.openapis.org,2021-09-28:oas/3.0/dialect") { + return KnownSchema::OAS_3_0_DIALECT_2021_09_28; + } + return KnownSchema::UNKNOWN; } @@ -700,6 +716,34 @@ auto sourcemeta::core::schema_resolver(const std::string_view identifier) sourcemeta::core::parse_json(R"EOF(@OPENAPI_OAS_3_1_META_BASE@)EOF")}; return SCHEMA; } + // The document schema is embedded rather than registered on its own, so + // that the dialect identifier is the only thing this resolver exposes. Its + // inner identifier must be kept for the references it makes to itself to + // keep resolving against its own scope + case KnownSchema::OAS_3_0_DIALECT_2024_10_18: { + static const auto SCHEMA{sourcemeta::core::parse_json(R"EOF({ + "id": "tag:spec.openapis.org,2024-10-18:oas/3.0/dialect", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "The OpenAPI v3.0.x Schema Object dialect", + "allOf": [ { "$ref": "#/definitions/openapi/definitions/Schema" } ], + "definitions": { + "openapi": @OPENAPI_OAS_3_0_SCHEMA_2024_10_18@ + } +})EOF")}; + return SCHEMA; + } + case KnownSchema::OAS_3_0_DIALECT_2021_09_28: { + static const auto SCHEMA{sourcemeta::core::parse_json(R"EOF({ + "id": "tag:spec.openapis.org,2021-09-28:oas/3.0/dialect", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "The OpenAPI v3.0.x Schema Object dialect", + "allOf": [ { "$ref": "#/definitions/openapi/definitions/Schema" } ], + "definitions": { + "openapi": @OPENAPI_OAS_3_0_SCHEMA_2021_09_28@ + } +})EOF")}; + return SCHEMA; + } case KnownSchema::UNKNOWN: return std::nullopt; } @@ -771,6 +815,8 @@ auto sourcemeta::core::schema_is_official( case KnownSchema::OAS_3_2_META_2025_09_17: case KnownSchema::OAS_3_1_DIALECT_BASE: case KnownSchema::OAS_3_1_META_BASE: + case KnownSchema::OAS_3_0_DIALECT_2024_10_18: + case KnownSchema::OAS_3_0_DIALECT_2021_09_28: case KnownSchema::UNKNOWN: return false; } diff --git a/test/jsonschema/jsonschema_resolver_test.cc b/test/jsonschema/jsonschema_resolver_test.cc index 1cab48a8c..1bfc3af57 100644 --- a/test/jsonschema/jsonschema_resolver_test.cc +++ b/test/jsonschema/jsonschema_resolver_test.cc @@ -423,6 +423,31 @@ TEST(openapi_3_2) { EXPECT_SCHEMA("https://spec.openapis.org/oas/3.2/meta/2025-09-17"); } +TEST(openapi_3_0) { + EXPECT_SCHEMA("tag:spec.openapis.org,2024-10-18:oas/3.0/dialect"); + EXPECT_SCHEMA("tag:spec.openapis.org,2021-09-28:oas/3.0/dialect"); +} + +TEST(openapi_3_0_document_schema_is_encapsulated) { + EXPECT_FALSE(sourcemeta::core::schema_resolver( + "https://spec.openapis.org/oas/3.0/schema/2024-10-18") + .has_value()); + EXPECT_FALSE(sourcemeta::core::schema_resolver( + "https://spec.openapis.org/oas/3.0/schema/2021-09-28") + .has_value()); +} + +TEST(openapi_3_0_dialect_points_to_the_schema_object) { + const auto result{sourcemeta::core::schema_resolver( + "tag:spec.openapis.org,2024-10-18:oas/3.0/dialect")}; + EXPECT_TRUE(result.has_value()); + const sourcemeta::core::JSON &document{result.value()}; + EXPECT_EQ(document.at("allOf").at(0).at("$ref").to_string(), + "#/definitions/openapi/definitions/Schema"); + EXPECT_EQ(document.at("definitions").at("openapi").at("id").to_string(), + "https://spec.openapis.org/oas/3.0/schema/2024-10-18"); +} + TEST(idempotency) { EXPECT_SCHEMA("https://json-schema.org/draft/2020-12/schema"); EXPECT_SCHEMA("https://json-schema.org/draft/2020-12/schema"); @@ -582,6 +607,21 @@ TEST(is_known_schema_openapi_3_2) { .has_value()); } +TEST(is_known_schema_openapi_3_0) { + EXPECT_TRUE(sourcemeta::core::schema_is_known( + "tag:spec.openapis.org,2024-10-18:oas/3.0/dialect")); + EXPECT_TRUE(sourcemeta::core::schema_resolver( + "tag:spec.openapis.org,2024-10-18:oas/3.0/dialect") + .has_value()); + EXPECT_TRUE(sourcemeta::core::schema_is_known( + "tag:spec.openapis.org,2021-09-28:oas/3.0/dialect")); + EXPECT_TRUE(sourcemeta::core::schema_resolver( + "tag:spec.openapis.org,2021-09-28:oas/3.0/dialect") + .has_value()); + EXPECT_FALSE(sourcemeta::core::schema_is_known( + "https://spec.openapis.org/oas/3.0/schema/2024-10-18")); +} + TEST(is_known_schema_unknown) { EXPECT_FALSE(sourcemeta::core::schema_is_known("https://example.com/foobar")); EXPECT_FALSE(sourcemeta::core::schema_resolver("https://example.com/foobar") @@ -746,6 +786,13 @@ TEST(is_official_schema_openapi_3_2) { "https://spec.openapis.org/oas/3.2/meta/2025-09-17")); } +TEST(is_official_schema_openapi_3_0) { + EXPECT_FALSE(sourcemeta::core::schema_is_official( + "tag:spec.openapis.org,2024-10-18:oas/3.0/dialect")); + EXPECT_FALSE(sourcemeta::core::schema_is_official( + "tag:spec.openapis.org,2021-09-28:oas/3.0/dialect")); +} + TEST(is_official_schema_unknown) { EXPECT_FALSE( sourcemeta::core::schema_is_official("https://example.com/foobar"));