cargo_coupling/web/
server.rs1use std::net::SocketAddr;
7use std::path::PathBuf;
8use std::sync::Arc;
9
10use axum::Router;
11use tokio::net::TcpListener;
12
13use crate::analyze_history;
14use crate::balance::score::IssueThresholds;
15use crate::cli_output::{JsonHistory, history_report_to_json};
16use crate::config::CompiledConfig;
17use crate::metrics::project::ProjectMetrics;
18
19use super::routes;
20
21pub const DEFAULT_HISTORY_MAX_POINTS: usize = 30;
22
23pub struct AppState {
25 pub metrics: ProjectMetrics,
26 pub thresholds: IssueThresholds,
27 pub api_endpoint: Option<String>,
28 pub history: JsonHistory,
29 pub analysis_path: PathBuf,
30 pub analysis_config: CompiledConfig,
31 pub git_months: usize,
32 pub no_git: bool,
33}
34
35pub struct ServerConfig {
37 pub port: u16,
38 pub open_browser: bool,
39 pub api_endpoint: Option<String>,
40 pub analysis_path: PathBuf,
41 pub analysis_config: CompiledConfig,
42 pub git_months: usize,
43 pub history_max_points: usize,
44 pub no_git: bool,
45}
46
47impl Default for ServerConfig {
48 fn default() -> Self {
49 Self {
50 port: 3000,
51 open_browser: true,
52 api_endpoint: None,
53 analysis_path: PathBuf::from("./src"),
54 analysis_config: CompiledConfig::empty(),
55 git_months: 6,
56 history_max_points: DEFAULT_HISTORY_MAX_POINTS,
57 no_git: false,
58 }
59 }
60}
61
62pub async fn start_server(
64 metrics: ProjectMetrics,
65 thresholds: IssueThresholds,
66 config: ServerConfig,
67) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
68 let history = load_history(&config, &thresholds);
69
70 let state = Arc::new(AppState {
71 metrics,
72 thresholds,
73 api_endpoint: config.api_endpoint.clone(),
74 history,
75 analysis_path: config.analysis_path.clone(),
76 analysis_config: config.analysis_config.clone(),
77 git_months: config.git_months,
78 no_git: config.no_git,
79 });
80
81 let app = Router::new()
82 .merge(routes::api_routes())
83 .merge(routes::static_routes())
84 .with_state(state);
85
86 let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
87 let listener = TcpListener::bind(addr).await?;
88
89 let url = format!("http://localhost:{}", config.port);
90 eprintln!("Starting web server at {}", url);
91
92 if config.open_browser {
93 eprintln!("Opening browser...");
94 if let Err(e) = open::that(&url) {
95 eprintln!("Warning: Could not open browser: {}", e);
96 eprintln!("Please open {} manually", url);
97 }
98 }
99
100 eprintln!("Press Ctrl+C to stop the server");
101
102 axum::serve(listener, app).await?;
103
104 Ok(())
105}
106
107fn load_history(config: &ServerConfig, thresholds: &IssueThresholds) -> JsonHistory {
108 if config.no_git {
109 return JsonHistory {
110 months: config.git_months,
111 points: Vec::new(),
112 skipped: Vec::new(),
113 };
114 }
115
116 match analyze_history(
117 &config.analysis_path,
118 &config.analysis_config,
119 thresholds,
120 config.git_months,
121 config.history_max_points,
122 ) {
123 Ok(report) => history_report_to_json(&report),
124 Err(e) => {
125 eprintln!("Warning: History analysis failed: {}", e);
126 JsonHistory {
127 months: config.git_months,
128 points: Vec::new(),
129 skipped: Vec::new(),
130 }
131 }
132 }
133}