Files
gpt-engineer-app[bot] 7181fdbcac Add comprehensive edge-function error handling
Enhance error handling and logging across all edge functions:
- Introduce a shared edgeFunctionWrapper with standardized error handling, request/response logging, tracing, and validation hooks.
- Add runtime type validation utilities (ValidationError, validators, and parse/validate helpers) and integrate into edge flow.
- Implement robust validation for incoming requests and known type mismatches, with detailed logs and structured responses.
- Add post-RPC and post-database error logging to surface type/mismatch issues early.
- Update approval/rejection entry points to leverage new validators and centralized error handling.
2025-11-11 02:54:50 +00:00

348 lines
7.5 KiB
Markdown

# Edge Function Shared Utilities
Comprehensive error handling, logging, and type validation utilities for Supabase Edge Functions.
## Quick Start
### Using the Edge Function Wrapper (Recommended)
The easiest way to create a new edge function with full error handling:
```typescript
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts';
import { corsHeaders } from '../_shared/cors.ts';
import { validateUUID, validateString } from '../_shared/typeValidation.ts';
serve(createEdgeFunction({
name: 'my-function',
requireAuth: true,
corsHeaders,
}, async (req, { requestId, span, userId }) => {
// Parse and validate request
const body = await req.json();
validateUUID(body.id, 'id', { requestId });
validateString(body.name, 'name', { requestId });
// Your business logic here
const result = await processRequest(body);
return new Response(
JSON.stringify({ success: true, data: result }),
{
status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
);
}));
```
This automatically provides:
- ✅ CORS handling
- ✅ Authentication validation
- ✅ Request/response logging
- ✅ Distributed tracing
- ✅ Comprehensive error handling
- ✅ Performance monitoring
## Type Validation
### Basic Validation Functions
```typescript
import {
validateRequired,
validateString,
validateUUID,
validateArray,
validateUUIDArray,
validateEntityType,
validateActionType,
validateObject,
} from '../_shared/typeValidation.ts';
// Validate required field
validateRequired(value, 'fieldName', { requestId });
// Validate string
validateString(value, 'name', { requestId });
// Validate UUID
validateUUID(value, 'userId', { requestId });
// Validate array with minimum length
validateArray(value, 'itemIds', 1, { requestId });
// Validate array of UUIDs
validateUUIDArray(value, 'submissionIds', 1, { requestId });
// Validate entity type (park, ride, company, etc.)
validateEntityType(value, 'item_type', { requestId });
// Validate action type (create, edit, delete)
validateActionType(value, 'action_type', { requestId });
```
### Submission Item Validation
```typescript
import { validateSubmissionItem } from '../_shared/typeValidation.ts';
const item = validateSubmissionItem(rawData, { requestId });
// Returns: { id: string, item_type: ValidEntityType, action_type: ValidActionType }
```
### Valid Entity Types
The following entity types are recognized by the system:
- `park`
- `ride`
- `manufacturer`
- `operator`
- `property_owner`
- `designer`
- `company` (consolidated type)
- `ride_model`
- `photo`
- `milestone`
- `timeline_event`
### Type Guards
```typescript
import {
isString,
isUUID,
isArray,
isObject,
isValidEntityType,
isValidActionType,
} from '../_shared/typeValidation.ts';
if (isString(value)) {
// TypeScript now knows value is a string
}
if (isValidEntityType(type)) {
// TypeScript knows type is ValidEntityType
}
```
## Error Handling
### Automatic Error Categorization
The edge function wrapper automatically handles:
#### Validation Errors (400 Bad Request)
```json
{
"error": "Invalid entity type: operator_invalid",
"field": "item_type",
"expected": "one of: park, ride, manufacturer, ...",
"received": "operator_invalid",
"requestId": "abc-123"
}
```
#### Database Errors
- **23505** → 409 Conflict (unique constraint violation)
- **23503** → 400 Bad Request (foreign key violation)
- **23514** → 400 Bad Request (check constraint violation)
- **P0001** → 400 Bad Request (raised exception)
- **42501** → 403 Forbidden (insufficient privilege)
#### Authentication Errors (401 Unauthorized)
```json
{
"error": "Missing Authorization header",
"requestId": "abc-123"
}
```
### Manual Error Formatting
```typescript
import { formatEdgeError, toError } from '../_shared/errorFormatter.ts';
try {
// Some operation
} catch (error) {
// Get human-readable error message
const message = formatEdgeError(error);
// Convert to Error instance
const err = toError(error);
throw err;
}
```
## Logging
### Structured Logging
```typescript
import { edgeLogger } from '../_shared/logger.ts';
edgeLogger.info('Processing submission', {
requestId,
submissionId,
itemCount,
});
edgeLogger.warn('Slow query detected', {
requestId,
queryTime: 1500,
query: 'fetch_submissions',
});
edgeLogger.error('Failed to process item', {
requestId,
error: formatEdgeError(error),
itemId,
});
```
### Sensitive Data Protection
The logger automatically redacts sensitive fields:
- `password`
- `token`
- `secret`
- `api_key`
- `authorization`
- `email`
- `phone`
- `ssn`
- `credit_card`
- `ip_address`
- `session_id`
## Distributed Tracing
### Using Spans
```typescript
import {
startSpan,
endSpan,
addSpanEvent,
setSpanAttributes,
logSpan,
} from '../_shared/logger.ts';
// Create a child span for a database operation
const dbSpan = startSpan(
'fetch_submissions',
'DATABASE',
getSpanContext(parentSpan),
{
'db.operation': 'select',
'db.table': 'content_submissions',
}
);
try {
addSpanEvent(dbSpan, 'query_start');
const result = await supabase
.from('content_submissions')
.select('*');
addSpanEvent(dbSpan, 'query_complete', {
rowCount: result.data?.length,
});
endSpan(dbSpan, 'ok');
} catch (error) {
addSpanEvent(dbSpan, 'query_failed', {
error: formatEdgeError(error),
});
endSpan(dbSpan, 'error', error);
throw error;
} finally {
logSpan(dbSpan);
}
```
## Migration Guide
### Before (Manual Error Handling)
```typescript
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: corsHeaders });
}
try {
const authHeader = req.headers.get('Authorization');
if (!authHeader) {
return new Response(
JSON.stringify({ error: 'Unauthorized' }),
{ status: 401 }
);
}
const body = await req.json();
// Business logic...
return new Response(
JSON.stringify({ success: true }),
{ status: 200 }
);
} catch (error) {
console.error('Error:', error);
return new Response(
JSON.stringify({ error: String(error) }),
{ status: 500 }
);
}
});
```
### After (Using Wrapper)
```typescript
import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts';
import { validateString } from '../_shared/typeValidation.ts';
serve(createEdgeFunction({
name: 'my-function',
requireAuth: true,
corsHeaders,
}, async (req, { requestId, userId }) => {
const body = await req.json();
validateString(body.name, 'name', { requestId });
// Business logic...
return new Response(
JSON.stringify({ success: true }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
}));
```
## Benefits
1. **Consistent Error Handling**: All errors are formatted consistently
2. **Better Debugging**: Request IDs and trace IDs link errors across services
3. **Type Safety**: Catch type mismatches early with clear error messages
4. **Security**: Automatic redaction of sensitive data in logs
5. **Performance Monitoring**: Built-in timing and span tracking
6. **Maintainability**: Less boilerplate code in each function
## Example: Moderation Approval Function
See `process-selective-approval/index.ts` for a complete example of using these utilities in a production edge function.
Key features demonstrated:
- Type validation for submission items
- Entity type checking
- Database error handling
- Distributed tracing across RPC calls
- Performance monitoring