204 lines
5.8 KiB
Text
204 lines
5.8 KiB
Text
|
---
|
||
|
description: Standards for consistent error handling patterns across frontend and backend
|
||
|
globs: **/*.{ts,tsx,js,jsx,go}
|
||
|
---
|
||
|
<rule>
|
||
|
filters:
|
||
|
- type: file_extension
|
||
|
pattern: "\\.(ts|tsx|js|jsx|go)$"
|
||
|
- type: content
|
||
|
pattern: "(?s)try|catch|error|err|throw|panic|recover"
|
||
|
|
||
|
actions:
|
||
|
- type: suggest
|
||
|
message: |
|
||
|
# Error Handling Standards
|
||
|
|
||
|
## Frontend (TypeScript/JavaScript)
|
||
|
|
||
|
1. Always use typed error handling:
|
||
|
```typescript
|
||
|
try {
|
||
|
// Operation that might fail
|
||
|
} catch (error) {
|
||
|
if (error instanceof ApiError) {
|
||
|
// Handle API errors
|
||
|
} else if (error instanceof ValidationError) {
|
||
|
// Handle validation errors
|
||
|
} else {
|
||
|
// Handle unexpected errors
|
||
|
console.error('Unexpected error:', error);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
2. Define custom error classes:
|
||
|
```typescript
|
||
|
class ApplicationError extends Error {
|
||
|
constructor(message: string) {
|
||
|
super(message);
|
||
|
this.name = 'ApplicationError';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class ApiError extends ApplicationError {
|
||
|
statusCode: number;
|
||
|
|
||
|
constructor(message: string, statusCode: number) {
|
||
|
super(message);
|
||
|
this.name = 'ApiError';
|
||
|
this.statusCode = statusCode;
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
3. For async functions, always use try/catch with async/await:
|
||
|
```typescript
|
||
|
async function fetchData() {
|
||
|
try {
|
||
|
const response = await api.get('/endpoint');
|
||
|
return response.data;
|
||
|
} catch (error) {
|
||
|
handleError(error);
|
||
|
throw error; // Re-throw if needed
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
4. For React components, implement error boundaries:
|
||
|
```tsx
|
||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||
|
|
||
|
function ErrorFallback({ error, resetErrorBoundary }) {
|
||
|
return (
|
||
|
<div role="alert">
|
||
|
<p>Something went wrong:</p>
|
||
|
<pre>{error.message}</pre>
|
||
|
<button onClick={resetErrorBoundary}>Try again</button>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function MyComponent() {
|
||
|
return (
|
||
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||
|
<ComponentThatMightError />
|
||
|
</ErrorBoundary>
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Backend (Go)
|
||
|
|
||
|
1. Return errors rather than using panic:
|
||
|
```go
|
||
|
func ProcessData(data []byte) (Result, error) {
|
||
|
if len(data) == 0 {
|
||
|
return Result{}, errors.New("empty data provided")
|
||
|
}
|
||
|
// Process data
|
||
|
return result, nil
|
||
|
}
|
||
|
```
|
||
|
|
||
|
2. Use error wrapping for context:
|
||
|
```go
|
||
|
import "fmt"
|
||
|
|
||
|
func FetchUserData(userID string) ([]byte, error) {
|
||
|
data, err := database.Query(userID)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("fetching user data: %w", err)
|
||
|
}
|
||
|
return data, nil
|
||
|
}
|
||
|
```
|
||
|
|
||
|
3. Use custom error types for specific cases:
|
||
|
```go
|
||
|
type NotFoundError struct {
|
||
|
Resource string
|
||
|
ID string
|
||
|
}
|
||
|
|
||
|
func (e NotFoundError) Error() string {
|
||
|
return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID)
|
||
|
}
|
||
|
|
||
|
// Usage
|
||
|
if data == nil {
|
||
|
return NotFoundError{Resource: "User", ID: userID}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
4. Check errors immediately:
|
||
|
```go
|
||
|
resp, err := http.Get(url)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
```
|
||
|
|
||
|
## General Principles
|
||
|
|
||
|
1. Log errors appropriately:
|
||
|
- Debug: For development information
|
||
|
- Info: For tracking normal operation
|
||
|
- Warn: For potential issues
|
||
|
- Error: For actual errors affecting operation
|
||
|
- Fatal: For errors requiring application shutdown
|
||
|
|
||
|
2. Don't expose system errors to users:
|
||
|
- Log the technical details
|
||
|
- Return user-friendly messages
|
||
|
|
||
|
3. Include contextual information:
|
||
|
- What operation was being performed
|
||
|
- What resources were involved
|
||
|
- Any IDs or references that help identify the context
|
||
|
|
||
|
4. Handle all error cases - never silently ignore errors
|
||
|
|
||
|
metadata:
|
||
|
priority: high
|
||
|
version: 1.0
|
||
|
</rule>
|
||
|
|
||
|
examples:
|
||
|
- input: |
|
||
|
// Bad: Untyped error handling
|
||
|
try {
|
||
|
const data = await fetchData();
|
||
|
} catch (error) {
|
||
|
console.log(error);
|
||
|
}
|
||
|
output: |
|
||
|
// Good: Typed error handling with proper logging and user feedback
|
||
|
try {
|
||
|
const data = await fetchData();
|
||
|
} catch (error) {
|
||
|
if (error instanceof ApiError) {
|
||
|
toast.error('Could not connect to the server. Please try again later.');
|
||
|
logger.error('API Error:', { error, endpoint: '/api/data' });
|
||
|
} else {
|
||
|
toast.error('An unexpected error occurred.');
|
||
|
logger.error('Unexpected error:', { error });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- input: |
|
||
|
// Bad: No error handling in Go function
|
||
|
func GetUser(id string) User {
|
||
|
data := db.Find(id)
|
||
|
return User{Name: data.Name, Email: data.Email}
|
||
|
}
|
||
|
output: |
|
||
|
// Good: Proper error handling and propagation
|
||
|
func GetUser(id string) (User, error) {
|
||
|
data, err := db.Find(id)
|
||
|
if err != nil {
|
||
|
return User{}, fmt.Errorf("failed to find user %s: %w", id, err)
|
||
|
}
|
||
|
return User{Name: data.Name, Email: data.Email}, nil
|
||
|
}
|