Repository: logto-io/logto
File: packages/core/src/middleware/koa-slonik-error-handler.ts (TypeScript)

Code before:
import type { SchemaLike } from '@logto/schemas';
import {
  SlonikError,
  NotFoundError,
  InvalidInputError,
  CheckIntegrityConstraintViolationError,
  UniqueIntegrityConstraintViolationError,
  ForeignKeyIntegrityConstraintViolationError,
} from '@silverhand/slonik';
import type { Middleware } from 'koa';

import RequestError from '#src/errors/RequestError/index.js';
import { DeletionError, InsertionError, UpdateError } from '#src/errors/SlonikError/index.js';

/* eslint-disable complexity */
export default function koaSlonikErrorHandler<StateT, ContextT>(): Middleware<StateT, ContextT> {
  return async (ctx, next) => {
    try {
      await next();
    } catch (error: unknown) {
      if (!(error instanceof SlonikError)) {
        throw error;
      }

      if (error instanceof InvalidInputError) {
        throw new RequestError({
          code: 'entity.invalid_input',
          status: 422,
        });
      }

      if (error instanceof UniqueIntegrityConstraintViolationError) {
        if (error.constraint === 'applications__protected_app_metadata_host') {
          throw new RequestError({
            code: 'application.protected_application_subdomain_exists',
            status: 422,
          });
        }

        if (error.constraint === 'applications__protected_app_metadata_custom_domain') {
          throw new RequestError({
            code: 'domain.hostname_already_exists',
            status: 422,

Review comment (suggestion):
```suggestion
 * We are now upgrading this error handler to handle both SlonikError and DatabaseError.
```

Code after:
import type { SchemaLike } from '@logto/schemas';
import {
  SlonikError,
  NotFoundError,
  InvalidInputError,
  CheckIntegrityConstraintViolationError,
  UniqueIntegrityConstraintViolationError,
  ForeignKeyIntegrityConstraintViolationError,
} from '@silverhand/slonik';
import type { Middleware } from 'koa';
import { DatabaseError } from 'pg-protocol';

import RequestError from '#src/errors/RequestError/index.js';
import { DeletionError, InsertionError, UpdateError } from '#src/errors/SlonikError/index.js';

/**
 * @remarks
 * We are now upgrading this error handler to simultaneously handle SlonikError and DatabaseError.
 * This is because DatabaseError and SlonikError, due to their different sources of definition, are not unified.
 * However, in practice, both are errors triggered by DB query failures.
 */
/* eslint-disable complexity */
export default function koaSlonikErrorHandler<StateT, ContextT>(): Middleware<StateT, ContextT> {
  return async (ctx, next) => {
    try {
      await next();
    } catch (error: unknown) {
      if (!(error instanceof SlonikError) && !(error instanceof DatabaseError)) {
        throw error;
      }

      if (error instanceof DatabaseError) {
        // https://www.postgresql.org/docs/14/errcodes-appendix.html
        // Can better infer SlonikError type.
        // eslint-disable-next-line unicorn/no-lonely-if
        if (error.code === '22001') {
          throw new RequestError(
            {
              code: 'entity.value_too_long',
              status: 422,
            },
            {
              message: error.message,