refactor: centralize validation logic with validate_with helper (#412)

* chore: sonarcloud fixes

* chore: coderabbit cr fixes
This commit is contained in:
2025-12-23 13:29:37 +02:00
committed by GitHub
parent 5b4e9c8e11
commit 96c305c557
18 changed files with 452 additions and 834 deletions

View File

@@ -227,3 +227,82 @@ class BaseValidator(ABC):
or ("${{" in value and "}}" in value)
or (value.strip().startswith("${{") and value.strip().endswith("}}"))
)
def propagate_errors(self, validator: BaseValidator, result: bool) -> bool:
"""Copy errors from another validator and return result.
Args:
validator: The validator to copy errors from
result: The validation result to return
Returns:
The result parameter unchanged
"""
for error in validator.errors:
if error not in self.errors:
self.add_error(error)
validator.clear_errors()
return result
def validate_with(
self, validator: BaseValidator, method: str, *args: Any, **kwargs: Any
) -> bool:
"""Call validator method and propagate errors.
Args:
validator: The validator instance to use
method: The method name to call on the validator
*args: Positional arguments to pass to the method
**kwargs: Keyword arguments to pass to the method
Returns:
The validation result
"""
result = getattr(validator, method)(*args, **kwargs)
return self.propagate_errors(validator, result)
def validate_enum(
self,
value: str,
name: str,
valid_values: list[str],
*,
case_sensitive: bool = False,
) -> bool:
"""Validate value is one of allowed options.
Args:
value: The value to validate
name: The name of the input for error messages
valid_values: List of allowed values
case_sensitive: Whether comparison should be case sensitive
Returns:
True if value is valid or empty/GitHub expression, False otherwise
"""
if not value or self.is_github_expression(value):
return True
check = value if case_sensitive else value.lower()
allowed = valid_values if case_sensitive else [v.lower() for v in valid_values]
if check not in allowed:
self.add_error(f"Invalid {name}: {value}. Must be one of: {', '.join(valid_values)}")
return False
return True
@staticmethod
def get_key_variant(inputs: dict[str, str], *variants: str) -> str | None:
"""Get first matching key variant from inputs.
Useful for inputs that may use underscore or hyphen variants.
Args:
inputs: Dictionary of inputs to check
*variants: Key variants to search for in order
Returns:
The first matching key, or None if no match
"""
for key in variants:
if key in inputs:
return key
return None

View File

@@ -5,6 +5,7 @@ This validator automatically applies validation based on input naming convention
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
@@ -424,7 +425,10 @@ class ConventionBasedValidator(BaseValidator):
if error not in self.errors:
self.add_error(error)
# Clear the module's errors after copying
validator_module.errors = []
if hasattr(validator_module, "clear_errors"):
validator_module.clear_errors()
else:
validator_module.errors = []
return result
# Method not found, skip validation
@@ -629,7 +633,8 @@ class ConventionBasedValidator(BaseValidator):
Args:
value: The comma-separated list value
input_name: The input name for error messages
item_pattern: Regex pattern each item must match (default: alphanumeric+hyphens+underscores)
item_pattern: Regex pattern each item must match
(default: alphanumeric+hyphens+underscores)
valid_items: Optional list of valid items for enum-style validation
check_injection: Whether to check for shell injection patterns
item_name: Descriptive name for items in error messages (e.g., "linter", "extension")
@@ -654,8 +659,6 @@ class ConventionBasedValidator(BaseValidator):
... )
True
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -895,14 +898,12 @@ class ConventionBasedValidator(BaseValidator):
# Validate valid_values count
if len(valid_values) < min_values:
raise ValueError(
f"Multi-value enum requires at least {min_values} valid values, got {len(valid_values)}"
)
msg = f"Multi-value enum needs >= {min_values} values, got {len(valid_values)}"
raise ValueError(msg)
if len(valid_values) > max_values:
raise ValueError(
f"Multi-value enum supports at most {max_values} valid values, got {len(valid_values)}"
)
msg = f"Multi-value enum allows <= {max_values} values, got {len(valid_values)}"
raise ValueError(msg)
if not value or value.strip() == "":
return True # Optional
@@ -1024,8 +1025,6 @@ class ConventionBasedValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -1123,8 +1122,6 @@ class ConventionBasedValidator(BaseValidator):
Valid: "0", "0,1,2", "5,10,15", "0,130", ""
Invalid: "256", "0,256", "-1", "0,abc", "0,,1"
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -1169,8 +1166,10 @@ class ConventionBasedValidator(BaseValidator):
Args:
value: The key-value list value (comma-separated KEY=VALUE pairs)
input_name: The input name for error messages
key_pattern: Regex pattern for key validation (default: alphanumeric+underscores+hyphens)
check_injection: Whether to check for shell injection patterns in values (default: True)
key_pattern: Regex pattern for key validation
(default: alphanumeric+underscores+hyphens)
check_injection: Whether to check for shell injection patterns
in values (default: True)
Returns:
True if valid, False otherwise
@@ -1179,7 +1178,6 @@ class ConventionBasedValidator(BaseValidator):
Valid: "KEY=value", "KEY1=value1,KEY2=value2", "BUILD_ARG=hello", ""
Invalid: "KEY", "=value", "KEY=", "KEY=value,", "KEY=val;whoami"
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -1260,8 +1258,6 @@ class ConventionBasedValidator(BaseValidator):
Returns:
bool: True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -1412,8 +1408,6 @@ class ConventionBasedValidator(BaseValidator):
Returns:
bool: True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional

View File

@@ -12,7 +12,8 @@ class TokenValidator(BaseValidator):
"""Validator for various authentication tokens."""
# Token patterns for different token types (based on official GitHub documentation)
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats
# See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/
# about-authentication-to-github#githubs-token-formats
# Note: The lengths include the prefix
TOKEN_PATTERNS: ClassVar[dict[str, str]] = {
# Personal access token (classic):