Files
Sankofa/api/src/lib/errors.ts
defiQUG 9daf1fd378 Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- Add comprehensive database migrations (001-024) for schema evolution
- Enhance API schema with expanded type definitions and resolvers
- Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth
- Implement new services: AI optimization, billing, blockchain, compliance, marketplace
- Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage)
- Update Crossplane provider with enhanced VM management capabilities
- Add comprehensive test suite for API endpoints and services
- Update frontend components with improved GraphQL subscriptions and real-time updates
- Enhance security configurations and headers (CSP, CORS, etc.)
- Update documentation and configuration files
- Add new CI/CD workflows and validation scripts
- Implement design system improvements and UI enhancements
2025-12-12 18:01:35 -08:00

205 lines
5.2 KiB
TypeScript

/**
* Standardized Error Handling
* Provides consistent error types and handling patterns across the codebase
*/
export enum ErrorCode {
// Authentication & Authorization
UNAUTHENTICATED = 'UNAUTHENTICATED',
FORBIDDEN = 'FORBIDDEN',
UNAUTHORIZED = 'UNAUTHORIZED',
// Validation
BAD_USER_INPUT = 'BAD_USER_INPUT',
VALIDATION_ERROR = 'VALIDATION_ERROR',
// Not Found
NOT_FOUND = 'NOT_FOUND',
RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
TENANT_NOT_FOUND = 'TENANT_NOT_FOUND',
// Business Logic
QUOTA_EXCEEDED = 'QUOTA_EXCEEDED',
RESOURCE_CONFLICT = 'RESOURCE_CONFLICT',
OPERATION_FAILED = 'OPERATION_FAILED',
// External Services
EXTERNAL_SERVICE_ERROR = 'EXTERNAL_SERVICE_ERROR',
BLOCKCHAIN_ERROR = 'BLOCKCHAIN_ERROR',
PROXMOX_ERROR = 'PROXMOX_ERROR',
// System
INTERNAL_ERROR = 'INTERNAL_ERROR',
DATABASE_ERROR = 'DATABASE_ERROR',
NETWORK_ERROR = 'NETWORK_ERROR',
}
export interface AppErrorOptions {
code: ErrorCode
message: string
details?: unknown
cause?: Error
statusCode?: number
}
/**
* Standardized application error
*/
export class AppError extends Error {
public readonly code: ErrorCode
public readonly details?: unknown
public readonly cause?: Error
public readonly statusCode: number
constructor(options: AppErrorOptions) {
super(options.message)
this.name = 'AppError'
this.code = options.code
this.details = options.details
this.cause = options.cause
this.statusCode = options.statusCode || this.getDefaultStatusCode(options.code)
// Maintain proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, AppError)
}
}
private getDefaultStatusCode(code: ErrorCode): number {
switch (code) {
case ErrorCode.UNAUTHENTICATED:
return 401
case ErrorCode.FORBIDDEN:
case ErrorCode.UNAUTHORIZED:
return 403
case ErrorCode.BAD_USER_INPUT:
case ErrorCode.VALIDATION_ERROR:
return 400
case ErrorCode.NOT_FOUND:
case ErrorCode.RESOURCE_NOT_FOUND:
case ErrorCode.TENANT_NOT_FOUND:
return 404
case ErrorCode.RESOURCE_CONFLICT:
return 409
case ErrorCode.QUOTA_EXCEEDED:
return 429
case ErrorCode.EXTERNAL_SERVICE_ERROR:
case ErrorCode.BLOCKCHAIN_ERROR:
case ErrorCode.PROXMOX_ERROR:
return 502
case ErrorCode.DATABASE_ERROR:
case ErrorCode.NETWORK_ERROR:
return 503
default:
return 500
}
}
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
details: this.details,
statusCode: this.statusCode,
}
}
}
/**
* Helper functions for common error scenarios
*/
export const AppErrors = {
unauthenticated: (message = 'Authentication required', details?: unknown) =>
new AppError({ code: ErrorCode.UNAUTHENTICATED, message, details }),
forbidden: (message = 'Access denied', details?: unknown) =>
new AppError({ code: ErrorCode.FORBIDDEN, message, details }),
badInput: (message: string, details?: unknown) =>
new AppError({ code: ErrorCode.BAD_USER_INPUT, message, details }),
validation: (message: string, details?: unknown) =>
new AppError({ code: ErrorCode.VALIDATION_ERROR, message, details }),
notFound: (resource: string, id?: string) =>
new AppError({
code: ErrorCode.RESOURCE_NOT_FOUND,
message: id ? `${resource} with id ${id} not found` : `${resource} not found`,
details: { resource, id },
}),
tenantNotFound: (tenantId: string) =>
new AppError({
code: ErrorCode.TENANT_NOT_FOUND,
message: `Tenant ${tenantId} not found`,
details: { tenantId },
}),
quotaExceeded: (message: string, details?: unknown) =>
new AppError({ code: ErrorCode.QUOTA_EXCEEDED, message, details }),
conflict: (message: string, details?: unknown) =>
new AppError({ code: ErrorCode.RESOURCE_CONFLICT, message, details }),
externalService: (service: string, message: string, cause?: Error) =>
new AppError({
code: ErrorCode.EXTERNAL_SERVICE_ERROR,
message: `${service}: ${message}`,
cause,
details: { service },
}),
blockchain: (message: string, cause?: Error) =>
new AppError({
code: ErrorCode.BLOCKCHAIN_ERROR,
message,
cause,
}),
proxmox: (message: string, cause?: Error) =>
new AppError({
code: ErrorCode.PROXMOX_ERROR,
message,
cause,
}),
database: (message: string, cause?: Error) =>
new AppError({
code: ErrorCode.DATABASE_ERROR,
message,
cause,
}),
internal: (message: string, cause?: Error, details?: unknown) =>
new AppError({
code: ErrorCode.INTERNAL_ERROR,
message,
cause,
details,
}),
}
/**
* Check if error is an AppError
*/
export function isAppError(error: unknown): error is AppError {
return error instanceof AppError
}
/**
* Convert any error to AppError
*/
export function toAppError(error: unknown, defaultMessage = 'An error occurred'): AppError {
if (isAppError(error)) {
return error
}
if (error instanceof Error) {
return AppErrors.internal(defaultMessage, error)
}
return AppErrors.internal(defaultMessage, undefined, { originalError: error })
}