Skip to content

refactor(actions): make Context a type-keyed extensions map#341

Merged
passcod merged 1 commit into
mainfrom
refactor/cli-context
May 23, 2026
Merged

refactor(actions): make Context a type-keyed extensions map#341
passcod merged 1 commit into
mainfrom
refactor/cli-context

Conversation

@passcod
Copy link
Copy Markdown
Member

@passcod passcod commented May 23, 2026

Summary

Replaces the two-slot `Context<TopArgs, SubArgs>` with a type-keyed extensions map, axum/React-style. Each subcommand level provides its own args struct into the context; leaf handlers extract whichever ancestors they need via `ctx.require::()`.

Motivation

The old `Context<A, B>` could only carry two args shapes (immediate parent + self), so a handler nested more than two deep would lose access to its grandparent's args (e.g. `Args` from inside a tamanu leaf). Deep features like the WIP `tamanu logs` and `reload` would either have to thread args through manually or duplicate state.

What changed

  • `context.rs`: `Context` is now a `HashMap<TypeId, Arc<dyn Any + Send + Sync>>` with `provide` / `get` / `require` methods. It's `Clone` (cheap — only Arc clones).
  • `subcommands!` macro: takes a `(args, ctx) -> (Action, Context)` prep closure; emits a uniform `async fn run(args, ctx)` dispatcher. Each level inserts its own args into ctx before dispatching to children.
  • All 25+ leaf and branch handlers migrated to the new `async fn run(args: Self, ctx: Context)` signature. Handlers access ancestor args via `ctx.require::()`.

Tradeoff

Compile-time "you definitely have these two levels" guarantee → runtime "panic if you forgot to provide". Mitigated by the macro inserting symmetrically and 5 unit tests on the Context primitives. Worst case is `bestool foo bar` panicking with a clear `required value of type `X` not provided` if I missed a provide() somewhere — which we'd catch on the first dev run.

@passcod passcod force-pushed the refactor/cli-context branch from c09067d to 987962c Compare May 23, 2026 03:01
Replace the two-slot `Context<TopArgs, SubArgs>` with a type-keyed
extensions map. Every subcommand level provides its own args struct into
the context; leaf handlers extract whichever ancestors they need via
`ctx.require::<T>()`. This frees deeply-nested handlers from being limited
to the immediate parent and self, and removes the generics noise on
handler signatures.

All handlers now share the same signature: `async fn run(args, ctx)`. The
`subcommands!` macro inserts each level's args into ctx before dispatch.
@passcod passcod force-pushed the refactor/cli-context branch from 987962c to 7bf31da Compare May 23, 2026 03:10
@passcod passcod added this pull request to the merge queue May 23, 2026
Merged via the queue into main with commit a1bb4db May 23, 2026
8 checks passed
@passcod passcod deleted the refactor/cli-context branch May 23, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant