diff --git a/.github/workflows/actions.yaml b/.github/workflows/actions.yaml index 8d04e7c6..62f9f00a 100644 --- a/.github/workflows/actions.yaml +++ b/.github/workflows/actions.yaml @@ -133,10 +133,10 @@ jobs: with: node-version: 13.x - - name: setup - install rust nightly + - name: setup - install rust uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable - name: setup - recall published version uses: actions/download-artifact@v1 @@ -152,7 +152,7 @@ jobs: chmod +x speedruns-linux-x86_64 ./speedruns-linux-x86_64 --help - cargo +nightly install speedruns --version $version + cargo install speedruns --version $version speedruns --help npm install -g speedruns@$version diff --git a/rust-toolchain b/rust-toolchain index b8ab2926..2bf5ad04 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-02-05 +stable diff --git a/rustfmt.toml b/rustfmt.toml index b7629d7a..916cafc3 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,14 +1,3 @@ -comment_width = 92 -format_code_in_doc_comments = true -inline_attribute_width = 64 -match_arm_blocks = false max_width = 92 -merge_imports = true newline_style = "Unix" -reorder_impl_items = true -struct_field_align_threshold = 64 -trailing_semicolon = false -unstable_features = true use_field_init_shorthand = true -wrap_comments = true -normalize_doc_attributes = true diff --git a/scripts/fix.bash b/scripts/fix.bash index 29264662..b13587b4 100755 --- a/scripts/fix.bash +++ b/scripts/fix.bash @@ -2,4 +2,4 @@ set -euxo pipefail tslint --fix --project . || echo "tslint failed" -cargo fix --workspace --allow-dirty --allow-staged -Z unstable-options --clippy +cargo fix --workspace --allow-dirty --allow-staged diff --git a/src/bin/import.rs b/src/bin/import.rs index 2e5fa4e5..e470625d 100644 --- a/src/bin/import.rs +++ b/src/bin/import.rs @@ -13,7 +13,8 @@ use std::{ use flate2::read::GzDecoder; use itertools::Itertools; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::{Deserializer as JsonDeserializer, Value as JsonValue}; use tempfile::NamedTempFile; @@ -59,7 +60,7 @@ pub fn main(args: Args) -> Result<(), Box> { for api_game in load_api_type::("data/api/games.jsonl.gz")? { if args.fixtures && !fixture_game_slugs.contains(&api_game.abbreviation().as_ref()) { - continue + continue; } else { fixture_game_ids.insert(api_game.id().clone()); } @@ -76,7 +77,7 @@ pub fn main(args: Args) -> Result<(), Box> { info!("Loading API runs..."); for api_run in load_api_type::("data/api/runs.jsonl.gz")? { if args.fixtures && !fixture_game_ids.contains(api_run.game()) { - continue + continue; } else { fixture_run_ids.insert(api_run.id().clone()); for player in api_run.players() { @@ -97,7 +98,7 @@ pub fn main(args: Args) -> Result<(), Box> { info!("Loading API users..."); for api_user in load_api_type::("data/api/users.jsonl.gz")? { if args.fixtures && !fixture_user_ids.contains(api_user.id()) { - continue + continue; } let user = api_user @@ -120,7 +121,7 @@ pub fn main(args: Args) -> Result<(), Box> { )))) { Ok(_) => { info!("Database validation successful."); - break + break; } Err(errors) => { error!("Database validation failed: {}", errors); @@ -142,8 +143,9 @@ pub fn main(args: Args) -> Result<(), Box> { User(user) => dead_user_ids.insert(*user.id()), Game(game) => dead_game_ids.insert(*game.id()), Level(level) => dead_level_ids.insert(*level.id()), - Category(category) => - dead_category_ids.insert(*category.id()), + Category(category) => { + dead_category_ids.insert(*category.id()) + } }; } IntegrityError::CheckFailed { .. } => { diff --git a/src/bin/scrape.rs b/src/bin/scrape.rs index efd4709c..f2d570d9 100644 --- a/src/bin/scrape.rs +++ b/src/bin/scrape.rs @@ -1,7 +1,8 @@ #![allow(clippy::useless_attribute, clippy::useless_vec)] use flate2::{read::GzDecoder, write::GzEncoder}; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; use serde_json::{Deserializer as JsonDeserializer, Value as JsonValue}; use std::{ collections::BTreeMap, @@ -12,7 +13,7 @@ use tempfile::NamedTempFile; #[derive(PartialEq, Eq, Hash)] struct Resource { - id: &'static str, + id: &'static str, order: &'static str, embed: &'static str, } @@ -39,7 +40,7 @@ const RESOURCES: [Resource; 3] = [ struct Spider { games_by_id: BTreeMap, users_by_id: BTreeMap, - runs_by_id: BTreeMap, + runs_by_id: BTreeMap, } impl Spider { @@ -173,18 +174,18 @@ impl Spider { Ok(mut response) => match response.json::() { Ok(response) => { response_data = response; - break + break; } Err(error) => { error!("response error: {:?}", error); std::thread::sleep(std::time::Duration::from_secs(32)); - continue + continue; } }, Err(error) => { error!("request error: {:?}", error); std::thread::sleep(std::time::Duration::from_secs(32)); - continue + continue; } } } @@ -212,11 +213,11 @@ impl Spider { if from_start { if self.resource_by_id(resource).len() == previous { // no new items at beginning of list - break + break; } } else if items.len() < 200 { // end of entire run list - break + break; }; // save progress diff --git a/src/bin/serve.rs b/src/bin/serve.rs index 93e183ef..07760841 100644 --- a/src/bin/serve.rs +++ b/src/bin/serve.rs @@ -12,7 +12,8 @@ use actix_web::{self, middleware, web, HttpResponse}; use juniper::{self, http::GraphQLRequest}; use lazy_static::lazy_static; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; use serde::de::DeserializeOwned; use serde_json::{Deserializer as JsonDeserializer, Value as JsonValue}; use speedruns::data::{ @@ -81,7 +82,7 @@ async fn diediedie() -> HttpResponse { pub struct Args { /// port to run server on #[argh(option)] - port: Option, + port: Option, /// whether to skip the database import (such as if you only need to run the server to /// briefly download the schema) #[argh(switch)] @@ -120,7 +121,7 @@ fn unpack_tables() -> Tables { if let crate::Subcommand::Serve(args) = args.subcommand { if args.no_data { info!("Skipping database import, will run with no data!"); - return Tables::new(vec![], vec![], vec![], vec![], vec![]) + return Tables::new(vec![], vec![], vec![], vec![], vec![]); } } diff --git a/src/bin/speedruns.rs b/src/bin/speedruns.rs index 9bbec2e1..5f2f35f5 100644 --- a/src/bin/speedruns.rs +++ b/src/bin/speedruns.rs @@ -7,7 +7,8 @@ use std::error::Error; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; mod import; mod scrape; diff --git a/src/lib/api/normalize.rs b/src/lib/api/normalize.rs index 0f108755..b7904156 100644 --- a/src/lib/api/normalize.rs +++ b/src/lib/api/normalize.rs @@ -1,7 +1,8 @@ use derive_more::From; use err_derive::Error; use lazy_static::lazy_static; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; use regex::Regex; use validator::Validate; @@ -54,17 +55,17 @@ impl Normalize for api::Names { fn normalize(&self) -> Result { if let Some(name) = self.international() { if !name.is_empty() { - return Ok(name.to_string()) + return Ok(name.to_string()); } } if let Some(name) = self.international() { if !name.is_empty() { - return Ok(name.to_string()) + return Ok(name.to_string()); } } if let Some(name) = self.japanese() { if !name.is_empty() { - return Ok(name.to_string()) + return Ok(name.to_string()); } } Err(Error::NoNames) @@ -76,11 +77,11 @@ impl Normalize for api::Game { fn normalize(&self) -> Result { let game = Game { - id: u64_from_base36(self.id())?, - name: self.names().normalize()?, - slug: slugify(self.abbreviation()), - src_slug: self.abbreviation().to_string(), - created: *self.created(), + id: u64_from_base36(self.id())?, + name: self.names().normalize()?, + slug: slugify(self.abbreviation()), + src_slug: self.abbreviation().to_string(), + created: *self.created(), primary_timing: self.ruleset().default_time().normalize()?, }; game.validate()?; @@ -91,11 +92,11 @@ impl Normalize for api::Game { .map(|api_category| -> Result { let category = Category { game_id: u64_from_base36(self.id())?, - id: u64_from_base36(api_category.id())?, - slug: slugify(api_category.name()), - name: api_category.name().to_string(), - rules: api_category.rules().clone().unwrap_or_else(String::new), - per: api_category.type_().normalize()?, + id: u64_from_base36(api_category.id())?, + slug: slugify(api_category.name()), + name: api_category.name().to_string(), + rules: api_category.rules().clone().unwrap_or_else(String::new), + per: api_category.type_().normalize()?, }; category.validate()?; @@ -110,10 +111,10 @@ impl Normalize for api::Game { .map(|api_level| -> Result { let level = Level { game_id: u64_from_base36(self.id())?, - id: u64_from_base36(api_level.id())?, - slug: slugify(api_level.name()), - name: api_level.name().to_string(), - rules: api_level.rules().clone().unwrap_or_default(), + id: u64_from_base36(api_level.id())?, + slug: slugify(api_level.name()), + name: api_level.name().to_string(), + rules: api_level.rules().clone().unwrap_or_default(), }; level.validate()?; @@ -134,23 +135,23 @@ impl Normalize for api::Run { match self.status() { api::RunStatus::Verified { .. } => { let run = Run { - game_id: u64_from_base36(self.game())?, - id: u64_from_base36(self.id())?, - created: *self.submitted(), - date: *self.date(), + game_id: u64_from_base36(self.game())?, + id: u64_from_base36(self.id())?, + created: *self.submitted(), + date: *self.date(), category_id: u64_from_base36(self.category())?, - level_id: match self.level() { + level_id: match self.level() { None => None, Some(level_id) => Some(u64_from_base36(level_id)?), }, - times_ms: self.times().normalize()?, - players: self + times_ms: self.times().normalize()?, + players: self .players() .iter() .map(Normalize::normalize) .map(Result::unwrap) .collect(), - videos: self + videos: self .videos() .as_ref() .map(|video| { @@ -254,8 +255,8 @@ impl Normalize for api::RunTimes { } Ok(RunTimesMs { - igt: self.ingame().as_ref().map(|s| parse_duration_ms(s)), - rta: self.realtime().as_ref().map(|s| parse_duration_ms(s)), + igt: self.ingame().as_ref().map(|s| parse_duration_ms(s)), + rta: self.realtime().as_ref().map(|s| parse_duration_ms(s)), rta_nl: self .realtime_noloads() .as_ref() diff --git a/src/lib/api/types.rs b/src/lib/api/types.rs index 4a98613b..d8966449 100644 --- a/src/lib/api/types.rs +++ b/src/lib/api/types.rs @@ -12,15 +12,15 @@ use std::collections::HashMap; #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct Category { - id: String, - links: Vec, + id: String, + links: Vec, miscellaneous: bool, - name: String, - players: CategoryPlayers, - rules: Option, + name: String, + players: CategoryPlayers, + rules: Option, #[serde(rename = "type")] - type_: CategoryType, - weblink: String, + type_: CategoryType, + weblink: String, } #[remain::sorted] @@ -63,27 +63,27 @@ pub struct Data { #[get = "pub"] pub struct Game { abbreviation: String, - assets: HashMap>, - categories: Data>, - created: Option>, - developers: Data>, - engines: Data>, - gametypes: Data>, - genres: Data>, - id: String, - levels: Data>, - links: Vec, - moderators: HashMap, - names: Names, - platforms: Data>, - publishers: Data>, - regions: Data>, + assets: HashMap>, + categories: Data>, + created: Option>, + developers: Data>, + engines: Data>, + gametypes: Data>, + genres: Data>, + id: String, + levels: Data>, + links: Vec, + moderators: HashMap, + names: Names, + platforms: Data>, + publishers: Data>, + regions: Data>, release_date: NaiveDate, - released: u32, - romhack: bool, - ruleset: GameRuleset, - variables: Data>, - weblink: String, + released: u32, + romhack: bool, + ruleset: GameRuleset, + variables: Data>, + weblink: String, } #[remain::sorted] @@ -92,8 +92,8 @@ pub struct Game { #[get = "pub"] pub struct GameAsset { height: Option, - uri: String, - width: Option, + uri: String, + width: Option, } #[remain::sorted] @@ -101,9 +101,9 @@ pub struct GameAsset { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct GameDeveloper { - id: String, + id: String, links: Vec, - name: String, + name: String, } #[remain::sorted] @@ -111,9 +111,9 @@ pub struct GameDeveloper { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct GameEngine { - id: String, + id: String, links: Vec, - name: String, + name: String, } #[remain::sorted] @@ -121,9 +121,9 @@ pub struct GameEngine { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct GameGenre { - id: String, + id: String, links: Vec, - name: String, + name: String, } #[remain::sorted] @@ -139,9 +139,9 @@ pub enum GameModeratorType { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct GamePublisher { - id: String, + id: String, links: Vec, - name: String, + name: String, } #[remain::sorted] @@ -149,12 +149,12 @@ pub struct GamePublisher { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct GameRuleset { - default_time: GameRulesetTiming, - emulators_allowed: bool, + default_time: GameRulesetTiming, + emulators_allowed: bool, require_verification: bool, - require_video: bool, - run_times: Vec, - show_milliseconds: bool, + require_video: bool, + run_times: Vec, + show_milliseconds: bool, } #[remain::sorted] @@ -176,9 +176,9 @@ pub enum GameRulesetTiming { #[get = "pub"] pub struct GameType { allows_base_game: Option, - id: String, - links: Vec, - name: String, + id: String, + links: Vec, + name: String, } #[remain::sorted] @@ -186,10 +186,10 @@ pub struct GameType { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct Level { - id: String, - links: Vec, - name: String, - rules: Option, + id: String, + links: Vec, + name: String, + rules: Option, weblink: String, } @@ -230,8 +230,8 @@ pub enum Link { #[get = "pub"] pub struct Names { international: Option, - japanese: Option, - twitch: Option, + japanese: Option, + twitch: Option, } #[remain::sorted] @@ -239,9 +239,9 @@ pub struct Names { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct Platform { - id: String, - links: Vec, - name: String, + id: String, + links: Vec, + name: String, released: u32, } @@ -250,9 +250,9 @@ pub struct Platform { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct Region { - id: String, + id: String, links: Vec, - name: String, + name: String, } #[remain::sorted] @@ -260,22 +260,22 @@ pub struct Region { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct Run { - category: String, - comment: Option, - date: Option, - game: String, - id: String, - level: Option, - links: Vec, - players: Vec, - splits: Option, - status: RunStatus, + category: String, + comment: Option, + date: Option, + game: String, + id: String, + level: Option, + links: Vec, + players: Vec, + splits: Option, + status: RunStatus, submitted: Option>, - system: RunSystem, - times: RunTimes, - values: HashMap, - videos: Option, - weblink: Option, + system: RunSystem, + times: RunTimes, + values: HashMap, + videos: Option, + weblink: Option, } #[remain::sorted] @@ -309,10 +309,10 @@ pub enum RunStatus { New, Rejected { examiner: Option, - reason: Option, + reason: Option, }, Verified { - examiner: Option, + examiner: Option, #[serde(rename = "verify-date")] verify_date: Option>, }, @@ -325,7 +325,7 @@ pub enum RunStatus { pub struct RunSystem { emulated: bool, platform: Option, - region: Option, + region: Option, } #[remain::sorted] @@ -333,14 +333,14 @@ pub struct RunSystem { #[serde(deny_unknown_fields, rename_all = "snake_case")] #[get = "pub"] pub struct RunTimes { - ingame: Option, - ingame_t: Option, - primary: Option, - primary_t: Option, - realtime: Option, - realtime_noloads: Option, + ingame: Option, + ingame_t: Option, + primary: Option, + primary_t: Option, + realtime: Option, + realtime_noloads: Option, realtime_noloads_t: Option, - realtime_t: Option, + realtime_t: Option, } #[remain::sorted] @@ -349,7 +349,7 @@ pub struct RunTimes { #[get = "pub"] pub struct RunVideos { links: Option>, - text: Option, + text: Option, } #[remain::sorted] @@ -365,19 +365,19 @@ pub struct Uri { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct User { - hitbox: Option, - id: String, - links: Vec, - location: Option, - name_style: UserNameStyle, - names: Names, - role: UserRole, - signup: Option>, + hitbox: Option, + id: String, + links: Vec, + location: Option, + name_style: UserNameStyle, + names: Names, + role: UserRole, + signup: Option>, speedrunslive: Option, - twitch: Option, - twitter: Option, - weblink: Option, - youtube: Option, + twitch: Option, + twitter: Option, + weblink: Option, + youtube: Option, } #[remain::sorted] @@ -386,7 +386,7 @@ pub struct User { #[get = "pub"] pub struct UserLocation { country: UserLocationCountry, - region: Option, + region: Option, } #[remain::sorted] @@ -394,7 +394,7 @@ pub struct UserLocation { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct UserLocationCountry { - code: Option, + code: Option, names: Names, } @@ -403,7 +403,7 @@ pub struct UserLocationCountry { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct UserLocationRegion { - code: Option, + code: Option, names: Names, } @@ -415,7 +415,7 @@ pub enum UserNameStyle { #[serde(rename = "color-from")] color_from: UserNameStyleColor, #[serde(rename = "color-to")] - color_to: UserNameStyleColor, + color_to: UserNameStyleColor, }, Solid { color: UserNameStyleColor, @@ -427,7 +427,7 @@ pub enum UserNameStyle { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct UserNameStyleColor { - dark: String, + dark: String, light: String, } @@ -450,16 +450,16 @@ pub enum UserRole { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[get = "pub"] pub struct Variable { - category: Option, - id: String, + category: Option, + id: String, is_subcategory: bool, - links: Vec, - mandatory: bool, - name: String, - obsoletes: bool, - scope: VariableScope, - user_defined: bool, - values: VariableValues, + links: Vec, + mandatory: bool, + name: String, + obsoletes: bool, + scope: VariableScope, + user_defined: bool, + values: VariableValues, } #[remain::sorted] @@ -478,10 +478,10 @@ pub enum VariableScope { #[get = "pub"] pub struct VariableValues { #[serde(rename = "_note")] - _note: String, + _note: String, choices: HashMap, default: Option, - values: HashMap, + values: HashMap, } #[remain::sorted] diff --git a/src/lib/data/database.rs b/src/lib/data/database.rs index 9337f1b7..f1ed98ac 100644 --- a/src/lib/data/database.rs +++ b/src/lib/data/database.rs @@ -11,7 +11,8 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use derive_more::From; use err_derive::Error; use getset::Getters; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; use serde::{Deserialize, Serialize}; use validator::{Validate, ValidationErrors}; @@ -41,7 +42,7 @@ impl Display for IntegrityErrors { for (i, error) in self.errors.iter().enumerate() { if i >= 16 { writeln!(f, " ...and more!")?; - break + break; } writeln!(f, "{:4}. {}", i + 1, error)?; @@ -63,10 +64,10 @@ pub enum IntegrityError { source )] ForeignKeyMissing { - target_type: &'static str, - target_id: u64, + target_type: &'static str, + target_id: u64, foreign_key_field: &'static str, - source: AnyModel, + source: AnyModel, }, #[error(display = "row validation check failed: {:?} in {:?}", errors, source)] CheckFailed { @@ -74,10 +75,7 @@ pub enum IntegrityError { source: AnyModel, }, #[error(display = "duplicate {:?} slug for {:?}", slug, sources)] - NonUniqueSlug { - slug: String, - sources: AnyModelVec, - }, + NonUniqueSlug { slug: String, sources: AnyModelVec }, #[error(display = "run is missing primary timing: {:?}", _0)] MissingPrimaryTiming(Run), } @@ -86,30 +84,30 @@ pub enum IntegrityError { #[derive(Debug, Default, Serialize, Deserialize, Clone, Getters)] #[get = "pub"] pub struct Tables { - runs: BTreeMap, - users: BTreeMap, - games: BTreeMap, + runs: BTreeMap, + users: BTreeMap, + games: BTreeMap, categories: BTreeMap, - levels: BTreeMap, + levels: BTreeMap, } /// A collection of [Tables] with various generated indexes. #[derive(Getters, Clone)] pub struct Database { #[get = "pub"] - tables: &'static Tables, + tables: &'static Tables, #[get = "pub"] - last_updated: DateTime, + last_updated: DateTime, #[get = "pub"] runs_by_game_id_and_category_id_and_level_id: BTreeMap<(u64, u64, Option), Vec<&'static Run>>, - games_by_slug: BTreeMap, - users_by_slug: BTreeMap, + games_by_slug: BTreeMap, + users_by_slug: BTreeMap, #[get = "pub"] per_game_categories_by_game_id_and_slug: BTreeMap<(u64, String), &'static Category>, #[get = "pub"] per_level_categories_by_game_id_and_slug: BTreeMap<(u64, String), &'static Category>, - levels_by_game_id_and_slug: BTreeMap<(u64, String), &'static Level>, + levels_by_game_id_and_slug: BTreeMap<(u64, String), &'static Level>, } impl Tables { @@ -179,7 +177,7 @@ impl Database { > = Default::default(); let mut levels_by_game_id_and_slug: BTreeMap<(u64, String), &'static Level> = Default::default(); - let index_errored = '_indexing: { + let index_errored = { for game in tables.games().values() { games_by_slug.insert(game.slug().to_string(), game); } @@ -197,9 +195,11 @@ impl Database { { runs.push(run); } else { - runs_by_game_id_and_category_id_and_level_id - .insert(key, vec![run]) - .unwrap_none(); + let existing = + runs_by_game_id_and_category_id_and_level_id.insert(key, vec![run]); + if existing != None { + unreachable!() + } } } @@ -494,7 +494,7 @@ pub struct Linked { #[serde(skip)] database: Arc, #[serde(flatten)] - item: &'static ModelType, + item: &'static ModelType, } impl Linked { @@ -594,10 +594,10 @@ impl Linked { if self.database.game_by_id(*self.game_id()).is_none() { errors.push(IntegrityError::ForeignKeyMissing { - target_type: "game", - target_id: *self.game_id(), + target_type: "game", + target_id: *self.game_id(), foreign_key_field: "game_id", - source: (*self.item).clone().into(), + source: (*self.item).clone().into(), }); } else { let game = self.game(); @@ -615,20 +615,20 @@ impl Linked { .is_none() { errors.push(IntegrityError::ForeignKeyMissing { - target_type: "category", - target_id: *self.category_id(), + target_type: "category", + target_id: *self.category_id(), foreign_key_field: "category_id", - source: (*self.item).clone().into(), + source: (*self.item).clone().into(), }); } if let Some(level_id) = self.level_id() { if self.database.level_by_id(*level_id).is_none() { errors.push(IntegrityError::ForeignKeyMissing { - target_type: "level", - target_id: *level_id, + target_type: "level", + target_id: *level_id, foreign_key_field: "level_id", - source: (*self.item).clone().into(), + source: (*self.item).clone().into(), }); } } @@ -637,10 +637,10 @@ impl Linked { if let RunPlayer::UserId(user_id) = player { if self.database.user_by_id(*user_id).is_none() { errors.push(IntegrityError::ForeignKeyMissing { - target_type: "user", - target_id: *user_id, + target_type: "user", + target_id: *user_id, foreign_key_field: "players[…].0", - source: (*self.item).clone().into(), + source: (*self.item).clone().into(), }); } } @@ -731,10 +731,10 @@ impl Linked { if self.database.game_by_id(*self.game_id()).is_none() { errors.push(IntegrityError::ForeignKeyMissing { - target_type: "game", - target_id: *self.game_id(), + target_type: "game", + target_id: *self.game_id(), foreign_key_field: "game_id", - source: (*self.item).clone().into(), + source: (*self.item).clone().into(), }); } @@ -785,10 +785,10 @@ impl Linked { if self.database.game_by_id(*self.game_id()).is_none() { errors.push(IntegrityError::ForeignKeyMissing { - target_type: "game", - target_id: *self.game_id(), + target_type: "game", + target_id: *self.game_id(), foreign_key_field: "game_id", - source: (*self.item).clone().into(), + source: (*self.item).clone().into(), }); } diff --git a/src/lib/data/graphql.rs b/src/lib/data/graphql.rs index 64740105..d9089306 100644 --- a/src/lib/data/graphql.rs +++ b/src/lib/data/graphql.rs @@ -66,7 +66,7 @@ pub struct User(DbLinked); #[derive(Debug, Clone)] pub struct CategoryLevel { category: DbLinked, - level: DbLinked, + level: DbLinked, } #[derive(Debug, Clone)] @@ -402,7 +402,7 @@ impl CategoryFields for Category { level_id = Some(level.id); } else { // level specified but not found - return vec![] + return vec![]; } } else { level_id = None; @@ -446,7 +446,7 @@ impl CategoryFields for Category { level_id = Some(level.id); } else { // level specified but not found - return vec![] + return vec![]; } } else { level_id = None; @@ -468,7 +468,7 @@ impl CategoryFields for Category { progress.iter().map(|r| ProgressionRun(r.clone())).collect() } else { - return vec![] + return vec![]; } } diff --git a/src/lib/data/leaderboard.rs b/src/lib/data/leaderboard.rs index 17354f34..ce5e3870 100644 --- a/src/lib/data/leaderboard.rs +++ b/src/lib/data/leaderboard.rs @@ -8,11 +8,11 @@ use crate::data::{database::Linked, types::*}; #[derive(Debug, Clone, Getters, Serialize)] #[get = "pub"] pub struct LeaderboardRun { - rank: u64, - time_ms: u64, - is_tied: bool, + rank: u64, + time_ms: u64, + is_tied: bool, tied_rank: u64, - run: Linked, + run: Linked, } /// Ranks a set of runs (all for the same game/category/level) using the @@ -21,7 +21,7 @@ pub struct LeaderboardRun { /// unless rank_obsoletes is true. pub fn leaderboard(runs: &[Linked], rank_obsoletes: bool) -> Vec { if runs.is_empty() { - return vec![] + return vec![]; } let mut runs: Vec> = runs.to_vec(); @@ -55,7 +55,7 @@ pub fn leaderboard(runs: &[Linked], rank_obsoletes: bool) -> Vec, + progress_ms: u64, + run: Linked, leaderboard_run: Option, } pub fn progression(runs: &[Linked]) -> Vec { if runs.is_empty() { - return vec![] + return vec![]; } let game_id = runs[0].game_id; diff --git a/src/lib/data/types.rs b/src/lib/data/types.rs index 5e9af374..de166132 100644 --- a/src/lib/data/types.rs +++ b/src/lib/data/types.rs @@ -10,7 +10,8 @@ use std::{ use chrono::{DateTime, NaiveDate, Utc}; use getset::Getters; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; use serde::{Deserialize, Serialize}; use validator::{Validate, ValidationError, ValidationErrors}; use validator_derive::Validate; @@ -41,12 +42,12 @@ use crate::utils::{base36, src_slugify}; pub struct Category { pub game_id: u64, #[validate(length(min = 1))] - pub slug: String, + pub slug: String, #[validate(length(min = 1))] - pub name: String, - pub id: u64, - pub per: CategoryType, - pub rules: String, + pub name: String, + pub id: u64, + pub per: CategoryType, + pub rules: String, } impl Category { @@ -86,10 +87,10 @@ pub enum CategoryType { pub struct User { pub created: Option>, #[validate(length(min = 1))] - pub slug: String, + pub slug: String, #[validate(length(min = 1))] - pub name: String, - pub id: u64, + pub name: String, + pub id: u64, } impl User { @@ -120,14 +121,14 @@ impl User { #[serde(deny_unknown_fields)] #[get = "pub"] pub struct Game { - pub id: u64, - pub created: Option>, + pub id: u64, + pub created: Option>, #[validate(length(min = 1))] - pub slug: String, + pub slug: String, #[validate(length(min = 1))] - pub src_slug: String, + pub src_slug: String, #[validate(length(min = 1))] - pub name: String, + pub name: String, pub primary_timing: TimingMethod, } @@ -164,12 +165,12 @@ pub enum TimingMethod { #[get = "pub"] pub struct Level { pub game_id: u64, - pub id: u64, + pub id: u64, #[validate(length(min = 1))] - pub slug: String, + pub slug: String, #[validate(length(min = 1))] - pub name: String, - pub rules: String, + pub name: String, + pub rules: String, } impl Level { @@ -199,17 +200,17 @@ impl Level { )] #[get = "pub"] pub struct Run { - pub game_id: u64, + pub game_id: u64, pub category_id: u64, - pub level_id: Option, - pub id: u64, - pub created: Option>, - pub date: Option, + pub level_id: Option, + pub id: u64, + pub created: Option>, + pub date: Option, #[validate] - pub times_ms: RunTimesMs, + pub times_ms: RunTimesMs, #[validate] - pub players: Vec, - pub videos: Vec, + pub players: Vec, + pub videos: Vec, } impl Run { @@ -230,17 +231,18 @@ impl std::fmt::Display for RunVideo { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use RunVideo::*; match self { - YouTube { id, start } => - write!(f, "https://youtu.be/{}?t={}", id, start.unwrap_or(0)), + YouTube { id, start } => { + write!(f, "https://youtu.be/{}?t={}", id, start.unwrap_or(0)) + } Link { url } => write!(f, "{}", url), } } } impl std::str::FromStr for RunVideo { - type Err = !; + type Err = std::convert::Infallible; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { // TODO: parse all youtube URL variants Ok(RunVideo::Link { url: s.to_string() }) } @@ -252,8 +254,8 @@ impl std::str::FromStr for RunVideo { #[serde(deny_unknown_fields)] #[get = "pub"] pub struct RunTimesMs { - pub igt: Option, - pub rta: Option, + pub igt: Option, + pub rta: Option, pub rta_nl: Option, } @@ -272,7 +274,7 @@ impl Validate for RunTimesMs { if self.igt == None && self.rta == None && self.rta_nl == None { let mut errors = ValidationErrors::new(); errors.add("", ValidationError::new("all times were None")); - return Err(errors) + return Err(errors); } Ok(()) } @@ -291,7 +293,7 @@ impl Validate for RunPlayer { if name.is_empty() { let mut errors = ValidationErrors::new(); errors.add("GuestName.0", ValidationError::new("name is empty")); - return Err(errors) + return Err(errors); } } Ok(()) diff --git a/src/lib/speedruns.rs b/src/lib/speedruns.rs index 0d2b9586..43d0c7fe 100644 --- a/src/lib/speedruns.rs +++ b/src/lib/speedruns.rs @@ -1,10 +1,4 @@ //! Tools to download, search, and mirror https://speedrun.com leaderboards. -#![feature( - arbitrary_self_types, - label_break_value, - option_unwrap_none, - never_type -)] #![allow(missing_docs, clippy::useless_attribute, clippy::useless_vec)] #![warn(missing_debug_implementations)] diff --git a/src/lib/utils/mod.rs b/src/lib/utils/mod.rs index 8de336b7..e9d12b86 100644 --- a/src/lib/utils/mod.rs +++ b/src/lib/utils/mod.rs @@ -1,16 +1,11 @@ //! Shared utils. -#![feature( - arbitrary_self_types, - label_break_value, - option_unwrap_none, - never_type -)] #![allow(missing_docs, clippy::useless_attribute, clippy::useless_vec)] #![warn(missing_debug_implementations)] use derive_more::From; use err_derive::Error; -#[allow(unused)] use log::{debug, error, info, trace, warn}; +#[allow(unused)] +use log::{debug, error, info, trace, warn}; use unidecode::unidecode; /// Errors for [u64_from_base36]. #[derive(Debug, Error, From, PartialEq)] @@ -60,7 +55,7 @@ pub fn slugify(s: &str) -> String { this_was_spacing = true; } // escaped at ends, entirely ignored elsewhere - '\'' => + '\'' => { if first_or_last { if !last_was_spacing { slug.push('-'); @@ -69,7 +64,8 @@ pub fn slugify(s: &str) -> String { slug.push_str("prime-"); } this_was_spacing = true; - }, + } + } // escaped at ends, converted to spacing elsewhere '.' => { if !last_was_spacing { @@ -176,7 +172,7 @@ pub fn u64_from_base36(digits: &str) -> Result { let mut value = 0; if digits.len() != 8 { - return Err(Base36DecodingError::WrongLength) + return Err(Base36DecodingError::WrongLength); } for digit in digits.chars() { diff --git a/src/pages/[game]/index.tsx b/src/pages/[game]/index.tsx index 3fb653f2..584edbfc 100644 --- a/src/pages/[game]/index.tsx +++ b/src/pages/[game]/index.tsx @@ -51,6 +51,12 @@ const GamePage: NextPage = () => { rel="canonical" href={`https://www.speedrun.com/${game.srcSlug}`} /> + + +

diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c2e5e933..c83d66d5 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -136,7 +136,14 @@ export const HomePage: NextPage<{}> = () => { return (
- Speedruns + speedruns.ca + + + + {gameIndex?.error || home?.error ? (
{JSON.stringify([gameIndex?.error, home?.error], null, 2)}