diff --git a/ethpm/backends/registry.py b/ethpm/backends/registry.py index 26e0798186..232b67259c 100644 --- a/ethpm/backends/registry.py +++ b/ethpm/backends/registry.py @@ -33,7 +33,10 @@ # TODO: Update registry ABI once ERC is finalized. REGISTRY_ABI = fetch_standard_registry_abi() -RegistryURI = namedtuple("RegistryURI", ["address", "chain_id", "name", "version", "ens"]) +RegistryURI = namedtuple( + "RegistryURI", + ["address", "chain_id", "name", "version", "namespaced_asset", "ens"] +) class RegistryURIBackend(BaseURIBackend): @@ -57,7 +60,7 @@ def fetch_uri_contents(self, uri: str) -> URI: """ Return content-addressed URI stored at registry URI. """ - address, chain_id, pkg_name, pkg_version, _ = parse_registry_uri(uri) + address, chain_id, pkg_name, pkg_version, _, _ = parse_registry_uri(uri) if chain_id != '1': # todo: support all testnets raise CannotHandleURI( @@ -104,15 +107,35 @@ def parse_registry_uri(uri: str) -> RegistryURI: raise CannotHandleURI( f"Invalid address or ENS domain found in uri: {uri}." ) - pkg_name, pkg_version = _process_pkg_path(parsed_uri.path) - return RegistryURI(address, chain_id, pkg_name, pkg_version, ens) + pkg_name, pkg_version, namespaced_asset = _process_pkg_path(parsed_uri.path) + return RegistryURI(address, chain_id, pkg_name, pkg_version, namespaced_asset, ens) + +def _process_pkg_path(raw_pkg_path: str) -> Tuple[Optional[str], Optional[str], Optional[str]]: + pkg_path = raw_pkg_path.strip("/") + if not pkg_path: + return None, None, None + + pkg_id, namespaced_asset = _parse_pkg_path(pkg_path) + pkg_name, pkg_version = _parse_pkg_id(pkg_id) + if not pkg_version and namespaced_asset: + raise EthPMValidationError( + "Invalid registry URI, missing package version." + "Version is required if namespaced assets are defined." + ) + return pkg_name, pkg_version, namespaced_asset + + +def _parse_pkg_path(pkg_path: str) -> Tuple[str, Optional[str]]: + if "/" in pkg_path: + pkg_id = pkg_path.split("/")[0] + namespaced_asset = "/".join(pkg_path.split("/")[1:]) + return pkg_id, namespaced_asset + else: + return pkg_path, None -def _process_pkg_path(pkg_path: str) -> Tuple[Optional[str], Optional[str]]: - pkg_id = pkg_path.strip("/") - if not pkg_id: - return None, None +def _parse_pkg_id(pkg_id: str) -> Tuple[str, Optional[str]]: if "@" not in pkg_id: return pkg_id, None pkg_name, safe_pkg_version = pkg_id.split("@") diff --git a/tests/ethpm/_utils/test_registry_utils.py b/tests/ethpm/_utils/test_registry_utils.py index bde45a1b58..93f30da5be 100644 --- a/tests/ethpm/_utils/test_registry_utils.py +++ b/tests/ethpm/_utils/test_registry_utils.py @@ -39,6 +39,7 @@ ("erc1319://0xd3CdA913deB6f67967B99D67aCDFa1712C293601/erc20@1.0.0"), ("erc1319://0xd3CdA913deB6f67967B99D67aCDFa1712C293601:1/erc20@1.0.0"), ("erc1319://0xd3CdA913deB6f67967B99D67aCDFa1712C293601:1/erc20@1.0.0/"), + ("erc1319://0xd3CdA913deB6f67967B99D67aCDFa1712C293601:1/erc20@1.0.0/deployments/ERC139") ), ) def test_is_registry_uri_validates(uri): diff --git a/tests/ethpm/test_uri.py b/tests/ethpm/test_uri.py index 36a1ea1e90..763cb0f849 100644 --- a/tests/ethpm/test_uri.py +++ b/tests/ethpm/test_uri.py @@ -75,63 +75,86 @@ def test_create_github_uri(): ( ( "erc1319://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", None, None, None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", None, None, None, None], ), ( "erc1319://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:3", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "3", None, None, None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "3", None, None, None, None], ), ( "erc1319://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:5/owned", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "5", "owned", None, None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "5", "owned", None, None, None], ), ( "erc1319://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:1/owned@1.0.0", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "owned", "1.0.0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "owned", "1.0.0", None, None], ), ( "erc1319://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:1/wallet@2.8.0/", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", None, None], ), # ethpm scheme ( "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:1/wallet@2.8.0", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", None, None], ), # w/o chain_id ( "erc1319://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/owned", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "owned", None, None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "owned", None, None, None], ), ( "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@2.8.0", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", None, None], ), ( "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@8%400", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "8@0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "8@0", None, None], ), # escaped chars ( "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:1/wallet@8%400", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "8@0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "8@0", None, None], ), ( "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:1/wallet@%250", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "%0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "%0", None, None], ), ( "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729:1/wallet@8%400/", - ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "8@0", None], + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "8@0", None, None], + ), + # with namespaced manifest contents + ( + "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@2.8.0/deployments", + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", "deployments", None], # noqa: E501 + ), + ( + "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@2.8.0/deployments/", + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", "deployments", None], # noqa: E501 + ), + ( + "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@2.8.0/deployments/WalletContract", # noqa: E501 + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", "deployments/WalletContract", None], # noqa: E501 + ), + ( + "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@2.8.0/deployments/WalletContract/", # noqa: E501 + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "2.8.0", "deployments/WalletContract", None], # noqa: E501 + ), + # unescaped chars & namespaced assets + ( + "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@20%26/deployments/WalletContract/", # noqa: E501 + ["0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729", "1", "wallet", "20&", "deployments/WalletContract", None], # noqa: E501 ), ), ) def test_parse_registry_uri(uri, expected): - address, chain_id, pkg_name, pkg_version, ens = parse_registry_uri(uri) + address, chain_id, pkg_name, pkg_version, namespaced_asset, ens = parse_registry_uri(uri) assert address == expected[0] assert chain_id == expected[1] assert pkg_name == expected[2] assert pkg_version == expected[3] + assert namespaced_asset == expected[4] @pytest.mark.parametrize( @@ -148,6 +171,9 @@ def test_parse_registry_uri(uri, expected): "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/ab@@1.0.0", "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/!bc@1.0.0", "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/!bc@1.0.0/", + # namespaced asset and missing version + "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet/deployments/WalletContract", + "ethpm://0x6b5DA3cA4286Baa7fBaf64EEEE1834C7d430B729/wallet@/deployments/WalletContract", ) ) def test_invalid_registry_uris(uri):