# YAML Configuration Reference

Riferimento completo per la sintassi del file `config.yaml` di genro-asgi.

## Stato

✅ **Implementato** - La maggior parte delle sezioni è implementata e testata.

⚠️ **Alcune feature legacy** - Vedere sezione "Elementi da Verificare".

## Configuration Precedence

L'ordine di override (successivi vincono sui precedenti):

```text
1. Built-in DEFAULTS           (priorità più bassa)
2. Global config: ~/.genro-asgi/config.yaml
3. Project config: <server_dir>/config.yaml
4. Environment variables: GENRO_ASGI_*
5. Command line arguments
6. Explicit constructor parameters  (priorità più alta)
```

## Struttura YAML Completa

```yaml
# ╔══════════════════════════════════════════════════════════════════╗
# ║                       SERVER SECTION                             ║
# ╚══════════════════════════════════════════════════════════════════╝
server:
  host: "0.0.0.0"           # Bind address (default: "127.0.0.1")
  port: 8000                # Listen port (default: 8000)
  reload: false             # Auto-reload on file changes (default: false)
  main_app: shop            # Optional: default app for "/" redirect

# ╔══════════════════════════════════════════════════════════════════╗
# ║                      OPENAPI SECTION                             ║
# ╚══════════════════════════════════════════════════════════════════╝
openapi:
  title: "My API"                          # Default: "genro-asgi API"
  version: "1.0.0"                         # Default: "0.1.0"
  description: "API description"
  contact:
    name: "Support"
    email: "support@example.com"
  license:
    name: "MIT"

# ╔══════════════════════════════════════════════════════════════════╗
# ║                    MIDDLEWARE SECTION                            ║
# ╚══════════════════════════════════════════════════════════════════╝
# Formato raccomandato: dict con on/off
middleware:
  errors: on       # Default: True, Order: 100
  logging: off     # Default: False, Order: 200
  cors: on         # Default: False, Order: 300
  auth: on         # Default: False, Order: 400
  compression: off # Default: False, Order: 900
  cache: off       # Default: False, Order: 900

# Formati alternativi (legacy):
# middleware: cors, auth           # String con virgole
# middleware:                      # Lista
#   - cors
#   - auth

# ╔══════════════════════════════════════════════════════════════════╗
# ║              MIDDLEWARE-SPECIFIC SETTINGS                        ║
# ╚══════════════════════════════════════════════════════════════════╝

# ────────────────── CORS MIDDLEWARE ──────────────────
cors_middleware:
  allow_origins: ["*"]                     # Default: ["*"]
  allow_methods: ["GET", "POST", "PUT", "DELETE"]  # Default: ["*"]
  allow_headers: ["Authorization", "Content-Type"] # Default: ["*"]
  allow_credentials: false                 # Default: false
  expose_headers: []                       # Default: []
  max_age: 3600                            # Default: 600 (preflight cache)

# ────────────────── AUTH MIDDLEWARE ──────────────────
auth_middleware:
  # Bearer Token Authentication
  bearer:
    reader_token:                          # Nome identificativo
      token: "tk_abc123"                   # Token value (lookup key)
      tags: "read"                         # Comma-separated o lista
    admin_token:
      token: "tk_admin"
      tags: ["read", "write", "admin"]     # Tags come lista

  # Basic Authentication
  basic:
    admin:                                 # Username (identity)
      password: "secret"                   # Password
      tags: "admin"                        # Tags
    readonly:
      password: "readonly123"
      # tags omesso → []

  # JWT Authentication (se configurato)
  jwt:
    internal:
      secret: "jwt_secret"
      algorithm: "HS256"                   # Default: HS256
      algorithms: ["HS256", "RS256"]       # Alternativamente, lista

# ────────────────── COMPRESSION MIDDLEWARE ──────────────────
compression_middleware:
  minimum_size: 500                        # Default: 500 bytes
  compression_level: 6                     # Default: 6 (1-9)

# ────────────────── LOGGING MIDDLEWARE ──────────────────
logging_middleware:
  level: "INFO"                            # Default: "INFO"
  include_headers: false                   # Default: false
  include_query: true                      # Default: true
  logger_name: "genro_asgi"                # Nome del logger

# ────────────────── ERROR MIDDLEWARE ──────────────────
error_middleware:
  debug: false                             # Show traceback in response

# ────────────────── CACHE MIDDLEWARE ──────────────────
cache_middleware:
  max_age: 3600                            # Default: 3600
  immutable: false                         # Default: false
  public: true                             # Default: true

# ╔══════════════════════════════════════════════════════════════════╗
# ║                     PLUGINS SECTION                              ║
# ╚══════════════════════════════════════════════════════════════════╝
plugins:                                   # genro-routes plugins
  logging:
    level: debug
  pydantic:
    strict: false

# ╔══════════════════════════════════════════════════════════════════╗
# ║                       APPS SECTION                               ║
# ╚══════════════════════════════════════════════════════════════════╝

# Formato SEMPLICE (solo modulo):
apps:
  shop: "main:ShopApp"                     # "module_path:ClassName"
  office: "office:OfficeApp"

# Formato ESTESO (con parametri):
apps:
  shop:
    module: "main:ShopApp"                 # Obbligatorio
    db_uri: "sqlite:///shop.db"            # → passato a on_init()
    cache_timeout: 300                     # → passato a on_init()

  admin:
    module: "admin:AdminApp"
    require_auth: true

  # StaticSite per servire file statici
  docs:
    module: "genro_asgi.applications.static_site:StaticSite"
    base_dir: "./public"
    index: "index.html"                    # Default file

  # StaticRouter (alias)
  static:
    module: "genro_asgi:StaticRouter"
    directory: "./public"

# Formato CONVENTION (nessun module specificato):
apps:
  shop:                                    # Cerca {shop}_app.py:Application
    # altri parametri opzionali

# ╔══════════════════════════════════════════════════════════════════╗
# ║                    SYS_APPS SECTION                              ║
# ╚══════════════════════════════════════════════════════════════════╝
sys_apps:
  swagger:
    module: "genro_asgi.sys_applications.swagger.swagger_app:Application"
  genro_api:
    module: "genro_asgi.sys_applications.genro_api.genro_api_app:Application"
```

## Server Options

| Option | Type | Default | Env Variable | Description |
|--------|------|---------|--------------|-------------|
| `host` | `str` | `"127.0.0.1"` | `GENRO_ASGI_HOST` | Bind address |
| `port` | `int` | `8000` | `GENRO_ASGI_PORT` | Listen port |
| `reload` | `bool` | `false` | `GENRO_ASGI_RELOAD` | Auto-reload |
| `server_dir` | `str` | `.` | `GENRO_ASGI_SERVER_DIR` | Server directory |
| `main_app` | `str` | `None` | - | Default app for "/" |

**Built-in defaults**:
```python
DEFAULTS = {"host": "127.0.0.1", "port": 8000, "reload": False}
```

## Middleware Registry

| Key | Class | Default | Order | Purpose |
|-----|-------|---------|-------|---------|
| `errors` | `ErrorMiddleware` | `True` | 100 | Exception handling |
| `logging` | `LoggingMiddleware` | `False` | 200 | Request logging |
| `cors` | `CORSMiddleware` | `False` | 300 | CORS headers |
| `auth` | `AuthMiddleware` | `False` | 400 | Authentication |
| `static` | `StaticMiddleware` | `False` | 800 | Static files |
| `compression` | `CompressionMiddleware` | `False` | 900 | Gzip responses |
| `cache` | `CacheMiddleware` | `False` | 900 | Response caching |

### Middleware Order Flow

```text
Request → ErrorMiddleware(100) → LoggingMiddleware(200) → CorsMiddleware(300)
        → AuthMiddleware(400) → Dispatcher → Response
```

L'ordine è determinato da `middleware_order` nella classe, non dalla posizione nel YAML.

## CORS Middleware Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `allow_origins` | `list[str]` | `["*"]` | Origini permesse |
| `allow_methods` | `list[str]` | `["*"]` | Metodi permessi |
| `allow_headers` | `list[str]` | `["*"]` | Headers permessi |
| `allow_credentials` | `bool` | `false` | Allow cookies/auth |
| `expose_headers` | `list[str]` | `[]` | Headers esposti al browser |
| `max_age` | `int` | `600` | Preflight cache (secondi) |

## Auth Middleware Options

### Bearer Token

```yaml
auth_middleware:
  bearer:
    token_name:                  # Identificativo (→ identity)
      token: "tk_xxx"            # Valore token (lookup key)
      tags: "read,write"         # Tags (stringa o lista)
```

### Basic Auth

```yaml
auth_middleware:
  basic:
    username:                    # Username (→ identity)
      password: "secret"         # Password
      tags: "admin"              # Tags
```

### JWT

```yaml
auth_middleware:
  jwt:
    config_name:
      secret: "jwt_secret"
      algorithm: "HS256"
```

### Authentication Output

Quando auth middleware è attivo, `scope["auth"]` contiene:

```python
# Bearer token valido
{"tags": ["read"], "identity": "reader_token", "backend": "bearer"}

# Basic auth valida
{"tags": ["admin"], "identity": "admin", "backend": "basic"}

# JWT valido
{"tags": ["read"], "identity": "sub_from_token", "backend": "jwt:internal"}

# No Authorization header
None
```

### Auth Behavior

| Scenario | Result |
|----------|--------|
| No Authorization header | `scope["auth"] = None`, request proceeds |
| Valid header | `scope["auth"] = {...}` |
| Invalid header | `HTTPException(401)` |

## Compression Middleware Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `minimum_size` | `int` | `500` | Min bytes to compress |
| `compression_level` | `int` | `6` | Gzip level (1-9) |

## Logging Middleware Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `level` | `str` | `"INFO"` | Log level |
| `include_headers` | `bool` | `false` | Log headers |
| `include_query` | `bool` | `true` | Log query params |
| `logger_name` | `str` | `"genro_asgi"` | Logger name |

## Cache Middleware Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `max_age` | `int` | `3600` | Cache max-age seconds |
| `immutable` | `bool` | `false` | Add immutable directive |
| `public` | `bool` | `true` | Public vs private cache |

## Apps Configuration

### Simple Format

Per apps senza parametri aggiuntivi:

```yaml
apps:
  shop: "main:ShopApp"           # "module_path:ClassName"
```

### Extended Format

Per apps con configurazione:

```yaml
apps:
  shop:
    module: "main:ShopApp"       # Obbligatorio
    db_uri: "sqlite:///shop.db"  # → passato a on_init()
    cache_timeout: 300           # → passato a on_init()
```

### Convention Format

Caricamento automatico basato su convenzione:

```yaml
apps:
  shop:                          # Cerca shop_app.py:Application
    db: "shop.db"                # Parametri passati a on_init()
```

### StaticSite Configuration

```yaml
apps:
  frontend:
    module: "genro_asgi.applications.static_site:StaticSite"
    base_dir: "./dist"           # Directory con file statici
    index: "index.html"          # Default file per SPA routing
```

## System Apps

```yaml
sys_apps:
  swagger:
    module: "genro_asgi.sys_applications.swagger.swagger_app:Application"
  genro_api:
    module: "genro_asgi.sys_applications.genro_api.genro_api_app:Application"
```

## Plugins (genro-routes)

```yaml
plugins:
  logging:
    level: debug
  pydantic:
    strict: true
```

Applicati a tutti i router via `router.plug(name, **opts)`.

### Middleware vs Plugins

| | Middleware | Plugins |
|-|------------|---------|
| **Level** | ASGI request/response | Individual handler |
| **Scope** | Whole server or per-app | Per-router, per-handler |
| **Config** | `middleware:` | `plugins:` |
| **Examples** | CORS, Compression | Logging, Validation |

## SmartOptions Engine

Il parsing YAML usa **SmartOptions** da `genro_toolbox`:

```python
from genro_toolbox import SmartOptions

# Da file YAML
config = SmartOptions("config.yaml")

# Da dict
config = SmartOptions({"host": "0.0.0.0"})

# Merge con operatore +
final = SmartOptions(DEFAULTS) + SmartOptions("config.yaml") + overrides

# Accesso
config.middleware          # Attributo
config["middleware"]       # Dict-like
config.missing_key         # → None (no AttributeError)
config.as_dict()           # → dict standard
```

### Type Conversion

SmartOptions converte automaticamente tipi basandosi su function signatures:

```python
def _server_opts_spec(
    port: int,     # "9000" → 9000
    reload: bool,  # "true" → True
) -> None:
    pass

# GENRO_ASGI_PORT="9000" diventa port=9000 (int)
```

### Merge Syntax

```yaml
# In override.yaml
middleware:
  +cors: on                # Il + fa merge invece di replace
```

## CLI Arguments

```bash
genro-asgi serve <server_dir> [options]

Options:
  --host HOST     Server host (default: 127.0.0.1)
  --port PORT     Server port (default: 8000)
  --reload        Enable auto-reload
  --config FILE   Config file name (default: config.yaml)
```

## Environment Variables

| Variable | Maps to |
|----------|---------|
| `GENRO_ASGI_HOST` | `server.host` |
| `GENRO_ASGI_PORT` | `server.port` |
| `GENRO_ASGI_RELOAD` | `server.reload` |
| `GENRO_ASGI_SERVER_DIR` | `server.server_dir` |

## Special Value Parsing

### Boolean on/off

```yaml
middleware:
  cors: on        # → True
  cors: off       # → False
  cors: true      # → True
  cors: yes       # → True
  cors: 1         # → True
```

### Tags as String or List

```yaml
auth_middleware:
  bearer:
    token1:
      tags: "read,write"           # → ["read", "write"]
    token2:
      tags: ["read", "write"]      # Già lista
    token3:
      # tags omesso → []
```

## Esempi dal Repository

### Minimo

```yaml
# examples/base_example/config.yaml
server:
  host: "127.0.0.1"
  port: 8000
```

### Con System Apps

```yaml
# examples/base_example/config_with_swagger.yaml
server:
  host: "127.0.0.1"
  port: 8000

sys_apps:
  swagger:
    module: "genro_asgi.sys_applications.swagger.swagger_app:Application"
  genro_api:
    module: "genro_asgi.sys_applications.genro_api.genro_api_app:Application"
```

### Completo con OpenAPI

```yaml
# examples/demo_shop/config.yaml
openapi:
  title: "Demo Shop API"
  version: "1.0.0"
  description: "Demo e-commerce API"

server:
  host: "127.0.0.1"
  port: 8000
  reload: true

middleware:
  cors: on

sys_apps:
  swagger:
    module: "genro_asgi.sys_applications.swagger.swagger_app:Application"
  genro_api:
    module: "genro_asgi.sys_applications.genro_api.genro_api_app:Application"
```

### Auth Complesso

```yaml
# examples/auth_demo/config.yaml
server:
  host: "127.0.0.1"
  port: 8003

middleware:
  cors: on
  auth: on

auth_middleware:
  bearer:
    guest_token:
      token: "tk_guest"
      tags: "guest"
    staff_token:
      token: "tk_staff"
      tags: "staff"
    senior_token:
      token: "tk_senior"
      tags: "staff,senior"
    manager_token:
      token: "tk_manager"
      tags: "staff,manager"
    admin_token:
      token: "tk_admin"
      tags: "staff,admin,senior"

  basic:
    guest:
      password: "guest"
      tags: "guest"
    staff:
      password: "staff123"
      tags: "staff"
    admin:
      password: "admin123"
      tags: "staff,admin,senior"
```

## Elementi da Verificare

| Feature | Stato | Note |
|---------|-------|------|
| `routesplugins` section | ⚠️ Legacy | Alias di `plugins`, verificare se obsoleto |
| `routedclass.configure()` | ⚠️ Legacy | Runtime config, potrebbe essere deprecato |
| Per-app `middleware` section | ⚠️ Da verificare | Documentato in missing_doc, non negli esempi |
| Per-app `routesplugins` | ⚠️ Da verificare | Documentato in missing_doc, non negli esempi |
| JWT authentication | 📋 Documentato | Non presente negli esempi attuali |
| `static` middleware | 📋 Documentato | Non negli esempi |

### Per-App Configuration (da verificare)

Sintassi documentata in `missing_doc` ma non presente negli esempi:

```yaml
# Global middleware
middleware:
  cors:
    allow_origins: ["*"]

# Global plugins
routesplugins:
  logging:
    level: info

apps:
  shop:
    module: "shop:ShopApp"
    # App-specific middleware (merge con global)
    middleware:
      compression:
        minimum_size: 1000
    # App-specific plugins (merge con global)
    routesplugins:
      pydantic:
        strict: true
```

## Related Documents

- [Configuration System](../../specifications/02_server_foundation/02_configuration.md) - Specifica principale
- [Middleware Chain](../middleware/chain.md) - Sistema middleware
- [Authentication](../middleware/authentication.md) - Auth middleware details
- [CORS](../middleware/cors.md) - CORS middleware details
- [genro-toolbox](../dependencies/genro-toolbox.md) - SmartOptions documentation

---

**Last Updated**: 2025-01-02
**Status**: ✅ Reference Document
