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

7.5 KiB

Edge Function Shared Utilities

Comprehensive error handling, logging, and type validation utilities for Supabase Edge Functions.

Quick Start

The easiest way to create a new edge function with full error handling:

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

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

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

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)

{
  "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)

{
  "error": "Missing Authorization header",
  "requestId": "abc-123"
}

Manual Error Formatting

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

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

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)

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)

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