Skip to content

Commit

Permalink
[BACKEND] Validate override variable names
Browse files Browse the repository at this point in the history
  • Loading branch information
jmbannon committed Dec 19, 2023
1 parent e92b1cd commit a068da0
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 42 deletions.
15 changes: 13 additions & 2 deletions src/ytdl_sub/config/overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ytdl_sub.entries.variables.override_variables import OverrideVariables
from ytdl_sub.script.parser import parse
from ytdl_sub.script.script import Script
from ytdl_sub.utils.exceptions import InvalidVariableNameException
from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.utils.scriptable import Scriptable
Expand Down Expand Up @@ -86,16 +87,26 @@ def ensure_variable_name_valid(self, name: str) -> None:
"""
Ensures the variable name does not collide with any entry variables or built-in functions.
"""
if not OverrideVariables.is_valid_name(name):
override_type = "function" if name.startswith("%") else "variable"
raise self._validation_exception(
f"Override {override_type} with name {name} is invalid. Names must be"
" lower_snake_cased and begin with a letter.",
exception_class=InvalidVariableNameException,
)

if OverrideVariables.is_entry_variable_name(name):
raise self._validation_exception(
f"Override variable with name {name} cannot be used since it is a"
" built-in ytdl-sub entry variable name."
" built-in ytdl-sub entry variable name.",
exception_class=InvalidVariableNameException,
)

if OverrideVariables.is_function_name(name):
raise self._validation_exception(
f"Override function definition with name {name} cannot be used since it is"
" a built-in ytdl-sub function name."
" a built-in ytdl-sub function name.",
exception_class=InvalidVariableNameException,
)

def initial_variables(
Expand Down
13 changes: 13 additions & 0 deletions src/ytdl_sub/entries/variables/override_variables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS
from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS
from ytdl_sub.script.functions import Functions
from ytdl_sub.script.utils.name_validation import is_valid_name

SUBSCRIPTION_NAME = "subscription_name"
SUBSCRIPTION_VALUE = "subscription_value"
Expand Down Expand Up @@ -81,3 +82,15 @@ def is_function_name(cls, name: str) -> bool:
if name.startswith("%"):
return name in CUSTOM_FUNCTION_SCRIPTS or Functions.is_built_in(name[1:])
return False

@classmethod
def is_valid_name(cls, name: str) -> bool:
"""
Returns
-------
True if the override name itself is valid. False otherwise.
"""
if name.startswith("%"):
return is_valid_name(name=name[1:])

return is_valid_name(name=name)
13 changes: 8 additions & 5 deletions src/ytdl_sub/validators/source_variable_validator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ytdl_sub.script.utils.name_validation import is_valid_name
from ytdl_sub.utils.exceptions import InvalidVariableNameException
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name
from ytdl_sub.validators.validators import ListValidator
from ytdl_sub.validators.validators import StringValidator

Expand All @@ -9,10 +9,13 @@ class SourceVariableNameValidator(StringValidator):

def __init__(self, name, value):
super().__init__(name, value)
try:
_ = is_valid_source_variable_name(self.value, raise_exception=True)
except InvalidVariableNameException as exc:
raise self._validation_exception(exc) from exc

if not is_valid_name(value):
raise self._validation_exception(
f"Variable with name {name} is invalid. Names must be"
" lower_snake_cased and begin with a letter.",
exception_class=InvalidVariableNameException,
)


class SourceVariableNameListValidator(ListValidator[SourceVariableNameValidator]):
Expand Down
34 changes: 0 additions & 34 deletions src/ytdl_sub/validators/string_formatter_validators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import re
from typing import Dict
from typing import Set
from typing import Union
Expand All @@ -9,46 +8,13 @@
from ytdl_sub.script.script import Script
from ytdl_sub.script.utils.exceptions import UserException
from ytdl_sub.script.utils.exceptions import VariableDoesNotExist
from ytdl_sub.utils.exceptions import InvalidVariableNameException
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
from ytdl_sub.validators.validators import DictValidator
from ytdl_sub.validators.validators import ListValidator
from ytdl_sub.validators.validators import LiteralDictValidator
from ytdl_sub.validators.validators import StringValidator
from ytdl_sub.validators.validators import Validator

_fields_validator = re.compile(r"{([a-z][a-z0-9_]*?)}")

_fields_validator_exception_message: str = (
"{variable_names} must start with a lowercase letter, should only contain lowercase letters, "
"numbers, underscores, and have a single open and close bracket."
)


def is_valid_source_variable_name(input_str: str, raise_exception: bool = False) -> bool:
"""
Parameters
----------
input_str
String to see if it can be a source variable
raise_exception
Raise InvalidVariableNameException False.
Returns
-------
True if it is. False otherwise.
Raises
------
InvalidVariableNameException
If raise_exception and output is False
"""
# Add brackets around it to pretend its a StringFormatter, see if it captures
is_source_variable_name = len(re.findall(_fields_validator, f"{{{input_str}}}")) > 0
if not is_source_variable_name and raise_exception:
raise InvalidVariableNameException(_fields_validator_exception_message)
return is_source_variable_name


class StringFormatterValidator(StringValidator):
"""
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/config/test_preset.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,33 @@ def test_preset_error_override_added_variable_collides_with_override(
},
)

@pytest.mark.parametrize(
"name", ["!ack", "*asfsaf", "1234352", "--234asdf", "___asdf", "1asdfasdfasd"]
)
@pytest.mark.parametrize("is_function", [True, False])
def test_preset_error_overrides_invalid_variable_name(
self, config_file, youtube_video, output_options, name: str, is_function: bool
):
name_type = "function" if is_function else "variable"
name = f"%{name}" if is_function else name

with pytest.raises(
ValidationException,
match=re.escape(
f"Override {name_type} with name {name} is invalid."
" Names must be lower_snake_cased and begin with a letter."
),
):
_ = Preset(
config=config_file,
name="test",
value={
"download": youtube_video,
"output_options": output_options,
"overrides": {name: "ack"},
},
)

def test_preset_error_added_url_variable_cannot_resolve(self, config_file, output_options):
with pytest.raises(
ValidationException,
Expand Down
1 change: 0 additions & 1 deletion tests/unit/plugins/test_regex_capture.py

This file was deleted.

0 comments on commit a068da0

Please sign in to comment.