Source code for linkforge.core.validation.result

"""Validation result data structures."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass, field
from enum import Enum

from ..exceptions import ValidationErrorCode


class Severity(Enum):
    """Severity level of a validation issue."""

    ERROR = "error"  # Blocking - will prevent export/simulation
    WARNING = "warning"  # Non-blocking - user should review


@dataclass
class ValidationIssue:
    """A single validation issue (error or warning).

    Attributes:
        severity: Error or warning
        title: Short description (e.g., "Duplicate link name")
        message: Detailed message with context
        affected_objects: Names of objects involved
        suggestion: How to fix this issue
        auto_fix: Optional function to automatically fix the issue

    """

    severity: Severity
    title: str
    message: str
    affected_objects: list[str] = field(default_factory=list)
    suggestion: str | None = None
    code: ValidationErrorCode | None = None
    auto_fix: Callable[[], None] | None = None

    @property
    def is_error(self) -> bool:
        """Check if this is an error (blocking)."""
        return self.severity == Severity.ERROR

    @property
    def is_warning(self) -> bool:
        """Check if this is a warning (non-blocking)."""
        return self.severity == Severity.WARNING

    def __str__(self) -> str:
        """String representation."""
        prefix = "ERROR" if self.is_error else "WARNING"
        code_str = f" [{self.code.name}]" if self.code else ""
        objects = f" [{', '.join(self.affected_objects)}]" if self.affected_objects else ""
        return f"{prefix}{code_str}: {self.title}{objects} - {self.message}"


[docs] @dataclass class ValidationResult: """Result of robot validation.""" issues: list[ValidationIssue] = field(default_factory=list) robot_name: str = "" @property def errors(self) -> list[ValidationIssue]: """Get only errors.""" return [issue for issue in self.issues if issue.is_error] @property def warnings(self) -> list[ValidationIssue]: """Get only warnings.""" return [issue for issue in self.issues if issue.is_warning] @property def is_valid(self) -> bool: """Check if validation passed (no errors). Note: Warnings don't block validity. """ return len(self.errors) == 0 @property def has_warnings(self) -> bool: """Check if there are any warnings.""" return len(self.warnings) > 0 @property def error_count(self) -> int: """Number of errors.""" return len(self.errors) @property def warning_count(self) -> int: """Number of warnings.""" return len(self.warnings)
[docs] def add_error( self, title: str, message: str, affected_objects: list[str] | None = None, suggestion: str | None = None, code: ValidationErrorCode | None = None, auto_fix: Callable[[], None] | None = None, ) -> None: """Add an error to the validation result.""" self.issues.append( ValidationIssue( severity=Severity.ERROR, title=title, message=message, affected_objects=affected_objects or [], suggestion=suggestion, code=code, auto_fix=auto_fix, ) )
[docs] def add_warning( self, title: str, message: str, affected_objects: list[str] | None = None, suggestion: str | None = None, code: ValidationErrorCode | None = None, auto_fix: Callable[[], None] | None = None, ) -> None: """Add a warning to the validation result.""" self.issues.append( ValidationIssue( severity=Severity.WARNING, title=title, message=message, affected_objects=affected_objects or [], suggestion=suggestion, code=code, auto_fix=auto_fix, ) )
[docs] def __str__(self) -> str: """String representation.""" if self.is_valid and not self.has_warnings: return f"Robot '{self.robot_name}' is valid" elif self.is_valid: return f"Robot '{self.robot_name}' is valid with {self.warning_count} warning(s)" else: return f"Robot '{self.robot_name}' has {self.error_count} error(s) and {self.warning_count} warning(s)"