OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / lib / errors.js

208 lines · javascript ref:
/**
 * Error Classes
 *
 * Structured error types for better error handling and consistent API responses.
 */

/**
 * Base application error
 * All custom errors extend this class
 */
export class AppError extends Error {
  constructor(message, code, status = 500) {
    super(message)
    this.name = 'AppError'
    this.code = code
    this.status = status
    this.timestamp = Date.now()

    // Capture stack trace
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor)
    }
  }

  /**
   * Convert to JSON for API responses
   */
  toJSON() {
    return {
      error: this.message,
      code: this.code,
      status: this.status
    }
  }
}

/**
 * Resource not found (404)
 */
export class NotFoundError extends AppError {
  constructor(message = 'Not found', resource = null) {
    super(message, 'NOT_FOUND', 404)
    this.name = 'NotFoundError'
    this.resource = resource
  }

  toJSON() {
    return {
      ...super.toJSON(),
      resource: this.resource
    }
  }
}

/**
 * Access forbidden (403)
 */
export class ForbiddenError extends AppError {
  constructor(message = 'Forbidden', requiredPermission = null) {
    super(message, 'FORBIDDEN', 403)
    this.name = 'ForbiddenError'
    this.requiredPermission = requiredPermission
  }

  toJSON() {
    return {
      ...super.toJSON(),
      requiredPermission: this.requiredPermission
    }
  }
}

/**
 * Authentication required (401)
 */
export class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required', authMethod = null) {
    super(message, 'UNAUTHORIZED', 401)
    this.name = 'UnauthorizedError'
    this.authMethod = authMethod
  }

  toJSON() {
    return {
      ...super.toJSON(),
      authMethod: this.authMethod
    }
  }
}

/**
 * Validation error (400)
 */
export class ValidationError extends AppError {
  constructor(message, field = null, details = null) {
    super(message, 'VALIDATION_ERROR', 400)
    this.name = 'ValidationError'
    this.field = field
    this.details = details
  }

  toJSON() {
    return {
      ...super.toJSON(),
      field: this.field,
      details: this.details
    }
  }
}

/**
 * Resource conflict (409)
 * Used for duplicate entries, concurrent modifications, etc.
 */
export class ConflictError extends AppError {
  constructor(message = 'Resource conflict', conflictType = null) {
    super(message, 'CONFLICT', 409)
    this.name = 'ConflictError'
    this.conflictType = conflictType
  }

  toJSON() {
    return {
      ...super.toJSON(),
      conflictType: this.conflictType
    }
  }
}

/**
 * Rate limit exceeded (429)
 */
export class RateLimitError extends AppError {
  constructor(message = 'Rate limit exceeded', retryAfter = null) {
    super(message, 'RATE_LIMIT', 429)
    this.name = 'RateLimitError'
    this.retryAfter = retryAfter
  }

  toJSON() {
    return {
      ...super.toJSON(),
      retryAfter: this.retryAfter
    }
  }
}

/**
 * Bad gateway / upstream error (502)
 */
export class GatewayError extends AppError {
  constructor(message = 'Gateway error', upstream = null) {
    super(message, 'GATEWAY_ERROR', 502)
    this.name = 'GatewayError'
    this.upstream = upstream
  }
}

/**
 * Service unavailable (503)
 */
export class ServiceUnavailableError extends AppError {
  constructor(message = 'Service temporarily unavailable', retryAfter = null) {
    super(message, 'SERVICE_UNAVAILABLE', 503)
    this.name = 'ServiceUnavailableError'
    this.retryAfter = retryAfter
  }
}

/**
 * Check if an error is an AppError instance
 */
export function isAppError(err) {
  return err instanceof AppError
}

/**
 * Wrap unknown errors in AppError
 */
export function wrapError(err, defaultMessage = 'An error occurred') {
  if (err instanceof AppError) {
    return err
  }

  const wrapped = new AppError(
    err.message || defaultMessage,
    'INTERNAL_ERROR',
    500
  )
  wrapped.originalError = err
  wrapped.stack = err.stack
  return wrapped
}

export default {
  AppError,
  NotFoundError,
  ForbiddenError,
  UnauthorizedError,
  ValidationError,
  ConflictError,
  RateLimitError,
  GatewayError,
  ServiceUnavailableError,
  isAppError,
  wrapError
}