# Throwing

Refuse from a handler by throwing a Notice, so the user gets a reason rather than silence. Covers what each kind of throw produces, the fields on a Notice, and what render receives.

A handler refuses by throwing. seedcord catches the throw at one boundary and sends the reply through the handler, so the class you throw sets what the reply says. A gate throws into the same boundary, and its refusal reaches the user the same way.

Say your ban handler checks for an open case in a helper. Returning a flag means `execute()` has to check it, and every caller of every helper that can refuse has to check one too. Throw from the helper and the handler stops at the line that found the problem.

You write a refusal as a [`Notice`](https://docs.seedcord.org/packages/core/latest/notice) subclass.

```ts title="src/handlers/Ban.ts"
import {
    BuilderComponent,
    Notice,
    SlashHandler,
    SlashRoute
} from '@seedcord/gateway';

import type { ReplyResponse } from '@seedcord/gateway';

class OpenCaseCard extends BuilderComponent<'container'> {
    constructor() {
        super('container');

        this.instance.addTextDisplayComponents((text) =>
            text.setContent(
                '### Cannot ban\nThat user already has an open case.'
            )
        );
    }
}

class OpenCaseExists extends Notice {
    constructor(id: string) {
        super(`user ${id} has an open case`);
    }

    public render(): ReplyResponse {
        return { components: [new OpenCaseCard().component] };
    }
}

@SlashRoute('ban')
export class Ban extends SlashHandler<'ban'> {
    public async execute(): Promise<void> {
        const target = this.options.getUser('target');
        await this.assertNoOpenCase(target.id);

        await this.reply(`Banned ${target.username}.`);
    }

    private async assertNoOpenCase(id: string): Promise<void> {
        const open = await cases.countOpen(id);
        if (open > 0) throw new OpenCaseExists(id);
    }
}
```

Your logs and your fault reports read the string you pass to `super()`. The user never sees it, so the id goes there. `render()` builds the reply and returns a [`ReplyResponse`](https://docs.seedcord.org/packages/types/latest/reply-response), the same `{ components }` shape that [`this.reply()`](https://docs.seedcord.org/packages/gateway/latest/repliable-handler#reply) takes.

`assertNoOpenCase` refuses on its own. `execute()` calls it and continues, since a refusal never comes back as a return value.

`super()` takes a second argument too, for the error you caught.

```ts
class ProfileUnavailable extends Notice {
    constructor(cause: unknown) {
        super('the profile lookup failed', { cause });
        this.report = true;
    }
}
```

`{ cause }` keeps that error on the throw. With `report` set to `true` there, the fault report prints its stack.

## What each throw produces

{/* prettier-ignore-start */}

| you throw                                                             | the user sees                           | reported              |
| --------------------------------------------------------------------- | --------------------------------------- | --------------------- |
| a `Notice` subclass                                                   | whatever your `render()` returns        | when you set `report` |
| a [`Fault`](https://docs.seedcord.org/packages/core/latest/fault)     | a generic card carrying a tracking uuid | yes, by default       |
| a [`Silence`](https://docs.seedcord.org/packages/core/latest/silence) | nothing                                 | no                    |
| any other error                                                       | that same generic card                  | yes                   |

{/* prettier-ignore-end */}

Reported means seedcord logs the throw and puts it on the bus, where a [webhook reporter](/replying/reporting) can send it to a Discord channel. [Faults](/replying/faults) explains the bottom three rows.

## The fields on a Notice

```ts
constructor(id: string) {
    super(`user ${id} has an open case`);

    this.report = true;
    this.ephemeral = false;
    this.summary = 'the target already has an open case';
}
```

{/* prettier-ignore-start */}

| field       | default | what it changes                                                                                                              |
| ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `report`    | `false` | `true` logs the throw with a uuid and publishes it to the `handledException` bus key                                         |
| `ephemeral` | `true`  | `false` shows the reply to everyone in the channel                                                                           |
| `summary`   | unset   | a one-line reason the [`or`](https://docs.seedcord.org/packages/core/latest/or) gate lists when every gate inside it refuses |

{/* prettier-ignore-end */}

Only the `or` gate reads `summary`. [Combining gates](/checks/combining) shows where it appears.

> **Warning**
>
> Throwing a custom `Notice` for a real bug shows its card and produces nothing else, because `report` starts `false`. Throw a [`Fault`](https://docs.seedcord.org/packages/core/latest/fault) when you need the error in your logs or set `report = true` on the `Notice`.

## What render receives

`render()` takes a [`RenderContext`](https://docs.seedcord.org/packages/types/latest/render-context). Leave the parameter off when you don't read any of its fields, as the first sample does.

{/* prettier-ignore-start */}

| field                   | holds                                                                                                                           |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `ctx.uuid`              | the id seedcord logs and reports for this throw                                                                                 |
| `ctx.developerUsername` | `notifications.developerUsername` from your bot config, set on [Faults](/replying/faults). `undefined` when you have not set it |
| `ctx.dispatch`          | the [dispatch context](/checks/dispatch-context) the handler and its gates read                                                 |

{/* prettier-ignore-end */}

```ts
public render(ctx: RenderContext): ReplyResponse {
    const contact = ctx.developerUsername ?? 'the developer';

    return {
        components: [new TraceCard(ctx.uuid, contact).component]
    };
}
```

`LookupFailed` builds its `TraceCard` inside `render()`. seedcord calls `render()` each time it shows the refusal, so every reply gets new builders with your bot color read at that moment.
