Skip to content

Commit

Permalink
schema_registry/json: add support and validation for draft7
Browse files Browse the repository at this point in the history
  • Loading branch information
andijcr committed Jul 5, 2024
1 parent c8ac944 commit d63c3d1
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 1 deletion.
243 changes: 242 additions & 1 deletion src/v/pandaproxy/schema_registry/json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -424,11 +424,237 @@ constexpr std::string_view json_draft_6_metaschema = R"json(
}
)json";

/*
From https://json-schema.org/draft-07/schema, this is the draft7 metaschema
used to validate draft7 json schemas.
TODO It's implemented in the draft4 dialect, because the current version
of rapidjson only support draft4, change this when it's upgraded
For reference, this is the diff applied to the original metaschema:
--- draft7.json 2024-07-02 09:53:23.943963373 +0200
+++ draft7.asdraft4.json 2024-07-05 15:55:21.160409278 +0200
@@ -1,4 +1,4 @@
{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "http://json-schema.org/draft-07/schema#",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
@@ -46,3 +46,4 @@
"type": "string",
- "format": "uri"
+ "format": "uri",
+ "enum": ["http://json-schema.org/draft-07/schema#"]
},
@@ -61,3 +62,3 @@
},
- "default": true,
+ "default": {},
"readOnly": {
@@ -72,3 +73,3 @@
"type": "array",
- "items": true
+ "items": {}
},
@@ -76,3 +77,4 @@
"type": "number",
- "exclusiveMinimum": 0
+ "minimum": 0,
+ "exclusiveMinimum": true
},
@@ -141,6 +143,6 @@
"propertyNames": { "$ref": "#" },
- "const": true,
+ "const": {},
"enum": {
"type": "array",
- "items": true,
+ "items": {},
"minItems": 1,
*/

constexpr std::string_view json_draft_7_metaschema = R"json(
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
},
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri",
"enum": ["http://json-schema.org/draft-07/schema#"]
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$comment": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"readOnly": {
"type": "boolean",
"default": false
},
"writeOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": {}
},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": true
},
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"const": {},
"enum": {
"type": "array",
"items": {},
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": { "$ref": "#" },
"then": { "$ref": "#" },
"else": { "$ref": "#" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"default": true
}
)json";

// this is the list of supported dialects
enum class json_schema_dialect {
draft4,
draft5,
draft6,
draft7,
};

constexpr std::string_view to_uri(json_schema_dialect draft) {
Expand All @@ -440,6 +666,8 @@ constexpr std::string_view to_uri(json_schema_dialect draft) {
return "http://json-schema.org/draft-05/schema#";
case draft6:
return "http://json-schema.org/draft-06/schema#";
case draft7:
return "http://json-schema.org/draft-07/schema#";
}
}

Expand All @@ -449,6 +677,7 @@ constexpr std::optional<json_schema_dialect> from_uri(std::string_view uri) {
.match(to_uri(draft4), draft4)
.match(to_uri(draft5), draft5)
.match(to_uri(draft6), draft6)
.match(to_uri(draft7), draft7)
.default_match(std::nullopt);
}

Expand Down Expand Up @@ -478,6 +707,8 @@ json::SchemaDocument const& get_metaschema() {
return json_draft_4_metaschema;
case json_schema_dialect::draft6:
return json_draft_6_metaschema;
case json_schema_dialect::draft7:
return json_draft_7_metaschema;
}
}();

Expand Down Expand Up @@ -506,6 +737,8 @@ result<void> validate_json_schema(
return get_metaschema<draft4>();
case draft6:
return get_metaschema<draft6>();
case draft7:
return get_metaschema<draft7>();
}
}();

Expand Down Expand Up @@ -548,7 +781,7 @@ result<void> try_validate_json_schema(json::Document const& schema) {

// no explicit $schema: try to validate from newest to oldest draft
auto first_error = std::optional<error_info>{};
for (auto d : {draft6, draft4}) {
for (auto d : {draft7, draft6, draft4}) {
auto res = validate_json_schema(d, schema);
if (res.has_value()) {
return outcome::success();
Expand Down Expand Up @@ -1570,6 +1803,14 @@ bool is_superset(json::Value const& older, json::Value const& newer) {
"contains",
"examples",
"propertyNames",
// draft 7 unhandled keywords
"contentEncoding",
"contentMediaType",
"readOnly",
"writeOnly",
"if",
"then",
"else",
// later drafts:
"prefixItems",
}) {
Expand Down
16 changes: 16 additions & 0 deletions src/v/pandaproxy/schema_registry/test/test_json_schema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@ static constexpr auto valid_test_cases = std::to_array<std::string_view>({
"b": true
},
"propertyNames": { "enum": ["a", "b"] }
}
)json",
R"json(
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"a": {
"type": "number",
"exclusiveMinimum": 0
},
"b": true
},
"propertyNames": { "enum": ["a", "b"] },
"if": true,
"then": true
}
)json",
});
Expand Down

0 comments on commit d63c3d1

Please sign in to comment.