Error Handling Examples
This document provides practical examples of error handling in the content system. It includes code snippets for common error scenarios and their recommended handling approaches.
Basic Error Handling Examples
Handling Content Not Found
typescript
import { ContentNotFoundError } from '@lib/content/errors'
async function loadArticle(uri) {
try {
return await store.read(uri)
} catch (error) {
if (error instanceof ContentNotFoundError) {
console.log(`Article not found: ${uri}`)
// Return a default "not found" article
return {
data: `# Article Not Found\n\nSorry, the article at ${uri} could not be found.`,
contentType: 'text/markdown',
metadata: {
title: 'Article Not Found',
status: 'error',
},
}
}
// Re-throw other errors
throw error
}
}
// Usage
const article = await loadArticle('articles/missing-article.md')
displayArticle(article) // Displays "Article Not Found" content
Handling Content Validation Errors
typescript
import { ContentValidationError } from '@lib/content/errors'
async function saveArticle(uri, content) {
try {
await store.write(uri, content)
showSuccessMessage('Article saved successfully')
return true
} catch (error) {
if (error instanceof ContentValidationError) {
// Extract validation errors from the error
const validationErrors = error.info?.validationErrors || []
// Display validation errors to the user
showValidationErrors(validationErrors)
// Return false to indicate save failed
return false
}
// Handle other errors
showErrorMessage(`Failed to save article: ${error.message}`)
return false
}
}
// Usage
const saved = await saveArticle('articles/new.md', invalidContent)
if (!saved) {
// Keep editor open for user to fix errors
keepEditorOpen()
}
Handling Access Errors
typescript
import { ContentAccessError } from '@lib/content/errors'
async function editArticle(uri) {
try {
const content = await store.read(uri)
openEditor(uri, content)
} catch (error) {
if (error instanceof ContentAccessError) {
// Prompt user to authenticate
showLoginPrompt('You need to log in to edit this article', () => {
// Retry after login
editArticle(uri)
})
} else {
// Handle other errors
showErrorMessage(`Could not open article: ${error.message}`)
}
}
}
// Usage
editArticle('articles/protected.md')
Handling Operation Errors
typescript
import { ContentOperationError } from '@lib/content/errors'
async function batchProcess(uris) {
const results = {
succeeded: [],
failed: [],
}
for (const uri of uris) {
try {
// Perform operation
const content = await store.read(uri)
const processed = processContent(content)
await store.write(uri, processed)
// Record success
results.succeeded.push(uri)
} catch (error) {
if (error instanceof ContentOperationError) {
console.error(
`Operation ${error.operation} failed on ${uri}: ${error.message}`
)
} else {
console.error(`Unknown error processing ${uri}:`, error)
}
// Record failure
results.failed.push({
uri,
error: error.message,
type: error.name,
})
}
}
return results
}
// Usage
const results = await batchProcess([
'articles/article1.md',
'articles/article2.md',
'articles/article3.md',
])
showProcessingResults(results)
Advanced Error Handling Examples
Using Error Recovery Functions
typescript
import { ContentError } from '@lib/content/errors'
async function loadContentWithRecovery(uri) {
try {
return await store.read(uri)
} catch (error) {
if (error instanceof ContentError && error.recoverable && error.recovery) {
try {
// Attempt to recover using the provided recovery function
console.log(`Attempting to recover content for ${uri}`)
const recoveredContent = await error.recovery()
// Mark content as recovered
if (recoveredContent) {
recoveredContent.metadata = {
...recoveredContent.metadata,
recovered: true,
recoveryTimestamp: new Date(),
}
}
return recoveredContent
} catch (recoveryError) {
console.error(`Recovery failed for ${uri}:`, recoveryError)
// Fall through to default error handling
}
}
// Handle unrecoverable errors
console.error(`Unrecoverable error for ${uri}:`, error)
throw error
}
}
// Usage
try {
const content = await loadContentWithRecovery('articles/corrupted.md')
displayContent(content)
// Show recovery notice if content was recovered
if (content.metadata.recovered) {
showRecoveryNotice()
}
} catch (error) {
showErrorMessage('Could not load content, even with recovery attempts')
}
Implementing Custom Error Classes
typescript
import { ContentError } from '@lib/content/errors'
// Create a custom error class for specific scenarios
class ArticlePublishingError extends ContentError {
constructor(uri, reason, options = {}) {
super(`Failed to publish article at ${uri}: ${reason}`, {
uri,
operation: 'publish',
source: 'publishing-service',
...options,
})
this.name = 'ArticlePublishingError'
this.reason = reason
}
}
// Publishing service with custom error
async function publishArticle(uri) {
try {
// Get article content
const article = await store.read(uri)
// Check if article is ready for publishing
if (!article.metadata.readyToPublish) {
throw new ArticlePublishingError(uri, 'Article is not marked as ready', {
recoverable: true,
recovery: async () => {
// Auto-fix by setting readyToPublish
article.metadata.readyToPublish = true
await store.write(uri, article)
return publishArticle(uri)
},
})
}
// Publish to external service
await publishToExternalService(article)
// Update status
article.metadata.status = 'published'
article.metadata.publishedAt = new Date()
await store.write(uri, article)
return {
success: true,
publishedAt: article.metadata.publishedAt,
}
} catch (error) {
if (error instanceof ArticlePublishingError) {
console.error(`Publishing error: ${error.message}`)
if (error.recoverable && error.recovery) {
if (await confirmRecovery(error.reason)) {
return error.recovery()
}
}
}
// Re-throw other errors
throw error
}
}
// Usage
try {
const result = await publishArticle('articles/draft.md')
showSuccessMessage(`Article published at ${result.publishedAt}`)
} catch (error) {
showErrorMessage(`Publishing failed: ${error.message}`)
}
Using Error Middleware
typescript
import { createMiddleware } from '@lib/content/middleware'
import {
ContentNotFoundError,
ContentValidationError,
} from '@lib/content/errors'
// Create custom error handling middleware
const errorHandlingMiddleware = createMiddleware((options = {}) => {
return async (context, next) => {
try {
// Attempt the normal operation
return await next()
} catch (error) {
// Log all errors
console.error(`Error in ${context.operation} on ${context.uri}:`, error)
// Handle specific error types
if (error instanceof ContentNotFoundError && options.provideFallbacks) {
// Provide fallback content
console.log(`Providing fallback for ${context.uri}`)
context.content = createFallbackContent(context.uri)
return context
}
if (error instanceof ContentValidationError && options.autoFix) {
// Attempt to auto-fix validation errors
if (context.operation === 'write' && context.content) {
console.log(`Attempting to auto-fix ${context.uri}`)
context.content = autofixContent(
context.content,
error.info?.validationErrors
)
// Try operation again with fixed content
return await next()
}
}
// Rethrow the error for other error types
throw error
}
}
})
// Create store with error handling middleware
const storeWithErrorHandling = createContentStore({
adapter: createMemoryAdapter(),
middleware: [
errorHandlingMiddleware({
provideFallbacks: true,
autoFix: true,
}),
],
})
// Helper functions
function createFallbackContent(uri) {
return {
data: `# Fallback Content\n\nOriginal content at ${uri} was not found.`,
contentType: 'text/markdown',
metadata: {
title: 'Fallback Content',
isFallback: true,
},
}
}
function autofixContent(content, validationErrors = []) {
// Clone content to avoid mutation
const fixedContent = {
...content,
metadata: { ...content.metadata },
}
// Apply fixes based on validation errors
for (const error of validationErrors) {
if (error.path === 'metadata.title' && !fixedContent.metadata.title) {
fixedContent.metadata.title = 'Untitled'
}
if (
error.path === 'metadata.createdAt' &&
!fixedContent.metadata.createdAt
) {
fixedContent.metadata.createdAt = new Date()
}
// Add other auto-fixes as needed
}
return fixedContent
}
// Usage
async function loadContentSafely(uri) {
// This will automatically use fallbacks for not found errors
const content = await storeWithErrorHandling.read(uri)
// Check if fallback was used
if (content.metadata.isFallback) {
showFallbackNotice()
}
return content
}
React Integration Examples
React Error Boundary
tsx
import React from 'react'
import {
ContentError,
ContentNotFoundError,
ContentAccessError,
} from '@lib/content/errors'
// Generic error boundary for content components
class ContentErrorBoundary extends React.Component {
state = {
hasError: false,
error: null,
}
static getDerivedStateFromError(error) {
return {
hasError: true,
error,
}
}
componentDidCatch(error, errorInfo) {
// Log error to service
console.error('Content error caught:', error)
logErrorToService(error, errorInfo)
}
render() {
if (!this.state.hasError) {
return this.props.children
}
const error = this.state.error
// Different UI based on error type
if (error instanceof ContentNotFoundError) {
return (
<div className="error-container not-found">
<h2>Content Not Found</h2>
<p>The content at {error.uri} could not be found.</p>
<button onClick={this.props.onRetry}>Retry</button>
</div>
)
}
if (error instanceof ContentAccessError) {
return (
<div className="error-container access-denied">
<h2>Access Denied</h2>
<p>You don't have permission to access {error.uri}.</p>
<button onClick={this.props.onLogin}>Log In</button>
</div>
)
}
// Generic error handling for other error types
return (
<div className="error-container">
<h2>Error Loading Content</h2>
<p>{error.message}</p>
<button onClick={this.props.onRetry}>Retry</button>
</div>
)
}
}
// Usage in content component
function ArticleViewer({ uri }) {
const [isLoading, setIsLoading] = useState(true)
const [article, setArticle] = useState(null)
const loadArticle = useCallback(async () => {
try {
setIsLoading(true)
const content = await store.read(uri)
setArticle(content)
setIsLoading(false)
} catch (error) {
console.error('Error loading article:', error)
// Let the error boundary handle the error
throw error
}
}, [uri])
useEffect(() => {
loadArticle()
}, [loadArticle])
if (isLoading) {
return <LoadingSpinner />
}
return (
<article>
<h1>{article.metadata.title}</h1>
<div className="content">{renderMarkdown(article.data)}</div>
</article>
)
}
// Wrapped with error boundary
function SafeArticleViewer({ uri }) {
const handleRetry = () => {
// Force re-mount of the component
setKey(prev => prev + 1)
}
const handleLogin = () => {
// Show login modal
showLoginModal(() => handleRetry())
}
const [key, setKey] = useState(0)
return (
<ContentErrorBoundary key={key} onRetry={handleRetry} onLogin={handleLogin}>
<ArticleViewer uri={uri} />
</ContentErrorBoundary>
)
}
React Hooks for Error Handling
tsx
import { useState, useEffect, useCallback } from 'react'
import { ContentNotFoundError, ContentAccessError } from '@lib/content/errors'
// Custom hook for content loading with error handling
function useContentWithErrorHandling(uri) {
const [state, setState] = useState({
isLoading: true,
content: null,
error: null,
errorType: null,
isNotFound: false,
isAccessDenied: false,
})
const loadContent = useCallback(async () => {
try {
setState(prev => ({ ...prev, isLoading: true, error: null }))
const content = await store.read(uri)
setState({
isLoading: false,
content,
error: null,
errorType: null,
isNotFound: false,
isAccessDenied: false,
})
} catch (error) {
// Categorize error
const isNotFound = error instanceof ContentNotFoundError
const isAccessDenied = error instanceof ContentAccessError
const errorType = error.name
setState({
isLoading: false,
content: null,
error,
errorType,
isNotFound,
isAccessDenied,
})
// Log error
console.error(`Error loading ${uri}:`, error)
}
}, [uri])
useEffect(() => {
loadContent()
}, [loadContent])
return {
...state,
reload: loadContent,
}
}
// Usage in a component
function ContentDisplay({ uri }) {
const { isLoading, content, error, isNotFound, isAccessDenied, reload } =
useContentWithErrorHandling(uri)
if (isLoading) {
return <LoadingSpinner />
}
if (isNotFound) {
return (
<div className="error not-found">
<h2>Content Not Found</h2>
<p>The requested content could not be found.</p>
<button onClick={reload}>Retry</button>
</div>
)
}
if (isAccessDenied) {
return (
<div className="error access-denied">
<h2>Access Denied</h2>
<p>You don't have permission to access this content.</p>
<button onClick={showLoginModal}>Log In</button>
</div>
)
}
if (error) {
return (
<div className="error">
<h2>Error</h2>
<p>{error.message}</p>
<button onClick={reload}>Retry</button>
</div>
)
}
return (
<div className="content">
<h1>{content.metadata.title}</h1>
<div>{renderContent(content)}</div>
</div>
)
}
Node.js Server Examples
Express API Error Handling
typescript
import express from 'express'
import {
ContentNotFoundError,
ContentAccessError,
ContentValidationError,
} from '@lib/content/errors'
const app = express()
// Content API routes
app.get('/api/content/:path(*)', async (req, res, next) => {
try {
const uri = req.params.path
const content = await store.read(uri)
res.json({
uri,
data: content.data,
metadata: content.metadata,
contentType: content.contentType,
})
} catch (error) {
// Pass the error to the error handler middleware
next(error)
}
})
app.put('/api/content/:path(*)', async (req, res, next) => {
try {
const uri = req.params.path
const { data, contentType, metadata } = req.body
await store.write(uri, { data, contentType, metadata })
res.json({
success: true,
uri,
})
} catch (error) {
next(error)
}
})
app.delete('/api/content/:path(*)', async (req, res, next) => {
try {
const uri = req.params.path
await store.delete(uri)
res.json({
success: true,
uri,
})
} catch (error) {
next(error)
}
})
// Error handling middleware
app.use((error, req, res, next) => {
if (error instanceof ContentNotFoundError) {
return res.status(404).json({
error: 'NOT_FOUND',
message: `Content not found: ${error.uri}`,
uri: error.uri,
})
}
if (error instanceof ContentAccessError) {
return res.status(403).json({
error: 'ACCESS_DENIED',
message: `Access denied: ${error.uri}`,
uri: error.uri,
})
}
if (error instanceof ContentValidationError) {
return res.status(400).json({
error: 'VALIDATION_ERROR',
message: `Validation failed: ${error.message}`,
validationErrors: error.info?.validationErrors || [],
uri: error.uri,
})
}
// Log unknown errors
console.error('Unhandled error in API:', error)
// Generic error response
res.status(500).json({
error: 'INTERNAL_ERROR',
message: 'An internal server error occurred',
})
})
// Start the server
app.listen(3000, () => {
console.log('Content API server running on port 3000')
})
HTTP Client with Error Handling
typescript
import fetch from 'node-fetch'
import {
ContentError,
ContentNotFoundError,
ContentAccessError,
} from '@lib/content/errors'
// HTTP client for content API
class ContentApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl
}
async read(uri) {
try {
const response = await fetch(`${this.baseUrl}/api/content/${uri}`)
if (!response.ok) {
await this.handleErrorResponse(response, 'read', uri)
}
const result = await response.json()
return {
data: result.data,
contentType: result.contentType,
metadata: result.metadata,
}
} catch (error) {
if (error instanceof ContentError) {
throw error
}
// Convert network/fetch errors to ContentErrors
throw new ContentError(`Network error reading ${uri}: ${error.message}`, {
uri,
operation: 'read',
source: 'api-client',
cause: error,
})
}
}
async write(uri, content) {
try {
const response = await fetch(`${this.baseUrl}/api/content/${uri}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(content),
})
if (!response.ok) {
await this.handleErrorResponse(response, 'write', uri)
}
return await response.json()
} catch (error) {
if (error instanceof ContentError) {
throw error
}
throw new ContentError(`Network error writing ${uri}: ${error.message}`, {
uri,
operation: 'write',
source: 'api-client',
cause: error,
})
}
}
async delete(uri) {
try {
const response = await fetch(`${this.baseUrl}/api/content/${uri}`, {
method: 'DELETE',
})
if (!response.ok) {
await this.handleErrorResponse(response, 'delete', uri)
}
return await response.json()
} catch (error) {
if (error instanceof ContentError) {
throw error
}
throw new ContentError(
`Network error deleting ${uri}: ${error.message}`,
{
uri,
operation: 'delete',
source: 'api-client',
cause: error,
}
)
}
}
async handleErrorResponse(response, operation, uri) {
let errorData
try {
errorData = await response.json()
} catch (e) {
errorData = {
error: 'UNKNOWN_ERROR',
message: `Unknown error: ${response.statusText}`,
}
}
switch (response.status) {
case 404:
throw new ContentNotFoundError(
errorData.message || `Content not found: ${uri}`,
{
uri,
operation,
source: 'api-client',
}
)
case 403:
throw new ContentAccessError(
errorData.message || `Access denied: ${uri}`,
{
uri,
operation,
source: 'api-client',
}
)
case 400:
throw new ContentValidationError(
errorData.message || `Validation error: ${uri}`,
{
uri,
operation,
source: 'api-client',
info: {
validationErrors: errorData.validationErrors || [],
},
}
)
default:
throw new ContentError(
errorData.message || `API error: ${response.statusText}`,
{
uri,
operation,
source: 'api-client',
info: errorData,
}
)
}
}
}
// Usage
const apiClient = new ContentApiClient('https://api.example.com')
try {
const content = await apiClient.read('articles/welcome.md')
console.log('Content loaded:', content.metadata.title)
} catch (error) {
if (error instanceof ContentNotFoundError) {
console.log('Article not found, creating a new one')
await apiClient.write('articles/welcome.md', {
data: '# Welcome\n\nThis is a new article.',
contentType: 'text/markdown',
metadata: { title: 'Welcome' },
})
} else if (error instanceof ContentAccessError) {
console.log('Authentication required')
await authenticateUser()
// Retry after authentication
} else {
console.error('Error accessing content:', error)
}
}