Skip to content

refactor: decouple AppState from per-adapter fields #1239

Description

@chaodu-agent

Problem

Every time a new adapter is added, all existing adapter test files must be updated to initialize the new fields — even though they never use them. This is because AppState is a single flat struct containing every adapter's state:

┌─────────────────────────────────────────────────────────┐
│                    AppState (God Struct)                 │
├─────────────────────────────────────────────────────────┤
│  discord: Option<...>                                   │
│  telegram: Option<...>                                  │
│  line: Option<...>                                      │
│  feishu: Option<...>                                    │
│  google_chat: Option<...>                               │
│  wecom: Option<...>                                     │
│  teams: Option<...>                                     │
│  vtuber: Option<...>            <-- new adapter         │
│  vtuber_pending: Arc<Mutex<..>> <-- new adapter         │
│  vtuber_ws_clients: Option<..>  <-- new adapter         │
│  ws_token: Option<...>                                  │
│  event_tx: ...                                          │
│  reply_token_cache: ...                                 │
└─────────────────────────────────────────────────────────┘

Adding vtuber forces changes in unrelated test files:

New adapter (vtuber)
       │
       ├──► line.rs tests   → add vtuber: None, vtuber_pending: ..., vtuber_ws_clients: None
       ├──► teams.rs tests  → add vtuber: None, vtuber_pending: ..., vtuber_ws_clients: None
       ├──► telegram tests  → add vtuber: None, ...
       └──► every future adapter test → same

Proposed Solution

Refactor to a registry/trait-object pattern so adapters are decoupled:

┌─────────────────────────────────┐
│         AppState (slim)         │
├─────────────────────────────────┤
│  adapters: AdapterRegistry      │  <-- dynamic dispatch
│  event_tx: ...                  │
│  shared_config: ...             │
└─────────────────────────────────┘
         │
         v
┌─────────────────────────────────┐
│       AdapterRegistry           │
│  HashMap<Platform, Box<dyn      │
│         ChatAdapter>>           │
└─────────────────────────────────┘
    │         │          │
    v         v          v
┌──────┐ ┌──────┐ ┌────────┐
│ Line │ │Teams │ │ VTuber │   <-- independent, no cross-knowledge
└──────┘ └──────┘ └────────┘

Benefits:

  • Adding a new adapter does NOT touch any existing adapter code or tests
  • Each adapter test only initializes its own state
  • Cleaner separation of concerns

Context

Observed during PR #1234 (vtuber adapter) where line.rs and teams.rs test fixtures had to add #[cfg(feature = "vtuber")] fields just to compile.

Notes

This is a non-blocking architectural improvement — not urgent, but will reduce maintenance overhead as more adapters are added.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions