Files

197 lines
5.5 KiB
Python

"""Custom exception classes for the boilerplates CLI.
This module defines specific exception types for better error handling
and diagnostics throughout the application.
"""
from __future__ import annotations
from dataclasses import dataclass, field
class BoilerplatesError(Exception):
"""Base exception for all boilerplates CLI errors."""
pass
class ConfigError(BoilerplatesError):
"""Raised when configuration operations fail."""
pass
class ConfigValidationError(ConfigError):
"""Raised when configuration validation fails."""
pass
class TemplateError(BoilerplatesError):
"""Base exception for template-related errors."""
pass
class TemplateNotFoundError(TemplateError):
"""Raised when a template cannot be found."""
def __init__(self, template_id: str, module_name: str | None = None):
self.template_id = template_id
self.module_name = module_name
msg = f"Template '{template_id}' not found"
if module_name:
msg += f" in module '{module_name}'"
super().__init__(msg)
class DuplicateTemplateError(TemplateError):
"""Raised when duplicate template IDs are found within the same library."""
def __init__(self, template_id: str, library_name: str):
self.template_id = template_id
self.library_name = library_name
super().__init__(
f"Duplicate template ID '{template_id}' found in library '{library_name}'. "
f"Each template within a library must have a unique ID."
)
class TemplateLoadError(TemplateError):
"""Raised when a template fails to load."""
pass
class TemplateSyntaxError(TemplateError):
"""Raised when a Jinja2 template has syntax errors."""
def __init__(self, template_id: str, errors: list[str]):
self.template_id = template_id
self.errors = errors
msg = f"Jinja2 syntax errors in template '{template_id}':\n" + "\n".join(errors)
super().__init__(msg)
class TemplateValidationError(TemplateError):
"""Raised when template validation fails."""
pass
@dataclass
class RenderErrorContext:
"""Context information for template rendering errors."""
file_path: str | None = None
line_number: int | None = None
column: int | None = None
context_lines: list[str] = field(default_factory=list)
variable_context: dict[str, str] = field(default_factory=dict)
suggestions: list[str] = field(default_factory=list)
original_error: Exception | None = None
class TemplateRenderError(TemplateError):
"""Raised when template rendering fails."""
def __init__(self, message: str, context: RenderErrorContext | None = None):
self.context = context or RenderErrorContext()
# Expose context fields as instance attributes for backward compatibility
self.file_path = self.context.file_path
self.line_number = self.context.line_number
self.column = self.context.column
self.context_lines = self.context.context_lines
self.variable_context = self.context.variable_context
self.suggestions = self.context.suggestions
self.original_error = self.context.original_error
# Build enhanced error message
parts = [message]
if self.context.file_path:
location = f"File: {self.context.file_path}"
if self.context.line_number:
location += f", Line: {self.context.line_number}"
if self.context.column:
location += f", Column: {self.context.column}"
parts.append(location)
super().__init__("\n".join(parts))
class VariableError(BoilerplatesError):
"""Base exception for variable-related errors."""
pass
class VariableValidationError(VariableError):
"""Raised when variable validation fails."""
def __init__(self, variable_name: str, message: str):
self.variable_name = variable_name
msg = f"Validation error for variable '{variable_name}': {message}"
super().__init__(msg)
class VariableTypeError(VariableError):
"""Raised when a variable has an incorrect type."""
def __init__(self, variable_name: str, expected_type: str, actual_type: str):
self.variable_name = variable_name
self.expected_type = expected_type
self.actual_type = actual_type
msg = f"Type error for variable '{variable_name}': expected {expected_type}, got {actual_type}"
super().__init__(msg)
class LibraryError(BoilerplatesError):
"""Raised when library operations fail."""
pass
class ModuleError(BoilerplatesError):
"""Raised when module operations fail."""
pass
class ModuleNotFoundError(ModuleError):
"""Raised when a module cannot be found."""
def __init__(self, module_name: str):
self.module_name = module_name
msg = f"Module '{module_name}' not found"
super().__init__(msg)
class ModuleLoadError(ModuleError):
"""Raised when a module fails to load."""
pass
class FileOperationError(BoilerplatesError):
"""Raised when file operations fail."""
pass
class RenderError(BoilerplatesError):
"""Raised when rendering operations fail."""
pass
class YAMLParseError(BoilerplatesError):
"""Raised when YAML parsing fails."""
def __init__(self, file_path: str, original_error: Exception):
self.file_path = file_path
self.original_error = original_error
msg = f"Failed to parse YAML file '{file_path}': {original_error}"
super().__init__(msg)