Skip to content

Error Handling

Overview

Use domain errors from @sws/common/domain instead of plain new Error() for domain-level failures. Each error carries an HTTP status code as a static property, which infrastructure layers can use when mapping to HTTP responses.

Base Error Types

From @sws/common/domain:

ErrorStatusWhen to Use
BadRequestError400Invalid input, malformed request
UnauthorizedError401Missing or invalid authentication
ForbiddenError403Insufficient permissions
NotFoundError404Resource not found
RateLimitError429Too many requests

Usage Examples

typescript
import { NotFoundError, BadRequestError, ForbiddenError } from '@sws/common/domain'

// Resource not found
throw new NotFoundError(`CexAccount with id '${id}' not found`)

// Validation error
throw new BadRequestError(`Invalid CEX name: '${cex}'. Must be one of: binance, kucoin, htx`)

// Authorization
throw new UnauthorizedError('Invalid API credentials')

// Permissions
throw new ForbiddenError('Partner does not have access to this resource')

Custom Domain Errors

Bounded contexts can define their own errors by extending base errors:

typescript
// packages/cex-wallets/domain/errors/CexAccountErrors.ts
import { NotFoundError, BadRequestError } from '@sws/common/domain'

export class CexAccountNotFoundError extends NotFoundError {
  constructor(id: string) {
    super(`CexAccount with id '${id}' not found`)
    this.name = 'CexAccountNotFoundError'
  }
}

export class InvalidCexError extends BadRequestError {
  constructor(cex: string) {
    super(`Invalid CEX: '${cex}'. Must be one of: binance, kucoin, htx`)
    this.name = 'InvalidCexError'
  }
}

Error Handling in Services

Catch errors at the application boundary (controllers/event subscribers), not inside domain/application services:

typescript
// In event subscriber — log error, don't let it crash the subscriber
async on(event: SomeDomainEvent): Promise<void> {
  try {
    await this.service.run({ ... })
  } catch (error) {
    this.logger.error(`Error in DoSomethingOnEventHappened`, {
      error: error instanceof Error ? error.message : String(error)
    })
  }
}

Error Mapping in Elysia Controllers

Map domain errors to HTTP responses in the Elysia route handler:

typescript
import { NotFoundError } from '@sws/common/domain'

app.get('/accounts/:id', async ({ params: { id }, set }) => {
  try {
    return await CexAccountFactory.cexAccountFinderController().run(id)
  } catch (error) {
    if (error instanceof NotFoundError) {
      set.status = NotFoundError.statusCode
      return { error: error.message }
    }
    set.status = 500
    return { error: 'Internal server error' }
  }
})