# App — FastAPI App Factory & Router

## Overview

This module provides two ways to create an ENTR adapter HTTP application: a **factory function** for the common case, and a **router function** for adapters that need full control over the FastAPI application. Both produce the same set of endpoints; the difference is how much control the adapter retains.

## Concepts

### Factory vs Router

The factory (`create_adapter_app()`) returns a fully configured `FastAPI` application in a single call. It handles lifespan management, router mounting, and endpoint registration. This is the right choice for ~90% of adapters.

The router (`create_adapter_router()`) returns an `APIRouter` that the adapter mounts into its own `FastAPI` instance. This is for adapters that need custom middleware, additional routes, or non-standard startup/shutdown behavior that the factory's `on_startup`/`on_shutdown` callbacks don't cover.

### Extension Path

Start with the factory. When requirements grow beyond what the factory supports, switch to the router — the adapter's handler code doesn't change, only the `main.py` entry point. The progression:

1. **Factory** — one function call, everything works
2. **Factory + callbacks** — add `on_startup`/`on_shutdown` for resource management (DB connections, client pools)
3. **Router** — full control: create your own `FastAPI()`, add middleware, mount the adapter router alongside custom routes

### Dispatcher

The `Dispatcher` is an internal component that routes incoming requests to the correct handler based on `adapter_identifier`. It is created automatically by both the factory and router — adapters never instantiate it directly.

When a `/process` request arrives:
1. The dispatcher looks up the handler by `payload.adapter_identifier`
2. It wraps the handler's `process()` call in a flight recorder (see `logging.md`)
3. It returns the `AdapterResponse`, with execution logs attached

Unknown identifiers and unhandled exceptions produce a failed `AdapterResponse` — they never surface as HTTP errors to Core.

### Architecture

The factory wraps the router:

```
create_adapter_app(handlers)
    └── create_adapter_router(handlers)
            └── Dispatcher(handlers)
                    └── execute_with_flight_recorder()
                            └── handler.process(payload)
```

## Patterns

### Bundled Endpoints

Both factory and router register these endpoints:

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/process` | POST | Document processing — dispatches to handler's `process()` |
| `/list_documents` | POST | Document retrieval — lists available documents from external source |
| `/download_document` | POST | Document retrieval — downloads a specific document |
| `/health` | GET | Liveness check — returns status and registered handler identifiers |
| `/debug/handlers` | GET | Debug info — only available when `ENV=local` |

Most adapters only need `/process`. The retrieval endpoints are only relevant for adapters that pull documents from external sources (email, FTP). These endpoints delegate to the handler's `retrieval_handler` if one is attached.

### Error Handling at the Endpoint Level

- Unknown `adapter_identifier` → `AdapterResponse(status="failed")` — never HTTP 404
- Missing retrieval handler → structured error response — never HTTP 500
- Unhandled handler exceptions → caught by flight recorder, returned as failed response with safe error message

### Lifecycle Callbacks

The factory accepts `on_startup` and `on_shutdown` callback lists. Both sync and async callbacks are supported.

- **Startup**: Executed in list order. Exceptions are fatal — the app will not start.
- **Shutdown**: Executed in list order. Exceptions are logged but non-fatal — all callbacks run even if one fails.

Common uses: opening/closing database connections, initializing HTTP client pools, warming caches.

### Debug Endpoints

The `/debug/handlers` endpoint is only registered when `ENV=local`. This is controlled by:
1. The `settings.ENV` attribute if a settings object is passed
2. The `ENV` environment variable otherwise
3. Defaults to `"production"` (debug endpoints disabled)

## Guidance

### When to Use Which

| Scenario | Approach |
|----------|----------|
| Standard adapter with default config | Factory |
| Need startup/shutdown callbacks | Factory with `on_startup`/`on_shutdown` |
| Need custom middleware (CORS, auth) | Router |
| Need additional custom endpoints | Router |
| Need custom lifespan management | Router |

## Examples

**Minimal adapter (factory):**
```python
from entr_adapter_core import create_adapter_app

app = create_adapter_app(
    handlers={"invoice": InvoiceHandler(settings=my_settings)}
)
```

**Adapter with resource management (factory + callbacks):**
```python
app = create_adapter_app(
    handlers={"invoice": InvoiceHandler(settings=my_settings)},
    on_startup=[connect_db, warm_cache],
    on_shutdown=[close_db],
)
```

**Adapter with custom middleware (router):**
```python
from fastapi import FastAPI
from entr_adapter_core import create_adapter_router

app = FastAPI(title="Custom Adapter")
app.add_middleware(CORSMiddleware, allow_origins=["*"])
app.include_router(create_adapter_router(handlers={"invoice": InvoiceHandler(settings=my_settings)}))
```

## Cross-References

- **API reference**: See docstrings in `factory.py`, `router.py`, and `dispatcher.py` for parameter details, return types, and exception behavior
- **Handler framework**: See `handlers.md` for how handlers process requests
- **Flight recorder**: See `logging.md` for how execution logs are captured during dispatch
- **Schemas**: See `schemas.md` for `DocumentPayload` and `AdapterResponse` contracts
- **Retrieval connectors**: See `connectors.md` for pre-built retrieval handlers (Gmail, Exchange)
