# User Functions & ExecutionContext

> How to write and register the custom functions that power your bot's dynamic behavior.

## Function Signatures

Every user function is `async` and receives an `ExecutionContext` as its first argument. The exact signature depends on the function type.

### Getter

Returns dynamic values used in flags and content templates. Registered when the name starts with `get_` or ends with `_getter`.

```python
async def get_greeting(context: ExecutionContext) -> Any:
    user = context.get_from_event("event_from_user")
    return f"Hello, {user.first_name}!"
```

### Condition

Gates handler execution. All conditions on a handler must return `True` for it to run. Registered when the name starts with `is_` / `has_` or ends with `_condition`.

```python
async def is_admin(context: ExecutionContext) -> bool:
    return context.user_id in ADMIN_IDS
```

### Validator

Validates user text input on scenes with `expect_input=True`. Returns `(True, None)` on success or `(False, "error message")` on failure. Registered when the name starts with `validate_` or ends with `_validator`.

```python
async def validate_email(value: str, context: ExecutionContext) -> tuple[bool, str | None]:
    if "@" not in value:
        return False, "Please enter a valid email address"
    return True, None
```

### Custom Action

Arbitrary logic triggered by `ActionType.CUSTOM`. Registered when the name starts with `action_` or ends with `_action`.

```python
async def action_add_to_cart(context: ExecutionContext, params: dict) -> Any:
    product_id = context.get_user_input("selected_product")
    # ... business logic ...
```

### Business Action

Triggered by `ActionType.BUSINESS_ACTION`. Unlike other function types, it receives the raw aiogram event as a second argument. Registered via the separate `business_actions` dict.

```python
async def process_payment(context: ExecutionContext, event: Message | CallbackQuery, **params) -> Any:
    amount = params.get("amount", 0)
    # ... payment logic ...
```

## ExecutionContext API

The `ExecutionContext` object is passed to every user function. It provides access to the current user, bot, event data, flags, and stored input.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `context.user_id` | `int \| None` | Telegram user ID |
| `context.chat_id` | `int \| None` | Telegram chat ID |
| `context.bot` | `Bot \| None` | aiogram Bot instance |
| `context.event` | `Message \| CallbackQuery \| None` | Raw aiogram event |
| `context.event_data` | `dict` | Contains `"event_from_user"` (aiogram User), `"bot"` (Bot), and any middleware-injected data |
| `context.navigation_state.current_scene` | `str` | Current scene ID |
| `context.event_mode` | `EventMode` | One of `MESSAGE`, `CALLBACK`, `SCHEDULED`, or `WEBHOOK` |

### Methods

| Method | Returns | Description |
|---|---|---|
| `context.get_flag(name, default=None)` | `Any` | Read a resolved flag value (sync, from cache) |
| `await context.get_flag_async(name, default=None)` | `Any` | Resolve a flag asynchronously if not yet cached |
| `context.get_user_input(key, default=None)` | `Any` | Read saved input (from `SAVE_INPUT` or `callback_params`) |
| `context.save_user_input(key, value)` | `None` | Store data manually |
| `context.clear_user_input(keys=None)` | `None` | Clear stored input; `None` clears all |
| `context.get_from_event(key, default=None)` | `Any` | Shortcut for `event_data.get(key)` |
| `context.get_container()` | `Any` | Get dishka DI container from event_data (if middleware configured) |
| `context.can_send_message()` | `bool` | `True` if bot and user_id are available |
| `context.can_edit_message()` | `bool` | `True` if context supports editing messages (callback mode) |

## Registration Methods

There are three ways to register functions.

### a) `custom_functions` dict in UIRouter schema

Maps a function name to a dotted import path. The name prefix/suffix determines which registry receives it:

- `get_*` or `*_getter` -- getters
- `validate_*` or `*_validator` -- validators
- `is_*`, `has_*`, or `*_condition` -- conditions
- `action_*` or `*_action` -- actions
- anything else -- getters (fallback)

```python
UIRouter(
    custom_functions={
        "get_greeting": "mybot.functions.get_greeting",
        "is_admin": "mybot.functions.is_admin",
        "validate_email": "mybot.functions.validate_email",
        "action_add_to_cart": "mybot.functions.action_add_to_cart",
    },
    ...
)
```

### b) Programmatic registration

Register directly on the executor's `MasterRegistry`:

```python
executor.registry.getters.register("get_greeting", get_greeting)
executor.registry.conditions.register("is_admin", is_admin)
executor.registry.validators.register("validate_email", validate_email)
executor.registry.actions.register("action_add_to_cart", action_add_to_cart)
```

### c) `business_actions` dict in UIRouter schema

Same format as `custom_functions`, but every entry is routed to the `business_actions` registry regardless of naming convention:

```python
UIRouter(
    business_actions={
        "process_payment": "mybot.payments.process_payment",
    },
    ...
)
```
