From ef28a5afbc5f706f57da6e0309116ab996ee3d47 Mon Sep 17 00:00:00 2001 From: Marton Vago Date: Fri, 7 Nov 2025 10:51:07 +0000 Subject: [PATCH] feat: :sparkles: handle grouped errors for primary key --- src/check_datapackage/check.py | 35 +++++++++++++++++++++++++++ tests/test_check.py | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index c0fd1994..30953939 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -304,12 +304,47 @@ def _handle_S_resources_x_schema_fields_x( return edits +def _handle_S_resources_x_schema_primary_key( + parent_error: SchemaError, + schema_errors: list[SchemaError], +) -> SchemaErrorEdits: + """Only flag errors for the relevant type and simplify errors.""" + PRIMARY_KEY_TYPES: tuple[type[Any], ...] = (list, str) + edits = SchemaErrorEdits(remove=[parent_error]) + errors_in_group = _get_errors_in_group(schema_errors, parent_error) + + key_type = type(parent_error.instance) + if key_type in PRIMARY_KEY_TYPES: + schema_for_type = f"primaryKey/oneOf/{PRIMARY_KEY_TYPES.index(key_type)}/" + edits.remove.extend( + _filter( + errors_in_group, + lambda error: schema_for_type not in error.schema_path, + ) + ) + return edits + + edits.remove.extend(errors_in_group) + edits.add.append( + SchemaError( + message="The `primaryKey` property must be a string or an array.", + type="type", + jsonpath=parent_error.jsonpath, + schema_path=parent_error.schema_path, + instance=parent_error.instance, + ) + ) + + return edits + + _schema_path_to_handler: list[ tuple[str, Callable[[SchemaError, list[SchemaError]], SchemaErrorEdits]] ] = [ ("resources/items/oneOf", _handle_S_resources_x), ("resources/items/properties/path/oneOf", _handle_S_resources_x_path), ("fields/items/oneOf", _handle_S_resources_x_schema_fields_x), + ("primaryKey/oneOf", _handle_S_resources_x_schema_primary_key), ] diff --git a/tests/test_check.py b/tests/test_check.py index 82156784..33a984d5 100644 --- a/tests/test_check.py +++ b/tests/test_check.py @@ -333,6 +333,49 @@ def test_fail_unknown_field_with_bad_property(): assert issues[0].jsonpath == "$.resources[0].schema.fields[0].type" +@mark.parametrize("primary_key", ["id", ["name", "address"]]) +def test_pass_good_primary_key(primary_key): + properties = example_package_properties() + properties["resources"][0]["schema"]["primaryKey"] = primary_key + + issues = check(properties) + + assert issues == [] + + +def test_fail_primary_key_of_bad_type(): + properties = example_package_properties() + properties["resources"][0]["schema"]["primaryKey"] = 123 + + issues = check(properties) + + assert len(issues) == 1 + assert issues[0].type == "type" + assert issues[0].jsonpath == "$.resources[0].schema.primaryKey" + + +def test_fail_primary_key_with_bad_array(): + properties = example_package_properties() + properties["resources"][0]["schema"]["primaryKey"] = [] + + issues = check(properties) + + assert len(issues) == 1 + assert issues[0].type == "minItems" + assert issues[0].jsonpath == "$.resources[0].schema.primaryKey" + + +def test_fail_primary_key_with_bad_array_item(): + properties = example_package_properties() + properties["resources"][0]["schema"]["primaryKey"] = [123, "name"] + + issues = check(properties) + + assert len(issues) == 1 + assert issues[0].type == "type" + assert issues[0].jsonpath == "$.resources[0].schema.primaryKey[0]" + + def test_error_as_true(): properties = { "name": 123,