- Extract shared types (Car, BestCarRecord, AnalysisContext) into src/lib.rs - Replace global static BEST_CARS with injected AnalysisContext for testability - Add semaphore-bounded concurrency (limit 20) in parse_all_times() - Replace hardcoded track name match with track_names.json loaded via include_str! - Replace magic number 115 with const MAX_RQ: u8 = 113 - Improve floating-point tie comparison with TIME_TOLERANCE (5ms) constant - Remove src/club.rs binary (topdrives.club no longer maintained) - Remove scraper dependency from Cargo.toml - Remove tracks.txt (replaced by track_names.json) - Remove empty/placeholder test bodies - Improve error handling in get_file() for response body read - Add load_my_cars_data() combining owned + wishlist extraction in one I/O - Update CHANGELOG, README, and all docs to reflect changes
13 KiB
API Documentation
Overview
This document provides comprehensive API documentation for the Top Drives Records Analyzer, including all public functions, data structures, and their usage patterns.
Core Modules
Records Module (src/records.rs)
Primary analysis engine for processing JSON-based timing data.
Public Functions
main()
Entry point for the records analyzer
#[tokio::main]
async fn main()
Purpose: Command-line interface and operation dispatcher
Arguments: Command line arguments
args[1]: Command (fetch,best,missing,trash,details,track,full_script)
Commands:
fetch: Download all data filesbest: Show best owned carsmissing: Show best unowned carstrash: Show removable carsdetails: Export all timing datatrack: Track-specific analysisfull_script: Run fetch + all analysis commands
Example:
cargo run --bin records -- best
print_best_cars(filter_out_owned: bool)
Displays ranked list of best performing cars
fn print_best_cars(filter_out_owned: bool)
Purpose: Aggregates performance data across all tracks and conditions
Arguments:
filter_out_owned: If true, excludes owned cars from output
Output Format:
RQ,Brand,Model,Year,Count
85,McLaren,F1,1993,15
82,Ferrari,250 GTO,1962,12
Algorithm:
- Count occurrences of each car across all track/RQ/condition combinations
- Sort by RQ level, then by car identifier
- Apply ownership filter if requested
print_best_times()
Exports comprehensive timing data for all combinations
fn print_best_times()
Purpose: Detailed performance export for external analysis
Output Format:
RQ,TrackID,Condition,CarRQ,Brand,Model,Year,Config,Time
85,laguna,Dry,85,McLaren,F1,1993,233,112.45
Data Volume: Large output file with all track/condition/RQ combinations
print_best_car(track: &String, condition: &String, rq: &String)
Finds best car for specific track conditions
fn print_best_car(track: &String, condition: &String, rq: &String)
Purpose: Targeted analysis for specific racing scenarios
Arguments:
track: Track identifier (e.g., "laguna", "monaco")condition: Surface condition (e.g., "Dry", "Wet")rq: RQ level limit
Example:
print_best_car(&"laguna".to_string(), &"Dry".to_string(), &"85".to_string());
print_trash()
Identifies cars safe for removal from collection
fn print_trash()
Purpose: Collection optimization recommendations
Safety Criteria:
- Car not among best performers on any track
- RQ level below 80 (preserves high-end cars)
- Not a prize car (may have collection value)
Output Format:
RQ,Brand,Model,Year
45,Toyota,Corolla,1995
parse_car_times(car: Car)
Processes timing data for a single car
async fn parse_car_times(car: Car)
Purpose: Core parsing logic for individual car performance data
Process Flow:
- Load car's JSON timing file
- Parse each configuration (111, 233, 323, etc.)
- Extract timing data for each track/condition
- Apply quality filters (downvotes, time validation)
- Update global best cars cache
Quality Filters:
- Minimum time: 1.2 seconds
- No downvotes from community
- Special handling for Test Bowl track
parse_all_times()
Orchestrates concurrent processing of all cars
async fn parse_all_times()
Purpose: Coordinates parallel processing of car timing files
Concurrency: One async task per car file, bounded by a semaphore (limit 20) Synchronization: Waits for all tasks to complete
update_best_car(track_id: &str, car_rq: u8, surface: &str, new_record: BestCarRecord)
Updates global best cars database with new performance data
fn update_best_car(track_id: &str, car_rq: u8, surface: &str, new_record: BestCarRecord)
Purpose: Implements core best-car selection algorithm
RQ Range Analysis:
- Tests car from base RQ up to
MAX_RQ(113) - Compares against existing best times
- Updates records for improved performance
Logic:
- For each RQ level from car_rq to
MAX_RQ(113): - If new time is better → replace record
- If new time ties → add to list (if not duplicate)
- If new time is worse → ignore
get_car_list() -> Vec<Car>
Loads complete car database from repository file
fn get_car_list() -> Vec<Car>
Purpose: Provides access to all known cars in the database
Source: cars_repo.json file
Returns: Vector of all cars with complete metadata
get_my_car_list() -> Vec<String>
Loads user's car collection from game export
fn get_my_car_list() -> Vec<String>
Purpose: Identifies which cars the user owns
Source: my_cars.json game export
Filter: Only locked: true cards
Returns: Vector of owned car GUIDs
fetch_times()
Downloads timing files for all cars with rate limiting
async fn fetch_times()
Purpose: Batch download of car timing data
Rate Limiting: 20 concurrent downloads maximum (semaphore) Progress: Shows download progress for each file
fetch_car(rid: String)
Downloads timing data for single car
async fn fetch_car(rid: String)
Purpose: Individual car data fetching
URL Format: https://api.topdrivesrecords.com/car/{rid}
Storage: times/{rid}.json
get_cars_file()
Downloads main car database
async fn get_cars_file()
Purpose: Updates car metadata database
Source: GitHub repository cars database
Destination: cars_repo.json
get_tracks_file()
Downloads track definitions
async fn get_tracks_file()
Purpose: Updates track name mappings
Source: GitHub repository tracks database
Destination: tracks_repo.json
get_file(file_url: &str, local_file_path: &str)
Generic file download utility
async fn get_file(file_url: &str, local_file_path: &str)
Purpose: Downloads files from remote URLs
Features:
- Automatic existing file removal
- Error handling with graceful failure
- Progress indication through errors
Shared Library (src/lib.rs)
Common utilities shared across the project.
translate_track_name(track_code: &str) -> &str
Converts track codes to human-readable names
fn translate_track_name(track_code: &str) -> &str
Purpose: User-friendly track name display
Implementation: Loads mappings from track_names.json at compile time via include_str!
Coverage: All tracks defined in track_names.json
Fallback: Returns original code if not found
translate_track_type(track_code: &str) -> &str
Converts condition codes to surface descriptions
fn translate_track_type(track_code: &str) -> &str
Purpose: Human-readable surface condition names
Mappings:
"00"→"Dry""01"→"Wet""10"→"Dirt""11"→"Dirt Wet"
split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a str, &'a str)>
String splitting utility for track ID parsing
fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a str, &'a str)>
Purpose: Parse track IDs with condition suffixes
Example:
split_prefix_postfix("laguna_a00", "_a") // Some(("laguna", "00"))
Data Structures
Records Module Data Types
Card
User's car collection entry
struct Card {
cardId: String, // Unique card identifier
locked: bool, // Ownership status
}
Car
Car metadata from database
struct Car {
rid: String, // Repository ID
brand: String, // Manufacturer
only_name: String, // Model name
year: u16, // Model year
rq: u8, // Rarity level
prize: bool, // Prize car status
guid: String, // Game GUID
}
BestCarRecord
Performance record for best cars
struct BestCarRecord {
car_rid: String, // Car identifier
brand: String, // Manufacturer
only_name: String, // Model name
year: u16, // Year
rq: u8, // RQ level
config: String, // Tune configuration
time: f64, // Track time
owned: bool, // User ownership
guid: String, // Game identifier
}
TripletKey
Composite key for track conditions
type TripletKey = (String, u8, String);
// Track ID RQ Level Surface Condition
Constants
MAX_RQ
const MAX_RQ: u8 = 113;
Purpose: Maximum RQ level for range analysis. Replaces former magic number 115 with a named constant.
Global State
BEST_CARS Cache
Thread-safe global performance database
static BEST_CARS: Lazy<Mutex<HashMap<TripletKey, Vec<BestCarRecord>>>>
Initialization: One-time setup with once_cell::Lazy
Thread Safety: Protected by Mutex for concurrent access
Structure: HashMap with composite keys for efficient lookups
Error Handling
Network Errors
- HTTP Failures: Logged but don't crash application
- Invalid JSON: Graceful fallback to defaults
- Missing Files: Clear error messages for user
Parse Errors
- Malformed Data: Skip invalid entries
- Type Conversion: Use safe defaults
- Missing Fields: Provide sensible fallbacks
User Input Errors
- Invalid Commands: Clear usage instructions
- Missing Arguments: Descriptive error messages
- Out of Range: Validation with helpful feedback
Usage Examples
Basic Analysis Workflow
// 1. Load user's car collection
let my_cars = get_my_car_list();
// 2. Process all car timing data
parse_all_times().await;
// 3. Generate best cars report
print_best_cars(false);
// 4. Find collection optimization opportunities
print_trash();
Track-Specific Analysis
// Analyze specific track conditions
let track = "laguna".to_string();
let condition = "Dry".to_string();
let rq = "85".to_string();
print_best_car(&track, &condition, &rq);
Data Fetching
// Download fresh data
get_cars_file().await;
get_tracks_file().await;
fetch_times().await;
Performance Characteristics
Time Complexity
- Parsing: O(n) where n = number of cars × tracks
- Best Car Selection: O(1) HashMap operations
- Sorting: O(n log n) for output ordering
Memory Usage
- Peak: ~200-300MB during full analysis
- Baseline: ~50MB application overhead
- Per Car: ~1-2KB metadata + timing data
Concurrency
- Downloads: 20 concurrent connections max (semaphore bounded)
- Processing: One task per car file
- Thread Safety: Mutex-protected shared state
Extension Points
Adding New Commands
- Add command parsing in
main() - Implement analysis function
- Update help documentation
Adding New Tracks
- Update
tracks_repo.jsonwith new track definitions - Add track name mapping to
track_names.json - Track names are compiled in at build time
Custom Analysis Types
- Create new output formatting functions
- Add aggregation algorithms
- Extend data structures as needed
Security Considerations
Network Security
- HTTPS-only external requests
- Certificate validation (with fallback)
- No sensitive data transmission
File System Security
- Read-only access to game files
- No modification of user data
- Safe file path handling
Input Validation
- Command injection prevention
- Path traversal protection
- Input sanitization
Troubleshooting API Issues
Common Problems
"Function not found"
- Check module imports
- Verify function signatures
- Ensure proper visibility
"Type mismatch"
- Review struct field types
- Check function signatures
- Verify generic parameters
"Deadlock"
- Avoid nested Mutex locks
- Check lock ordering
- Use timeout where possible
Debug Strategies
Logging:
println!("Processing car: {}", car.rid);
eprintln!("Error: {}", error);
Tracing:
// Add at function entry
let _span = tracing::span!(Level::INFO, "function_name");
let _enter = _span.enter();
Profiling:
cargo flamegraph --bin records -- best
Migration Guide
Version Compatibility
- Current: 0.2.0 API stable
- Previous: 0.1.0 fully compatible
- Future: Semantic versioning maintained
Breaking Changes
- Club module (
src/club.rs) has been removed scraperdependency removedtracks.txtremoved (replaced bytrack_names.json)- Magic number 115 replaced with
const MAX_RQ: u8 = 113 translate_track_name()now loads fromtrack_names.jsoninstead of hardcoded match
API Version: 0.2.0 Last Updated: 2026-05-06 Compatibility: Rust 1.70.0+