diff --git a/swh/graphql/app.py b/swh/graphql/app.py --- a/swh/graphql/app.py +++ b/swh/graphql/app.py @@ -39,5 +39,4 @@ scalars.id_scalar, scalars.datetime_scalar, scalars.swhid_scalar, - scalars.content_hash_scalar, ) diff --git a/swh/graphql/resolvers/content.py b/swh/graphql/resolvers/content.py --- a/swh/graphql/resolvers/content.py +++ b/swh/graphql/resolvers/content.py @@ -5,6 +5,9 @@ from typing import Union +from swh.graphql.errors import InvalidInputError +from swh.model import hashutil + from .base_node import BaseSWHNode from .directory_entry import BaseDirectoryEntryNode from .release import BaseReleaseNode @@ -78,7 +81,16 @@ """ def _get_node_data(self): - hashes = dict(self.kwargs.get("hashes")) + try: + hashes = { + hash_type: hashutil.hash_to_bytes(hash_value) + for (hash_type, hash_value) in self.kwargs.items() + } + except ValueError as e: + # raise an input error in case of an invalid hash + raise InvalidInputError("Invalid content hash", e) + if not hashes: + raise InvalidInputError("At least one of the four hashes must be provided") return self._get_content_by_hashes(hashes) diff --git a/swh/graphql/resolvers/scalars.py b/swh/graphql/resolvers/scalars.py --- a/swh/graphql/resolvers/scalars.py +++ b/swh/graphql/resolvers/scalars.py @@ -9,7 +9,6 @@ from swh.graphql.errors import InvalidInputError from swh.graphql.utils import utils -from swh.model import hashutil from swh.model.exceptions import ValidationError from swh.model.model import TimestampWithTimezone from swh.model.swhids import CoreSWHID @@ -17,7 +16,6 @@ datetime_scalar = ScalarType("DateTime") swhid_scalar = ScalarType("SWHID") id_scalar = ScalarType("ID") -content_hash_scalar = ScalarType("ContentHash") @id_scalar.serializer @@ -49,15 +47,3 @@ @swhid_scalar.serializer def serialize_swhid(value): return str(value) - - -@content_hash_scalar.value_parser -def validate_content_hash(value): - try: - hash_type, hash_string = value.split(":") - hash_value = hashutil.hash_to_bytes(hash_string) - except ValueError as e: - raise InvalidInputError("Invalid content hash", e) - if hash_type not in hashutil.ALGORITHMS: - raise InvalidInputError("Invalid hash algorithm") - return hash_type, hash_value diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -8,11 +8,6 @@ """ scalar DateTime -""" -Content identifier in the form hash-type:hash-value -""" -scalar ContentHash - """ Object with an id """ @@ -1085,14 +1080,18 @@ ): Content """ - Get the content by one or more hashes - Use multiple hashes for an accurate result + Get the content that match the given set of hashes. + At least one of the four hashes must be provided. + Use multiple hashes for a narrower result. """ contentByHashes( - """ - List of hashType:hashValue strings - """ - hashes: [ContentHash]! + sha1: String + + sha256: String + + sha1_git: String + + blake2s256: String ): Content """ diff --git a/swh/graphql/tests/functional/test_content.py b/swh/graphql/tests/functional/test_content.py --- a/swh/graphql/tests/functional/test_content.py +++ b/swh/graphql/tests/functional/test_content.py @@ -62,11 +62,26 @@ assert data["content"] == response +def test_get_content_with_invalid_swhid(client): + query_str = """ + query getContent($swhid: SWHID!) { + content(swhid: $swhid) { + swhid + } + } + """ + errors = utils.get_error_response(client, query_str, swhid="invalid") + # API will throw an error in case of an invalid SWHID + assert len(errors) == 1 + assert "Input error: Invalid SWHID" in errors[0]["message"] + + @pytest.mark.parametrize("content", get_contents()) def test_get_content_with_hash(client, content): query_str = """ - query getContent($hashes: [ContentHash]!) { - contentByHashes(hashes: $hashes) { + query getContent($sha1: String, $sha1_git: String, $sha256: String, $blake2s256: String) { + contentByHashes(sha1: $sha1, sha1_git: $sha1_git, sha256: $sha256, + blake2s256: $blake2s256) { swhid } } @@ -74,35 +89,57 @@ data, _ = utils.get_query_response( client, query_str, - hashes=[ - f"blake2s256:{content.blake2s256.hex()}", - f"sha1:{content.sha1.hex()}", - f"sha1_git:{content.sha1_git.hex()}", - f"sha256:{content.sha256.hex()}", - ], + sha1=content.sha1.hex(), + sha1_git=content.sha1_git.hex(), + sha256=content.sha256.hex(), + blake2s256=content.blake2s256.hex(), ) assert data["contentByHashes"] == {"swhid": str(content.swhid())} -def test_get_content_with_invalid_swhid(client): +@pytest.mark.parametrize("content", get_contents()) +def test_get_content_with_single_hash(client, content): query_str = """ - query getContent($swhid: SWHID!) { - content(swhid: $swhid) { + query getContent($sha1: String, $sha1_git: String, $sha256: String, $blake2s256: String) { + contentByHashes(sha1: $sha1, sha1_git: $sha1_git, sha256: $sha256, + blake2s256: $blake2s256) { swhid } } """ - errors = utils.get_error_response(client, query_str, swhid="invalid") - # API will throw an error in case of an invalid SWHID - assert len(errors) == 1 - assert "Input error: Invalid SWHID" in errors[0]["message"] + data, _ = utils.get_query_response( + client, + query_str, + sha1=content.sha1.hex(), + ) + assert data["contentByHashes"] == {"swhid": str(content.swhid())} + + +@pytest.mark.parametrize("content", get_contents()) +def test_get_content_with_one_wrong_hash(client, content): + query_str = """ + query getContent($sha1: String, $sha1_git: String, $sha256: String, $blake2s256: String) { + contentByHashes(sha1: $sha1, sha1_git: $sha1_git, sha256: $sha256, + blake2s256: $blake2s256) { + swhid + } + } + """ + utils.assert_missing_object( + client, + query_str, + obj_type="contentByHashes", + sha1=content.sha1.hex(), + sha1_git=content.blake2s256.hex(), # wrong hash value + ) def test_get_content_with_invalid_hashes(client): content = get_contents()[0] query_str = """ - query getContent($hashes: [ContentHash]!) { - contentByHashes(hashes: $hashes) { + query getContent($sha1: String, $sha1_git: String, $sha256: String, $blake2s256: String) { + contentByHashes(sha1: $sha1, sha1_git: $sha1_git, sha256: $sha256, + blake2s256: $blake2s256) { swhid } } @@ -110,33 +147,33 @@ errors = utils.get_error_response( client, query_str, - hashes=[ - "invalid", # Only one hash is invalid - f"sha1:{content.sha1.hex()}", - f"sha1_git:{content.sha1_git.hex()}", - f"sha256:{content.sha256.hex()}", - ], + sha1="invalid", # Only one hash is invalid + sha1_git=content.sha1_git.hex(), + sha256=content.sha256.hex(), ) # API will throw an error in case of an invalid content hash assert len(errors) == 1 assert "Input error: Invalid content hash" in errors[0]["message"] -def test_get_content_with_invalid_hash_algorithm(client): - content = get_contents()[0] +def test_get_content_with_no_hashes(client): query_str = """ - query getContent($hashes: [ContentHash]!) { - contentByHashes(hashes: $hashes) { + query getContent($sha1: String, $sha1_git: String, $sha256: String, $blake2s256: String) { + contentByHashes(sha1: $sha1, sha1_git: $sha1_git, sha256: $sha256, + blake2s256: $blake2s256) { swhid } } """ - data, errors = utils.get_query_response( - client, query_str, hashes=[f"test:{content.sha1.hex()}"] + errors = utils.get_error_response( + client, + query_str, ) - assert data is None assert len(errors) == 1 - assert "Input error: Invalid hash algorithm" in errors[0]["message"] + assert ( + "Input error: At least one of the four hashes must be provided" + in errors[0]["message"] + ) def test_get_content_as_target(client): diff --git a/swh/graphql/tests/functional/utils.py b/swh/graphql/tests/functional/utils.py --- a/swh/graphql/tests/functional/utils.py +++ b/swh/graphql/tests/functional/utils.py @@ -27,6 +27,5 @@ def get_error_response(client, query_str: str, **kwargs) -> Dict: data, errors = get_query_response(client, query_str, **kwargs) - assert data is None assert len(errors) > 0 return errors diff --git a/swh/graphql/tests/unit/resolvers/test_scalars.py b/swh/graphql/tests/unit/resolvers/test_scalars.py --- a/swh/graphql/tests/unit/resolvers/test_scalars.py +++ b/swh/graphql/tests/unit/resolvers/test_scalars.py @@ -32,23 +32,3 @@ def test_validate_swhid(): swhid = scalars.validate_swhid(f"swh:1:rev:{'1' * 40}") assert str(swhid) == "swh:1:rev:1111111111111111111111111111111111111111" - - -@pytest.mark.parametrize("content_hash", ["invalid", "test:invalid"]) -def test_validate_content_hash_invalid_value(content_hash): - with pytest.raises(InvalidInputError) as e: - scalars.validate_content_hash(content_hash) - assert "Invalid content hash" in str(e.value) - - -def test_validate_content_hash_invalid_hash_algo(): - with pytest.raises(InvalidInputError) as e: - scalars.validate_content_hash(f"invalid:{'1' * 40}") - assert "Invalid hash algorithm" in str(e.value) - - -def test_validate_content_hash(): - assert ( - "sha1", - b"\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11", - ) == scalars.validate_content_hash(f"sha1:{'1' * 40}")