refactor: modular architecture with unified error handling and DashMap concurrency
- Break monolithic records.rs into commands/, output/, parsing/ modules - Add lib.rs with shared types (AppError, Car, BestCarRecord, etc.) - Replace Arc<Mutex<HashMap>> with DashMap for concurrent access - Add unified AppError enum with thiserror - Add configurable CLI paths (--my-cars, --cars-db, --times-dir, --tracks-db) - Add serde deserialization for Car struct - Add CSV escaping for fields with commas/quotes/newlines - Fix track name splitting: split_once -> rsplit_once for _a handling - Add integration tests, proptest property-based tests, and unit tests - Add rustfmt.toml configuration - Update documentation (API, ARCHITECTURE, DATA_STRUCTURES, README)
This commit is contained in:
parent
9d57b18a4e
commit
a9a46ee551
24 changed files with 3541 additions and 1470 deletions
63
src/commands/mod.rs
Normal file
63
src/commands/mod.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
//! Command dispatch and shared helpers.
|
||||
//!
|
||||
//! Provides the `run_analysis` orchestration function and `load_my_cars_or_warn`
|
||||
//! helper used by all command handlers.
|
||||
|
||||
pub mod all;
|
||||
pub mod best;
|
||||
pub mod details;
|
||||
pub mod fetch;
|
||||
pub mod track;
|
||||
pub mod trash;
|
||||
pub mod wishlist;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use top_drives::{
|
||||
AppError, AnalysisContext, CarCounts, MyCarsData,
|
||||
compute_car_counts, load_my_cars_data,
|
||||
};
|
||||
|
||||
use crate::parsing::parse_all_times;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: load my_cars.json with graceful degradation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Loads user car data, printing a warning and returning empty data if the file is missing.
|
||||
///
|
||||
/// Only `MissingFile` errors are gracefully degraded — parse errors and other
|
||||
/// failures are propagated to the caller, because running analysis with
|
||||
/// silently-corrupted ownership data would produce misleading results.
|
||||
pub fn load_my_cars_or_warn(path: &str) -> Result<MyCarsData, AppError> {
|
||||
match load_my_cars_data(path) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(AppError::MissingFile(msg)) => {
|
||||
eprintln!("Warning: {msg}");
|
||||
eprintln!(" Analysis will run without ownership data.");
|
||||
Ok(MyCarsData::default())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Analysis orchestration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Common analysis setup: loads user data, parses all timing files, and computes car counts.
|
||||
///
|
||||
/// Returns the analysis context (for stdout commands) and the aggregated car counts
|
||||
/// (for CSV output commands). The car list is stored inside `CarCounts`.
|
||||
pub async fn run_analysis(
|
||||
owned: &HashSet<String>,
|
||||
cars_db_path: &str,
|
||||
times_dir: &str,
|
||||
) -> Result<(AnalysisContext, CarCounts), AppError> {
|
||||
let ctx = AnalysisContext::new();
|
||||
let owned = Arc::new(owned.clone());
|
||||
let car_list = parse_all_times(owned, &ctx, cars_db_path, times_dir).await?;
|
||||
let counts = compute_car_counts(&ctx, car_list)?;
|
||||
Ok((ctx, counts))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue