diff --git a/src/ytdl_sub/script/types/map.py b/src/ytdl_sub/script/types/map.py index 624b6206..60b99b0b 100644 --- a/src/ytdl_sub/script/types/map.py +++ b/src/ytdl_sub/script/types/map.py @@ -11,7 +11,7 @@ from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import ResolvableToJson from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.types.variable_dependency import VariableDependency -from ytdl_sub.utils.exceptions import StringFormattingException +from ytdl_sub.script.utils.exceptions import KeyNotHashableRuntimeException @dataclass(frozen=True) @@ -42,7 +42,9 @@ class UnresolvedMap(Map, VariableDependency, AnyArgument): arg=key, resolved_variables=resolved_variables, custom_functions=custom_functions ) if not isinstance(resolved_key, Hashable): - raise StringFormattingException("key is not hashable") + raise KeyNotHashableRuntimeException( + f"Tried to use {resolved_key.type_name()} as a Map key, but it is not hashable." + ) output[resolved_key] = self._resolve_argument_type( arg=value, resolved_variables=resolved_variables, custom_functions=custom_functions diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index c0182bb7..a40744fc 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -12,7 +12,6 @@ from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.utils.exceptions import UNREACHABLE -from ytdl_sub.utils.exceptions import StringFormattingException @dataclass(frozen=True) @@ -78,7 +77,8 @@ class VariableDependency(ABC): return arg if isinstance(arg, Variable): if arg not in resolved_variables: - raise StringFormattingException("should never reach@") + # All variables should exist and be resolved at this point + raise UNREACHABLE return resolved_variables[arg] if isinstance(arg, VariableDependency): return arg.resolve( diff --git a/src/ytdl_sub/script/utils/exceptions.py b/src/ytdl_sub/script/utils/exceptions.py index dc9e63b6..762a0a56 100644 --- a/src/ytdl_sub/script/utils/exceptions.py +++ b/src/ytdl_sub/script/utils/exceptions.py @@ -43,10 +43,18 @@ class CycleDetected(UserException): """A cycle exists within a user's script""" -class FunctionRuntimeException(ValueError): +class RuntimeException(ValueError, ABC): + """Exception thrown at runtime during resolution""" + + +class FunctionRuntimeException(RuntimeException): """Exception thrown when a ytdl-sub function has an error occur at runtime""" +class KeyNotHashableRuntimeException(RuntimeException): + """Map tried to use a non-hashable key at runtime""" + + class UserThrownRuntimeError(ValidationException): """An error explicitly thrown by the user via a function""" diff --git a/tests/unit/script/types/test_map.py b/tests/unit/script/types/test_map.py index 51159760..36603a77 100644 --- a/tests/unit/script/types/test_map.py +++ b/tests/unit/script/types/test_map.py @@ -15,6 +15,7 @@ from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.utils.exceptions import InvalidSyntaxException +from ytdl_sub.script.utils.exceptions import KeyNotHashableRuntimeException class TestMap: @@ -159,3 +160,26 @@ class TestMap: def test_map_key_not_hashable(self, value: str): with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_NOT_HASHABLE))): Script({"map": value}).resolve() + + def test_map_key_is_hashable_variable(self): + assert Script( + { + "map": "{{key_variable : 'value' }}", + "key_variable": "hashable", + } + ).resolve() == { + "key_variable": String("hashable"), + "map": ResolvedMap({String("hashable"): String("value")}), + } + + def test_map_key_is_non_hashable_variable(self): + with pytest.raises( + KeyNotHashableRuntimeException, + match=re.escape("Tried to use Array as a Map key, but it is not hashable."), + ): + Script( + { + "map": "{{key_variable : 'value' }}", + "key_variable": "{['non-hashable']}", + } + ).resolve()