Coverage for src/alprina_cli/utils/errors.py: 39%
57 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
1"""
2Friendly error messages for Alprina CLI.
4All errors are user-friendly with clear solutions.
5"""
7from rich.console import Console
8from rich.panel import Panel
9from rich import box
11console = Console()
14class AlprinaError(Exception):
15 """Base class for all Alprina CLI errors."""
17 def __init__(self, message: str, solution: str = None, title: str = "Error"):
18 self.message = message
19 self.solution = solution
20 self.title = title
21 super().__init__(message)
23 def display(self):
24 """Display error with solution in a nice panel."""
25 error_text = f"[bold red]❌ {self.message}[/bold red]"
27 if self.solution:
28 error_text += f"\n\n[yellow]💡 Solution:[/yellow]\n{self.solution}"
30 console.print(Panel.fit(
31 error_text,
32 title=self.title,
33 border_style="red",
34 box=box.ROUNDED
35 ))
38class AuthenticationError(AlprinaError):
39 """Raised when user is not authenticated."""
41 def __init__(self):
42 super().__init__(
43 message="You're not signed in",
44 solution="Run: [bold]alprina auth login[/bold]\n\n"
45 "Or get your API key from: https://platform.alprina.com/api-keys",
46 title="Authentication Required"
47 )
50class RateLimitError(AlprinaError):
51 """Raised when user hits rate limit."""
53 def __init__(self, limit: int, reset_time: str = "in 1 hour"):
54 super().__init__(
55 message=f"You've reached your scan limit ({limit} scans)",
56 solution=f"Your limit resets {reset_time}\n\n"
57 f"Or upgrade for more scans: [bold]https://alprina.com/pricing[/bold]",
58 title="Rate Limit Reached"
59 )
62class APIError(AlprinaError):
63 """Raised when API request fails."""
65 def __init__(self, status_code: int, message: str = None):
66 solutions = {
67 400: "Check your request parameters",
68 401: "Your API key is invalid or expired\nRun: [bold]alprina auth login[/bold]",
69 403: "You don't have permission for this action\nUpgrade at: https://alprina.com/pricing",
70 404: "The requested resource was not found",
71 429: "Too many requests. Please wait a moment and try again",
72 500: "Alprina server error. Please try again later\nIf this persists, contact support@alprina.com",
73 503: "Alprina service is temporarily unavailable\nCheck status: https://status.alprina.com"
74 }
76 default_message = message or f"API request failed with status {status_code}"
77 solution = solutions.get(status_code, "Please try again later\nIf this persists, contact support@alprina.com")
79 super().__init__(
80 message=default_message,
81 solution=solution,
82 title=f"API Error ({status_code})"
83 )
86class FileNotFoundError(AlprinaError):
87 """Raised when target file/directory doesn't exist."""
89 def __init__(self, path: str):
90 super().__init__(
91 message=f"File or directory not found: {path}",
92 solution="Check the path and try again\n\n"
93 "Examples:\n"
94 " [bold]alprina scan ./[/bold] - Scan current directory\n"
95 " [bold]alprina scan app.py[/bold] - Scan single file\n"
96 " [bold]alprina scan src/[/bold] - Scan src directory",
97 title="File Not Found"
98 )
101class NetworkError(AlprinaError):
102 """Raised when network request fails."""
104 def __init__(self, details: str = None):
105 message = "Cannot connect to Alprina API"
106 if details:
107 message += f": {details}"
109 super().__init__(
110 message=message,
111 solution="Check your internet connection\n\n"
112 "If you're behind a proxy, set these environment variables:\n"
113 " [bold]HTTP_PROXY=http://proxy:port[/bold]\n"
114 " [bold]HTTPS_PROXY=https://proxy:port[/bold]\n\n"
115 "Still having issues? support@alprina.com",
116 title="Network Error"
117 )
120class InvalidTierError(AlprinaError):
121 """Raised when user's tier doesn't support a feature."""
123 def __init__(self, feature: str, required_tier: str):
124 super().__init__(
125 message=f"This feature requires {required_tier} tier",
126 solution=f"Upgrade to unlock {feature}:\n"
127 f"[bold]https://alprina.com/pricing[/bold]\n\n"
128 f"Or contact sales for custom plans:\n"
129 f"sales@alprina.com",
130 title="Upgrade Required"
131 )
134class ScanError(AlprinaError):
135 """Raised when scan fails."""
137 def __init__(self, reason: str = None):
138 message = "Security scan failed"
139 if reason:
140 message += f": {reason}"
142 super().__init__(
143 message=message,
144 solution="Try again with verbose mode for more details:\n"
145 "[bold]alprina scan ./ --verbose[/bold]\n\n"
146 "Or check logs at:\n"
147 "~/.alprina/logs/alprina.log",
148 title="Scan Failed"
149 )
152def handle_error(error: Exception, verbose: bool = False):
153 """
154 Handle any error and display it nicely.
156 Args:
157 error: The exception to handle
158 verbose: Show full traceback
159 """
160 if isinstance(error, AlprinaError):
161 error.display()
162 elif isinstance(error, KeyboardInterrupt):
163 console.print("\n[yellow]⚠️ Operation cancelled by user[/yellow]")
164 elif isinstance(error, FileNotFoundError):
165 FileNotFoundError(str(error)).display()
166 else:
167 # Generic error
168 console.print(Panel.fit(
169 f"[bold red]❌ Unexpected error:[/bold red]\n\n"
170 f"{str(error)}\n\n"
171 f"[yellow]💡 What to do:[/yellow]\n"
172 f"1. Try running with [bold]--verbose[/bold] for more details\n"
173 f"2. Check logs at [bold]~/.alprina/logs/alprina.log[/bold]\n"
174 f"3. Report this bug: [bold]https://github.com/alprina/issues[/bold]",
175 title="Error",
176 border_style="red",
177 box=box.ROUNDED
178 ))
180 if verbose:
181 import traceback
182 console.print("\n[dim]Full traceback:[/dim]")
183 console.print(traceback.format_exc())