Error Handling Guide
This guide explains effective error handling strategies for the content system. It covers error types, handling patterns, recovery strategies, and best practices.
Understanding the Error System
The content system uses a structured error hierarchy that provides rich context and recovery options.
Error Hierarchy
All errors in the system extend from ContentError
:
ContentError (base error class)
├── ContentNotFoundError
├── ContentAccessError
├── ContentValidationError
├── ContentFormatError
├── ContentOperationError
│ ├── ContentReadError
│ ├── ContentWriteError
│ ├── ContentDeleteError
│ └── ContentListError
├── ContentAdapterError
│ ├── AdapterInitializationError
│ ├── AdapterOperationError
│ └── AdapterDisposeError
└── ContentRegistryError
├── StoreNotFoundError
├── AdapterNotFoundError
└── PluginNotFoundError
Error Context
All errors provide rich context information:
// Base error properties
interface ContentError extends Error {
// Basic information
name: string
message: string
stack?: string
// Context information
uri?: string
operation?: string
source?: string
// Recovery options
recoverable: boolean
recovery?: () => Promise<any>
// Additional information
cause?: Error
info?: Record<string, any>
}
Basic Error Handling
Try-Catch Pattern
The simplest error handling approach:
try {
// Attempt to read content
const content = await store.read('articles/welcome.md')
// Process content if read succeeds
displayContent(content)
} catch (error) {
// Handle the error
console.error('Failed to read content:', error.message)
// Show error message to user
showErrorNotification('Could not load the article')
}
Error Type Checking
Handle specific error types differently:
try {
await store.write('articles/draft.md', newContent)
} catch (error) {
if (error instanceof ContentValidationError) {
// Handle validation errors
displayValidationErrors(error.info.validationErrors)
} else if (error instanceof ContentAccessError) {
// Handle permission errors
promptForAuthentication()
} else {
// Handle other errors
logError(error)
showGenericErrorMessage()
}
}
Error Properties
Access error properties for detailed handling:
try {
await store.delete('articles/protected.md')
} catch (error) {
if (error instanceof ContentError) {
console.error(`Error in ${error.operation} operation on ${error.uri}`)
console.error(`Source: ${error.source}`)
if (error.cause) {
console.error('Caused by:', error.cause)
}
if (error.info) {
console.error('Additional info:', error.info)
}
}
}
Advanced Error Handling
Automatic Recovery
Use the recovery options provided by errors:
try {
const content = await store.read('articles/cached.md')
displayContent(content)
} catch (error) {
if (error instanceof ContentError && error.recoverable && error.recovery) {
try {
// Attempt automatic recovery
const recoveredContent = await error.recovery()
displayContent(recoveredContent)
showNotification('Using cached version')
} catch (recoveryError) {
// Recovery failed
console.error('Recovery failed:', recoveryError)
showErrorNotification('Could not load content, even from cache')
}
} else {
// Unrecoverable error
showErrorNotification('Could not load content')
}
}
Error Boundaries
Create error boundaries for component-level handling:
class ContentErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Log error details
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
const error = this.state.error;
// Specific error handling based on type
if (error instanceof ContentNotFoundError) {
return <NotFoundMessage uri={error.uri} />;
} else if (error instanceof ContentAccessError) {
return <AccessDeniedMessage uri={error.uri} />;
} else {
return <GenericErrorMessage error={error} />;
}
}
return this.props.children;
}
}
// Usage
function ArticleView({ uri }) {
return (
<ContentErrorBoundary>
<ContentDisplay uri={uri} />
</ContentErrorBoundary>
);
}
Fallback Content
Provide fallback content for errors:
async function loadContentWithFallback(uri) {
try {
// Attempt to load content
return await store.read(uri)
} catch (error) {
if (error instanceof ContentNotFoundError) {
// Return empty content with not found notice
return {
data: '# Not Found\n\nThe requested content could not be found.',
contentType: 'text/markdown',
metadata: { title: 'Not Found' },
}
} else if (error instanceof ContentAccessError) {
// Return access denied notice
return {
data: '# Access Denied\n\nYou do not have permission to view this content.',
contentType: 'text/markdown',
metadata: { title: 'Access Denied' },
}
} else {
// Log other errors and return generic error notice
console.error('Error loading content:', error)
return {
data: '# Error\n\nAn error occurred while loading this content.',
contentType: 'text/markdown',
metadata: { title: 'Error' },
}
}
}
}
Error Prevention Strategies
Content Validation
Validate content before writing:
import { validateContent } from '@lib/content/validation'
async function saveContent(uri, content) {
// Validate content before attempting to write
const validationResult = await validateContent(content, {
schema: articleSchema,
strict: true,
})
if (!validationResult.valid) {
// Handle validation errors without throwing
displayValidationErrors(validationResult.errors)
return false
}
try {
// Write pre-validated content
await store.write(uri, content)
return true
} catch (error) {
// Only network/permission errors should reach here
handleOperationError(error)
return false
}
}
Existence Checking
Check if content exists before operations:
async function updateOrCreateContent(uri, content) {
try {
// Check if content exists
const exists = await store.exists(uri)
if (exists) {
// Update existing content
await store.write(uri, content, { overwrite: true })
showNotification('Content updated')
} else {
// Create new content
await store.write(uri, content, { createDirectories: true })
showNotification('Content created')
}
return true
} catch (error) {
handleOperationError(error)
return false
}
}
Defensive Options
Use operation options to prevent errors:
// Prevent "not found" errors
const content = await store.read('articles/optional.md', {
failIfNotFound: false,
defaultContent: {
data: '# Empty Article',
contentType: 'text/markdown',
metadata: { title: 'Empty Article' },
},
})
// Prevent "already exists" errors
await store.write('articles/new.md', newContent, {
failIfExists: false,
overwrite: false,
})
// Prevent "not found" errors during delete
await store.delete('articles/maybe-exists.md', {
failIfNotFound: false,
})
Error Handling Middleware
Creating Error Handling Middleware
Custom middleware for centralized error handling:
function errorHandlingMiddleware(options = {}) {
return async (context, next) => {
try {
// Attempt the operation
return await next()
} catch (error) {
// Log the error
console.error(`Error in ${context.operation} operation:`, error)
// Enrich error with context
if (error instanceof ContentError) {
error.operation = error.operation || context.operation
error.uri = error.uri || context.uri
}
// Specific handling based on error type
if (error instanceof ContentNotFoundError && options.provideFallback) {
// Return fallback content
context.content = getFallbackContent(context.uri)
return context
}
// Rethrow to propagate to caller
throw error
}
}
}
// Apply middleware to store
const store = createContentStore({
adapter: createFileSystemAdapter({ basePath: '/content' }),
middleware: [errorHandlingMiddleware({ provideFallback: true })],
})
Retry Middleware
Middleware that automatically retries failed operations:
function retryMiddleware(options = { maxRetries: 3, delay: 500 }) {
return async (context, next) => {
let attempts = 0
while (true) {
try {
attempts++
return await next()
} catch (error) {
// Check if we should retry
const isRetryable =
!(error instanceof ContentValidationError) && // Don't retry validation errors
!(error instanceof ContentAccessError) && // Don't retry access errors
attempts <= options.maxRetries
if (!isRetryable) {
// Add retry information to error
if (error instanceof ContentError) {
error.info = {
...error.info,
retryAttempts: attempts,
}
}
throw error
}
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, options.delay))
// Exponential backoff
options.delay *= 2
}
}
}
}
Environment-Specific Error Handling
Node.js Error Handling
Error handling in Node.js applications:
// Global error handler for unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason)
// Attempt to identify content system errors
if (reason instanceof ContentError) {
// Log structured error information
logStructuredError(reason)
}
})
// Express error handling middleware
app.use((err, req, res, next) => {
if (err instanceof ContentNotFoundError) {
res.status(404).json({
error: 'Not Found',
message: err.message,
path: err.uri,
})
} else if (err instanceof ContentAccessError) {
res.status(403).json({
error: 'Access Denied',
message: err.message,
path: err.uri,
})
} else if (err instanceof ContentValidationError) {
res.status(400).json({
error: 'Validation Error',
message: err.message,
validationErrors: err.info?.validationErrors || [],
})
} else {
// Generic error
res.status(500).json({
error: 'Internal Server Error',
message: 'An error occurred while processing your request',
})
}
})
Browser Error Handling
Error handling in browser applications:
// Global error handler
window.addEventListener('error', event => {
// Check if it's a content system error
if (event.error instanceof ContentError) {
// Prevent default browser error handling
event.preventDefault()
// Log the error
console.error('Content system error:', event.error)
// Show user-friendly notification
showErrorNotification(
getUserFriendlyMessage(event.error),
event.error.recoverable
)
}
})
// Helper to get user-friendly error messages
function getUserFriendlyMessage(error) {
if (error instanceof ContentNotFoundError) {
return `The content "${error.uri}" could not be found.`
} else if (error instanceof ContentAccessError) {
return `You don't have permission to access "${error.uri}".`
} else if (error instanceof ContentValidationError) {
return `The content has validation errors.`
} else {
return `An error occurred: ${error.message}`
}
}
Error Logging and Monitoring
Structured Error Logging
Create structured logs for errors:
function logContentError(error) {
if (!(error instanceof ContentError)) {
return logGenericError(error)
}
const logEntry = {
timestamp: new Date().toISOString(),
errorType: error.name,
message: error.message,
operation: error.operation,
uri: error.uri,
source: error.source,
recoverable: error.recoverable,
info: error.info,
stack: error.stack,
}
// Add cause if available
if (error.cause) {
logEntry.cause = {
name: error.cause.name,
message: error.cause.message,
stack: error.cause.stack,
}
}
// Log to service
logger.error(logEntry)
// Send to monitoring service in production
if (process.env.NODE_ENV === 'production') {
errorMonitoringService.captureError(error, {
extra: logEntry,
})
}
}
Error Metrics
Collect metrics about errors:
class ErrorMetrics {
private counts = {
total: 0,
byType: {} as Record<string, number>,
byOperation: {} as Record<string, number>,
recoverable: 0,
nonRecoverable: 0,
}
recordError(error: Error) {
// Increment total
this.counts.total++
if (error instanceof ContentError) {
// Increment by type
const type = error.name
this.counts.byType[type] = (this.counts.byType[type] || 0) + 1
// Increment by operation
if (error.operation) {
this.counts.byOperation[error.operation] =
(this.counts.byOperation[error.operation] || 0) + 1
}
// Increment by recoverability
if (error.recoverable) {
this.counts.recoverable++
} else {
this.counts.nonRecoverable++
}
}
// Send metrics to monitoring service
if (this.counts.total % 10 === 0) {
this.flushMetrics()
}
}
flushMetrics() {
metricsService.gauge('content.errors.total', this.counts.total)
Object.entries(this.counts.byType).forEach(([type, count]) => {
metricsService.gauge(`content.errors.byType.${type}`, count)
})
metricsService.gauge('content.errors.recoverable', this.counts.recoverable)
metricsService.gauge(
'content.errors.nonRecoverable',
this.counts.nonRecoverable
)
}
}
// Create singleton instance
const errorMetrics = new ErrorMetrics()
// Register middleware
store.use(errorTrackingMiddleware())
function errorTrackingMiddleware() {
return async (context, next) => {
try {
return await next()
} catch (error) {
// Record error metrics
errorMetrics.recordError(error)
// Rethrow
throw error
}
}
}
Best Practices
- Type Checking: Always check error types for specific handling
- Proper Context: Include operation, URI, and source in errors
- User-Friendly Messages: Convert technical errors to user-friendly messages
- Recovery Options: Provide recovery paths for recoverable errors
- Centralized Handling: Use middleware for consistent error handling
- Environment Adaptation: Adapt error handling to the runtime environment
- Structured Logging: Log errors with structured metadata for analysis
- Fallback Content: Provide sensible defaults when content cannot be loaded
- Error Prevention: Use validation and checks to prevent errors
- Monitoring: Track error metrics for performance and reliability analysis