ferric_ai/handler.rs
1// Extensible command handler system for ferric CLI
2//
3// This module provides a clean, extensible architecture for handling CLI commands.
4// The handler pattern allows easy addition of new commands without modifying
5// existing code, following the Open-Closed Principle.
6//
7// To add a new command:
8// 1. Add a new variant to the Commands enum in cli/mod.rs
9// 2. Create a new handler struct implementing CommandHandler in commands/
10// 3. Add a new match arm in CommandRouter::route()
11//
12// Example for adding a new "analyze" command:
13// ```
14// // In cli/mod.rs - add to Commands enum:
15// Analyze { threshold: Option<f64> },
16//
17// // In commands/analyze.rs - create handler:
18// pub struct AnalyzeHandler { threshold: f64 }
19// impl CommandHandler for AnalyzeHandler { ... }
20//
21// // In handler.rs - add to route():
22// Commands::Analyze { threshold } => {
23// let handler = AnalyzeHandler::new(threshold.unwrap_or(5.0));
24// handler.execute(cli)
25// }
26// ```
27
28use crate::cli::{Cli, Commands};
29use crate::commands::{PrettyPrintHandler, LlmGuideHandler};
30use crate::commands::stats::StatsHandler;
31use crate::error::Result;
32use crate::parser::Tree;
33use crate::helpers::{resolve_path_to_files, parse_single_svg};
34
35/// Trait for command handlers - this makes the system extensible
36///
37/// Each command should implement this trait to provide its functionality.
38/// The trait receives the full CLI structure, allowing access to global options
39/// like the flamegraph file path and any command-specific parameters.
40pub trait CommandHandler {
41 /// Execute the command with the given CLI arguments
42 ///
43 /// # Arguments
44 /// * `cli` - The parsed CLI arguments containing the flamegraph path and command details
45 ///
46 /// # Returns
47 /// * `Result<()>` - Success or a FerricError describing what went wrong
48 fn execute(&self, cli: &Cli) -> Result<()>;
49
50 /// Parse flamegraph inputs into Tree structures for comparison
51 ///
52 /// # Arguments
53 /// * `cli` - The parsed CLI arguments containing flamegraph paths
54 ///
55 /// # Returns
56 /// * `Result<(Vec<Tree>, Vec<Tree>)>` - (primary_trees, comparison_trees)
57 /// - primary_trees: Trees from -f input (1 file = 1 tree, directory = multiple trees)
58 /// - comparison_trees: Trees from -c input (empty if no comparison)
59 #[allow(unused)]
60 fn parse_trees(&self, cli: &Cli) -> Result<(Vec<Tree>, Vec<Tree>)> {
61 let mut primary_trees = Vec::new();
62 let mut comparison_trees = Vec::new();
63
64 // Parse primary input (-f) using glob-aware resolution
65 let primary_files = resolve_path_to_files(&cli.flamegraph)?;
66 for file in primary_files {
67 let tree = parse_single_svg(&file)?;
68 primary_trees.push(tree);
69 }
70
71 // Parse comparison input (-c) if provided using glob-aware resolution
72 if let Some(compare_path) = &cli.compare {
73 let comparison_files = resolve_path_to_files(compare_path)?;
74 for file in comparison_files {
75 let tree = parse_single_svg(&file)?;
76 comparison_trees.push(tree);
77 }
78 }
79
80 Ok((primary_trees, comparison_trees))
81 }
82}
83
84
85/// Main router that routes commands to their handlers
86pub struct CommandRouter;
87
88impl CommandRouter {
89 /// Create a new router
90 pub fn new() -> Self {
91 Self
92 }
93
94 /// Route a command to the appropriate handler
95 /// This method is extensible - new commands can be added by adding new variants
96 /// to the Commands enum and adding corresponding match arms here
97 pub fn route(&self, cli: &Cli) -> Result<()> {
98 match &cli.command {
99 Commands::PrettyPrint => {
100 let handler = PrettyPrintHandler;
101 handler.execute(cli)
102 }
103 Commands::LlmGuide => {
104 let handler = LlmGuideHandler;
105 handler.execute(cli)
106 }
107 Commands::Stats(_) => {
108 let handler = StatsHandler;
109 handler.execute(cli)
110 }
111 // Future commands can be added here:
112 // Commands::Analyze { .. } => {
113 // let handler = AnalyzeHandler::new(params);
114 // handler.execute(cli)
115 // }
116 // Commands::Find { .. } => {
117 // let handler = FindHandler::new(params);
118 // handler.execute(cli)
119 // }
120 }
121 }
122}
123
124/// Default implementation for the router
125impl Default for CommandRouter {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_router_creation() {
137 let _router = CommandRouter::new();
138 // Just verify it can be created
139 }
140
141 #[test]
142 fn test_default_router() {
143 let _router = CommandRouter;
144 // Just verify it can be created with default
145 }
146}