@@ -16,7 +16,31 @@ Each subcommand of `tern` (e.g., `tern verify`) is organized as follows:
1616 }
1717 ```
1818
19- 3 . ** Enum Variant** : Reference the struct as a tuple variant in ` CliCommand ` (in ` src/cli/mod.rs ` ):
19+ 3 . ** Dispatch Method** : Each arguments struct must implement a ` dispatch() ` method that contains the full command implementation:
20+ ``` rust
21+ impl Verify {
22+ /// Dispatch the verify command.
23+ pub async fn dispatch (self ) -> miette :: Result <()> {
24+ // Full implementation here using self.* fields
25+ let backend = load_backend (self . path. as_deref ());
26+ ensure_backend_initialized (& backend ). await ? ;
27+
28+ // ... rest of implementation
29+
30+ Ok (())
31+ }
32+ }
33+ ```
34+
35+ 4 . ** Helper Functions** : Keep reusable helper functions (like ` confirm_destructive_changes() ` , ` find_migration() ` , ` parse_migration_id() ` ) as separate freestanding functions within the same module:
36+ ``` rust
37+ /// Helper function for user confirmation.
38+ fn confirm_destructive_changes () -> miette :: Result <bool > {
39+ // ...
40+ }
41+ ```
42+
43+ 5 . ** Enum Variant** : Reference the struct as a tuple variant in ` CliCommand ` (in ` src/cli/mod.rs ` ):
2044 ``` rust
2145 #[derive(Debug , Subcommand , Clone )]
2246 pub enum CliCommand {
@@ -26,14 +50,23 @@ Each subcommand of `tern` (e.g., `tern verify`) is organized as follows:
2650 }
2751 ```
2852
29- 4 . ** Dispatch** : Handle the tuple variant in the ` dispatch ` method:
53+ 6 . ** Enum Dispatch** : The ` CliCommand::dispatch ` method simply delegates to the wrapped type's ` dispatch() ` method:
3054 ``` rust
31- CliCommand :: Verify (args ) => {
32- verify :: run_verify (& args . database_url, args . format). await
55+ impl CliCommand {
56+ pub async fn dispatch (self ) -> miette :: Result <()> {
57+ match self {
58+ CliCommand :: Verify (args ) => args . dispatch (). await ,
59+ // ...
60+ }
61+ }
3362 }
3463 ```
3564
36- This pattern keeps command definitions close to their implementations and makes the ` CliCommand ` enum more concise.
65+ This pattern:
66+ - Keeps command definitions close to their implementations
67+ - Makes the ` CliCommand ` enum dispatch trivial (just delegation)
68+ - Ensures no business logic lives in ` src/cli/mod.rs `
69+ - Makes each command self-contained and testable
3770
3871## Nested Subcommands
3972
@@ -47,7 +80,7 @@ For nested subcommands (e.g., `tern schema export`), the same pattern applies re
4780 src/cli/schema/migrate/ # Nested subcommand
4881 ```
4982
50- 2 . ** Arguments Struct** : Each nested subcommand has its own struct in its ` mod.rs ` :
83+ 2 . ** Arguments Struct with Dispatch ** : Each nested subcommand has its own struct with a ` dispatch() ` method containing the full implementation :
5184 ``` rust
5285 // src/cli/schema/export/mod.rs
5386 #[derive(Debug , Clone , clap:: Args )]
@@ -56,6 +89,18 @@ For nested subcommands (e.g., `tern schema export`), the same pattern applies re
5689 pub output : Option <PathBuf >,
5790 // ...
5891 }
92+
93+ impl Export {
94+ /// Dispatch the schema export command.
95+ pub async fn dispatch (self ) -> miette :: Result <()> {
96+ let backend = load_backend (self . path. as_deref ());
97+ ensure_backend_initialized (& backend ). await ? ;
98+
99+ // Full implementation using self.output, self.path, etc.
100+
101+ Ok (())
102+ }
103+ }
59104 ```
60105
611063 . ** Parent Enum** : The parent command defines a subcommand enum using tuple variants:
@@ -70,16 +115,69 @@ For nested subcommands (e.g., `tern schema export`), the same pattern applies re
70115 }
71116 ```
72117
73- 4 . ** Dispatch** : The parent enum's dispatch method delegates to the nested commands :
118+ 4 . ** Parent Dispatch** : The parent enum's dispatch method delegates to each nested command's ` dispatch() ` :
74119 ``` rust
75120 impl SchemaAction {
76121 pub async fn dispatch (self ) -> miette :: Result <()> {
77122 match self {
78- SchemaAction :: Export (args ) => {
79- schema :: run_schema_export (args . output, args . path. as_deref (), args . format). await
80- }
123+ SchemaAction :: Export (args ) => args . dispatch (). await ,
124+ SchemaAction :: Diff (args ) => args . dispatch (). await ,
81125 // ...
82126 }
83127 }
84128 }
85129 ```
130+
131+ ## Deprecated Commands
132+
133+ For deprecated commands, include the deprecation warning at the start of the ` dispatch() ` method:
134+
135+ ``` rust
136+ impl Compile {
137+ /// Dispatch the compile command.
138+ pub async fn dispatch (self ) -> miette :: Result <()> {
139+ anstream :: eprintln! (
140+ " WARNING: 'compile' is deprecated. Use 'tern import' + 'tern build' instead."
141+ );
142+
143+ // Full implementation follows...
144+ let backend = load_backend (self . path. as_deref ());
145+ // ...
146+ }
147+ }
148+ ```
149+
150+ Also mark the command as hidden in the enum:
151+ ``` rust
152+ #[derive(Debug , Subcommand , Clone )]
153+ pub enum CliCommand {
154+ /// [DEPRECATED] Compile a migration to source code
155+ #[command(hide = true)]
156+ Compile (compile :: Compile ),
157+ // ...
158+ }
159+ ```
160+
161+ ## Testing Commands
162+
163+ When writing tests for commands, use the struct-based approach rather than calling helper functions:
164+
165+ ``` rust
166+ #[tokio:: test]
167+ async fn test_show_migration () {
168+ let temp_dir = TempDir :: new (). unwrap ();
169+ // ... setup ...
170+
171+ let show = Show {
172+ migration_id : baseline_id ,
173+ format : OutputFormat :: Text ,
174+ path : Some (temp_dir . path (). to_path_buf ()),
175+ };
176+ show . dispatch (). await . unwrap ();
177+ }
178+ ```
179+
180+ This approach:
181+ - Tests the actual command interface users will use
182+ - Ensures argument parsing and dispatch work together
183+ - Makes tests more representative of real usage
0 commit comments