"""Validation for task-input manifest values.""" from __future__ import annotations import re from collections.abc import Mapping, Sequence from datetime import date, datetime from .discovery import InputField def validate_inputs( fields: Sequence[InputField], values: Mapping[str, list[str]], ) -> tuple[dict[str, str | int | list[str]], dict[str, str]]: result: dict[str, str | int | list[str]] = {} errors: dict[str, str] = {} for field in fields: raw_values = values.get(field.name, []) value, error = validate_field(field, raw_values) if error: errors[field.name] = error elif value is not None: result[field.name] = value return result, errors def validate_field( field: InputField, raw_values: list[str], ) -> tuple[str | int | list[str] | None, str | None]: if field.type == "multi_choice": return validate_multi_choice(field, raw_values) if len(raw_values) > 1: return None, "Only one value is allowed." raw = raw_values[0] if raw_values else "" if not raw: if field.default is not None: return field.default, None if field.required: return None, "This field is required." return None, None if field.type == "integer": try: value = int(raw) except ValueError: return None, "Enter a whole number." if field.minimum is not None and value < field.minimum: return None, f"Enter a value of at least {field.minimum}." if field.maximum is not None and value > field.maximum: return None, f"Enter a value no greater than {field.maximum}." if field.step is not None and (value - (field.minimum or 0)) % field.step: return None, f"Enter a value in increments of {field.step}." return value, None if field.type == "date": try: date.fromisoformat(raw) except ValueError: return None, "Enter a valid date." elif field.type == "datetime": try: datetime.fromisoformat(raw) except ValueError: return None, "Enter a valid date and time." elif field.type == "choice" and raw not in {option.value for option in field.options}: return None, "Choose one of the available options." elif field.type == "text" and field.pattern and not re.fullmatch(field.pattern, raw): return None, "Enter a value in the required format." return raw, None def validate_multi_choice( field: InputField, raw_values: list[str], ) -> tuple[list[str] | None, str | None]: if not raw_values: if field.default is not None: return list(field.default), None if field.required: return None, "Choose at least one option." return None, None allowed = {option.value for option in field.options} if any(value not in allowed for value in raw_values): return None, "Choose only from the available options." if len(raw_values) != len(set(raw_values)): return None, "Each option can be selected only once." return raw_values, None