654 lines
No EOL
15 KiB
Markdown
654 lines
No EOL
15 KiB
Markdown
# 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**
|
||
|
||
```rust
|
||
#[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`)
|
||
|
||
**Commands**:
|
||
- `fetch`: Download all data files
|
||
- `best`: Show best owned cars
|
||
- `missing`: Show best unowned cars
|
||
- `trash`: Show removable cars
|
||
- `details`: Export all timing data
|
||
- `track`: Track-specific analysis
|
||
|
||
**Example**:
|
||
```bash
|
||
cargo run --bin records -- best
|
||
```
|
||
|
||
##### `print_best_cars(filter_out_owned: bool)`
|
||
**Displays ranked list of best performing cars**
|
||
|
||
```rust
|
||
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**:
|
||
```csv
|
||
RQ,Brand,Model,Year,Count
|
||
85,McLaren,F1,1993,15
|
||
82,Ferrari,250 GTO,1962,12
|
||
```
|
||
|
||
**Algorithm**:
|
||
1. Count occurrences of each car across all track/RQ/condition combinations
|
||
2. Sort by RQ level, then by car identifier
|
||
3. Apply ownership filter if requested
|
||
|
||
##### `print_best_times()`
|
||
**Exports comprehensive timing data for all combinations**
|
||
|
||
```rust
|
||
fn print_best_times()
|
||
```
|
||
|
||
**Purpose**: Detailed performance export for external analysis
|
||
|
||
**Output Format**:
|
||
```csv
|
||
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**
|
||
|
||
```rust
|
||
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**:
|
||
```rust
|
||
print_best_car(&"laguna".to_string(), &"Dry".to_string(), &"85".to_string());
|
||
```
|
||
|
||
##### `print_trash()`
|
||
**Identifies cars safe for removal from collection**
|
||
|
||
```rust
|
||
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**:
|
||
```csv
|
||
RQ,Brand,Model,Year
|
||
45,Toyota,Corolla,1995
|
||
```
|
||
|
||
##### `parse_car_times(car: Car)`
|
||
**Processes timing data for a single car**
|
||
|
||
```rust
|
||
async fn parse_car_times(car: Car)
|
||
```
|
||
|
||
**Purpose**: Core parsing logic for individual car performance data
|
||
|
||
**Process Flow**:
|
||
1. Load car's JSON timing file
|
||
2. Parse each configuration (111, 233, 323, etc.)
|
||
3. Extract timing data for each track/condition
|
||
4. Apply quality filters (downvotes, time validation)
|
||
5. 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**
|
||
|
||
```rust
|
||
async fn parse_all_times()
|
||
```
|
||
|
||
**Purpose**: Coordinates parallel processing of car timing files
|
||
|
||
**Concurrency**: One async task per car file
|
||
**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**
|
||
|
||
```rust
|
||
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 maximum (115)
|
||
- Compares against existing best times
|
||
- Updates records for improved performance
|
||
|
||
**Logic**:
|
||
1. For each RQ level from car_rq to 115:
|
||
2. If new time is better → replace record
|
||
3. If new time ties → add to list (if not duplicate)
|
||
4. If new time is worse → ignore
|
||
|
||
##### `get_car_list() -> Vec<Car>`
|
||
**Loads complete car database from repository file**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
async fn fetch_times()
|
||
```
|
||
|
||
**Purpose**: Batch download of car timing data
|
||
|
||
**Rate Limiting**: 20 concurrent downloads maximum
|
||
**Progress**: Shows download progress for each file
|
||
|
||
##### `fetch_car(rid: String)`
|
||
**Downloads timing data for single car**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
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
|
||
|
||
##### `translate_track_name(track_code: &str) -> &str`
|
||
**Converts track codes to human-readable names**
|
||
|
||
```rust
|
||
fn translate_track_name(track_code: &str) -> &str
|
||
```
|
||
|
||
**Purpose**: User-friendly track name display
|
||
|
||
**Coverage**: 200+ track mappings
|
||
**Fallback**: Returns original code if not found
|
||
|
||
##### `translate_track_type(track_code: &str) -> &str`
|
||
**Converts condition codes to surface descriptions**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a str, &'a str)>
|
||
```
|
||
|
||
**Purpose**: Parse track IDs with condition suffixes
|
||
|
||
**Example**:
|
||
```rust
|
||
split_prefix_postfix("laguna_a00", "_a") // Some(("laguna", "00"))
|
||
```
|
||
|
||
### Club Module (`src/club.rs`)
|
||
|
||
Alternative data source using web scraping from topdrives.club.
|
||
|
||
#### Public Functions
|
||
|
||
##### `main()`
|
||
**Entry point for club scraper**
|
||
|
||
```rust
|
||
#[tokio::main]
|
||
async fn main()
|
||
```
|
||
|
||
**Purpose**: Web scraping interface for live data
|
||
|
||
**Arguments**:
|
||
- Command line arguments for RQ and track filtering
|
||
- `-d` flag for detailed output
|
||
|
||
##### `get_page(url: &str) -> String`
|
||
**Fetches HTML content from URLs**
|
||
|
||
```rust
|
||
async fn get_page(url: &str) -> String
|
||
```
|
||
|
||
**Purpose**: Downloads web pages for scraping
|
||
|
||
**Security**: Accepts invalid SSL certificates for compatibility
|
||
|
||
##### `parse_best_car(text: String) -> Car`
|
||
**Extracts car data from HTML content**
|
||
|
||
```rust
|
||
fn parse_best_car(text: String) -> Car
|
||
```
|
||
|
||
**Purpose**: Parses HTML tables to extract car information
|
||
|
||
**CSS Selectors**:
|
||
- `.carList__item-manufacturer`: Car brand
|
||
- `.carList__item-model`: Car model
|
||
- `.carList__item-year`: Model year
|
||
- `.carList__item-level`: Class and RQ (complex parsing)
|
||
- `.carList__item-upgrade`: Upgrade configuration
|
||
|
||
##### `parse_tracks(file_name: &str) -> HashMap<u16, String>`
|
||
**Loads track definitions from file**
|
||
|
||
```rust
|
||
fn parse_tracks(file_name: &str) -> HashMap<u16, String>
|
||
```
|
||
|
||
**Purpose**: Creates track ID to name mappings
|
||
|
||
**Format**: Text file with `ID NAME` format per line
|
||
|
||
##### `get_best_car(track: u16, track_name: String, max_level: u16, detailed: bool) -> String`
|
||
**Scrapes best car data for specific track**
|
||
|
||
```rust
|
||
async fn get_best_car(track: u16, track_name: String, max_level: u16, detailed: bool) -> String
|
||
```
|
||
|
||
**Purpose**: Fetches and parses best car from topdrives.club
|
||
|
||
**API Integration**:
|
||
- Constructs topdrives.club API URLs
|
||
- Parses HTML response tables
|
||
- Formats output based on detail level
|
||
|
||
##### `parse_argument(args: &Vec<String>, index: usize, min: u16, max: u16) -> u16`
|
||
**Validates command line arguments**
|
||
|
||
```rust
|
||
fn parse_argument(args: &Vec<String>, index: usize, min: u16, max: u16) -> u16
|
||
```
|
||
|
||
**Purpose**: Input validation with range checking
|
||
|
||
**Panics**: On invalid values with descriptive messages
|
||
|
||
## Data Structures
|
||
|
||
### Records Module Data Types
|
||
|
||
#### `Card`
|
||
**User's car collection entry**
|
||
|
||
```rust
|
||
struct Card {
|
||
cardId: String, // Unique card identifier
|
||
locked: bool, // Ownership status
|
||
}
|
||
```
|
||
|
||
#### `Car`
|
||
**Car metadata from database**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
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**
|
||
|
||
```rust
|
||
type TripletKey = (String, u8, String);
|
||
// Track ID RQ Level Surface Condition
|
||
```
|
||
|
||
### Club Module Data Types
|
||
|
||
#### `Car`
|
||
**Scraped car data from website**
|
||
|
||
```rust
|
||
struct Car {
|
||
class: String, // Class letter (S, A, B, etc.)
|
||
rq: u8, // Rarity level
|
||
manufacturer: String, // Brand name
|
||
model: String, // Model name
|
||
year: u16, // Model year
|
||
upgrade: String, // Upgrade config
|
||
}
|
||
```
|
||
|
||
## Global State
|
||
|
||
### `BEST_CARS` Cache
|
||
**Thread-safe global performance database**
|
||
|
||
```rust
|
||
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
|
||
|
||
```rust
|
||
// 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
|
||
|
||
```rust
|
||
// 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
|
||
|
||
```rust
|
||
// 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
|
||
- **Processing**: One task per car file
|
||
- **Thread Safety**: Mutex-protected shared state
|
||
|
||
## Extension Points
|
||
|
||
### Adding New Commands
|
||
1. Add command parsing in `main()`
|
||
2. Implement analysis function
|
||
3. Update help documentation
|
||
|
||
### Adding New Data Sources
|
||
1. Implement new module (similar to `club.rs`)
|
||
2. Add data source selection logic
|
||
3. Update configuration system
|
||
|
||
### Custom Analysis Types
|
||
1. Create new output formatting functions
|
||
2. Add aggregation algorithms
|
||
3. Extend data structures as needed
|
||
|
||
## Testing
|
||
|
||
### Unit Tests
|
||
```rust
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[tokio::test]
|
||
async fn test_parse_car_times() {
|
||
// Test implementation
|
||
}
|
||
}
|
||
```
|
||
|
||
### Integration Tests
|
||
- Full workflow testing
|
||
- File I/O validation
|
||
- Network request mocking
|
||
|
||
### Benchmarking
|
||
- Parse performance measurement
|
||
- Memory usage profiling
|
||
- Concurrent operation testing
|
||
|
||
## 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**:
|
||
```rust
|
||
println!("Processing car: {}", car.rid);
|
||
eprintln!("Error: {}", error);
|
||
```
|
||
|
||
**Tracing**:
|
||
```rust
|
||
// Add at function entry
|
||
let _span = tracing::span!(Level::INFO, "function_name");
|
||
let _enter = _span.enter();
|
||
```
|
||
|
||
**Profiling**:
|
||
```bash
|
||
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
|
||
- None currently planned
|
||
- Advance notice provided for API changes
|
||
- Deprecation warnings for removed features
|
||
|
||
---
|
||
|
||
**API Version**: 0.2.0
|
||
**Last Updated**: 2024-12-XX
|
||
**Compatibility**: Rust 1.70.0+ |