degenbot_cli_core/command.rs
1//! The constructor-parsed command model (ADR-051 D1).
2//!
3//! One `Command` enum, one execution entry. The argv facade maps clap matches
4//! into a `Command` value; it never re-encodes semantics.
5
6use crate::aave::{self, AaveCommand};
7use crate::cancel::CancelHandle;
8use crate::context::CliContext;
9use crate::database;
10use crate::error::CliError;
11use crate::exchange::{self, ExchangeCommand};
12use crate::fleet::{self, FleetCommand};
13use crate::path::{self, PathCommand};
14use crate::pool::{self, PoolCommand};
15use crate::prompt::{PromptPlan, Prompter};
16use crate::report::CommandReport;
17
18/// A console command.
19#[derive(Debug, Clone, PartialEq)]
20pub enum Command {
21 /// The `database` command group.
22 Database(DatabaseCommand),
23 /// The `exchange` command group.
24 Exchange(ExchangeCommand),
25 /// The `pool` command group.
26 Pool(PoolCommand),
27 /// The `aave` command group.
28 Aave(AaveCommand),
29 /// The `fleet` command group (ADR-051 D6).
30 Fleet(FleetCommand),
31 /// The `path` command group (ADR-051 D6).
32 Path(PathCommand),
33}
34
35impl Command {
36 /// The command's declared confirmation policy (ADR-051 D4).
37 #[must_use]
38 pub fn prompt_plan(&self, ctx: &CliContext<'_>) -> PromptPlan {
39 match self {
40 Self::Database(command) => command.prompt_plan(ctx),
41 Self::Exchange(command) => command.prompt_plan(ctx),
42 Self::Pool(command) => command.prompt_plan(ctx),
43 Self::Aave(command) => command.prompt_plan(ctx),
44 Self::Fleet(command) => command.prompt_plan(ctx),
45 Self::Path(command) => command.prompt_plan(ctx),
46 }
47 }
48
49 /// Execute the command, asking `prompter` per the command's [`PromptPlan`].
50 ///
51 /// The updater arms thread a fresh [`CancelHandle`]; use
52 /// [`execute_with_cancel`](Self::execute_with_cancel) to share the facade's
53 /// SIGINT-driven handle.
54 ///
55 /// # Errors
56 ///
57 /// [`CliError`] for a declined prompt, a database failure, a schema refusal,
58 /// an unresolved driver-domain value, or a core arm failure.
59 pub fn execute(
60 &self,
61 ctx: &CliContext<'_>,
62 prompter: &dyn Prompter,
63 ) -> Result<CommandReport, CliError> {
64 self.execute_with_cancel(ctx, prompter, &CancelHandle::new())
65 }
66
67 /// Execute the command with the caller's [`CancelHandle`] (ADR-051 D7: the
68 /// facade installs the SIGINT policy and hands the handle in).
69 ///
70 /// # Errors
71 ///
72 /// As [`execute`](Self::execute).
73 pub fn execute_with_cancel(
74 &self,
75 ctx: &CliContext<'_>,
76 prompter: &dyn Prompter,
77 cancel: &CancelHandle,
78 ) -> Result<CommandReport, CliError> {
79 match self {
80 Self::Database(command) => {
81 database::execute(command, ctx, prompter).map(CommandReport::Database)
82 }
83 Self::Exchange(command) => {
84 exchange::execute(command, ctx, prompter).map(CommandReport::Exchange)
85 }
86 Self::Pool(command) => {
87 pool::execute(command, ctx, prompter, cancel).map(CommandReport::Pool)
88 }
89 Self::Aave(command) => {
90 aave::execute(command, ctx, prompter, cancel).map(CommandReport::Aave)
91 }
92 Self::Fleet(command) => fleet::execute(command, ctx).map(CommandReport::Fleet),
93 Self::Path(command) => path::execute(command, ctx).map(CommandReport::Path),
94 }
95 }
96}
97
98/// The `database` command group (the template group; ports
99/// `src/degenbot/cli/database.py` arm for arm).
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum DatabaseCommand {
102 /// Back up the database to its `.db.bak` sibling. Confirms replacement only
103 /// when the target already exists.
104 Backup,
105 /// Remove and recreate the database (confirms unless `--force`).
106 Reset {
107 /// Skip the confirmation prompt.
108 force: bool,
109 },
110 /// RETIRED: render the pointed-retirement error and exit 1. The database
111 /// upgrades itself at open (ADR-052).
112 Upgrade {
113 /// Accepted for argv parity; the retirement error fires before any prompt.
114 force: bool,
115 },
116 /// Compact the database (`VACUUM`); never prompts.
117 Compact,
118 /// Flip an Alembic-stamped DB into Rust schema ownership (ADR-010).
119 Cutover {
120 /// Report the schema state and what cutover would do; write nothing.
121 dry_run: bool,
122 /// Skip the confirmation prompt.
123 force: bool,
124 },
125 /// Rebuild an Alembic-stamped DB into Rust ownership out-of-place (ADR-011).
126 Heal {
127 /// Report the schema state and what heal would do; write nothing.
128 dry_run: bool,
129 /// Skip the confirmation prompt.
130 force: bool,
131 },
132 /// Read-only schema-state inspection; never triggers a write.
133 Inspect,
134}
135
136impl DatabaseCommand {
137 /// The arm's declared confirmation policy — ported verbatim from the click
138 /// handlers (backup: replace-target-exists; reset/cutover/heal: unless-force;
139 /// compact/inspect: none).
140 ///
141 /// `upgrade` reports [`PromptPlan::UnlessForce`] for argv parity with the
142 /// retired handler, but its arm returns [`CliError::DatabaseUpgradeRetired`]
143 /// before any prompt is reached.
144 #[must_use]
145 pub fn prompt_plan(&self, ctx: &CliContext<'_>) -> PromptPlan {
146 match self {
147 Self::Backup => PromptPlan::OnCondition(
148 database::database_backup_path(&ctx.database_path().value).exists(),
149 ),
150 Self::Reset { .. }
151 | Self::Upgrade { .. }
152 | Self::Cutover { .. }
153 | Self::Heal { .. } => PromptPlan::UnlessForce,
154 Self::Compact | Self::Inspect => PromptPlan::None,
155 }
156 }
157}