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