> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Exception

> A structured error system: a base Exception class plus specialized HTTP exceptions carrying status codes, typed immutable data, timestamps, and JSON stack traces.

The `@ooneex/exception` package gives the framework one consistent way to fail. Instead of throwing bare `Error` objects, you throw an `Exception` — a subclass of the native `Error` that also carries an HTTP status code, an optional grouping `key`, immutable contextual `data`, the timestamp it was created, and (when you wrap a native error) the original error. Specialized subclasses like `NotFoundException` preset the status code so the most common HTTP failures read clearly at the throw site and map straight onto an error response.

## Why this system

* **HTTP status mapping.** Every exception carries a `status` code (default `500`). Status codes come from [`@ooneex/http-status`](https://www.npmjs.com/package/@ooneex/http-status), so an exception maps directly onto the response it should produce.
* **Typed, immutable data.** Attach a plain object of contextual `data` (the failing field, an entity id) at the throw site. It is frozen with `Object.freeze`, so the context that explains the failure cannot be mutated as the error propagates.
* **Built-in timestamp.** Each exception records its creation `date`, so logs and error responses have an accurate moment of failure without extra bookkeeping.
* **JSON stack traces.** `stackToJson()` parses the raw stack string into a structured `ExceptionStackFrameType[]` — function name, file, line, column — ready for logging or a debug-only response field.
* **Native error wrapping.** Pass a caught `Error` straight into the constructor; its message is reused and the original is preserved on `native`, so you keep the underlying stack and context.
* **Specialized exceptions.** `BadRequestException`, `NotFoundException`, `UnauthorizedException`, and `MethodNotAllowedException` preset the right status code so intent is obvious at the throw site.

## How it works

`Exception extends Error implements IException`. The constructor takes the message (or an `Error` to wrap) and an options object; it reads `status`, `key`, and `data`, freezes `data`, sets `name` to the concrete class name, and — if the message was an `Error` — stores it on `native`. The `date` is captured the moment the instance is created.

| Member          | Type                                | Description                                                            |
| --------------- | ----------------------------------- | ---------------------------------------------------------------------- |
| `key`           | `string \| null`                    | Optional grouping key for the error (`null` by default).               |
| `date`          | `Date`                              | Timestamp captured when the exception is constructed.                  |
| `status`        | `StatusCodeType`                    | HTTP status code; defaults to `500`.                                   |
| `data`          | `Readonly<Record<string, unknown>>` | Frozen contextual data attached at the throw site.                     |
| `native`        | `Error \| undefined`                | The original error, when a native `Error` was wrapped.                 |
| `message`       | `string`                            | Error message (inherited from `Error`).                                |
| `name`          | `string`                            | The concrete class name, e.g. `"NotFoundException"`.                   |
| `stack`         | `string \| undefined`               | Raw stack trace string (inherited from `Error`).                       |
| `stackToJson()` | `ExceptionStackFrameType[] \| null` | Parses `stack` into structured frames, or `null` when no stack exists. |

The base constructor signature is:

```typescript theme={null}
new Exception(
  message: string | Error,
  options?: {
    key?: string | null;
    status?: StatusCodeType;
    data?: Record<string, unknown>;
  }
)
```

Each frame returned by `stackToJson()` matches `ExceptionStackFrameType`:

```typescript theme={null}
type ExceptionStackFrameType = {
  functionName?: string;
  fileName?: string;
  lineNumber?: number;
  columnNumber?: number;
  source: string;
};
```

## The base Exception

Throw an `Exception` directly when no specialized type fits. With only a message it defaults to status `500` and empty data:

```typescript theme={null}
import { Exception } from "@ooneex/exception";

throw new Exception("Something went wrong");
```

Add a status code and typed contextual data — both are read straight off the instance later:

```typescript theme={null}
import { Exception } from "@ooneex/exception";
import { HttpStatus } from "@ooneex/http-status";

throw new Exception("Validation failed", {
  status: HttpStatus.Code.BadRequest,
  key: "user.email.invalid",
  data: {
    field: "email",
    value: "not-an-email",
    constraint: "Must be a valid email address",
  },
});
```

The `data` object is frozen, so reading it downstream is safe — attempting to mutate it has no effect.

## Specialized exceptions

The specialized exceptions extend `Exception` and preset the status code from `@ooneex/http-status`. Their constructor is positional: a `message`, a required `key` string, and an optional `data` object (defaults to `{}`).

```typescript theme={null}
new NotFoundException(message: string, key: string, data?: Record<string, unknown>)
```

| Exception                   | Status code | `HttpStatus.Code`  |
| --------------------------- | ----------- | ------------------ |
| `BadRequestException`       | `400`       | `BadRequest`       |
| `UnauthorizedException`     | `401`       | `Unauthorized`     |
| `NotFoundException`         | `404`       | `NotFound`         |
| `MethodNotAllowedException` | `405`       | `MethodNotAllowed` |

```typescript theme={null}
import {
  BadRequestException,
  MethodNotAllowedException,
  NotFoundException,
  UnauthorizedException,
} from "@ooneex/exception";

// 404 Not Found
throw new NotFoundException("User not found", "user.not_found", { userId: "123" });

// 400 Bad Request
throw new BadRequestException("Invalid input", "user.invalid", {
  errors: ["email is required", "name is too short"],
});

// 401 Unauthorized
throw new UnauthorizedException("Invalid credentials", "auth.invalid");

// 405 Method Not Allowed
throw new MethodNotAllowedException("POST is not allowed here", "route.method");
```

## Throwing from a service or controller

Throw exceptions where the failure is detected — in a service or a controller — and let the calling layer turn them into a response. Because each exception carries its own `status` and `data`, the handler does not need to know how to classify the failure:

```typescript theme={null}
import { Exception, NotFoundException } from "@ooneex/exception";
import type { ContextType, IController } from "@ooneex/controller";

export class UserController implements IController {
  public async index(context: ContextType) {
    try {
      const user = await this.findUser(context.params.id);
      return context.response.json({ user });
    } catch (error) {
      if (error instanceof Exception) {
        // status and data travel with the exception
        return context.response.exception(error.message, {
          status: error.status,
          data: error.data,
        });
      }

      throw error;
    }
  }

  private async findUser(id: string) {
    const user = await db.users.find(id);
    if (!user) {
      throw new NotFoundException("User not found", "user.not_found", { id });
    }
    return user;
  }
}
```

The `status` and `data` you attached at the throw site flow straight into the error response. See [Response](/basics/response) for how `context.response.exception(...)` and the related helpers serialize this, and [Controller](/basics/controller) for where this handling belongs.

## Wrapping a native error

When a third-party call or a built-in throws a plain `Error`, wrap it instead of swallowing it. Pass the caught error as the message — the constructor reuses its message and keeps the original on `native`, so the underlying context is never lost:

```typescript theme={null}
import { Exception } from "@ooneex/exception";

try {
  JSON.parse(rawConfig);
} catch (error) {
  throw new Exception(error as Error, {
    status: 500,
    data: { context: "Parsing configuration file" },
  });
}
```

Downstream you can inspect both layers: `exception.message` and `exception.data` describe the application-level failure, while `exception.native` holds the original error and its stack.

## Inspecting the stack as JSON

`stackToJson()` turns the raw stack string into structured frames you can log or filter, rather than a single opaque string:

```typescript theme={null}
import { Exception } from "@ooneex/exception";

try {
  throw new Exception("Test error");
} catch (error) {
  if (error instanceof Exception) {
    const frames = error.stackToJson();
    frames?.forEach((frame, index) => {
      console.log(`${index + 1}. ${frame.functionName ?? "<anonymous>"}`);
      console.log(`   at ${frame.fileName}:${frame.lineNumber}:${frame.columnNumber}`);
    });
  }
}
```

## Best practices

* **Throw exceptions, not bare errors.** Use `Exception` (or a specialized subclass) so every failure carries a status code and structured data that a handler can act on uniformly.
* **Pick the most specific type.** Reach for `NotFoundException`, `BadRequestException`, `UnauthorizedException`, or `MethodNotAllowedException` when they fit; their preset status codes make intent obvious at the throw site.
* **Attach context as `data`, not in the message.** Put ids, field names, and constraints in `data` so they stay machine-readable; keep the `message` human-readable.
* **Use `key` for grouping.** A stable `key` lets you classify, route, and translate errors without parsing the message string.
* **Log with structure.** Send exceptions to the [Logger](/components/logger) and use `stackToJson()` and `data` for searchable, structured records rather than concatenated strings.
* **Guard debug output.** Expose `stackToJson()` only in non-production responses; in production surface `message`, `status`, and safe `data` only.
* **Wrap, don't discard.** When catching a native error, wrap it so its message and stack survive on `native` while you add application context.
