# Combining gates

Combine gates with or() and and(), for example to let a command run when any one gate passes, or to group checks you reuse. Covers which handlers a combined gate fits, when an or moves on to its next arm, and what the user sees when every arm refuses.

[`@Gated`](https://docs.seedcord.org/packages/gateway/latest/gated) requires every gate you list. A command that a bot owner or a moderator can run needs only one of those two checks to pass.

[`or`](https://docs.seedcord.org/packages/core/latest/or) combines gates into one that passes when any arm passes. [`and`](https://docs.seedcord.org/packages/core/latest/and) combines them into one that needs every arm. The result goes on a handler like any other gate, or inside another combinator. Both take two arms or more.

## or, when any one of them is enough

`or` runs each arm in order and stops at the first one that passes. The rest never run.

```ts title="src/handlers/Award.ts"
import {
    Gated,
    OwnerOnly,
    RequireRole,
    SlashHandler,
    SlashRoute,
    or
} from '@seedcord/gateway';

@Gated(or(OwnerOnly(), RequireRole('221439658686939136')))
@SlashRoute('award')
export class Award extends SlashHandler<'award'> {
    public async execute(): Promise<void> {
        const member = this.options.getUser('member');

        await this.reply(`Awarded ${member.username}.`);
    }
}
```

A bot owner passes the first arm and never reaches the role check. Anyone holding the role passes the second.

## and, for a reusable pair

`@Gated(A(), B())` and `and(A(), B())` refuse under the same conditions. Use `and` when you want that pair as one value, usually to put it inside an `or`.

```ts
const staff = and(GuildOnly(), RequireRole('221439658686939136'));

const staffOrOwner = or(staff, OwnerOnly());
```

`staff` runs `GuildOnly` before `RequireRole`, since arms run left to right. The first refusal stops the rest.

The combined gate carries the names of its arms, joined with `&` for an `and` and `|` for an `or`. A combinator nested inside another gets parentheses around its name, which is why `staffOrOwner` above reads `(GuildOnly & RequireRole) | OwnerOnly`.

```ts
const guildOwner = and(GuildOnly(), OwnerOnly());
```

A compile error prints that name when the gate goes on a handler it doesn't fit.

## What each one accepts

The arms decide which handlers the combined gate fits.

{/* prettier-ignore-start */}

|       | required context              | a handler fits when       |
| ----- | ----------------------------- | ------------------------- |
| `and` | the intersection of every arm | it satisfies all of them  |
| `or`  | the union of every arm        | it satisfies at least one |

{/* prettier-ignore-end */}

An `and` of an event-only gate and an interaction-only gate doesn't fit any handler, since a handler can't provide both contexts. The compiler catches it at the `@Gated` line.

## What counts as declining

An `or` arm declines *only* when it throws a [`Notice`](https://docs.seedcord.org/packages/core/latest/notice) with `report` left at `false`. Anything else stops the whole `or`, and the user sees whatever that throw produces.

{/* prettier-ignore-start */}

| an arm throws                                                         | `or` does                                                              |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| a `Notice` with `report` false                                        | tries the next arm                                                     |
| a `Notice` with `report` true                                         | stops, reports it, shows that Notice                                   |
| a [`Fault`](https://docs.seedcord.org/packages/core/latest/fault)     | stops, shows the generic card, reports it unless its `report` is false |
| a [`Silence`](https://docs.seedcord.org/packages/core/latest/silence) | stops, and your bot doesn't reply                                      |
| any other error                                                       | stops, reports it, shows the generic card                              |

{/* prettier-ignore-end */}

Setting `report = true` on your own `Notice` to get a refusal into your logs takes that arm out of the decline path.

## The refusal when every arm refuses

`or` picks what to throw in three steps.

1. The `fail` option, when you passed one.
2. A list of what each arm needed, when every refusal carried a `summary`.
3. A generic refusal reading "You are not allowed to use this command."

Every catalog gate that refuses with a `Notice` sets a `summary`, so an `or` built from those alone reaches step two. A `notice` you pass to a catalog gate replaces its default, summary included.

```txt output
You need to meet at least one of these:
• be the bot owner
• hold the <@&221439658686939136> role
```

A [gate of your own](/checks/your-own) joins that list once its `Notice` sets `summary`. A single arm without a summary drops the whole list to the generic refusal, since a partial list would mislead by leaving an option out.

## Wording it yourself

The summary list reads as a checklist in seedcord's wording. When the caller needs one sentence instead, like "Staff only" or a note on how to get the role, pass `fail` in a trailing options object after the arms. Pass a `Notice` for a fixed refusal.

```ts
or(OwnerOnly(), RequireRole('221439658686939136'), {
    fail: new StaffOnly('caller is neither')
});
```

Pass a function when the refusal needs to read the context. It receives the same `ctx` the arms did.

```ts
or(OwnerOnly(), RequireRole('221439658686939136'), {
    fail: (ctx) => new StaffOnly(`${ctx.userId ?? 'nobody'} tried`)
});
```

`or` throws `fail` first and never builds the summary list.

## Two arms, at least

An `or` with one arm is that gate on its own, so the compiler rejects it.

```ts
or(OwnerOnly());
```

The options object never counts as an arm, so `or(A(), { fail })` fails the same way.
