/**
* Error Classes for hyper-booking
* Adapted from hypernote/pear-git pattern.
*/
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()
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
toJSON () {
return { error: this.message, code: this.code, status: this.status }
}
}
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 }
}
}
export class ForbiddenError extends AppError {
constructor (message = 'Forbidden', requiredPermission = null) {
super(message, 'FORBIDDEN', 403)
this.name = 'ForbiddenError'
this.requiredPermission = requiredPermission
}
}
export class UnauthorizedError extends AppError {
constructor (message = 'Authentication required') {
super(message, 'UNAUTHORIZED', 401)
this.name = 'UnauthorizedError'
}
}
export class ValidationError extends AppError {
constructor (message, field = null) {
super(message, 'VALIDATION_ERROR', 400)
this.name = 'ValidationError'
this.field = field
}
}
export class ConflictError extends AppError {
constructor (message = 'Resource conflict') {
super(message, 'CONFLICT', 409)
this.name = 'ConflictError'
}
}
export class RateLimitError extends AppError {
constructor (message = 'Rate limit exceeded', retryAfter = null) {
super(message, 'RATE_LIMIT', 429)
this.name = 'RateLimitError'
this.retryAfter = retryAfter
}
}
export function isAppError (err) {
return err instanceof 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
}