diff --git a/tdkpin-rs/highscore-server/Cargo.lock b/tdkpin-rs/highscore-server/Cargo.lock index e38a20b..364fd10 100644 --- a/tdkpin-rs/highscore-server/Cargo.lock +++ b/tdkpin-rs/highscore-server/Cargo.lock @@ -66,6 +66,12 @@ version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytes" version = "1.12.1" @@ -124,9 +130,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" @@ -183,20 +189,29 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "foldhash", ] [[package]] name = "hashlink" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248" dependencies = [ - "hashbrown", + "hashbrown 0.17.1", ] [[package]] @@ -285,6 +300,16 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.189" @@ -293,9 +318,9 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libsqlite3-sys" -version = "0.35.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" dependencies = [ "cc", "pkg-config", @@ -392,10 +417,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "rusqlite" -version = "0.37.0" +name = "rsqlite-vfs" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror", +] + +[[package]] +name = "rusqlite" +version = "0.40.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" dependencies = [ "bitflags", "fallible-iterator", @@ -403,6 +438,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -418,6 +454,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "ryu" version = "1.0.23" @@ -451,7 +493,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] @@ -518,6 +560,29 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "3.0.4" @@ -562,6 +627,26 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "tokio" version = "1.53.1" @@ -584,7 +669,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] @@ -653,6 +738,51 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + [[package]] name = "windows-link" version = "0.2.1" diff --git a/tdkpin-rs/highscore-server/Cargo.toml b/tdkpin-rs/highscore-server/Cargo.toml index de06490..0ea322f 100644 --- a/tdkpin-rs/highscore-server/Cargo.toml +++ b/tdkpin-rs/highscore-server/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] axum = "0.8" -rusqlite = { version = "0.37", features = ["bundled"] } +rusqlite = { version = "0.40", features = ["bundled"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["macros", "net", "rt-multi-thread"] } @@ -15,3 +15,11 @@ tokio = { version = "1", features = ["macros", "net", "rt-multi-thread"] } http-body-util = "0.1" tempfile = "3" tower = { version = "0.5", features = ["util"] } + +[lints.clippy] +pedantic = { level = "warn", priority = -1 } +todo = "warn" +unwrap_used = "warn" + +[lints.rust] +unsafe_code = "forbid" diff --git a/tdkpin-rs/highscore-server/README.md b/tdkpin-rs/highscore-server/README.md index d1588fb..4aac5c6 100644 --- a/tdkpin-rs/highscore-server/README.md +++ b/tdkpin-rs/highscore-server/README.md @@ -26,3 +26,6 @@ cargo run --manifest-path highscore-server/Cargo.toml Place [nginx.conf.example](nginx.conf.example) inside the public site's existing `server` block. The browser client expects the API at `/api/highscores` on the same origin as the game. + +The crate inherits the parent [`rustfmt.toml`](../rustfmt.toml); run +`just fmt-highscore-server` when formatting it directly. diff --git a/tdkpin-rs/highscore-server/src/lib.rs b/tdkpin-rs/highscore-server/src/lib.rs index 26e2709..6599c6b 100644 --- a/tdkpin-rs/highscore-server/src/lib.rs +++ b/tdkpin-rs/highscore-server/src/lib.rs @@ -39,10 +39,21 @@ impl From for StoreError { pub struct HighScoreStore(Arc>); impl HighScoreStore { + /// Open or create a SQLite-backed high-score store. + /// + /// # Errors + /// + /// Returns the SQLite error raised while opening or initializing the + /// database. pub fn open(path: impl AsRef) -> Result { Self::from_connection(Connection::open(path)?) } + /// Create an in-memory high-score store for tests or short-lived runs. + /// + /// # Errors + /// + /// Returns the SQLite error raised while initializing the database. pub fn open_in_memory() -> Result { Self::from_connection(Connection::open_in_memory()?) } @@ -60,6 +71,11 @@ impl HighScoreStore { Ok(Self(Arc::new(Mutex::new(connection)))) } + /// Return the current table in descending score order. + /// + /// # Errors + /// + /// Returns an error if the database lock or query fails. pub fn list(&self) -> Result, StoreError> { let connection = self.0.lock().map_err(|_| StoreError::Poisoned)?; let mut statement = connection.prepare( @@ -83,6 +99,11 @@ impl HighScoreStore { Ok(scores) } + /// Add one score and return the resulting canonical top ten. + /// + /// # Errors + /// + /// Returns an error if the database lock or transaction fails. pub fn submit(&self, entry: &HighScore) -> Result, StoreError> { { let mut connection = self.0.lock().map_err(|_| StoreError::Poisoned)?; @@ -125,7 +146,7 @@ fn invalid_request(message: &'static str) -> Response { .into_response() } -fn validate_request(request: SubmitRequest) -> Result { +fn validate_request(request: &SubmitRequest) -> Result { let name = request.name.trim(); if name.is_empty() { return Err("name must not be empty"); @@ -157,7 +178,7 @@ async fn submit_high_score( State(store): State, Json(request): Json, ) -> Response { - let entry = match validate_request(request) { + let entry = match validate_request(&request) { Ok(entry) => entry, Err(message) => return invalid_request(message), }; diff --git a/tdkpin-rs/highscore-server/src/main.rs b/tdkpin-rs/highscore-server/src/main.rs index f9d42fb..6d8b2c3 100644 --- a/tdkpin-rs/highscore-server/src/main.rs +++ b/tdkpin-rs/highscore-server/src/main.rs @@ -8,8 +8,7 @@ use tokio::net::TcpListener; async fn main() -> Result<(), Box> { let bind = env::var("TDKPIN_HIGHSCORE_BIND").unwrap_or_else(|_| "127.0.0.1:3000".to_owned()); let database = env::var_os("TDKPIN_HIGHSCORE_DB") - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from("highscores.sqlite3")); + .map_or_else(|| PathBuf::from("highscores.sqlite3"), PathBuf::from); let app: Router = router(HighScoreStore::open(database)?); let listener = TcpListener::bind(&bind).await?; println!("TDK high-score service listening on http://{bind}"); diff --git a/tdkpin-rs/justfile b/tdkpin-rs/justfile index ad22253..f83ec0f 100644 --- a/tdkpin-rs/justfile +++ b/tdkpin-rs/justfile @@ -28,7 +28,10 @@ test-highscore-server: clippy-highscore-server: cargo clippy --manifest-path highscore-server/Cargo.toml --all-targets --all-features -- -D warnings -fmt: +fmt-highscore-server: + cargo +nightly fmt --manifest-path highscore-server/Cargo.toml + +fmt: fmt-highscore-server cargo +nightly fmt tombi format fd -tf -e md -x prettier --write --prose-wrap always --print-width 80