-
Notifications
You must be signed in to change notification settings - Fork 1
ERROR_HANDLING_SYSTEM
Complete implementation of the centralized error handling system for nself-admin.
The error handling system provides:
- 100+ error codes organized by category
- User-friendly error messages with actionable guidance
- Automatic retry logic with exponential backoff
- React error boundaries for graceful error handling
- Network status detection and offline support
- Type-safe error handling utilities
src/lib/errors/
├── codes.ts - 100+ ErrorCode enum values
├── messages.ts - Error code → user message mapping
├── ApiError.ts - Custom error class
├── ErrorBoundary.tsx - React error boundary
└── utils.ts - Error handling utilities
src/lib/
└── api-client.ts - Fetch wrapper with retry logic
src/components/
├── ErrorDisplay.tsx - Error UI component
└── OfflineBanner.tsx - Network status banner
-
NETWORK_ERROR(1000) - Network connection failed -
TIMEOUT(1001) - Request timed out -
OFFLINE(1002) - User is offline -
CONNECTION_REFUSED(1003) - Cannot connect to server -
DNS_LOOKUP_FAILED(1004) - DNS resolution failed -
SSL_ERROR(1005) - SSL certificate error -
REQUEST_ABORTED(1006) - Request was cancelled -
RATE_LIMITED(1007) - Too many requests
-
UNAUTHORIZED(2000) - Not authorized -
SESSION_EXPIRED(2001) - Session expired -
INVALID_CREDENTIALS(2002) - Invalid password -
PASSWORD_TOO_WEAK(2003) - Password requirements not met -
PASSWORD_MISMATCH(2004) - Passwords don't match -
CSRF_TOKEN_INVALID(2005) - Invalid CSRF token -
CSRF_TOKEN_MISSING(2006) - Missing CSRF token -
SESSION_NOT_FOUND(2007) - Session not found -
AUTHENTICATION_REQUIRED(2008) - Login required -
FORBIDDEN(2009) - Permission denied -
TOO_MANY_LOGIN_ATTEMPTS(2010) - Rate limited login
-
CLI_NOT_FOUND(3000) - nself CLI not installed -
CLI_EXECUTION_FAILED(3001) - CLI command failed -
CLI_TIMEOUT(3002) - CLI command timed out -
CLI_INVALID_COMMAND(3003) - Invalid CLI syntax -
CLI_PERMISSION_DENIED(3004) - Insufficient permissions -
CLI_OUTPUT_PARSE_ERROR(3005) - Cannot parse CLI output -
CLI_VERSION_MISMATCH(3006) - Incompatible CLI version -
CLI_NOT_INSTALLED(3007) - CLI not installed -
CLI_COMMAND_NOT_FOUND(3008) - Command not found -
CLI_INSUFFICIENT_PERMISSIONS(3009) - Needs elevated privileges
-
DOCKER_NOT_RUNNING(4000) - Docker not running -
CONTAINER_NOT_FOUND(4001) - Container not found -
CONTAINER_START_FAILED(4002) - Failed to start -
CONTAINER_STOP_FAILED(4003) - Failed to stop -
CONTAINER_RESTART_FAILED(4004) - Failed to restart -
CONTAINER_REMOVE_FAILED(4005) - Failed to remove -
IMAGE_NOT_FOUND(4006) - Image not found -
IMAGE_PULL_FAILED(4007) - Failed to pull image -
DOCKER_NETWORK_ERROR(4008) - Network error -
DOCKER_VOLUME_ERROR(4009) - Volume error -
DOCKER_COMPOSE_ERROR(4010) - docker-compose error -
DOCKER_PERMISSION_DENIED(4011) - Permission denied -
DOCKER_OUT_OF_MEMORY(4012) - Out of memory -
DOCKER_OUT_OF_DISK_SPACE(4013) - Out of disk space
-
DB_CONNECTION_FAILED(5000) - Cannot connect to database -
QUERY_ERROR(5001) - Query failed -
DB_NOT_INITIALIZED(5002) - Database not initialized -
DB_TIMEOUT(5003) - Query timed out -
DB_SYNTAX_ERROR(5004) - SQL syntax error -
DB_CONSTRAINT_VIOLATION(5005) - Constraint violation -
DB_TABLE_NOT_FOUND(5006) - Table not found -
DB_COLUMN_NOT_FOUND(5007) - Column not found -
DB_MIGRATION_FAILED(5008) - Migration failed -
DB_SEED_FAILED(5009) - Seeding failed -
DB_BACKUP_FAILED(5010) - Backup failed -
DB_RESTORE_FAILED(5011) - Restore failed -
DB_PERMISSION_DENIED(5012) - Permission denied -
DB_TRANSACTION_FAILED(5013) - Transaction failed
-
VALIDATION_ERROR(6000) - Validation failed -
INVALID_INPUT(6001) - Invalid input -
REQUIRED_FIELD_MISSING(6002) - Required field missing -
INVALID_FORMAT(6003) - Invalid format -
VALUE_OUT_OF_RANGE(6004) - Value out of range -
INVALID_EMAIL(6005) - Invalid email address -
INVALID_URL(6006) - Invalid URL -
INVALID_PORT(6007) - Invalid port number -
INVALID_PATH(6008) - Invalid file path -
INVALID_JSON(6009) - Invalid JSON -
INVALID_ENV_VAR_NAME(6010) - Invalid env var name -
INVALID_SERVICE_NAME(6011) - Invalid service name -
DUPLICATE_KEY(6012) - Duplicate key -
INVALID_FILE_TYPE(6013) - Invalid file type -
FILE_TOO_LARGE(6014) - File too large
-
FILE_NOT_FOUND(7000) - File not found -
FILE_READ_ERROR(7001) - Cannot read file -
FILE_WRITE_ERROR(7002) - Cannot write file -
FILE_DELETE_ERROR(7003) - Cannot delete file -
DIRECTORY_NOT_FOUND(7004) - Directory not found -
DIRECTORY_CREATE_ERROR(7005) - Cannot create directory -
PERMISSION_DENIED(7006) - Permission denied -
PATH_TRAVERSAL_DETECTED(7007) - Path traversal detected -
DISK_FULL(7008) - Disk is full -
FILE_ALREADY_EXISTS(7009) - File already exists
-
SERVICE_NOT_FOUND(8000) - Service not found -
SERVICE_START_FAILED(8001) - Failed to start -
SERVICE_STOP_FAILED(8002) - Failed to stop -
SERVICE_RESTART_FAILED(8003) - Failed to restart -
SERVICE_HEALTH_CHECK_FAILED(8004) - Health check failed -
SERVICE_DEPENDENCY_FAILED(8005) - Dependency failed -
SERVICE_ALREADY_RUNNING(8006) - Already running -
SERVICE_ALREADY_STOPPED(8007) - Already stopped -
SERVICE_CONFIG_INVALID(8008) - Invalid configuration -
SERVICE_PORT_CONFLICT(8009) - Port conflict
-
PROJECT_NOT_INITIALIZED(9000) - Project not initialized -
PROJECT_INITIALIZATION_FAILED(9001) - Init failed -
PROJECT_BUILD_FAILED(9002) - Build failed -
PROJECT_VALIDATION_FAILED(9003) - Validation failed -
PROJECT_CONFIG_NOT_FOUND(9004) - Config not found -
PROJECT_CONFIG_INVALID(9005) - Config invalid -
PROJECT_PATH_INVALID(9006) - Path invalid -
PROJECT_ALREADY_INITIALIZED(9007) - Already initialized
-
DEPLOYMENT_FAILED(10000) - Deployment failed -
DEPLOYMENT_VALIDATION_FAILED(10001) - Validation failed -
DEPLOYMENT_ROLLBACK_FAILED(10002) - Rollback failed -
ENVIRONMENT_NOT_FOUND(10003) - Environment not found -
ENVIRONMENT_CONFIG_INVALID(10004) - Config invalid -
DEPLOYMENT_TIMEOUT(10005) - Deployment timed out -
DEPLOYMENT_HEALTH_CHECK_FAILED(10006) - Health check failed
-
PLUGIN_NOT_FOUND(11000) - Plugin not found -
PLUGIN_INSTALL_FAILED(11001) - Install failed -
PLUGIN_UNINSTALL_FAILED(11002) - Uninstall failed -
PLUGIN_ACTIVATION_FAILED(11003) - Activation failed -
PLUGIN_DEACTIVATION_FAILED(11004) - Deactivation failed -
PLUGIN_CONFIG_INVALID(11005) - Config invalid -
PLUGIN_VERSION_INCOMPATIBLE(11006) - Version incompatible -
PLUGIN_DEPENDENCY_MISSING(11007) - Dependency missing
-
BACKUP_CREATE_FAILED(12000) - Backup creation failed -
BACKUP_NOT_FOUND(12001) - Backup not found -
BACKUP_RESTORE_FAILED(12002) - Restore failed -
BACKUP_VERIFY_FAILED(12003) - Verification failed -
BACKUP_CORRUPTED(12004) - Backup corrupted -
BACKUP_DELETE_FAILED(12005) - Delete failed
-
UNKNOWN_ERROR(99000) - Unknown error -
INTERNAL_SERVER_ERROR(99001) - Internal server error -
NOT_IMPLEMENTED(99002) - Not implemented -
OPERATION_CANCELLED(99003) - Operation cancelled -
OPERATION_TIMEOUT(99004) - Operation timed out
import { NextResponse } from 'next/server'
import { ApiError } from '@/lib/errors/ApiError'
import { ErrorCode } from '@/lib/errors/codes'
import { createErrorResponse } from '@/lib/errors/utils'
export async function GET() {
try {
// Your code here
const result = await someOperation()
return NextResponse.json({ success: true, data: result })
} catch (error) {
// Create ApiError if needed
const apiError =
error instanceof ApiError ? error : new ApiError(ErrorCode.UNKNOWN_ERROR, 'Operation failed')
// Return error response
return NextResponse.json(createErrorResponse(apiError), {
status: apiError.statusCode,
})
}
}import { api } from '@/lib/api-client'
// GET request with automatic retry
const response = await api.get('/api/services')
const data = await response.json()
// POST request with retry
const response = await api.post('/api/services/start', {
service: 'postgres',
})
// Disable retry for specific request
const response = await api.get('/api/status', { retry: false })
// Custom retry configuration
const response = await api.get('/api/data', {
maxRetries: 5,
timeout: 60000,
})import { ErrorDisplay } from '@/components/ErrorDisplay'
function MyComponent() {
const [error, setError] = useState(null)
const handleRetry = () => {
setError(null)
// Retry operation
}
return (
<>
{error && (
<ErrorDisplay
error={error}
title="Operation Failed"
onRetry={handleRetry}
onDismiss={() => setError(null)}
showDetails={true}
/>
)}
</>
)
}import { ErrorBoundary } from '@/lib/errors/ErrorBoundary'
function App() {
return (
<ErrorBoundary
onError={(error, errorInfo) => {
// Log to error tracking service
console.error('Error caught:', error, errorInfo)
}}
>
<YourApp />
</ErrorBoundary>
)
}import { OfflineBanner } from '@/components/OfflineBanner'
function Layout({ children }) {
return (
<>
<OfflineBanner />
{children}
</>
)
}All API errors follow this format:
{
success: false,
error: {
code: ErrorCode, // Numeric error code
message: string, // Technical message
userMessage: string, // User-friendly message
action?: string, // What to do next
retryable: boolean, // Can retry this operation?
details?: { // Additional context
[key: string]: any
}
}
}- Max retries: 3 attempts
- Backoff: Exponential (1s, 2s, 4s)
- Retryable errors: Network errors (1xxx)
- Non-retryable errors: Auth (2xxx), Validation (6xxx)
- First attempt fails → Wait 1 second
- Second attempt fails → Wait 2 seconds
- Third attempt fails → Wait 4 seconds
- Fourth attempt fails → Throw error
// Disable retry
await api.get('/api/data', { retry: false })
// Custom max retries
await api.get('/api/data', { maxRetries: 5 })
// Custom timeout
await api.get('/api/data', { timeout: 60000 })Extract user-friendly error message from any error type.
Extract technical error message for logging.
Check if error can be retried.
Determine error code from error object.
Check if error is a network error (1xxx).
Check if error is an auth error (2xxx).
Check if error is a validation error (6xxx).
Format error for structured logging.
Create standardized error response for API routes.
Convert Node.js errno errors to ApiError.
import { ApiError } from '@/lib/errors/ApiError'
import { ErrorCode } from '@/lib/errors/codes'
describe('ApiError', () => {
it('creates error with code', () => {
const error = new ApiError(ErrorCode.NETWORK_ERROR)
expect(error.code).toBe(ErrorCode.NETWORK_ERROR)
expect(error.retryable).toBe(true)
})
it('maps status code correctly', () => {
const error = new ApiError(ErrorCode.UNAUTHORIZED)
expect(error.statusCode).toBe(401)
})
})test('handles network error with retry', async () => {
// Mock network failure
mockFetch.mockRejectedValueOnce(new Error('Network error'))
mockFetch.mockResolvedValueOnce({ ok: true, json: () => ({}) })
// Should retry and succeed
const response = await api.get('/api/data')
expect(response.ok).toBe(true)
})
test('shows offline banner when offline', async () => {
// Simulate going offline
window.dispatchEvent(new Event('offline'))
// Banner should appear
expect(screen.getByText(/offline/i)).toBeInTheDocument()
})// Old: Generic error handling
try {
const response = await fetch('/api/data')
if (!response.ok) {
throw new Error('Failed to fetch')
}
} catch (error) {
console.error(error)
toast.error('Something went wrong')
}// New: Structured error handling with retry
import { api } from '@/lib/api-client'
import { ErrorDisplay } from '@/components/ErrorDisplay'
try {
const response = await api.get('/api/data') // Auto-retry
const data = await response.json()
} catch (error) {
return <ErrorDisplay error={error} onRetry={handleRetry} />
}- Always use ApiError in API routes - Don't throw generic Error
- Use api client for all requests - Gets retry logic and error handling
- Show ErrorDisplay for user errors - Consistent error UI
- Log technical details - Use formatErrorForLogging()
- Don't retry auth/validation errors - Already handled automatically
- Provide actionable error messages - Tell users what to do next
- Use ErrorBoundary at app root - Catch unexpected errors
- Test error scenarios - Unit tests for all error codes
✅ Lint: 0 errors, 0 warnings ✅ Format: All files formatted with Prettier ✅ Type-check: Passing (pending ua-parser-js dependency)
-
/src/lib/errors/codes.ts(100+ error codes) -
/src/lib/errors/messages.ts(Error messages mapping) -
/src/lib/errors/ApiError.ts(Custom error class) -
/src/lib/errors/ErrorBoundary.tsx(React error boundary) -
/src/lib/errors/utils.ts(Error utilities) -
/src/lib/api-client.ts(Enhanced with retry logic) -
/src/components/ErrorDisplay.tsx(Error UI component) -
/src/components/OfflineBanner.tsx(Network status banner) -
/docs/ERROR_HANDLING_SYSTEM.md(This documentation)
- Add ErrorBoundary to app layout
- Add OfflineBanner to app layout
- Replace all fetch calls with api client
- Replace generic error messages with ErrorDisplay
- Add error logging service integration (Sentry/etc)
- Create E2E tests for error scenarios
- Update API routes to use createErrorResponse()
Version: 1.0.0 | Updated: 2026-09-16 11:21 UTC | GitHub