basic parsing

This commit is contained in:
Robin Appelman 2020-04-19 19:42:45 +02:00
commit 0151dfce7d
10 changed files with 6974 additions and 2 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
.env

1468
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,14 @@ version = "0.1.0"
authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sqlx = { version = "0.3", default_features = false, features = ["macros", "postgres", "json", "runtime-tokio"] }
dotenv = "0.15.0"
main_error = "0.1.0"
tokio = { version = "0.2.13", features = ["macros", "time"] }
reqwest = { version = "0.10.4", features = [] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
steamid-ng = "0.3.4"
test-case = "1.0.0"

395
schema.sql Normal file
View file

@ -0,0 +1,395 @@
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
CREATE TYPE team AS ENUM ('Blue', 'Red');
CREATE TYPE class_type AS ENUM ('scout', 'soldier', 'pyro', 'demoman', 'heavyweapons', 'engineer', 'medic', 'sniper', 'spy');
CREATE TYPE game_mode AS ENUM ('ultiduo', '4v4', '6v6', '7v7', '9v9', 'other');
CREATE TYPE event_type AS ENUM ('charge', 'pointcap', 'medic_death', 'round_win');
CREATE TYPE medigun AS ENUM ('medigun', 'kritzkrieg', 'quickfix', 'vacinator');
CREATE TYPE weapon_id AS ENUM (
'sniperrifle',
'taunt_sniper',
'sydney_sleeper',
'the_winger',
'hot_hand',
'deflect_rocket',
'scout_sword',
'voodoo_pin',
'degreaser',
'shortstop',
'robot_arm',
'tf_pumpkin_bomb',
'kunai',
'wrench',
'necro_smasher',
'headtaker',
'proto_syringe',
'eternal_reward',
'eureka_effect',
'dragons_fury_bonus',
'grappling_hook',
'pep_pistol',
'guillotine',
'lava_axe',
'wrangler_kill',
'panic_attack',
'loose_cannon',
'thirddegree',
'taunt_pyro',
'iron_bomber',
'persian_persuader',
'amputator',
'tf_projectile_pipe',
'awper_hand',
'demokatana',
'loose_cannon_reflect',
'obj_minisentry',
'back_scratcher',
'pomson',
'the_classic',
'sandman',
'axtinguisher',
'obj_sentrygun2',
'jar',
'samrevolver',
'deflect_flare',
'loch_n_load',
'tf_projectile_rocket',
'annihilator',
'rocketpack_stomp',
'ullapool_caber_explosion',
'minigun',
'deflect_promode',
'liberty_launcher',
'nessieclub',
'quickiebomb_launcher',
'cow_mangler',
'wrap_assassin',
'blackbox',
'candy_cane',
'rocketlauncher_directhit',
'ai_flamethrower',
'soda_popper',
'frontier_justice',
'rescue_ranger_reflect',
'warrior_spirit',
'widowmaker',
'quake_rl',
'tf_projectile_arrow',
'sledgehammer',
'deflect_arrow',
'market_gardener',
'unique_pickaxe_escape',
'pistol_scout',
'boston_basher',
'spellbook_bats',
'holymackerel',
'club',
'ball',
'fireaxe',
'backburner',
'eviction_notice',
'holiday_punch',
'taunt_soldier',
'shotgun_primary',
'nonnonviolent_protest',
'force_a_nature',
'paintrain',
'freedom_staff',
'shotgun_hwg',
'long_heatmaker',
'shotgun_soldier',
'knife',
'batsaber',
'mailbox',
'compound_bow',
'ullapool_caber',
'ambassador',
'player_penetration',
'powerjack',
'crusaders_crossbow',
'scotland_shard',
'wrench_jag',
'scattergun',
'unknown',
'shahanshah',
'pistol',
'spellbook_boss',
'smg',
'dragons_fury',
'revolver',
'player',
'the_maul',
'skullbat',
'ham_shank',
'solemn_vow',
'iron_curtain',
'bonesaw',
'dumpster_device',
'bushwacka',
'builder',
'bread_bite',
'southern_hospitality',
'tribalkukri',
'the_capper',
'fists',
'disciplinary_action',
'tf_projectile_flare',
'bleed_kill',
'black_rose',
'letranger',
'tomislav',
'atomizer',
'back_scatter',
'pep_brawlerblaster',
'tf_projectile_pipe_remote',
'battleaxe',
'deflect_flare_detonator',
'taunt_medic',
'telefrag',
'stickybomb_defender',
'splendid_screen',
'claidheamohmor',
'airstrike',
'righteous_bison',
'gloves_running_urgently',
'sword',
'mantreads',
'deflect_sticky',
'enforcer',
'scorch_shot',
'pro_rifle',
'spy_cicle',
'bat',
'sharp_dresser',
'spellbook_lightning',
'tide_turner',
'shotgun_pyro',
'lava_bat',
'taunt_heavy',
'bottle',
'unique_pickaxe',
'phlogistinator',
'crossing_guard',
'giger_counter',
'charged_smg',
'syringegun_medic',
'gloves',
'bazaar_bargain',
'spellbook_mirv',
'big_earner',
'battleneedle',
'warfan',
'obj_sentrygun',
'manmelter',
'family_business',
'reserve_shooter',
'short_circuit',
'flaregun',
'spellbook_fireball',
'world',
'jar_milk',
'flamethrower',
'shooting_star',
'trigger_hurt',
'blutsauger',
'taunt_spy',
'tf_projectile_energy_ball',
'jar_gas',
'tf_projectile_mechanicalarmorb',
'obj_sentrygun3',
'diamondback',
'shovel',
'brass_beast',
'loose_cannon_impact',
'demoshield',
'prinny_machete',
'machina',
'rocketlauncher_fireball',
'sticky_resistance',
'detonator',
'tf_projectile_sentryrocket',
'unarmed_combat',
'spellbook_skeleton',
'ubersaw',
'maxgun',
'robot_arm_combo_kill',
'rescue_ranger',
'apocofists',
'natascha',
'pro_smg',
'steel_fists',
'fryingpan',
);
CREATE TABLE logs (
id INTEGER PRIMARY KEY,
red_score INTEGER NOT NULL,
blue_score INTEGER NOT NULL,
length INTEGER NOT NULL,
game_mode game_mode NOT NULL,
map TEXT NOT NULL,
date TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
CREATE INDEX logs_map_idx
ON logs USING BTREE (map);
CREATE INDEX logs_mode_idx
ON logs USING BTREE (game_mode);
CREATE TABLE rounds (
id SERIAL PRIMARY KEY,
round INTEGER NOT NULL,
log_id INTEGER NOT NULL REFERENCES logs(id),
length INTEGER NOT NULL,
winner team NOT NULL,
first_cap team NOT NULL,
red_score INTEGER NOT NULL,
blue_score INTEGER NOT NULL,
red_kills INTEGER NOT NULL,
blue_kills INTEGER NOT NULL,
red_dmg INTEGER NOT NULL,
blue_dmg INTEGER NOT NULL,
red_ubers INTEGER NOT NULL,
blue_ubers INTEGER NOT NULL
);
CREATE INDEX rounds_log_id_idx
ON rounds USING BTREE (log_id);
CREATE UNIQUE INDEX rounds_round_log_id_idx
ON rounds USING BTREE (round, log_id);
CREATE INDEX rounds_winner_idx
ON rounds USING BTREE (winner);
CREATE INDEX rounds_first_cap_idx
ON rounds USING BTREE (first_cap);
CREATE TABLE events_charge (
id SERIAL PRIMARY KEY,
round_id INTEGER NOT NULL REFERENCES rounds(id),
time INTEGER NOT NULL,
team team NOT NULL,
steam_id BIGINT NOT NULL
);
CREATE INDEX events_charge_round_id_idx
ON events_charge USING BTREE (round_id);
CREATE INDEX events_charge_steam_id_idx
ON events_charge USING BTREE (steam_id);
CREATE TABLE events_point_cap (
id SERIAL PRIMARY KEY,
round_id INTEGER NOT NULL REFERENCES rounds(id),
time INTEGER NOT NULL,
team team NOT NULL,
point INTEGER NOT NULL
);
CREATE INDEX events_point_cap_round_id_idx
ON events_point_cap USING BTREE (round_id);
CREATE TABLE events_medic_death (
id SERIAL PRIMARY KEY,
round_id INTEGER NOT NULL REFERENCES rounds(id),
time INTEGER NOT NULL,
team team NOT NULL,
steam_id BIGINT NOT NULL,
killer BIGINT NOT NULL
);
CREATE INDEX events_medic_death_round_id_idx
ON events_medic_death USING BTREE (round_id);
CREATE TABLE events_round_win (
id SERIAL PRIMARY KEY,
round_id INTEGER NOT NULL REFERENCES rounds(id),
time INTEGER NOT NULL,
team team NOT NULL
);
CREATE UNIQUE INDEX events_round_win_round_id_idx
ON events_round_win USING BTREE (round_id);
CREATE TABLE players (
id SERIAL PRIMARY KEY,
log_id INTEGER NOT NULL REFERENCES logs(id),
steam_id BIGINT NOT NULL,
name TEXT NOT NULL,
kills INTEGER NOT NULL,
deaths INTEGER NOT NULL,
assists INTEGER NOT NULL,
suicides INTEGER NOT NULL,
dmg INTEGER NOT NULL,
damage_taken INTEGER NOT NULL,
ubers INTEGER NOT NULL,
medigun_ubers INTEGER NOT NULL,
kritzkrieg_ubers INTEGER NOT NULL,
quickfix_ubers INTEGER NOT NULL,
vacinator_ubers INTEGER NOT NULL,
drops INTEGER NOT NULL,
medkits INTEGER NOT NULL,
medkits_hp INTEGER NOT NULL,
backstabs INTEGER NOT NULL,
headshots INTEGER NOT NULL,
heal INTEGER NOT NULL,
heals_received INTEGER NOT NULL,
scout_kills INTEGER NOT NULL,
soldier_kills INTEGER NOT NULL,
pyro_kills INTEGER NOT NULL,
demoman_kills INTEGER NOT NULL,
heavy_kills INTEGER NOT NULL,
engineer_kills INTEGER NOT NULL,
medic_kills INTEGER NOT NULL,
sniper_kills INTEGER NOT NULL,
spy_kills INTEGER NOT NULL
);
CREATE INDEX players_log_id_idx
ON players USING BTREE (log_id);
CREATE UNIQUE INDEX players_log_steam_id_idx
ON players USING BTREE (log_id, steam_id);
CREATE INDEX players_steam_id_idx
ON players USING BTREE (steam_id);
CREATE TABLE class_stats (
id SERIAL PRIMARY KEY,
player_id INTEGER NOT NULL REFERENCES players(id),
type class_type NOT NULL,
time INTEGER NOT NULL,
kills INTEGER NOT NULL,
deaths INTEGER NOT NULL,
assists INTEGER NOT NULL,
dmg INTEGER NOT NULL
);
CREATE INDEX class_stats_player_id_idx
ON class_stats USING BTREE (player_id);
CREATE UNIQUE INDEX class_stats_player_id_type_idx
ON class_stats USING BTREE (player_id, type);
CREATE TABLE player_weapon_stats (
id SERIAL PRIMARY KEY,
class_stat_id INTEGER NOT NULL REFERENCES class_stats(id),
weapon weapon_id NOT NULL,
kills INTEGER NOT NULL,
shots INTEGER NOT NULL,
hits INTEGER NOT NULL,
dmg INTEGER NOT NULL
);
CREATE INDEX player_weapon_stats_class_stat_id_idx
ON player_weapon_stats USING BTREE (class_stat_id);
CREATE UNIQUE INDEX player_weapon_stats_class_stat_id_weapon_idx
ON player_weapon_stats USING BTREE (class_stat_id, weapon);

272
src/data.rs Normal file
View file

@ -0,0 +1,272 @@
use serde::Deserialize;
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum Team {
Blue,
Red,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum Class {
Scout,
Soldier,
Pyro,
Demoman,
HeavyWeapons,
Engineer,
Medic,
Sniper,
Spy,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum GameMode {
UltiDuo,
Fours,
Sixes,
Sevens,
Highlander,
Other,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum EventType {
Charge,
PointCap,
MedicDeath,
RoundWin,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize, Hash, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Medigun {
Medigun,
KritzKrieg,
QuickFix,
Vacinator
}
impl Default for Medigun {
fn default() -> Self {
Medigun::Medigun
}
}
#[derive(Debug, Clone, sqlx::Type, Deserialize, Hash, Eq, PartialEq)]
// #[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum Weapon {
Sniperrifle,
TauntSniper,
SydneySleeper,
TheWinger,
HotHand,
DeflectRocket,
ScoutSword,
VoodooPin,
Degreaser,
Shortstop,
RobotArm,
TfPumpkinBomb,
Kunai,
Wrench,
NecroSmasher,
Headtaker,
ProtoSyringe,
EternalReward,
EurekaEffect,
DragonsFuryBonus,
GrapplingHook,
PepPistol,
Guillotine,
LavaAxe,
WranglerKill,
PanicAttack,
LooseCannon,
Thirddegree,
TauntPyro,
IronBomber,
PersianPersuader,
Amputator,
TfProjectilePipe,
AwperHand,
Demokatana,
LooseCannonReflect,
ObjMinisentry,
BackScratcher,
Pomson,
TheClassic,
Sandman,
Axtinguisher,
ObjSentrygun2,
Jar,
Samrevolver,
DeflectFlare,
LochNLoad,
TfProjectileRocket,
Annihilator,
RocketpackStomp,
UllapoolCaberExplosion,
Minigun,
DeflectPromode,
LibertyLauncher,
Nessieclub,
QuickiebombLauncher,
CowMangler,
WrapAssassin,
Blackbox,
CandyCane,
RocketlauncherDirecthit,
AiFlamethrower,
SodaPopper,
FrontierJustice,
RescueRangerReflect,
WarriorSpirit,
Widowmaker,
QuakeRl,
TfProjectileArrow,
Sledgehammer,
DeflectArrow,
MarketGardener,
UniquePickaxeEscape,
PistolScout,
BostonBasher,
SpellbookBats,
Holymackerel,
Club,
Ball,
Fireaxe,
Backburner,
EvictionNotice,
HolidayPunch,
TauntSoldier,
ShotgunPrimary,
NonnonviolentProtest,
ForceANature,
Paintrain,
FreedomStaff,
ShotgunHwg,
LongHeatmaker,
ShotgunSoldier,
Knife,
Batsaber,
Mailbox,
CompoundBow,
UllapoolCaber,
Ambassador,
PlayerPenetration,
Powerjack,
CrusadersCrossbow,
ScotlandShard,
WrenchJag,
Scattergun,
Unknown,
Shahanshah,
Pistol,
SpellbookBoss,
Smg,
DragonsFury,
Revolver,
Player,
TheMaul,
Skullbat,
HamShank,
SolemnVow,
IronCurtain,
Bonesaw,
DumpsterDevice,
Bushwacka,
Builder,
BreadBite,
SouthernHospitality,
Tribalkukri,
TheCapper,
Fists,
DisciplinaryAction,
TfProjectileFlare,
BleedKill,
BlackRose,
Letranger,
Tomislav,
Atomizer,
BackScatter,
PepBrawlerblaster,
TfProjectilePipeRemote,
Battleaxe,
DeflectFlareDetonator,
TauntMedic,
Telefrag,
StickybombDefender,
SplendidScreen,
Claidheamohmor,
Airstrike,
RighteousBison,
GlovesRunningUrgently,
Sword,
Mantreads,
DeflectSticky,
Enforcer,
ScorchShot,
ProRifle,
SpyCicle,
Bat,
SharpDresser,
SpellbookLightning,
TideTurner,
ShotgunPyro,
LavaBat,
TauntHeavy,
Bottle,
UniquePickaxe,
Phlogistinator,
CrossingGuard,
GigerCounter,
ChargedSmg,
SyringegunMedic,
Gloves,
BazaarBargain,
SpellbookMirv,
BigEarner,
Battleneedle,
Warfan,
ObjSentrygun,
Manmelter,
FamilyBusiness,
ReserveShooter,
ShortCircuit,
Flaregun,
SpellbookFireball,
World,
JarMilk,
Flamethrower,
ShootingStar,
TriggerHurt,
Blutsauger,
TauntSpy,
TfProjectileEnergyBall,
JarGas,
TfProjectileMechanicalarmorb,
ObjSentrygun3,
Diamondback,
Shovel,
BrassBeast,
LooseCannonImpact,
Demoshield,
PrinnyMachete,
Machina,
RocketlauncherFireball,
StickyResistance,
Detonator,
TfProjectileSentryrocket,
UnarmedCombat,
SpellbookSkeleton,
Ubersaw,
Maxgun,
RobotArmComboKill,
RescueRanger,
Apocofists,
Natascha,
ProSmg,
SteelFists,
Fryingpan,
}

View file

@ -1,3 +1,6 @@
mod data;
mod raw;
fn main() {
println!("Hello, world!");
}

294
src/raw.rs Normal file
View file

@ -0,0 +1,294 @@
use std::collections::HashMap;
use steamid_ng::SteamID;
use crate::data::{Medigun, Class, Weapon, Team as TeamId};
use serde::Deserialize;
use serde::export::TryFrom;
#[derive(Debug, Clone, Deserialize)]
pub struct RawLog {
#[serde(default)]
pub version: u8,
#[serde(default)]
pub length: u32,
pub teams: Option<Teams>,
pub players: HashMap<SteamID, Player>,
pub names: HashMap<SteamID, String>,
pub rounds: Option<Vec<Round>>,
pub healspread: HashMap<SteamID, HashMap<SteamID, u32>>,
pub classkills: HashMap<SteamID, ClassNumbers>,
pub classdeaths: HashMap<SteamID, ClassNumbers>,
pub classkillassists: Option<HashMap<SteamID, ClassNumbers>>,
pub chat: Vec<ChatMessage>,
pub info: Info,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Teams {
pub red: Team,
pub blue: Team,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Team {
pub score: u32,
pub kills: u32,
#[serde(default)]
pub deaths: u32,
pub dmg: u32,
#[serde(default)]
pub charges: u32,
#[serde(default)]
pub drops: u32,
#[serde(default)]
pub firstcaps: u32,
#[serde(default)]
pub caps: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Player {
pub team: TeamId,
pub kills: u16,
pub deaths: u16,
pub assists: u16,
#[serde(default)]
pub suicides: u16,
#[serde(default)]
pub dmg: u32,
#[serde(default)]
pub dmg_real: u32,
#[serde(default)]
pub dt: u32,
#[serde(default)]
pub dt_real: u32,
#[serde(default)]
pub hr: u16,
#[serde(default)]
pub lks: u16,
pub ubers: u8,
#[serde(default)]
pub ubertypes: HashMap<Medigun, u8>,
pub drops: u8,
pub medkits: u8,
#[serde(default)]
pub medkits_hp: u16,
pub backstabs: u8,
pub headshots: u8,
#[serde(default)]
pub headshots_hit: u8,
pub heal: u32,
pub cpc: u8,
pub ic: u8,
pub medicstat: Option<MedicStats>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MedicStats {
pub advantages_lost: u8,
pub biggest_advantage_list: u16,
pub deaths_within_20s_after_uber: u8,
pub deaths_with_95_99_uber: u8,
pub avg_time_before_healing: f32,
pub avg_time_to_build: f32,
pub avg_time_before_using: f32,
pub avg_uber_length: f32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ClassStat {
#[serde(rename = "type")]
pub class: Class,
pub kills: u16,
pub dmg: u32,
pub total_time: u32,
pub weapon: HashMap<Weapon, WeaponStat>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WeaponStat {
pub kills: u32,
pub dmg: u32,
pub avg_dmg: u32,
pub shots: u32,
pub hits: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Round {
#[serde(default)]
pub start_time: u64,
pub winner: TeamId,
#[serde(rename = "firstcap")]
pub first_cap: Option<TeamId>,
pub length: u32,
pub team: Option<Teams>,
pub players: HashMap<SteamID, RoundPlayer>,
pub events: Vec<Event>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RoundPlayer {
pub kills: u32,
pub dmg: u32,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum Event {
Charge {
#[serde(default)]
medigun: Medigun,
time: u32,
steamid: SteamID,
team: TeamId,
},
#[serde(rename = "pointcap")]
PointCap {
time: u32,
team: TeamId,
point: u8,
},
MedicDeath {
time: u32,
team: TeamId,
steamid: SteamID,
killer: SteamID,
},
RoundWin {
time: u32,
team: TeamId,
},
Drop {
time: u32,
steamid: SteamID,
team: TeamId,
},
}
#[derive(Debug, Clone, Deserialize)]
pub struct ClassNumbers {
#[serde(default)]
pub scout: u8,
#[serde(default)]
pub soldier: u8,
#[serde(default)]
pub pyro: u8,
#[serde(default)]
pub demoman: u8,
#[serde(default)]
pub heavyweapons: u8,
#[serde(default)]
pub engineer: u8,
#[serde(default)]
pub medic: u8,
#[serde(default)]
pub sniper: u8,
#[serde(default)]
pub spy: u8,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ChatMessage {
pub steamid: ChatFrom,
pub name: String,
pub msg: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
#[serde(try_from = "String")]
pub enum ChatFrom {
Player(SteamID),
Console,
}
impl TryFrom<String> for ChatFrom {
type Error = steamid_ng::SteamIDParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value == "Console" {
Ok(ChatFrom::Console)
} else {
value.parse().map(ChatFrom::Player)
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Info {
pub map: String,
pub total_length: u32,
#[serde(default)]
pub supplemental: bool,
#[serde(default)]
pub has_real_damage: bool,
#[serde(default)]
pub has_weapon_damage: bool,
#[serde(default)]
pub has_accuracy: bool,
#[serde(default)]
pub has_hp: bool,
#[serde(default)]
pub has_hp_real: bool,
#[serde(default)]
pub has_hs: bool,
#[serde(default)]
pub has_hs_hit: bool,
#[serde(default)]
pub has_bs: bool,
#[serde(default)]
pub has_cp: bool,
#[serde(default)]
pub has_sb: bool,
#[serde(default)]
pub has_dt: bool,
#[serde(default)]
pub has_as: bool,
#[serde(default)]
pub has_hr: bool,
#[serde(default)]
pub has_intel: bool,
pub title: String,
pub date: u64,
pub uploader: Uploader,
pub rounds: Option<Vec<Round>>,
#[serde(flatten)]
pub teams: Option<InfoTeams>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct InfoTeams {
pub red: InfoTeam,
pub blue: InfoTeam,
}
#[derive(Debug, Clone, Deserialize)]
pub struct InfoTeam {
pub score: u32
}
#[derive(Debug, Clone, Deserialize)]
pub struct Uploader {
pub id: SteamID,
pub name: String,
pub info: Option<String>,
}
#[cfg(test)]
mod tests {
use std::fs;
use test_case::test_case;
use super::*;
#[test_case("1.json")]
#[test_case("550237.json")]
fn test_parse(file: &str) {
let content = fs::read_to_string(format!("tests/data/{}", file)).unwrap();
let parsed: RawLog = serde_json::from_str(&content).unwrap();
assert!(parsed.teams.is_some() || parsed.info.teams.is_some());
assert!(parsed.rounds.is_some() || parsed.info.rounds.is_some());
}
}

1989
tests/data/1.json Normal file

File diff suppressed because it is too large Load diff

2542
tests/data/550237.json Normal file

File diff suppressed because it is too large Load diff

0
tests/raw.rs Normal file
View file