Coverage for src / qdrant_loader / config / error_formatter.py: 94%
66 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 04:48 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 04:48 +0000
1"""User-friendly configuration error formatting with Rich."""
3from __future__ import annotations
5from typing import Any
7from rich.console import Console
8from rich.panel import Panel
9from rich.table import Table
11_stderr_console = Console(stderr=True)
14def format_validation_errors(errors: list[dict[str, Any]]) -> Table:
15 """Format Pydantic validation errors as a Rich table."""
16 table = Table(title="Configuration Errors", show_lines=True)
17 table.add_column("Field", style="red", no_wrap=True)
18 table.add_column("Error", style="yellow")
19 table.add_column("Suggestion", style="green")
21 for err in errors:
22 loc = " -> ".join(str(part) for part in err.get("loc", []))
23 msg = err.get("msg", "Unknown error")
24 suggestion = _suggest_fix(loc, msg)
25 table.add_row(loc or "(root)", msg, suggestion)
27 return table
30def print_config_error(error: Exception) -> None:
31 """Print a user-friendly configuration error to stderr using Rich."""
32 try:
33 from pydantic import ValidationError
35 if isinstance(error, ValidationError):
36 _stderr_console.print(
37 Panel(
38 "[bold red]Configuration validation failed[/bold red]",
39 style="red",
40 )
41 )
42 table = format_validation_errors(error.errors())
43 _stderr_console.print(table)
44 _stderr_console.print(
45 "\n[dim]Tip: Run 'qdrant-loader setup' to generate a valid configuration.[/dim]"
46 )
47 return
48 except ImportError:
49 pass
51 try:
52 import yaml
54 if isinstance(error, yaml.YAMLError):
55 msg = "YAML syntax error in configuration file"
56 mark = getattr(error, "problem_mark", None)
57 if mark is not None:
58 msg += f" at line {mark.line + 1}, column {mark.column + 1}"
59 _stderr_console.print(
60 Panel(
61 f"[bold red]{msg}[/bold red]\n\n"
62 f"[yellow]{str(error)}[/yellow]\n\n"
63 "[green]Suggestion:[/green] Check YAML syntax. Common issues:\n"
64 " - Incorrect indentation (use spaces, not tabs)\n"
65 " - Missing colons after keys\n"
66 " - Unquoted special characters",
67 title="YAML Error",
68 style="red",
69 )
70 )
71 return
72 except ImportError:
73 pass
75 if isinstance(error, FileNotFoundError):
76 _stderr_console.print(
77 Panel(
78 "[bold red]Configuration file not found[/bold red]\n\n"
79 f"[yellow]{str(error)}[/yellow]\n\n"
80 "[green]Suggestions:[/green]\n"
81 " - Run 'qdrant-loader setup' to generate configuration files\n"
82 " - Create config.yaml manually\n"
83 " - Specify path with --config flag",
84 title="File Not Found",
85 style="red",
86 )
87 )
88 return
90 if isinstance(error, ValueError):
91 _stderr_console.print(
92 Panel(
93 "[bold red]Configuration error[/bold red]\n\n"
94 f"[yellow]{str(error)}[/yellow]\n\n"
95 f"[green]Suggestion:[/green] {_suggest_fix('', str(error))}",
96 title="Validation Error",
97 style="red",
98 )
99 )
100 return
102 # Generic fallback for any other exception type
103 _stderr_console.print(
104 Panel(
105 "[bold red]Configuration error[/bold red]\n\n"
106 f"[yellow]{type(error).__name__}: {str(error)}[/yellow]",
107 title="Error",
108 style="red",
109 )
110 )
113def _suggest_fix(field: str, message: str) -> str:
114 """Return a human-readable fix suggestion based on the error field and message."""
115 msg_lower = message.lower()
116 field_lower = field.lower()
118 if "qdrant" in field_lower and "api_key" in field_lower:
119 return "Set QDRANT_API_KEY in .env or environment"
120 if "api_key" in field_lower or "api_key" in msg_lower:
121 return "Set OPENAI_API_KEY in .env or environment"
122 if "collection_name" in field_lower:
123 return "Set QDRANT_COLLECTION_NAME or use default 'documents'"
124 if "url" in field_lower and "qdrant" in field_lower:
125 return "Set QDRANT_URL or use default http://localhost:6333"
126 if "sources" in field_lower or "source" in msg_lower:
127 return "Add at least one source under 'sources:' in config.yaml"
128 if "database_path" in field_lower:
129 return "Set STATE_DB_PATH or use default ./state.db"
130 if "chunk_size" in field_lower or "chunk" in msg_lower:
131 return "chunk_size must be a positive integer (recommended: 1000-2000)"
132 if "required" in msg_lower:
133 return f"Provide a value for '{field}' in config.yaml or .env"
135 return "Check the configuration documentation"