From 9c288d051c72e6d19e128787c7a36c16ba2bce31 Mon Sep 17 00:00:00 2001 From: David Skrundz Date: Mon, 7 Sep 2026 22:31:04 -0700 Subject: [PATCH] Update dependencies and lints --- .clippy.toml | 4 + .gitignore | 30 +- .rustfmt.toml | 1 - .tombi.toml | 10 + .zed/settings.json | 13 +- Cargo.lock | 1599 +++++++---------- Cargo.toml | 614 ++++++- README.md | 24 +- crates/cli-mux/Cargo.toml | 33 +- crates/cli-mux/src/cli.rs | 50 +- crates/cli-mux/src/main.rs | 23 +- crates/cli-mux/src/model.rs | 67 +- crates/cli-mux/src/mux.rs | 116 +- crates/cli-mux/src/parser.rs | 71 +- crates/cli-mux/src/probe.rs | 27 +- crates/cli-mux/src/scan.rs | 27 +- crates/cli/Cargo.toml | 33 +- crates/cli/src/cli/flix.rs | 19 +- crates/cli/src/cli/mod.rs | 120 +- crates/cli/src/cli/tmdb.rs | 36 +- crates/cli/src/config.rs | 16 +- crates/cli/src/db.rs | 28 +- crates/cli/src/main.rs | 56 +- crates/cli/src/run/flix.rs | 37 +- crates/cli/src/run/mod.rs | 6 +- crates/cli/src/run/tmdb.rs | 815 ++------- crates/db/Cargo.toml | 14 +- crates/db/src/connection.rs | 18 +- crates/db/src/entity/content.rs | 328 ++-- crates/db/src/entity/info.rs | 268 +-- crates/db/src/entity/mod.rs | 176 +- crates/db/src/entity/tmdb.rs | 145 +- crates/db/src/entity/watched.rs | 123 +- crates/db/src/lib.rs | 8 +- .../db/src/migration/m_000001/collections.rs | 24 +- .../{m_000001.rs => m_000001/mod.rs} | 6 + crates/db/src/migration/m_000001/seasons.rs | 24 +- crates/db/src/migration/m_000001/shows.rs | 24 +- crates/db/src/migration/mod.rs | 2 +- crates/flix/Cargo.toml | 12 +- crates/flix/src/lib.rs | 2 +- crates/fs/Cargo.toml | 15 +- crates/fs/src/error.rs | 24 +- crates/fs/src/item.rs | 11 +- crates/fs/src/lib.rs | 2 +- crates/fs/src/macros.rs | 4 + crates/fs/src/scanner/collection.rs | 30 +- crates/fs/src/scanner/episode.rs | 17 +- crates/fs/src/scanner/generic.rs | 51 +- crates/fs/src/scanner/library.rs | 16 +- crates/fs/src/scanner/mod.rs | 98 +- crates/fs/src/scanner/movie.rs | 17 +- crates/fs/src/scanner/season.rs | 25 +- crates/fs/src/scanner/show.rs | 23 +- crates/model/Cargo.toml | 12 +- crates/model/src/id.rs | 35 +- crates/model/src/lib.rs | 2 +- crates/model/src/numbers.rs | 86 +- crates/model/src/text.rs | 61 +- crates/tmdb/Cargo.toml | 13 +- crates/tmdb/src/api/collections.rs | 34 +- crates/tmdb/src/api/episodes.rs | 34 +- crates/tmdb/src/api/mod.rs | 84 +- crates/tmdb/src/api/movies.rs | 34 +- crates/tmdb/src/api/seasons.rs | 34 +- crates/tmdb/src/api/shows.rs | 34 +- crates/tmdb/src/cache.rs | 57 +- crates/tmdb/src/client.rs | 87 +- crates/tmdb/src/config.rs | 22 +- crates/tmdb/src/lib.rs | 21 +- crates/tmdb/src/model/collection.rs | 39 +- crates/tmdb/src/model/episode.rs | 38 +- crates/tmdb/src/model/id.rs | 87 +- crates/tmdb/src/model/mod.rs | 22 +- crates/tmdb/src/model/movie.rs | 51 +- crates/tmdb/src/model/season.rs | 39 +- crates/tmdb/src/model/show.rs | 42 +- justfile | 44 + rust-toolchain.toml | 2 +- 79 files changed, 3473 insertions(+), 2923 deletions(-) create mode 100644 .clippy.toml create mode 100644 .tombi.toml rename crates/db/src/migration/{m_000001.rs => m_000001/mod.rs} (78%) create mode 100644 justfile diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..5951be3 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,4 @@ +allow-expect-in-tests = true +check-private-items = true + +allowed-duplicate-crates = [] diff --git a/.gitignore b/.gitignore index 9bf72ff..9fbfa1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,23 @@ -# OS Files -*~ -._* -.DS_Store +* +!**/ -# Rust -/target +!.zed/settings.json +!.gitignore -# Flix -flix.db -flix.redb +!/CONTRIBUTING.md +!/LICENSE.md +!/README.md + +!/.clippy.toml +!/.rustfmt.toml +!/.tombi.toml +!/Cargo.toml +!/Cargo.lock +!/justfile +!/rust-toolchain.toml + +!/crates/**/*.rs +!/crates/**/Cargo.toml +!/crates/**/README.md + +!/lints/*.toml diff --git a/.rustfmt.toml b/.rustfmt.toml index 5e7f9e2..218e203 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1 @@ hard_tabs = true -wrap_comments = true diff --git a/.tombi.toml b/.tombi.toml new file mode 100644 index 0000000..9d33042 --- /dev/null +++ b/.tombi.toml @@ -0,0 +1,10 @@ +[format.rules] +indent-style = "tab" +indent-width = 4 +line-width = 120 + +# Disable TOML 1.1 features from tombi as long as we support rust <1.94 +[[schemas]] +toml-version = "v1.0.0" +path = "tombi://www.schemastore.org/cargo.json" +include = ["Cargo.toml"] diff --git a/.zed/settings.json b/.zed/settings.json index 14b551a..893dfca 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,11 +1,12 @@ { - "languages": { - "TOML": { - "format_on_save": "on", - "formatter": { "language_server": { "name": "tombi" } }, - }, - }, "lsp": { + "just-lsp": { + "initialization_options": { + "formatting": { + "indentation": "\t", + }, + }, + }, "rust-analyzer": { "initialization_options": { "imports": { diff --git a/Cargo.lock b/Cargo.lock index f2ce997..e7686ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.12" @@ -29,9 +18,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -50,9 +39,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -109,21 +98,21 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "arrow" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd47f2a6ddc39244bd722a27ee5da66c03369d087b9e024eafdb03e98b98ea7" +checksum = "6cfdd0833e32a9874d2b55089333ad310c0be208aafa277385ce2461dec90be3" dependencies = [ "arrow-arith", "arrow-array", @@ -139,9 +128,9 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7bbd679c5418b8639b92be01f361d60013c4906574b578b77b63c78356594c" +checksum = "0a41203398f0eaa6f7ec8e62c0da742a21abf282c148fc157f6c35c90e29981a" dependencies = [ "arrow-array", "arrow-buffer", @@ -153,17 +142,17 @@ dependencies = [ [[package]] name = "arrow-array" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8a4ab47b3f3eac60f7fd31b81e9028fda018607bcc63451aca4f2b755269862" +checksum = "ae33dad492b7df00a217563a7b0ef2874df68a0deea1b1a3acf628152f7f7a69" dependencies = [ - "ahash 0.8.12", + "ahash", "arrow-buffer", "arrow-data", "arrow-schema", "chrono", "half", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "num-complex", "num-integer", "num-traits", @@ -171,9 +160,9 @@ dependencies = [ [[package]] name = "arrow-buffer" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d18b89b4c4f4811d0858175e79541fe98e33e18db3b011708bc287b1240593f" +checksum = "b9552f96391c005e6ab449fa941420935e7e062489b12b8b1b08879b2163f5b5" dependencies = [ "bytes", "half", @@ -183,9 +172,9 @@ dependencies = [ [[package]] name = "arrow-cast" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722b5c41dd1d14d0a879a1bce92c6fe33f546101bb2acce57a209825edd075b3" +checksum = "3a8a327c9649f30d8406995f27642b68df354713cca3baaaf100f076f18d5f34" dependencies = [ "arrow-array", "arrow-buffer", @@ -204,9 +193,9 @@ dependencies = [ [[package]] name = "arrow-data" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1683705c63dcf0d18972759eda48489028cbbff67af7d6bef2c6b7b74ab778a" +checksum = "2b24852db04738907e06c04ea61e42fe7fda962a34513022dc0d0e754fb7976b" dependencies = [ "arrow-buffer", "arrow-schema", @@ -217,9 +206,9 @@ dependencies = [ [[package]] name = "arrow-ord" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "082342947d4e5a2bcccf029a0a0397e21cb3bb8421edd9571d34fb5dd2670256" +checksum = "63a083ec750f5c043f02946b4baf05fcdbb55f4560a3277055caca5cc99f3eb0" dependencies = [ "arrow-array", "arrow-buffer", @@ -230,9 +219,9 @@ dependencies = [ [[package]] name = "arrow-row" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a931b520a2a5e22033e01a6f2486b4cdc26f9106b759abeebc320f125e94d7" +checksum = "514ba0ef0d4c5896202dae736251ce415abb43a950bed570fb7981b8716c0e4c" dependencies = [ "arrow-array", "arrow-buffer", @@ -243,17 +232,17 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4cf0d4a6609679e03002167a61074a21d7b1ad9ea65e462b2c0a97f8a3b2bc6" +checksum = "21ca356ad6425cecb6eb7b28e4f659f1ee7880fbb1a16127de7dd62901efee9e" [[package]] name = "arrow-select" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b320d86a9806923663bb0fd9baa65ecaba81cb0cd77ff8c1768b9716b4ef891" +checksum = "c58da39eb3d8350ad4a549e5c2bc49284dac554016c69829310350f1731b0aad" dependencies = [ - "ahash 0.8.12", + "ahash", "arrow-array", "arrow-buffer", "arrow-data", @@ -263,9 +252,9 @@ dependencies = [ [[package]] name = "arrow-string" -version = "57.3.1" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b493e99162e5764077e7823e50ba284858d365922631c7aaefe9487b1abd02c2" +checksum = "b6789b388467525e3271326b6b4915666ecfdf5142aef09779445c954b67543c" dependencies = [ "arrow-array", "arrow-buffer", @@ -297,18 +286,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -334,9 +323,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ "aws-lc-sys", "zeroize", @@ -344,14 +333,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -360,12 +350,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bigdecimal" version = "0.4.10" @@ -382,25 +366,13 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" dependencies = [ "serde_core", ] -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -411,10 +383,19 @@ dependencies = [ ] [[package]] -name = "borsh" -version = "1.6.1" +name = "block-buffer" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" dependencies = [ "borsh-derive", "bytes", @@ -423,15 +404,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -440,28 +421,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "byteorder" version = "1.5.0" @@ -470,15 +429,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.63" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" dependencies = [ "find-msvc-tools", "jobserver", @@ -494,9 +453,20 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] [[package]] name = "chrono" @@ -512,9 +482,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -522,9 +492,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -534,14 +504,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -559,6 +529,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "colorchoice" version = "1.0.5" @@ -567,41 +543,25 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "memchr", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "console" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" dependencies = [ "encode_unicode", "libc", - "unicode-width", "windows-sys 0.61.2", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-random" version = "0.1.18" @@ -647,6 +607,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -664,18 +633,18 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crunchy" @@ -685,14 +654,32 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "darling" version = "0.20.11" @@ -713,7 +700,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -724,18 +711,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.117", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "syn 2.0.119", ] [[package]] @@ -744,10 +720,20 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -766,41 +752,40 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.119", "unicode-xid", ] -[[package]] -name = "dialoguer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" -dependencies = [ - "console", - "shell-words", -] - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "block-buffer 0.10.4", + "crypto-common 0.1.6", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", ] [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -817,9 +802,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" dependencies = [ "serde", ] @@ -837,36 +822,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "etcetera" -version = "0.8.0" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] [[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "fastrand" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "flix" -version = "0.0.20" +version = "0.1.0" dependencies = [ "flix-db", "flix-fs", @@ -876,13 +875,12 @@ dependencies = [ [[package]] name = "flix-cli" -version = "0.0.20" +version = "0.1.0" dependencies = [ "anyhow", "chrono", "clap", "flix", - "futures", "sea-orm", "serde", "tokio", @@ -893,7 +891,7 @@ dependencies = [ [[package]] name = "flix-db" -version = "0.0.20" +version = "0.1.0" dependencies = [ "chrono", "flix-model", @@ -906,12 +904,13 @@ dependencies = [ [[package]] name = "flix-fs" -version = "0.0.20" +version = "0.1.0" dependencies = [ "async-stream", "either", "flix-model", "regex", + "tempfile", "thiserror", "tokio", "tokio-stream", @@ -919,9 +918,9 @@ dependencies = [ [[package]] name = "flix-model" -version = "0.0.20" +version = "0.1.0" dependencies = [ - "itertools", + "itertools 0.15.0", "seamantic", "serde", "thiserror", @@ -929,12 +928,11 @@ dependencies = [ [[package]] name = "flix-mux" -version = "0.0.20" +version = "0.1.0" dependencies = [ "anyhow", "clap", "console", - "dialoguer", "indicatif", "serde", "serde_json", @@ -943,7 +941,7 @@ dependencies = [ [[package]] name = "flix-tmdb" -version = "0.0.20" +version = "0.1.0" dependencies = [ "bytes", "chrono", @@ -956,6 +954,7 @@ dependencies = [ "serde", "serde_json", "serde_test", + "tempfile", "thiserror", "url", "url-macro", @@ -963,9 +962,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -978,12 +977,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -1005,31 +998,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1037,15 +1010,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1065,21 +1038,21 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-timer" @@ -1089,9 +1062,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-io", @@ -1104,9 +1077,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -1134,16 +1107,30 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] -name = "glob" -version = "0.3.3" +name = "getrandom" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "governor" @@ -1160,7 +1147,7 @@ dependencies = [ "nonzero_ext", "parking_lot", "portable-atomic", - "rand 0.9.4", + "rand 0.9.5", "smallvec", "spinning_top", "web-time", @@ -1178,26 +1165,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -1206,7 +1173,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -1217,11 +1184,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -1244,36 +1211,27 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", + "digest 0.11.3", ] [[package]] name = "http" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1281,9 +1239,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1291,9 +1249,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -1309,10 +1267,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "hyper" -version = "1.10.1" +name = "hybrid-array" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -1392,9 +1359,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1406,9 +1373,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1419,9 +1386,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1433,16 +1400,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1453,15 +1421,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -1501,9 +1469,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -1511,9 +1479,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" dependencies = [ "console", "portable-atomic", @@ -1529,17 +1497,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "inherent" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "inventory" version = "0.3.24" @@ -1551,9 +1508,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is_terminal_polyfill" @@ -1570,6 +1527,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1603,7 +1569,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1622,28 +1588,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1652,9 +1617,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "lexical-core" @@ -1715,9 +1677,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libm" @@ -1725,23 +1687,11 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.8.1", -] - [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -1749,10 +1699,16 @@ dependencies = [ ] [[package]] -name = "litemap" -version = "0.8.2" +name = "linux-raw-sys" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lock_api" @@ -1765,9 +1721,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lru-slab" @@ -1797,19 +1753,19 @@ dependencies = [ [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memoffset" @@ -1822,9 +1778,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi", @@ -1852,30 +1808,14 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.6", - "smallvec", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -1893,24 +1833,13 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1969,7 +1898,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1996,20 +1925,11 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -2031,38 +1951,11 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "pluralizer" @@ -2076,15 +1969,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -2113,33 +2006,11 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2152,36 +2023,16 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "version_check", "yansi", ] -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -2199,15 +2050,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", "rustls", @@ -2221,23 +2073,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2249,16 +2101,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "radium" -version = "0.7.0" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2267,14 +2119,25 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2314,10 +2177,25 @@ dependencies = [ ] [[package]] -name = "redb" -version = "4.1.0" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e925444704b5f17d32bf42f5b6e2df050bceebc3dcd6e71cc73dafe8092e839" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "redb" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de6c3b63e007e90ce536ec2ae4690826136a20ec8dbbbb400daef1bb999d2e36" dependencies = [ "libc", ] @@ -2331,20 +2209,11 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" -dependencies = [ - "bitflags", -] - [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2354,9 +2223,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -2365,18 +2234,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -2429,67 +2289,18 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rkyv" -version = "0.7.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rust_decimal" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c5108e3d4d903e21aac27f12ba5377b6b34f9f44b325e4894c7924169d06995" +checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", - "rand 0.8.6", - "rkyv", + "rand 0.8.8", + "rand 0.9.5", "serde", "serde_json", "wasm-bindgen", @@ -2497,9 +2308,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -2511,10 +2322,23 @@ dependencies = [ ] [[package]] -name = "rustls" -version = "0.23.40" +name = "rustix" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" dependencies = [ "aws-lc-rs", "once_cell", @@ -2539,9 +2363,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", @@ -2576,9 +2400,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "aws-lc-rs", "ring", @@ -2588,9 +2412,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -2624,31 +2448,31 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sea-bae" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" +checksum = "260bbc7148a8d6818ac5032a1b9970da1a68bdd723d45b12d59f7c1bf3565e24" dependencies = [ "heck 0.4.1", - "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "sea-orm" -version = "2.0.0-rc.38" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5428ce6a0c8f6b9858df21ad1aa00c2fb94e1c9f344a0436bc855391e5a225" +checksum = "a334e83ced3ae3ee44db0f84d1fcf8d2087a1ad9bb9036f00f9f6067156ea197" dependencies = [ "async-stream", "async-trait", "bigdecimal", "chrono", + "derive-where", "derive_more", "futures-util", "inventory", - "itertools", + "itertools 0.14.0", "log", "mac_address", "ouroboros", @@ -2662,19 +2486,21 @@ dependencies = [ "serde", "serde_json", "sqlx", + "sqlx-core", "strum", "thiserror", "time", "tracing", "url", "uuid", + "web-time", ] [[package]] name = "sea-orm-arrow" -version = "2.0.0-rc.3" +version = "2.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2eee8405f16c1f337fe3a83389361caea83c928d14dbd666a480407072c365" +checksum = "4c800d9db902534d7d01728faf98e33d13c1d57bb8c57d8e4c518309172bddda" dependencies = [ "arrow", "sea-query", @@ -2683,9 +2509,9 @@ dependencies = [ [[package]] name = "sea-orm-cli" -version = "2.0.0-rc.38" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd42605c3b611785eed593406900f463b86c61792e723272e0434e77ed9cd8d" +checksum = "5a53505884d7c907bcf4f7b4ddb1b29425e62fef8b98aea9c99e17781cceb798" dependencies = [ "chrono", "glob", @@ -2701,25 +2527,25 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "2.0.0-rc.40" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a91def07bceb98aab308f7dd16c27496b76a6b7b92b94a61b309b5043d93d5" +checksum = "4039a86f9acc4d3b52747508b347dddc6fd725bbc429902ebeb6d26225fc2528" dependencies = [ "heck 0.5.0", - "itertools", + "itertools 0.14.0", "pluralizer", "proc-macro2", "quote", "sea-bae", - "syn 2.0.117", + "syn 2.0.119", "unicode-ident", ] [[package]] name = "sea-orm-migration" -version = "2.0.0-rc.38" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f6ce467587c910bb2842cf001ea600ac6228ba5f3f39c1dc499929e34a8f29" +checksum = "bd09adbef87100d07131a60a8c5508b53d0cf2136521f654aae47af5e6a097fe" dependencies = [ "async-trait", "sea-orm", @@ -2731,12 +2557,11 @@ dependencies = [ [[package]] name = "sea-query" -version = "1.0.0-rc.33" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04cdb0135c16e829504e93fbe7880513578d56f07aaea152283526590111828" +checksum = "546040c653a705e60ec65ecd3191a809603734bebbc225775916dea9ae409b31" dependencies = [ "chrono", - "inherent", "ordered-float", "rust_decimal", "sea-query-derive", @@ -2755,15 +2580,15 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "thiserror", ] [[package]] name = "sea-query-sqlx" -version = "0.8.0-rc.15" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04aeecfe00614fece56336fd35dc385bb9ffed0c75660695ba925e42a3991ef" +checksum = "4eaa419cdb9157da1361186b1959983eb2ea0dcb9a3c69dc45c449ecb2af8fef" dependencies = [ "sea-query", "sqlx", @@ -2771,9 +2596,9 @@ dependencies = [ [[package]] name = "sea-schema" -version = "0.17.0-rc.17" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b363dd21c20fe4d1488819cb2bc7f8d4696c62dd9f39554f97639f54d57dd0ab" +checksum = "3553c77dceed56e95bece9ea876c4dd67ca879ef51055a0b97a7bb89a8ae4fed" dependencies = [ "async-trait", "sea-query", @@ -2791,20 +2616,14 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "seamantic" -version = "0.0.14" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04509950290d9b38f521bb7e5a2490330b1e9c4ee433ce33cc2268efb5d06cab" +checksum = "a36a0006a97037f038dd018b43165ae412113dac1105f05c24bf0e243807d145" dependencies = [ "sea-orm", "sea-orm-migration", @@ -2841,9 +2660,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2851,29 +2670,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -2914,13 +2733,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -2930,8 +2749,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -2943,33 +2773,17 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -2989,18 +2803,18 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3008,9 +2822,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -3024,21 +2838,11 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3049,12 +2853,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64", "bytes", + "cfg-if", "chrono", "crc", "crossbeam-queue", @@ -3064,18 +2869,17 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "rust_decimal", "rustls", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror", "time", @@ -3084,98 +2888,82 @@ dependencies = [ "tracing", "url", "uuid", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck 0.5.0", "hex", - "once_cell", "proc-macro2", "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.117", + "syn 2.0.119", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64", "bitflags", "byteorder", "bytes", "chrono", "crc", - "digest", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand 0.8.6", - "rsa", "rust_decimal", "serde", "sha1", - "sha2", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror", "time", "tracing", "uuid", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64", @@ -3191,17 +2979,15 @@ dependencies = [ "hex", "hkdf", "hmac", - "home", "itoa", "log", "md-5", "memchr", - "once_cell", - "rand 0.8.6", + "rand 0.10.2", "rust_decimal", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", @@ -3214,13 +3000,14 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "chrono", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -3230,7 +3017,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror", "time", @@ -3282,9 +3068,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -3293,9 +3079,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", @@ -3319,52 +3105,57 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "tap" -version = "1.0.1" +name = "tempfile" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -3374,15 +3165,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -3399,9 +3190,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -3409,9 +3200,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" dependencies = [ "tinyvec_macros", ] @@ -3424,9 +3215,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -3439,20 +3230,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" dependencies = [ "rustls", "tokio", @@ -3460,9 +3251,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -3471,9 +3262,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" dependencies = [ "serde_core", "serde_spanned", @@ -3493,9 +3284,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.12+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", "toml_datetime", @@ -3505,9 +3296,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] @@ -3577,7 +3368,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3643,12 +3434,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -3681,9 +3466,9 @@ dependencies = [ [[package]] name = "url-macro" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f8fd02d998459be6962aa74831961202461c1545cf056725e2c73b9f860c91" +checksum = "a77b65c7a7ab8283571978ef99afd537e1485bb48a3e5341de9bbedb9ca5c57d" dependencies = [ "url", ] @@ -3702,9 +3487,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ "js-sys", "serde_core", @@ -3750,24 +3535,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -3779,9 +3558,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -3789,9 +3568,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3799,31 +3578,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -3841,40 +3620,27 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "0.26.11" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.7", -] - -[[package]] -name = "webpki-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "winapi" @@ -3928,7 +3694,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3939,7 +3705,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3966,31 +3732,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -4002,197 +3750,75 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] @@ -4205,18 +3831,9 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "yansi" @@ -4243,28 +3860,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -4284,21 +3901,21 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -4307,9 +3924,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -4318,17 +3935,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 89fb551..79914af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,74 +1,570 @@ [workspace] -resolver = "2" +resolver = "3" members = ["crates/*"] [workspace.package] -license-file = "LICENSE.md" - edition = "2024" -rust-version = "1.89.0" +rust-version = "1.94" +license-file = "LICENSE.md" +publish = false [workspace.dependencies] -flix = { path = "crates/flix", version = "=0.0.20", default-features = false } -flix-cli = { path = "crates/cli", version = "=0.0.20", default-features = false } -flix-db = { path = "crates/db", version = "=0.0.20", default-features = false } -flix-fs = { path = "crates/fs", version = "=0.0.20", default-features = false } -flix-model = { path = "crates/model", version = "=0.0.20", default-features = false } -flix-mux = { path = "crates/cli-mux", version = "=0.0.20", default-features = false } -flix-tmdb = { path = "crates/tmdb", version = "=0.0.20", default-features = false } +flix = { path = "crates/flix", version = "=0.1.0", default-features = false } +flix-cli = { path = "crates/cli", version = "=0.1.0", default-features = false } +flix-db = { path = "crates/db", version = "=0.1.0", default-features = false } +flix-fs = { path = "crates/fs", version = "=0.1.0", default-features = false } +flix-model = { path = "crates/model", version = "=0.1.0", default-features = false } +flix-mux = { path = "crates/cli-mux", version = "=0.1.0", default-features = false } +flix-tmdb = { path = "crates/tmdb", version = "=0.1.0", default-features = false } -seamantic = { version = "^0.0.14", default-features = false } - -sea-orm = { version = "=2.0.0-rc.38", default-features = false } -sea-orm-migration = { version = "=2.0.0-rc.38", default-features = false } - -anyhow = { version = "^1", default-features = false } -async-stream = { version = "^0.3", default-features = false } -bytes = { version = "^1", default-features = false } -chrono = { version = "^0.4", default-features = false } -clap = { version = "^4", default-features = false } -console = { version = "^0.16", default-features = false } -dialoguer = { version = "^0.12", default-features = false } -either = { version = "^1", default-features = false } -futures = { version = "^0.3", default-features = false } -governor = { version = "^0.10", default-features = false } -indicatif = { version = "^0.18", default-features = false } -itertools = { version = "^0.14", default-features = false } -nonzero_ext = { version = "^0.3", default-features = false } -redb = { version = "^4", default-features = false } -regex = { version = "^1", default-features = false } -reqwest = { version = "^0.13", default-features = false } -serde = { version = "^1", default-features = false } -serde_json = { version = "^1", default-features = false } -serde_test = { version = "^1", default-features = false } -thiserror = { version = "^2", default-features = false } -tokio = { version = "^1", default-features = false } -tokio-stream = { version = "^0.1", default-features = false } -toml = { version = "^1", default-features = false } -tracing = { version = "^0.1", default-features = false } -tracing-subscriber = { version = "^0.3", default-features = false } -url = { version = "^2", default-features = false } -url-macro = { version = "^0.2", default-features = false } -walkdir = { version = "^2", default-features = false } +anyhow = { version = "^1.0.87", default-features = false } +async-stream = { version = "^0.3.5", default-features = false } +bytes = { version = "^1.7.1", default-features = false } +chrono = { version = "^0.4.40", default-features = false } +clap = { version = "^4.3.2", default-features = false } +console = { version = "^0.16.0", default-features = false } +either = { version = "^1.6.1", default-features = false } +governor = { version = "^0.10.0", default-features = false } +indicatif = { version = "^0.18.0", default-features = false } +itertools = { version = "^0.15.0", default-features = false } +nonzero_ext = { version = "^0.3.0", default-features = false } +redb = { version = "^4.0.0", default-features = false } +regex = { version = "^1.11.2", default-features = false } +reqwest = { version = "^0.13.0", default-features = false } +sea-orm = { version = "^2.0.1", default-features = false } +sea-orm-migration = { version = "^2.0.0", default-features = false } +seamantic = { version = "^1.0.1", default-features = false } +serde = { version = "^1.0.225", default-features = false } +serde_json = { version = "^1.0.142", default-features = false } +serde_test = { version = "^1.0.0", default-features = false } +tempfile = { version = "^3.0.0", default-features = false } +thiserror = { version = "^2.0.18", default-features = false } +tokio = { version = "^1.48.0", default-features = false } +tokio-stream = { version = "^0.1.8", default-features = false } +toml = { version = "^1.0.0", default-features = false } +tracing = { version = "^0.1.37", default-features = false } +tracing-subscriber = { version = "^0.3.17", default-features = false } +url = { version = "^2.4.0", default-features = false } +url-macro = { version = "^0.2.0", default-features = false } +walkdir = { version = "^2.0.0", default-features = false } [workspace.lints.clippy] -arithmetic_side_effects = "forbid" -as_conversions = "forbid" -checked_conversions = "forbid" -default_union_representation = "forbid" -expect_used = "forbid" -indexing_slicing = "forbid" -integer_division = "forbid" -integer_division_remainder_used = "forbid" -transmute_undefined_repr = "forbid" -unchecked_time_subtraction = "forbid" -unwrap_used = "forbid" +cargo = { level = "warn", priority = -1 } +complexity = { level = "warn", priority = -1 } +correctness = { level = "deny", priority = -1 } +perf = { level = "warn", priority = -1 } +style = { level = "warn", priority = -1 } +suspicious = { level = "warn", priority = -1 } + +absolute-paths = "allow" +alloc-instead-of-core = "warn" +allow-attributes = "warn" +allow-attributes-without-reason = "deny" +arbitrary-source-item-ordering = "allow" +arithmetic-side-effects = "deny" +as-conversions = "deny" +as-pointer-underscore = "deny" +as-ptr-cast-mut = "deny" +as-underscore = "deny" +assertions-on-result-states = "warn" +assigning-clones = "warn" +big-endian-bytes = "allow" +bool-to-int-with-if = "warn" +borrow-as-ptr = "warn" +branches-sharing-code = "warn" +case-sensitive-file-extension-comparisons = "warn" +cast-lossless = "deny" +cast-possible-truncation = "deny" +cast-possible-wrap = "deny" +cast-precision-loss = "deny" +cast-ptr-alignment = "deny" +cast-sign-loss = "deny" +cfg-not-test = "deny" +checked-conversions = "deny" +clear-with-drain = "warn" +clone-on-ref-ptr = "warn" +cloned-instead-of-copied = "warn" +coerce-container-to-any = "warn" +cognitive-complexity = "allow" +collapsible-else-if = "warn" +collection-is-never-read = "warn" +comparison-chain = "warn" +copy-iterator = "warn" +create-dir = "warn" +dbg-macro = "warn" +debug-assert-with-mut-call = "deny" +decimal-bitwise-operands = "warn" +decimal-literal-representation = "warn" +default-numeric-fallback = "warn" +default-trait-access = "warn" +default-union-representation = "allow" +deref-by-slicing = "warn" +derive-partial-eq-without-eq = "warn" +disallowed-script-idents = "deny" +doc-broken-link = "warn" +doc-comment-double-space-linebreaks = "warn" +doc-include-without-cfg = "warn" +doc-link-code = "warn" +doc-link-with-quotes = "warn" +doc-markdown = "warn" +doc-paragraphs-missing-punctuation = "warn" +duration-suboptimal-units = "warn" +elidable-lifetime-names = "warn" +else-if-without-else = "warn" +empty-drop = "warn" +empty-enum-variants-with-brackets = "warn" +empty-enums = "warn" +empty-structs-with-brackets = "warn" +enum-glob-use = "warn" +equatable-if-let = "warn" +error-impl-error = "warn" +exhaustive-enums = "warn" +exhaustive-structs = "warn" +exit = "warn" +expect-used = "warn" +expl-impl-clone-on-copy = "warn" +explicit-deref-methods = "warn" +explicit-into-iter-loop = "warn" +explicit-iter-loop = "warn" +fallible-impl-from = "warn" +field-scoped-visibility-modifiers = "warn" +filetype-is-file = "warn" +filter-map-next = "warn" +flat-map-option = "warn" +float-arithmetic = "warn" +float-cmp = "warn" +float-cmp-const = "warn" +fn-params-excessive-bools = "allow" +fn-to-numeric-cast-any = "warn" +format-collect = "warn" +format-push-string = "warn" +future-not-send = "warn" +get-unwrap = "warn" +host-endian-bytes = "allow" +if-not-else = "warn" +if-then-some-else-none = "warn" +ignore-without-reason = "warn" +ignored-unit-patterns = "warn" +impl-trait-in-params = "warn" +implicit-clone = "warn" +implicit-hasher = "warn" +implicit-return = "allow" +imprecise-flops = "warn" +inconsistent-struct-constructor = "warn" +index-refutable-slice = "deny" +indexing-slicing = "deny" +inefficient-to-string = "warn" +infinite-loop = "warn" +inline-always = "warn" +inline-asm-x86-att-syntax = "warn" +inline-asm-x86-intel-syntax = "warn" +inline-modules = "allow" +inline-trait-bounds = "allow" +integer-division = "deny" +integer-division-remainder-used = "deny" +into-iter-without-iter = "warn" +invalid-upcast-comparisons = "warn" +ip-constant = "warn" +items-after-statements = "warn" +iter-filter-is-ok = "warn" +iter-filter-is-some = "warn" +iter-not-returning-iterator = "warn" +iter-on-empty-collections = "warn" +iter-on-single-items = "warn" +iter-over-hash-type = "warn" +iter-with-drain = "warn" +iter-without-into-iter = "warn" +large-digit-groups = "warn" +large-futures = "warn" +large-include-file = "warn" +large-stack-arrays = "warn" +large-stack-frames = "warn" +large-types-passed-by-value = "warn" +let-underscore-must-use = "warn" +let-underscore-untyped = "warn" +linkedlist = "warn" +literal-string-with-formatting-args = "warn" +little-endian-bytes = "allow" +lossy-float-literal = "warn" +macro-use-imports = "warn" +manual-assert = "warn" +manual-assert-eq = "warn" +manual-ilog2 = "warn" +manual-instant-elapsed = "warn" +manual-is-power-of-two = "warn" +manual-is-variant-and = "warn" +manual-let-else = "warn" +manual-midpoint = "warn" +manual-string-new = "warn" +many-single-char-names = "warn" +map-err-ignore = "warn" +map-unwrap-or = "warn" +map-with-unused-argument-over-ranges = "warn" +match-bool = "warn" +match-same-arms = "warn" +match-wild-err-arm = "warn" +match-wildcard-for-single-variants = "warn" +maybe-infinite-iter = "warn" +mem-forget = "warn" +min-ident-chars = "allow" +mismatching-type-param-order = "warn" +missing-assert-message = "warn" +missing-asserts-for-indexing = "warn" +missing-const-for-fn = "warn" +missing-docs-in-private-items = "warn" +missing-errors-doc = "warn" +missing-fields-in-debug = "warn" +missing-inline-in-public-items = "warn" +missing-panics-doc = "warn" +missing-trait-methods = "warn" +mixed-read-write-in-expression = "warn" +mod-module-files = "allow" +module-name-repetitions = "warn" +modulo-arithmetic = "warn" +multiple-crate-versions = "allow" +multiple-inherent-impl = "warn" +multiple-unsafe-ops-per-block = "warn" +must-use-candidate = "warn" +mut-mut = "warn" +mutex-atomic = "warn" +mutex-integer = "warn" +naive-bytecount = "warn" +needless-bitwise-bool = "warn" +needless-collect = "warn" +needless-continue = "warn" +needless-for-each = "warn" +needless-pass-by-ref-mut = "warn" +needless-pass-by-value = "warn" +needless-raw-string-hashes = "warn" +needless-raw-strings = "warn" +needless-type-cast = "warn" +no-effect-underscore-binding = "warn" +no-mangle-with-rust-abi = "warn" +non-ascii-literal = "allow" +non-send-fields-in-send-ty = "warn" +non-std-lazy-statics = "warn" +non-zero-suggestions = "warn" +nonminimal-bool = "warn" +nonstandard-macro-braces = "warn" +option-as-ref-cloned = "warn" +option-if-let-else = "warn" +option-option = "warn" +or-fun-call = "warn" +overly-complex-bool-expr = "allow" +panic = "warn" +panic-in-result-fn = "warn" +partial-pub-fields = "warn" +path-buf-push-overwrite = "warn" +pathbuf-init-then-push = "warn" +pattern-type-mismatch = "allow" +pointer-format = "warn" +precedence-bits = "warn" +print-stderr = "warn" +print-stdout = "warn" +ptr-as-ptr = "warn" +ptr-cast-constness = "warn" +ptr-offset-by-literal = "warn" +pub-underscore-fields = "warn" +pub-use = "allow" +pub-with-shorthand = "allow" +pub-without-shorthand = "warn" +question-mark-used = "allow" +range-minus-one = "warn" +range-plus-one = "warn" +rc-buffer = "warn" +rc-mutex = "warn" +read-zero-byte-vec = "warn" +redundant-clone = "warn" +redundant-closure-for-method-calls = "warn" +redundant-else = "warn" +redundant-pub-crate = "allow" +redundant-test-prefix = "warn" +redundant-type-annotations = "warn" +ref-as-ptr = "warn" +ref-binding-to-reference = "warn" +ref-option = "warn" +ref-option-ref = "warn" +ref-patterns = "warn" +renamed-function-params = "warn" +rest-pat-in-fully-bound-structs = "warn" +return-and-then = "warn" +return-self-not-must-use = "warn" +same-functions-in-if-condition = "warn" +same-length-and-capacity = "warn" +same-name-method = "warn" +search-is-some = "warn" +self-named-module-files = "warn" +self-only-used-in-recursion = "warn" +semicolon-if-nothing-returned = "warn" +semicolon-inside-block = "warn" +semicolon-outside-block = "allow" +separated-literal-suffix = "allow" +set-contains-or-insert = "warn" +shadow-reuse = "allow" +shadow-same = "allow" +shadow-unrelated = "allow" +should-panic-without-expect = "warn" +significant-drop-in-scrutinee = "warn" +significant-drop-tightening = "warn" +similar-names = "allow" +single-call-fn = "allow" +single-char-lifetime-names = "allow" +single-char-pattern = "allow" +single-match-else = "warn" +single-option-map = "warn" +stable-sort-primitive = "warn" +std-instead-of-alloc = "warn" +std-instead-of-core = "warn" +str-split-at-newline = "warn" +str-to-string = "warn" +string-add = "warn" +string-add-assign = "warn" +string-lit-as-bytes = "warn" +string-lit-chars-any = "warn" +string-slice = "warn" +struct-excessive-bools = "allow" +struct-field-names = "warn" +suboptimal-flops = "warn" +suspicious-operation-groupings = "warn" +suspicious-xor-used-as-pow = "warn" +tests-outside-test-module = "warn" +todo = "warn" +too-long-first-doc-paragraph = "allow" +too-many-lines = "allow" +trailing-empty-array = "warn" +trait-duplication-in-bounds = "warn" +transmute-ptr-to-ptr = "warn" +transmute-undefined-repr = "warn" +trivial-regex = "warn" +trivially-copy-pass-by-ref = "warn" +try-err = "warn" +tuple-array-conversions = "warn" +type-repetition-in-bounds = "warn" +unchecked-time-subtraction = "deny" +undocumented-unsafe-blocks = "deny" +unicode-not-nfc = "warn" +unimplemented = "warn" +uninhabited-references = "warn" +uninlined-format-args = "warn" +unnecessary-box-returns = "warn" +unnecessary-debug-formatting = "warn" +unnecessary-join = "warn" +unnecessary-literal-bound = "warn" +unnecessary-safety-comment = "warn" +unnecessary-safety-doc = "warn" +unnecessary-self-imports = "warn" +unnecessary-semicolon = "warn" +unnecessary-struct-initialization = "warn" +unnecessary-trailing-comma = "warn" +unnecessary-wraps = "warn" +unneeded-field-pattern = "warn" +unnested-or-patterns = "warn" +unreachable = "warn" +unreadable-literal = "warn" +unsafe-derive-deserialize = "warn" +unseparated-literal-suffix = "warn" +unused-async = "warn" +unused-async-trait-impl = "warn" +unused-peekable = "warn" +unused-result-ok = "warn" +unused-rounding = "warn" +unused-self = "warn" +unused-trait-names = "warn" +unwrap-in-result = "warn" +unwrap-used = "warn" +use-debug = "warn" +use-self = "warn" +used-underscore-binding = "warn" +used-underscore-items = "warn" +useless-let-if-seq = "warn" +verbose-bit-mask = "warn" +verbose-file-reads = "warn" +volatile-composites = "warn" +while-float = "warn" +wildcard-enum-match-arm = "warn" +wildcard-imports = "warn" +with-capacity-zero = "warn" +zero-sized-map-values = "warn" [workspace.lints.rust] -arithmetic_overflow = "forbid" -missing_docs = "forbid" -unsafe_code = "forbid" -unused_doc_comments = "forbid" +deprecated-safe = "forbid" +future-incompatible = "deny" +keyword-idents = "deny" +let-underscore = "warn" +nonstandard-style = "warn" +refining-impl-trait = "warn" +rust-2018-compatibility = "deny" +rust-2018-idioms = "deny" +rust-2021-compatibility = "deny" +rust-2024-compatibility = "deny" +unknown-or-malformed-diagnostic-attributes = "deny" +unused = { level = "warn", priority = -1 } + +ambiguous-glob-reexports = "warn" +ambiguous-negative-literals = "deny" +ambiguous-wide-pointer-comparisons = "warn" +arithmetic-overflow = "deny" +asm-sub-register = "warn" +async-fn-in-trait = "warn" +bad-asm-style = "warn" +binary-asm-labels = "deny" +bindings-with-variant-name = "deny" +break-with-label-and-loop = "warn" +c-void-returns = "warn" +clashing-extern-declarations = "warn" +closure-returning-async-block = "warn" +confusable-idents = "warn" +const-item-interior-mutations = "warn" +const-item-mutation = "warn" +dangerous-implicit-autorefs = "deny" +dangling-pointers-from-locals = "warn" +dangling-pointers-from-temporaries = "warn" +dead-code-pub-in-binary = "warn" +deprecated = "warn" +deprecated-in-future = "allow" +deprecated-where-clause-location = "warn" +deref-into-dyn-supertrait = "warn" +deref-nullptr = "deny" +double-negations = "warn" +drop-bounds = "warn" +dropping-copy-types = "warn" +dropping-references = "warn" +duplicate-features = "deny" +duplicate-macro-attributes = "warn" +dyn-drop = "warn" +enum-intrinsics-non-enums = "deny" +explicit-builtin-cfgs-in-flags = "deny" +exported-private-dependencies = "warn" +ffi-unwind-calls = "warn" +for-loops-over-fallibles = "warn" +forgetting-copy-types = "warn" +forgetting-references = "warn" +function-casts-as-integer = "warn" +function-item-references = "warn" +hidden-glob-reexports = "warn" +impl-trait-redundant-captures = "warn" +improper-ctypes = "warn" +improper-ctypes-definitions = "warn" +improper-gpu-kernel-arg = "warn" +incomplete-features = "warn" +incomplete-include = "deny" +ineffective-unstable-trait-impl = "deny" +inline-no-sanitize = "warn" +integer-to-ptr-transmutes = "warn" +internal-features = "warn" +invalid-atomic-ordering = "deny" +invalid-doc-attributes = "warn" +invalid-from-utf8 = "warn" +invalid-from-utf8-unchecked = "deny" +invalid-nan-comparisons = "warn" +invalid-null-arguments = "deny" +invalid-reference-casting = "deny" +invalid-runtime-symbol-definitions = "deny" +invalid-value = "warn" +irrefutable-let-patterns = "warn" +large-assignments = "warn" +linker-info = "allow" +linker-messages = "warn" +long-running-const-eval = "deny" +macro-use-extern-crate = "warn" +meta-variable-misuse = "warn" +mismatched-lifetime-syntaxes = "warn" +missing-abi = "warn" +missing-copy-implementations = "warn" +missing-debug-implementations = "warn" +missing-docs = "warn" +missing-gpu-kernel-export-name = "warn" +mixed-script-confusables = "warn" +mutable-transmutes = "deny" +named-arguments-used-positionally = "warn" +named-asm-labels = "deny" +no-mangle-const-items = "deny" +no-mangle-generic-items = "warn" +non-ascii-idents = "deny" +non-contiguous-range-endpoints = "warn" +non-local-definitions = "warn" +non-shorthand-field-patterns = "warn" +noop-method-call = "warn" +opaque-hidden-inferred-bound = "warn" +overflowing-literals = "deny" +overlapping-range-endpoints = "warn" +private-bounds = "warn" +private-interfaces = "warn" +ptr-to-integer-transmute-in-consts = "warn" +redundant-imports = "warn" +redundant-lifetimes = "warn" +renamed-and-removed-lints = "warn" +rtsan-nonblocking-async = "warn" +single-use-lifetimes = "warn" +special-module-name = "warn" +stable-features = "warn" +suspicious-double-ref-op = "warn" +suspicious-runtime-symbol-definitions = "warn" +text-direction-codepoint-in-comment = "deny" +text-direction-codepoint-in-literal = "deny" +trivial-bounds = "warn" +trivial-casts = "warn" +trivial-numeric-casts = "warn" +type-alias-bounds = "warn" +uncommon-codepoints = "warn" +unconditional-panic = "deny" +unconditional-recursion = "warn" +undropped-manually-drops = "deny" +unexpected-cfgs = "warn" +unfulfilled-lint-expectations = "warn" +ungated-async-fn-track-caller = "warn" +unit-bindings = "warn" +unknown-crate-types = "deny" +unknown-lints = "warn" +unnameable-test-items = "warn" +unnameable-types = "warn" +unnecessary-transmutes = "warn" +unpredictable-function-pointer-comparisons = "warn" +unreachable-cfg-select-predicates = "warn" +unreachable-pub = "warn" +unsafe-code = "forbid" +unstable-features = "forbid" +unused-associated-type-bounds = "warn" +unused-comparisons = "warn" +unused-crate-dependencies = "warn" +unused-import-braces = "warn" +unused-lifetimes = "warn" +unused-qualifications = "warn" +unused-results = "warn" +useless-deprecated = "deny" +useless-ptr-null-checks = "warn" +uses-power-alignment = "warn" +variant-size-differences = "warn" +warnings = "warn" +while-true = "warn" + +# unstable lints +# default-overrides-default-fields = "deny" +# deprecated-llvm-intrinsic = "warn" +# implicit-provenance-casts = "deny" +# multiple-supertrait-upcastable = "warn" +# must-not-suspend = "warn" +# non-exhaustive-omitted-patterns = "warn" +# resolving-to-items-shadowing-supertrait-items = "warn" +# shadowing-supertrait-items = "warn" +# tail-call-track-caller = "warn" +# test-unstable-lint = "deny" +# unqualified-local-imports = "deny" + +[workspace.lints.rustdoc] +bare-urls = "warn" +broken-intra-doc-links = "warn" +invalid-codeblock-attributes = "warn" +invalid-html-tags = "warn" +invalid-rust-codeblocks = "warn" +missing-crate-level-docs = "warn" +private-doc-tests = "warn" +private-intra-doc-links = "warn" +redundant-explicit-links = "warn" +unescaped-backticks = "warn" + +# unstable lints +# missing-doc-code-examples = "warn" [profile.release] opt-level = 3 diff --git a/README.md b/README.md index 80cb862..15958aa 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,20 @@ Libraries and tools for dealing with media metadata -## Various builds +## Development -- build: `cargo hack --feature-powerset build` -- clippy: `cargo hack --feature-powerset clippy -- -D warnings` -- test: `cargo hack --feature-powerset test` -- test old: `cargo +1.89 hack --feature-powerset test` -- fmt: `cargo fmt --check` -- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` -- install: `cargo install --path crates/cli` -- install: `cargo install --path crates/cli-mux` -- semver: `cargo semver-checks --all-features` -- publish: `cargo publish --dry-run --workspace` +Run the full validation suite before committing: + +```sh +just all +just install +``` + +##### Publish + +- `cargo semver-checks --all-features` +- `cargo publish --dry-run -p ` +- `cargo publish --dry-run --workspace` ## Building flix.db diff --git a/crates/cli-mux/Cargo.toml b/crates/cli-mux/Cargo.toml index 9e9f135..c516fae 100644 --- a/crates/cli-mux/Cargo.toml +++ b/crates/cli-mux/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "flix-mux" -version = "0.0.20" -license-file.workspace = true - +version = "0.1.0" description = "CLI for bulk media muxing" -repository = "https://github.com/QuantumShade/flix" -categories = ["command-line-utilities"] +repository = "https://git.skrundz.dev/quantumshade/flix" +keywords = ["flix"] +categories = ["command-line-utilities", "multimedia"] +include = ["/src"] +publish = true edition.workspace = true rust-version.workspace = true +license-file.workspace = true [package.metadata.docs.rs] all-features = true @@ -31,27 +33,10 @@ clap = { workspace = true, features = [ "usage", ] } console = { workspace = true } -dialoguer = { workspace = true } indicatif = { workspace = true } serde = { workspace = true, features = ["derive", "std"] } serde_json = { workspace = true, features = ["alloc"] } walkdir = { workspace = true } -[lints.clippy] -arithmetic_side_effects = "deny" -as_conversions = "deny" -checked_conversions = "deny" -default_union_representation = "deny" -expect_used = "deny" -indexing_slicing = "deny" -integer_division = "deny" -integer_division_remainder_used = "deny" -transmute_undefined_repr = "deny" -unchecked_time_subtraction = "deny" -unwrap_used = "deny" - -[lints.rust] -arithmetic_overflow = "forbid" -missing_docs = "forbid" -unsafe_code = "forbid" -unused_doc_comments = "forbid" +[lints] +workspace = true diff --git a/crates/cli-mux/src/cli.rs b/crates/cli-mux/src/cli.rs index 835f64b..5075d4e 100644 --- a/crates/cli-mux/src/cli.rs +++ b/crates/cli-mux/src/cli.rs @@ -1,17 +1,18 @@ -use std::path::PathBuf; +//! Command line argument parsing. -use clap::Parser; +use std::path::PathBuf; use crate::parser::Selector; -#[derive(Parser)] +/// Command line argument parser struct. +#[derive(Debug, clap::Parser)] #[command(version, about, long_about = None)] -pub struct Cli { - /// Dry run and print commands +pub(crate) struct Args { + /// Dry run and print commands. #[arg(short, long)] dry_run: bool, - /// Stream selectors + /// Stream selectors. #[arg( short, long, @@ -21,29 +22,38 @@ pub struct Cli { )] selectors: Vec, - /// The path to the directory to scan + /// The path to the directory to scan. #[arg(value_name = "DIR")] scan_dir: PathBuf, } -impl Cli { - pub fn is_dry(&self) -> bool { +impl Args { + /// True if the user requested a dry run. + pub(crate) const fn is_dry_run(&self) -> bool { self.dry_run } - pub fn selectors(&self) -> &[Selector] { + /// Returns a slice of provided [Selector]. + pub(crate) fn selectors(&self) -> &[Selector] { &self.selectors } - pub fn scan_dir_path(&self) -> PathBuf { - fn expect_home_dir() -> PathBuf { - #[allow(clippy::expect_used)] - std::env::home_dir().expect("you do not have a home directory") - } - - match self.scan_dir.strip_prefix("~/") { - Ok(path) => expect_home_dir().join(path), - Err(_) => self.scan_dir.to_owned(), - } + /// Get the path to scan. + /// + /// # Panics + /// If the user doesn't have a home directory. + pub(crate) fn scan_dir_path(&self) -> PathBuf { + self.scan_dir.strip_prefix("~/").map_or_else( + |_| self.scan_dir.clone(), + |path| { + #[expect( + clippy::expect_used, + reason = "without a home directory this program is useless" + )] + std::env::home_dir() + .expect("you do not have a home directory") + .join(path) + }, + ) } } diff --git a/crates/cli-mux/src/main.rs b/crates/cli-mux/src/main.rs index db13a88..69b4b3d 100644 --- a/crates/cli-mux/src/main.rs +++ b/crates/cli-mux/src/main.rs @@ -1,8 +1,8 @@ -//! flix-mux +//! flix-mux. use core::time::Duration; -use clap::Parser; +use clap::Parser as _; use console::style; use indicatif::{HumanBytes, ProgressBar, ProgressStyle}; @@ -16,14 +16,17 @@ mod parser; mod probe; mod scan; +#[cfg_attr(test, expect(clippy::missing_panics_doc, reason = "main function"))] +#[expect(clippy::print_stderr, reason = "we want to print in a binary")] +#[expect(clippy::print_stdout, reason = "we want to print in a binary")] fn main() { - let cli = cli::Cli::parse(); + let cli = cli::Args::parse(); let (files, size) = scan_directory( &cli.scan_dir_path(), || { let progress = ProgressBar::new_spinner(); - progress.set_message(format!("Scanning {:?}", &cli.scan_dir_path())); + progress.set_message(format!("Scanning {}", cli.scan_dir_path().display())); progress.enable_steady_tick(Duration::from_millis(50)); progress }, @@ -31,7 +34,8 @@ fn main() { |len| { let progress = ProgressBar::new(u64::try_from(len).unwrap_or(0)); progress.set_style( - #[expect(clippy::expect_used)] + #[expect(clippy::expect_used, reason = "panic if the template is invalid")] + #[expect(clippy::literal_string_with_formatting_args, reason = "template")] ProgressStyle::with_template("[{elapsed_precise}] {wide_bar} {pos}/{len} ({msg})") .expect("static template"), ); @@ -40,19 +44,20 @@ fn main() { |progress| progress.inc(1), |progress| progress.finish_and_clear(), |progress, msg| { - progress.suspend(|| eprintln!("{} {}", style("[WARN]").bold().yellow(), msg)) + progress.suspend(|| eprintln!("{} {msg}", style("[WARN]").bold().yellow())); }, ); println!("Found {} files ({})", files.len(), HumanBytes(size)); mux_files( - cli.is_dry(), + cli.is_dry_run(), &files, cli.selectors(), |len| { let progress = ProgressBar::new(u64::try_from(len).unwrap_or(0)); progress.set_style( - #[expect(clippy::expect_used)] + #[expect(clippy::expect_used, reason = "panic if the template is invalid")] + #[expect(clippy::literal_string_with_formatting_args, reason = "template")] ProgressStyle::with_template("[{elapsed_precise}] {wide_bar} {pos}/{len} ({msg})") .expect("static template"), ); @@ -61,6 +66,6 @@ fn main() { }, |progress| progress.inc(1), |progress| progress.finish_with_message("done"), - |progress, msg| progress.suspend(|| eprintln!("{} {}", style("[ERR]").bold().red(), msg)), + |progress, msg| progress.suspend(|| eprintln!("{} {msg}", style("[ERR]").bold().red())), ); } diff --git a/crates/cli-mux/src/model.rs b/crates/cli-mux/src/model.rs index ffd5756..2a17df4 100644 --- a/crates/cli-mux/src/model.rs +++ b/crates/cli-mux/src/model.rs @@ -1,42 +1,61 @@ +//! Model for handling media streams. + use std::path::PathBuf; use serde::Deserialize; +/// Represents a file that was scanned. #[derive(Debug, Clone)] -pub struct MediaFile { +pub(crate) struct MediaFile { + /// The path to the file. pub path: PathBuf, + /// The size of the file. pub byte_size: u64, + /// The streams in the file. pub streams: Streams, } +/// Represents the collection of streams found in a media file. #[derive(Debug, Clone)] -pub struct Streams { +pub(crate) struct Streams { + /// The video streams. pub video: Vec, + /// The audio streams. pub audio: Vec, + /// The subtitle streams. pub subtitle: Vec, } -pub trait FFStream: serde::de::DeserializeOwned { +/// This trait helps with deserializing `ffmpeg` output. +pub(crate) trait FFStream: serde::de::DeserializeOwned { + /// The type name that `ffmpeg` uses. const FF_TYPE_NAME: &str; } +/// Represents an individual video stream in a media file. #[derive(Debug, Clone, Deserialize)] -pub struct VideoStream { +pub(crate) struct VideoStream { + /// The name of the video codec. codec_name: String, + /// Additional tags on the stream. tags: Option, } +/// Represents tags on a video stream. #[derive(Debug, Clone, Deserialize)] -pub struct VideoTags { +pub(crate) struct VideoTags { + /// The language of the stream. language: Option, } impl VideoStream { - pub fn codec(&self) -> &str { + /// Get the video codec name. + pub(crate) fn codec(&self) -> &str { &self.codec_name } - pub fn language(&self) -> Option<&str> { + /// Get the stream language. + pub(crate) fn language(&self) -> Option<&str> { self.tags.as_ref()?.language.as_deref() } } @@ -45,23 +64,30 @@ impl FFStream for VideoStream { const FF_TYPE_NAME: &str = "v"; } +/// Represents an individual audio stream in a media file. #[derive(Debug, Clone, Deserialize)] -pub struct AudioStream { +pub(crate) struct AudioStream { + /// The name of the audio codec. codec_name: String, + /// Additional tags on the stream. tags: Option, } +/// Represents tags on an audio stream. #[derive(Debug, Clone, Deserialize)] -pub struct AudioTags { +pub(crate) struct AudioTags { + /// The language of the stream. language: Option, } impl AudioStream { - pub fn codec(&self) -> &str { + /// Get the audio codec name. + pub(crate) fn codec(&self) -> &str { &self.codec_name } - pub fn language(&self) -> Option<&str> { + /// Get the stream language. + pub(crate) fn language(&self) -> Option<&str> { self.tags.as_ref()?.language.as_deref() } } @@ -70,28 +96,37 @@ impl FFStream for AudioStream { const FF_TYPE_NAME: &str = "a"; } +/// Represents an individual subtitle stream in a media file. #[derive(Debug, Clone, Deserialize)] -pub struct SubtitleStream { +pub(crate) struct SubtitleStream { + /// The name of the subtitle codec. codec_name: String, + /// Additional tags on the stream. tags: Option, } +/// Represents tags on a subtitle stream. #[derive(Debug, Clone, Deserialize)] -pub struct SubtitleTags { +pub(crate) struct SubtitleTags { + /// The language of the stream. language: Option, + /// The title of the stream. title: Option, } impl SubtitleStream { - pub fn codec(&self) -> &str { + /// Get the subtitle codec name. + pub(crate) fn codec(&self) -> &str { &self.codec_name } - pub fn language(&self) -> Option<&str> { + /// Get the stream language. + pub(crate) fn language(&self) -> Option<&str> { self.tags.as_ref()?.language.as_deref() } - pub fn title(&self) -> Option<&str> { + /// Get the stream title. + pub(crate) fn title(&self) -> Option<&str> { self.tags.as_ref()?.title.as_deref() } } diff --git a/crates/cli-mux/src/mux.rs b/crates/cli-mux/src/mux.rs index 44a8995..dcc5ef8 100644 --- a/crates/cli-mux/src/mux.rs +++ b/crates/cli-mux/src/mux.rs @@ -1,3 +1,5 @@ +//! Wrapper around `ffmpeg` to mux media files. + use std::process::Command; use anyhow::{Context as _, Result}; @@ -5,14 +7,19 @@ use anyhow::{Context as _, Result}; use crate::model::MediaFile; use crate::parser::{Matcher, Selector, StreamFlag, StreamType}; +/// Helper struct for tracking which output index is currently available. #[derive(Default)] struct OutputIndex { + /// The next free video index. video: usize, + /// The next free audio index. audio: usize, + /// The next free subtitle index. subtitle: usize, } -pub fn mux_files( +/// For each input file, apply all selectors and mux the selection. +pub(crate) fn mux_files( dry_run: bool, files: &[MediaFile], selectors: &[Selector], @@ -24,14 +31,20 @@ pub fn mux_files( let mut progress = fixed_length_start(files.len()); for file in files { if let Err(err) = mux(dry_run, file, selectors) { - print_fn(&progress, &format!("{:?}", err)); + print_fn(&progress, &format!("{err:?}")); } fixed_length_update(&mut progress); } fixed_length_end(progress); } -#[expect(clippy::expect_used)] +/// Apply all selectors and mux the file. +/// +/// # Errors +/// If the call to `ffmpeg` fails. +/// +/// # Panics +/// If the system fails to allocate a temporary file. fn mux(dry_run: bool, file: &MediaFile, selectors: &[Selector]) -> Result<()> { let mut command = Command::new("ffmpeg"); let mut command = command.args(["-v", "error"]); @@ -40,10 +53,14 @@ fn mux(dry_run: bool, file: &MediaFile, selectors: &[Selector]) -> Result<()> { command = command.arg(file.path.as_os_str()); for selector in selectors { - command = command.args( - make_map_args(file, selector) - .with_context(|| format!("Failed to mux {:?}", file.path))?, - ); + command = command.args(make_map_args(file, selector).with_context(|| { + let info = if dry_run { + format!("Streams:\n{file:#?}") + } else { + String::from("use -d for stream info") + }; + format!("Failed to mux {}\n{info}", file.path.display()) + })?); } command = command.args(["-c:v", "copy", "-c:a", "copy", "-c:s", "mov_text"]); @@ -56,25 +73,34 @@ fn mux(dry_run: bool, file: &MediaFile, selectors: &[Selector]) -> Result<()> { let mut ouput_index = OutputIndex::default(); for selector in selectors { command = command.args( - make_metadata_args(file, selector, &mut ouput_index) - .with_context(|| format!("Failed to mux {:?}", file.path))?, + make_metadata_args(file, selector, &mut ouput_index).with_context(|| { + let info = if dry_run { + format!("Streams:\n{file:#?}") + } else { + String::from("use -d for stream info") + }; + format!("Failed to mux {}\n{info}", file.path.display()) + })?, ); } let temp_path = file.path.with_extension("mp4"); - command = command.arg(temp_path.file_name().expect("file name exists")); + #[expect(clippy::expect_used, reason = "files must have names")] + { + command = command.arg(temp_path.file_name().expect("file name exists")); + } if dry_run { print_command(command); } else { let output = command .output() - .with_context(|| format!("Failed to mux {:?}", file.path))?; + .with_context(|| format!("Failed to mux {}", file.path.display()))?; if !output.status.success() { anyhow::bail!( - "ffmpeg failed for {:?}:\n\n{}", - file.path, + "ffmpeg failed for {}:\n\n{}", + file.path.display(), String::from_utf8_lossy(&output.stderr) ); } @@ -83,23 +109,31 @@ fn mux(dry_run: bool, file: &MediaFile, selectors: &[Selector]) -> Result<()> { Ok(()) } +/// Generate the `-map` argument list for `ffmpeg`. +/// +/// # Errors +/// If the stream selection is unsatisfyable for the file. fn make_map_args(file: &MediaFile, selector: &Selector) -> Result> { - let source_index = 0; + let source_index: u32 = 0; let stream_type = selector.stream_type.as_ref(); let Some(stream_index) = find_stream_index(file, selector) else { if selector.optional { return Ok(vec![]); - } else { - anyhow::bail!("unsatisfied stream selection"); } + + anyhow::bail!("unsatisfied stream selection"); }; Ok(vec![ String::from("-map"), - format!("{}:{}:{}", source_index, stream_type, stream_index), + format!("{source_index}:{stream_type}:{stream_index}"), ]) } +/// Generate the `-metadata` argument list for `ffmpeg`. +/// +/// # Errors +/// If the stream selection is unsatisfyable for the file. fn make_metadata_args( file: &MediaFile, selector: &Selector, @@ -112,9 +146,9 @@ fn make_metadata_args( let Some(_) = find_stream_index(file, selector) else { if selector.optional { return Ok(vec![]); - } else { - anyhow::bail!("unsatisfied stream selection"); } + + anyhow::bail!("unsatisfied stream selection"); }; let counter = match selector.stream_type { @@ -126,8 +160,8 @@ fn make_metadata_args( *counter = counter.saturating_add(1); let mut args = vec![ - format!("-metadata:s:{}:{}", stream_type, stream_index), - format!("language={}", stream_language), + format!("-metadata:s:{stream_type}:{stream_index}"), + format!("language={stream_language}"), ]; if selector.stream_type == StreamType::Subtitle { @@ -137,25 +171,26 @@ fn make_metadata_args( None => match stream_language { "eng" => "English", "jpn" => "Japanese", - _ => anyhow::bail!("Unhandled subtitle language: {}", stream_language), + _ => anyhow::bail!("Unhandled subtitle language: {stream_language}"), }, }; - args.push(format!("-metadata:s:s:{}", stream_index)); - args.push(format!("title={}", sub_title)); + args.push(format!("-metadata:s:s:{stream_index}")); + args.push(format!("title={sub_title}")); } Ok(args) } +/// Walk the streams of a file to match the given [Selector]. fn find_stream_index(file: &MediaFile, selector: &Selector) -> Option { let needs_forced = selector.flag == Some(StreamFlag::Forced); let needs_sdh = selector.flag == Some(StreamFlag::Sdh); match selector.stream_type { - StreamType::Video => match selector.matcher { - Matcher::Index(index) => (file.streams.video.len() > index).then_some(index), - Matcher::Language(ref language) => file + StreamType::Video => match &selector.matcher { + Matcher::Index(index) => (file.streams.video.len() > *index).then_some(*index), + Matcher::Language(language) => file .streams .video .iter() @@ -163,7 +198,7 @@ fn find_stream_index(file: &MediaFile, selector: &Selector) -> Option { .filter(|(_, c)| c.language() == Some(language.as_str())) .map(|(i, _)| i) .next(), - Matcher::Codec(ref codec) => file + Matcher::Codec(codec) => file .streams .video .iter() @@ -172,9 +207,9 @@ fn find_stream_index(file: &MediaFile, selector: &Selector) -> Option { .map(|(i, _)| i) .next(), }, - StreamType::Audio => match selector.matcher { - Matcher::Index(index) => (file.streams.audio.len() > index).then_some(index), - Matcher::Language(ref language) => file + StreamType::Audio => match &selector.matcher { + Matcher::Index(index) => (file.streams.audio.len() > *index).then_some(*index), + Matcher::Language(language) => file .streams .audio .iter() @@ -182,7 +217,7 @@ fn find_stream_index(file: &MediaFile, selector: &Selector) -> Option { .filter(|(_, c)| c.language() == Some(language.as_str())) .map(|(i, _)| i) .next(), - Matcher::Codec(ref codec) => file + Matcher::Codec(codec) => file .streams .audio .iter() @@ -191,9 +226,9 @@ fn find_stream_index(file: &MediaFile, selector: &Selector) -> Option { .map(|(i, _)| i) .next(), }, - StreamType::Subtitle => match selector.matcher { - Matcher::Index(index) => (file.streams.subtitle.len() > index).then_some(index), - Matcher::Language(ref language) => { + StreamType::Subtitle => match &selector.matcher { + Matcher::Index(index) => (file.streams.subtitle.len() > *index).then_some(*index), + Matcher::Language(language) => { file.streams .subtitle .iter() @@ -214,7 +249,7 @@ fn find_stream_index(file: &MediaFile, selector: &Selector) -> Option { .map(|(i, _)| i) .next() } - Matcher::Codec(ref codec) => file + Matcher::Codec(codec) => file .streams .subtitle .iter() @@ -226,6 +261,8 @@ fn find_stream_index(file: &MediaFile, selector: &Selector) -> Option { } } +/// Print out the command to stdout. +#[expect(clippy::print_stdout, reason = "the purpose is to print")] fn print_command(cmd: &Command) { let program = cmd.get_program().to_string_lossy(); @@ -235,15 +272,16 @@ fn print_command(cmd: &Command) { .collect::>() .join(" "); - println!("{} {}", program, args); + println!("{program} {args}"); } +/// Escape a string to be used by a shell. fn shell_escape(s: &str) -> String { if s.chars() .all(|c| c.is_ascii_alphanumeric() || "-_./".contains(c)) { - s.to_string() + s.to_owned() } else { - format!("{:?}", s) + format!("{s:?}") } } diff --git a/crates/cli-mux/src/parser.rs b/crates/cli-mux/src/parser.rs index 9933d22..6c451cd 100644 --- a/crates/cli-mux/src/parser.rs +++ b/crates/cli-mux/src/parser.rs @@ -1,20 +1,26 @@ +//! Parsing types for `ffmpeg` output. + use core::error::Error; use core::fmt; use core::str::FromStr; +/// The type of a stream. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StreamType { +pub(crate) enum StreamType { + /// A video stream. Video, + /// An audio stream. Audio, + /// A subtitle stream. Subtitle, } impl AsRef for StreamType { fn as_ref(&self) -> &str { match self { - StreamType::Video => "v", - StreamType::Audio => "a", - StreamType::Subtitle => "s", + Self::Video => "v", + Self::Audio => "a", + Self::Subtitle => "s", } } } @@ -24,18 +30,22 @@ impl FromStr for StreamType { fn from_str(s: &str) -> Result { match s { - "v" => Ok(StreamType::Video), - "a" => Ok(StreamType::Audio), - "s" => Ok(StreamType::Subtitle), - other => Err(ParseSelectorError::InvalidStreamType(other.to_string())), + "v" => Ok(Self::Video), + "a" => Ok(Self::Audio), + "s" => Ok(Self::Subtitle), + other => Err(ParseSelectorError::InvalidStreamType(other.to_owned())), } } } +/// A property of a stream to match on. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Matcher { +pub(crate) enum Matcher { + /// Match a specific stream index. Index(usize), + /// Match the first stream of the given language. Language(String), + /// Match the first stream of the given codec. Codec(String), } @@ -44,19 +54,22 @@ impl FromStr for Matcher { fn from_str(s: &str) -> Result { if let Ok(idx) = s.parse::() { - return Ok(Matcher::Index(idx)); + return Ok(Self::Index(idx)); } if s.len() == 3 { - return Ok(Matcher::Language(s.to_ascii_lowercase())); + return Ok(Self::Language(s.to_ascii_lowercase())); } - Ok(Matcher::Codec(s.to_ascii_lowercase())) + Ok(Self::Codec(s.to_ascii_lowercase())) } } +/// Flags applied to streams. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StreamFlag { +pub(crate) enum StreamFlag { + /// Forced flag. Forced, + /// SDH flag. Sdh, } @@ -65,24 +78,31 @@ impl FromStr for StreamFlag { fn from_str(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { - "forced" => Ok(StreamFlag::Forced), - "sdh" => Ok(StreamFlag::Sdh), - other => Err(ParseSelectorError::InvalidFlag(other.to_string())), + "forced" => Ok(Self::Forced), + "sdh" => Ok(Self::Sdh), + other => Err(ParseSelectorError::InvalidFlag(other.to_owned())), } } } +/// A complete selector which has everything it needs to find a specific stream. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Selector { +pub(crate) struct Selector { + /// The type of the stream to select. pub stream_type: StreamType, + /// The matcher to use when selecting. pub matcher: Matcher, + /// A desired flag. pub flag: Option, + /// Whether or not to bail if the stream cannot be found. pub optional: bool, + /// The output language of the stream. pub out_lang: String, } impl Selector { - pub fn language(&self) -> &str { + /// Get the target language of the selector. + pub(crate) fn language(&self) -> &str { &self.out_lang } } @@ -125,7 +145,7 @@ impl FromStr for Selector { (true, _) => return Err(ParseSelectorError::UnspecifiedLanguage), }; - Ok(Selector { + Ok(Self { stream_type, matcher, flag, @@ -135,15 +155,22 @@ impl FromStr for Selector { } } +/// Errors that can arise when parsing a selector string. #[derive(Debug)] -pub enum ParseSelectorError { +pub(crate) enum ParseSelectorError { + /// The selector string is empty. Empty, + /// The selector string has an invalid format. InvalidFormat, + /// The requested stream type in invalid. InvalidStreamType(String), + /// The requested flag is invalid. InvalidFlag(String), + /// The target language is unspecified. UnspecifiedLanguage, } +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl Error for ParseSelectorError {} impl fmt::Display for ParseSelectorError { @@ -151,8 +178,8 @@ impl fmt::Display for ParseSelectorError { match self { Self::Empty => write!(f, "selector was empty"), Self::InvalidFormat => write!(f, "invalid selector format"), - Self::InvalidStreamType(s) => write!(f, "invalid stream type '{}'", s), - Self::InvalidFlag(s) => write!(f, "invalid stream flag '{}'", s), + Self::InvalidStreamType(s) => write!(f, "invalid stream type '{s}'"), + Self::InvalidFlag(s) => write!(f, "invalid stream flag '{s}'"), Self::UnspecifiedLanguage => write!(f, "unspecified output language"), } } diff --git a/crates/cli-mux/src/probe.rs b/crates/cli-mux/src/probe.rs index 595420c..bdfa352 100644 --- a/crates/cli-mux/src/probe.rs +++ b/crates/cli-mux/src/probe.rs @@ -1,4 +1,6 @@ -use std::os::unix::fs::MetadataExt; +//! Helpers for using `ffprobe` to gather stream information from media files. + +use std::os::unix::fs::MetadataExt as _; use std::process::Command; use anyhow::{Context as _, Result}; @@ -7,11 +9,20 @@ use walkdir::DirEntry; use crate::model::{FFStream, MediaFile, Streams}; +/// The top-level containing type for `ffprobe` output. #[derive(Debug, Deserialize)] struct FFProbeOutput { + /// The list of returned streams. streams: Vec, } +/// Calls `ffprobe` on a [`DirEntry`] to gather one type of stream. +/// +/// # Errors +/// Forwards any IO or parsing errors. +/// +/// # Panics +/// If the file path is not utf8. fn probe_file_streams(entry: &DirEntry) -> Result> { let output = Command::new("ffprobe") .args([ @@ -22,16 +33,16 @@ fn probe_file_streams(entry: &DirEntry) -> Result> { "-show_streams", "-select_streams", S::FF_TYPE_NAME, - #[expect(clippy::expect_used)] + #[expect(clippy::expect_used, reason = "panic if path is not utf8")] entry.path().to_str().expect("path should be utf8"), ]) .output() - .with_context(|| format!("Failed to run ffprobe on {:?}", entry.path()))?; + .with_context(|| format!("Failed to run ffprobe on {}", entry.path().display()))?; if !output.status.success() { anyhow::bail!( - "ffprobe failed for {:?}:\n\n{}", - entry.path(), + "ffprobe failed for {}:\n\n{}", + entry.path().display(), String::from_utf8_lossy(&output.stderr) ); } @@ -40,7 +51,11 @@ fn probe_file_streams(entry: &DirEntry) -> Result> { Ok(parsed.streams) } -pub fn probe_file(entry: &DirEntry) -> Result { +/// Calls `ffprobe` on a [`DirEntry`] to detect all streams. +/// +/// # Errors +/// Forwards any IO or parsing errors. +pub(crate) fn probe_file(entry: &DirEntry) -> Result { Ok(MediaFile { path: entry.path().to_path_buf(), byte_size: entry.metadata()?.size(), diff --git a/crates/cli-mux/src/scan.rs b/crates/cli-mux/src/scan.rs index 6e592f6..8ac5589 100644 --- a/crates/cli-mux/src/scan.rs +++ b/crates/cli-mux/src/scan.rs @@ -1,3 +1,5 @@ +//! Helpers for filesystem scanning. + use std::path::Path; use walkdir::WalkDir; @@ -5,7 +7,8 @@ use walkdir::WalkDir; use crate::model::MediaFile; use crate::probe::probe_file; -pub fn scan_directory( +/// Recursively scan a directory. +pub(crate) fn scan_directory( path: &Path, unknown_length_start: impl FnOnce() -> T, unknown_length_end: impl FnOnce(T), @@ -18,32 +21,26 @@ pub fn scan_directory( let files: Vec<_> = WalkDir::new(path) .into_iter() .filter_map(Result::ok) - .filter(|e| e.file_type().is_file()) .filter(|e| { - let is_mkv = e - .path() - .extension() - .unwrap_or_default() - .eq_ignore_ascii_case("mkv"); - let is_mp4 = e - .path() - .extension() - .unwrap_or_default() - .eq_ignore_ascii_case("mp4"); - is_mkv || is_mp4 + #[expect(clippy::filetype_is_file, reason = "we only want regular files")] + e.file_type().is_file() + }) + .filter(|e| { + let ext = e.path().extension().unwrap_or_default(); + ext.eq_ignore_ascii_case("mkv") || ext.eq_ignore_ascii_case("mp4") }) .collect(); unknown_length_end(spinner); let mut progress = fixed_length_start(files.len()); - let mut total_byte_size = 0u64; + let mut total_byte_size = 0_u64; let files: Vec<_> = files .iter() .filter_map(|entry| { let file = match probe_file(entry) { Ok(file) => Some(file), Err(err) => { - print_fn(&progress, &format!("{:?}", err)); + print_fn(&progress, &format!("{err:?}")); None } }; diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ad51ae6..1c3626e 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "flix-cli" -version = "0.0.20" -license-file.workspace = true - +version = "0.1.0" description = "CLI for interacting with a flix database" -repository = "https://github.com/QuantumShade/flix" -categories = ["command-line-utilities"] +repository = "https://git.skrundz.dev/quantumshade/flix" +keywords = ["flix"] +categories = ["command-line-utilities", "multimedia"] +include = ["/src"] +publish = true edition.workspace = true rust-version.workspace = true +license-file.workspace = true [package.metadata.docs.rs] all-features = true @@ -32,7 +34,6 @@ clap = { workspace = true, features = [ "usage", ] } flix = { workspace = true, features = ["tmdb"] } -futures = { workspace = true } sea-orm = { workspace = true, features = ["debug-print", "runtime-tokio"] } serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["fs", "macros", "rt"] } @@ -40,21 +41,5 @@ toml = { workspace = true, features = ["parse", "serde"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } -[lints.clippy] -arithmetic_side_effects = "deny" -as_conversions = "deny" -checked_conversions = "deny" -default_union_representation = "deny" -expect_used = "deny" -indexing_slicing = "deny" -integer_division = "deny" -integer_division_remainder_used = "deny" -transmute_undefined_repr = "deny" -unchecked_time_subtraction = "deny" -unwrap_used = "deny" - -[lints.rust] -arithmetic_overflow = "forbid" -missing_docs = "forbid" -unsafe_code = "forbid" -unused_doc_comments = "forbid" +[lints] +workspace = true diff --git a/crates/cli/src/cli/flix.rs b/crates/cli/src/cli/flix.rs index 5c40e12..e90c8fb 100644 --- a/crates/cli/src/cli/flix.rs +++ b/crates/cli/src/cli/flix.rs @@ -1,29 +1,40 @@ +//! Command line argument parsing for the `flix` subcommand. + use flix::model::numbers::{EpisodeNumber, SeasonNumber}; use chrono::NaiveDate; use clap::Subcommand; +/// Subcommand for adding `flix` media. #[derive(Subcommand)] -pub enum AddCommand { - /// Add a flix collection +pub(crate) enum AddCommand { + /// Add a flix collection. Collection { + /// The collection's title. #[arg(value_name = "TITLE")] title: String, + /// The collection's overview. #[arg(value_name = "OVERVIEW")] overview: String, }, - /// Add a flix episode + /// Add a flix episode. Episode { + /// The episode's show's web slug. #[arg(value_name = "SHOW_WEB_SLUG")] - show_slug: String, + show_web_slug: String, + /// The episode's season number. #[arg(value_name = "NUMBER")] season_number: SeasonNumber, + /// The episode's number. #[arg(value_name = "NUMBER")] episode_number: EpisodeNumber, + /// The episode's title. #[arg(value_name = "TITLE")] title: String, + /// The episode's overview. #[arg(value_name = "OVERVIEW")] overview: String, + /// The episode's air date. #[arg(value_name = "DATE")] air_date: NaiveDate, }, diff --git a/crates/cli/src/cli/mod.rs b/crates/cli/src/cli/mod.rs index 3d65dc1..3180cdf 100644 --- a/crates/cli/src/cli/mod.rs +++ b/crates/cli/src/cli/mod.rs @@ -1,15 +1,18 @@ +//! Command line argument parsing. + use std::path::{Path, PathBuf}; use anyhow::{Result, anyhow}; use clap::{Args, Parser, Subcommand}; -pub mod flix; -pub mod tmdb; +pub(crate) mod flix; +pub(crate) mod tmdb; +/// Command line argument parser struct. #[derive(Parser)] #[command(version, about, long_about = None)] -pub struct Cli { - /// Use a custom config file +pub(crate) struct Cli { + /// Use a custom config file. #[arg( short, long, @@ -17,41 +20,53 @@ pub struct Cli { default_value = "~/.config/flix/config.toml" )] config: PathBuf, - - /// Use a custom cache file + /// Use a custom cache file. #[arg(short = 'C', long, value_name = "FILE", default_value = "./flix.redb")] cache: PathBuf, - - /// Use a custom database file + /// Use a custom database file. #[arg(short, long, value_name = "FILE", default_value = "./flix.db")] database: PathBuf, - - /// Enable tracing + /// Enable tracing. #[arg(short, long)] - pub trace: bool, - + trace: bool, + /// Subcommand to execute. #[command(subcommand)] command: Command, } impl Cli { - pub fn config_path(&self) -> PathBuf { - fn expect_home_dir() -> PathBuf { - #[allow(clippy::expect_used)] - std::env::home_dir().expect("you do not have a home directory") - } - - match self.config.strip_prefix("~/") { - Ok(path) => expect_home_dir().join(path), - Err(_) => self.config.to_owned(), - } + /// Get the config file path. + /// + /// # Panics + /// If the user doesn't have a home directory. + #[inline] + pub(crate) fn config_path(&self) -> PathBuf { + self.config.strip_prefix("~/").map_or_else( + |_| self.config.clone(), + |path| { + #[expect( + clippy::expect_used, + reason = "without a home directory this program is useless" + )] + std::env::home_dir() + .expect("you do not have a home directory") + .join(path) + }, + ) } - pub fn cache_path(&self) -> &Path { + /// Get the path to the cache file. + #[inline] + pub(crate) fn cache_path(&self) -> &Path { &self.cache } - pub fn database_path(&self) -> Result { + /// Get the path to the destination database. + /// + /// # Errors + /// If the database path is not utf8. + #[inline] + pub(crate) fn database_path(&self) -> Result { self.database .as_os_str() .to_str() @@ -59,77 +74,78 @@ impl Cli { .ok_or_else(|| anyhow!(".as_os_str().to_str()")) } - pub fn command(self) -> Command { + /// Get whether or not the trace flag was enabled. + #[inline] + pub(crate) const fn trace(&self) -> bool { + self.trace + } + + /// Get the subcommand to run. + #[inline] + pub(crate) fn get_command(self) -> Command { self.command } } +/// Optional overrides for media information. #[derive(Args)] -pub struct AddOverrides { +pub(crate) struct AddOverrides { + /// Override the displayed title. #[arg(long)] pub title: Option, + /// Override the title used to sort. #[arg(long)] pub sort_title: Option, + /// Override the filesystem slug. #[arg(long)] pub fs_slug: Option, + /// Overrride the web slug. #[arg(long)] pub web_slug: Option, } +/// Top level cli commands. #[derive(Subcommand)] -pub enum Command { - /// Initialize a new database +pub(crate) enum Command { + /// Initialize a new database. Init, - /// Add new items to the database + /// Add new items to the database. Add { + /// Overrides. #[command(flatten)] overrides: AddOverrides, + /// Command. #[command(subcommand)] command: AddCommand, }, - /// Update an existing item in the database - Update { - #[command(subcommand)] - command: UpdateCommand, - }, } +/// Wrapper for `add` around different backends. #[derive(Subcommand)] -pub enum AddCommand { - /// Use the flix backend +pub(crate) enum AddCommand { + /// Use the flix backend. Flix { + /// Command backend. #[command(subcommand)] command: flix::AddCommand, }, - /// Use the TMDB backend + /// Use the TMDB backend. Tmdb { + /// Command backend. #[command(subcommand)] command: tmdb::Command, }, } impl From for AddCommand { + #[inline] fn from(value: flix::AddCommand) -> Self { Self::Flix { command: value } } } impl From for AddCommand { - fn from(value: tmdb::Command) -> Self { - Self::Tmdb { command: value } - } -} - -#[derive(Subcommand)] -pub enum UpdateCommand { - /// Use the TMDB backend - Tmdb { - #[command(subcommand)] - command: tmdb::Command, - }, -} - -impl From for UpdateCommand { + #[inline] fn from(value: tmdb::Command) -> Self { Self::Tmdb { command: value } } diff --git a/crates/cli/src/cli/tmdb.rs b/crates/cli/src/cli/tmdb.rs index 15c9ba6..9f09299 100644 --- a/crates/cli/src/cli/tmdb.rs +++ b/crates/cli/src/cli/tmdb.rs @@ -1,41 +1,53 @@ +//! Command line argument parsing for the `tmdb` subcommand. + use flix::model::numbers::{EpisodeNumber, SeasonNumber}; -use flix::tmdb::model::id::RawId; +use flix::tmdb::model::id::TmdbRepr; use clap::Subcommand; +/// Subcommand for adding `tmdb` media. #[derive(Subcommand)] -pub enum Command { - /// Process a TMDB collection +pub(crate) enum Command { + /// Process a TMDB collection. Collection { + /// The collection's ID. #[arg(value_name = "TMDB_ID")] - id: RawId, + id: TmdbRepr, }, - /// Process a TMDB movie + /// Process a TMDB movie. Movie { + /// The movie's ID. #[arg(value_name = "TMDB_ID")] - id: RawId, + id: TmdbRepr, }, - /// Process a TMDB show + /// Process a TMDB show. Show { + /// The show's ID. #[arg(value_name = "TMDB_ID")] - id: RawId, + id: TmdbRepr, }, - /// Process a TMDB season + /// Process a TMDB season. Season { + /// The season's show's ID. #[arg(value_name = "TMDB_ID")] - id: RawId, + id: TmdbRepr, + /// The season's number. #[arg(value_name = "SEASON_NUM")] season: SeasonNumber, }, - /// Process a TMDB episode + /// Process a TMDB episode. #[command(trailing_var_arg = true)] Episode { + /// The episode's show's ID. #[arg(value_name = "TMDB_ID")] - id: RawId, + id: TmdbRepr, + /// The episode's season's number. #[arg(value_name = "SEASON_NUM")] season: SeasonNumber, + /// The episode's number. #[arg(value_name = "EPISODE_NUM")] episode: EpisodeNumber, + /// Additional episode numbers for merged media files. #[arg(value_name = "...")] episodes: Vec, }, diff --git a/crates/cli/src/config.rs b/crates/cli/src/config.rs index af3febf..35e8cca 100644 --- a/crates/cli/src/config.rs +++ b/crates/cli/src/config.rs @@ -1,21 +1,29 @@ +//! CLI configuration. + +/// Top level config struct. #[derive(serde::Deserialize)] -pub struct Config { +pub(crate) struct Config { + /// The TMDB config. tmdb: TmdbConfig, } +/// TMDB config struct. #[derive(serde::Deserialize)] -pub struct TmdbConfig { +pub(crate) struct TmdbConfig { + /// The bearer token to use for API requests. bearer_token: String, } impl Config { - pub fn tmdb(&self) -> &TmdbConfig { + /// Get the TMDB config. + pub(crate) const fn tmdb(&self) -> &TmdbConfig { &self.tmdb } } impl TmdbConfig { - pub fn bearer_token(&self) -> &str { + /// Get the bearer token. + pub(crate) fn bearer_token(&self) -> &str { &self.bearer_token } } diff --git a/crates/cli/src/db.rs b/crates/cli/src/db.rs index d0c2468..e397c54 100644 --- a/crates/cli/src/db.rs +++ b/crates/cli/src/db.rs @@ -1,9 +1,17 @@ +//! Databse helpers. + +use std::path::Path; + use flix::db::connection::Connection; -use anyhow::{Context, Result, bail}; +use anyhow::{Context as _, Result, bail}; use sea_orm::{ConnectOptions, Database}; use tokio::fs; +/// Connect to a database using a connection string. +/// +/// # Errors +/// If the database cannot be opened. async fn connect(string: String) -> Result { Connection::try_from( Database::connect(ConnectOptions::new(string)) @@ -14,14 +22,22 @@ async fn connect(string: String) -> Result { .context("Connection::try_from") } -pub async fn open(database_path: String) -> Result { - connect(format!("sqlite:{database_path}?mode=rw")).await +/// Helper for opening an existing database at the given path. +/// +/// # Errors +/// If the database cannot be opened. +pub(crate) async fn open(path: &Path) -> Result { + connect(format!("sqlite:{}?mode=rw", path.display())).await } -pub async fn open_new(database_path: String) -> Result { - if fs::try_exists(&database_path).await? { +/// Helper for creating then opening a database at the given path. +/// +/// # Errors +/// If the database already exists or cannot be openend after creation. +pub(crate) async fn open_new(path: &Path) -> Result { + if fs::try_exists(path).await? { bail!("database already exists"); } - connect(format!("sqlite:{database_path}?mode=rwc")).await + connect(format!("sqlite:{}?mode=rwc", path.display())).await } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 430e74f..e48da8b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,15 +1,19 @@ -//! flix-cli +//! flix-cli. -use std::rc::Rc; +extern crate alloc; + +use std::path::Path; + +use alloc::sync::Arc; use flix::tmdb::{self, CachePolicy, Client, RedbCache}; -use anyhow::{Context, Result}; -use clap::Parser; +use anyhow::{Context as _, Result}; +use clap::Parser as _; use tokio::fs; mod cli; -use cli::{AddCommand, Cli, Command, UpdateCommand}; +use cli::{AddCommand, Cli, Command}; mod config; use config::Config; @@ -20,47 +24,57 @@ mod db; mod run; #[tokio::main(flavor = "current_thread")] +#[cfg_attr(test, expect(clippy::missing_errors_doc, reason = "main function"))] +#[cfg_attr(test, expect(clippy::missing_panics_doc, reason = "main function "))] async fn main() -> Result<()> { let cli = Cli::parse(); let config = fs::read_to_string(cli.config_path()) .await - .with_context(|| format!("could not read config: {:?}", cli.config_path()))?; + .with_context(|| format!("could not read config: {}", cli.config_path().display()))?; let config: Config = toml::from_str(config.as_str()) - .with_context(|| format!("could not parse config: {:?}", cli.config_path()))?; + .with_context(|| format!("could not parse config: {}", cli.config_path().display()))?; let database_path = cli.database_path()?; + let database_path = Path::new(&database_path); let config = tmdb::Config::new(config.tmdb().bearer_token().to_owned()); - let cache = Rc::new(RedbCache::new(cli.cache_path())?); + let cache = Arc::new(RedbCache::new(cli.cache_path())?); let client = Client::new(config, cache, CachePolicy::Full); - if cli.trace { + if cli.trace() { tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .with_test_writer() .init(); } - match cli.command() { + match cli.get_command() { Command::Init => exec_init(database_path).await?, Command::Add { command, overrides } => { - exec_add(client, database_path, command, overrides).await? + exec_add(client, database_path, command, overrides).await?; } - Command::Update { command } => exec_update(client, database_path, command).await?, } Ok(()) } -async fn exec_init(database_path: String) -> Result<()> { - db::open_new(database_path).await?; +/// Execute the `init` command. +/// +/// # Errors +/// Forwards any errors. +async fn exec_init(database_path: &Path) -> Result<()> { + drop(db::open_new(database_path).await?); Ok(()) } +/// Execute the `add` command. +/// +/// # Errors +/// Forwards any errors. async fn exec_add( client: Client, - database_path: String, + database_path: &Path, command: AddCommand, overrides: AddOverrides, ) -> Result<()> { @@ -77,15 +91,3 @@ async fn exec_add( Ok(()) } - -async fn exec_update(client: Client, database_path: String, command: UpdateCommand) -> Result<()> { - let database = db::open(database_path).await?; - - match command { - UpdateCommand::Tmdb { command } => { - run::tmdb::update(client, database.as_ref(), command).await?; - } - } - - Ok(()) -} diff --git a/crates/cli/src/run/flix.rs b/crates/cli/src/run/flix.rs index 5733ac4..570b254 100644 --- a/crates/cli/src/run/flix.rs +++ b/crates/cli/src/run/flix.rs @@ -1,3 +1,5 @@ +//! The `flix` runtime backend. + use flix::db::entity; use flix::model::id::{CollectionId, ShowId}; use flix::model::numbers::{EpisodeNumber, SeasonNumber}; @@ -5,12 +7,19 @@ use flix::model::text; use anyhow::Result; use sea_orm::ActiveValue::{NotSet, Set}; -use sea_orm::{ActiveModelTrait, DatabaseConnection, DbErr, TransactionError, TransactionTrait}; +use sea_orm::{ + ActiveModelTrait as _, DatabaseConnection, DbErr, TransactionError, TransactionTrait as _, +}; use crate::cli::AddOverrides; use crate::cli::flix::AddCommand; -pub async fn add( +/// Execute an `add` command. +/// +/// # Errors +/// Forwards any database transaction failures. +#[expect(clippy::print_stdout, reason = "we want to print in a binary")] +pub(crate) async fn add( db: &DatabaseConnection, command: AddCommand, overrides: AddOverrides, @@ -50,15 +59,16 @@ pub async fn add( match result { Ok(_) => {} - Err(TransactionError::Connection(err)) => Err(err)?, - Err(TransactionError::Transaction(err)) => Err(err)?, - }; - println!("Created Collection: {}", title); + Err(TransactionError::Connection(err) | TransactionError::Transaction(err)) => { + return Err(err.into()); + } + } + println!("Created Collection: {title}"); Ok(()) } AddCommand::Episode { - show_slug, + show_web_slug, season_number, episode_number, title, @@ -70,11 +80,11 @@ pub async fn add( let title = overrides.title.unwrap_or_else(|| title.clone()); Box::pin(async move { - let show = entity::info::shows::Entity::find_by_web_slug(&show_slug) + let show = entity::info::shows::Entity::find_by_web_slug(&show_web_slug) .one(txn) .await? .ok_or_else(|| { - DbErr::Custom(format!("show '{}' does not exist", show_slug)) + DbErr::Custom(format!("show '{show_web_slug}' does not exist")) })?; let flix = entity::info::episodes::ActiveModel { @@ -95,10 +105,11 @@ pub async fn add( match result { Ok(_) => {} - Err(TransactionError::Connection(err)) => Err(err)?, - Err(TransactionError::Transaction(err)) => Err(err)?, - }; - println!("Created Episode: {}", title); + Err(TransactionError::Connection(err) | TransactionError::Transaction(err)) => { + return Err(err.into()); + } + } + println!("Created Episode: {title}"); Ok(()) } diff --git a/crates/cli/src/run/mod.rs b/crates/cli/src/run/mod.rs index 9acb9e7..ee909be 100644 --- a/crates/cli/src/run/mod.rs +++ b/crates/cli/src/run/mod.rs @@ -1,2 +1,4 @@ -pub mod flix; -pub mod tmdb; +//! Various runtime backends. + +pub(crate) mod flix; +pub(crate) mod tmdb; diff --git a/crates/cli/src/run/tmdb.rs b/crates/cli/src/run/tmdb.rs index ed2972f..42b7594 100644 --- a/crates/cli/src/run/tmdb.rs +++ b/crates/cli/src/run/tmdb.rs @@ -1,4 +1,6 @@ -use std::collections::HashMap; +//! The `flix` runtime backend. + +use alloc::collections::BTreeMap; use flix::db::entity; use flix::model::id::{CollectionId, MovieId, ShowId}; @@ -9,17 +11,24 @@ use flix::tmdb::model::id::{ CollectionId as TmdbCollectionId, MovieId as TmdbMovieId, ShowId as TmdbShowId, }; -use anyhow::{Context, Result, bail}; -use chrono::{Datelike, Utc}; +use anyhow::{Context as _, Result, bail}; +use chrono::{Datelike as _, Utc}; use sea_orm::ActiveValue::{NotSet, Set}; use sea_orm::{ - ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait, TransactionError, TransactionTrait, + ActiveModelTrait as _, DatabaseConnection, DbErr, EntityTrait as _, TransactionError, + TransactionTrait as _, }; use crate::cli::AddOverrides; use crate::cli::tmdb::Command; -pub async fn add( +/// Execute an `add` command. +/// +/// # Errors +/// Forwards any database transaction failures or TMDB API failures. +#[expect(clippy::print_stderr, reason = "we want to print in a binary")] +#[expect(clippy::print_stdout, reason = "we want to print in a binary")] +pub(crate) async fn add( client: Client, db: &DatabaseConnection, command: Command, @@ -54,13 +63,15 @@ pub async fn add( .web_slug .unwrap_or_else(|| text::make_web_slug(&title)); - const COLLECTION_SUFFIX_TO_REMOVE: &str = "-collection"; - if web_slug.ends_with(COLLECTION_SUFFIX_TO_REMOVE) { - web_slug.truncate( - web_slug - .len() - .saturating_sub(COLLECTION_SUFFIX_TO_REMOVE.len()), - ); + { + const COLLECTION_SUFFIX_TO_REMOVE: &str = "-collection"; + if web_slug.ends_with(COLLECTION_SUFFIX_TO_REMOVE) { + web_slug.truncate( + web_slug + .len() + .saturating_sub(COLLECTION_SUFFIX_TO_REMOVE.len()), + ); + } } let result: Result> = db @@ -78,7 +89,7 @@ pub async fn add( .insert(txn) .await?; - entity::tmdb::collections::ActiveModel { + _ = entity::tmdb::collections::ActiveModel { tmdb_id: Set(id), flix_id: Set(flix.id), last_update: Set(Utc::now()), @@ -94,10 +105,11 @@ pub async fn add( match result { Ok(_) => {} - Err(TransactionError::Connection(err)) => Err(err)?, - Err(TransactionError::Transaction(err)) => Err(err)?, - }; - println!("Created Collection: {}", title); + Err(TransactionError::Connection(err) | TransactionError::Transaction(err)) => { + return Err(err.into()); + } + } + println!("Created Collection: {title}"); Ok(()) } @@ -145,7 +157,7 @@ pub async fn add( .insert(txn) .await?; - entity::tmdb::movies::ActiveModel { + _ = entity::tmdb::movies::ActiveModel { tmdb_id: Set(id), flix_id: Set(flix.id), last_update: Set(Utc::now()), @@ -162,10 +174,11 @@ pub async fn add( match result { Ok(_) => {} - Err(TransactionError::Connection(err)) => Err(err)?, - Err(TransactionError::Transaction(err)) => Err(err)?, - }; - println!("Created Movie: {} ({})", title, year); + Err(TransactionError::Connection(err) | TransactionError::Transaction(err)) => { + return Err(err.into()); + } + } + println!("Created Movie: {title} ({year})"); Ok(()) } @@ -183,7 +196,7 @@ pub async fn add( .await .with_context(|| format!("shows().get_details({})", id.into_raw()))?; let mut seasons = Vec::new(); - let mut episodes = HashMap::new(); + let mut episodes = BTreeMap::new(); for season in 1..=show.number_of_seasons { let season = SeasonNumber::new(season); @@ -191,12 +204,11 @@ pub async fn add( .seasons() .get_details(id, season, None) .await - .with_context(|| { - format!("seasons().get_details({}, {})", id.into_raw(), season) - }) { + .with_context(|| format!("seasons().get_details({}, {season})", id.into_raw())) + { Ok(season) => season, Err(err) => { - eprintln!("{err:?}"); + eprintln!("{err}"); continue; } }; @@ -227,11 +239,9 @@ pub async fn add( Ok(value) => value, Err(err) => { eprintln!( - "skipping episode ({}, {}, {}) - {}", + "skipping episode ({}, {}, {episode}) - {err}", id.into_raw(), season.season_number, - episode, - err ); break; } @@ -239,7 +249,7 @@ pub async fn add( season_episodes.push(episode); } - episodes.insert(season.season_number, season_episodes); + drop(episodes.insert(season.season_number, season_episodes)); seasons.push(season); } @@ -273,7 +283,7 @@ pub async fn add( .insert(txn) .await?; - entity::tmdb::shows::ActiveModel { + _ = entity::tmdb::shows::ActiveModel { tmdb_id: Set(id), flix_id: Set(flix.id), last_update: Set(Utc::now()), @@ -283,17 +293,19 @@ pub async fn add( .await?; for season in seasons { - entity::info::seasons::ActiveModel { - show_id: Set(flix.id), - season_number: Set(season.season_number), - title: Set(season.title), - overview: Set(season.overview), - date: Set(season.air_date), - } - .insert(txn) - .await?; + drop( + entity::info::seasons::ActiveModel { + show_id: Set(flix.id), + season_number: Set(season.season_number), + title: Set(season.title), + overview: Set(season.overview), + date: Set(season.air_date), + } + .insert(txn) + .await?, + ); - entity::tmdb::seasons::ActiveModel { + _ = entity::tmdb::seasons::ActiveModel { tmdb_show: Set(id), tmdb_season: Set(season.season_number), flix_show: Set(flix.id), @@ -306,18 +318,20 @@ pub async fn add( for (season, episodes) in episodes { for episode in episodes { - entity::info::episodes::ActiveModel { - show_id: Set(flix.id), - season_number: Set(season), - episode_number: Set(episode.episode_number), - title: Set(episode.title), - overview: Set(episode.overview), - date: Set(episode.air_date), - } - .insert(txn) - .await?; + drop( + entity::info::episodes::ActiveModel { + show_id: Set(flix.id), + season_number: Set(season), + episode_number: Set(episode.episode_number), + title: Set(episode.title), + overview: Set(episode.overview), + date: Set(episode.air_date), + } + .insert(txn) + .await?, + ); - entity::tmdb::episodes::ActiveModel { + _ = entity::tmdb::episodes::ActiveModel { tmdb_show: Set(id), tmdb_season: Set(season), tmdb_episode: Set(episode.episode_number), @@ -339,10 +353,11 @@ pub async fn add( match result { Ok(_) => {} - Err(TransactionError::Connection(err)) => Err(err)?, - Err(TransactionError::Transaction(err)) => Err(err)?, - }; - println!("Created Show: {} ({})", title, year); + Err(TransactionError::Connection(err) | TransactionError::Transaction(err)) => { + return Err(err.into()); + } + } + println!("Created Show: {title} ({year})"); Ok(()) } @@ -366,11 +381,7 @@ pub async fn add( .get_details(id, season_number, None) .await .with_context(|| { - format!( - "seasons().get_details({}, {})", - id.into_raw(), - season_number - ) + format!("seasons().get_details({}, {season_number})", id.into_raw()) })?; let mut episodes = Vec::new(); @@ -391,11 +402,9 @@ pub async fn add( Ok(value) => value, Err(err) => { eprintln!( - "skipping episode ({}, {}, {}) - {}", + "skipping episode ({}, {}, {episode}) - {err}", id.into_raw(), season.season_number, - episode, - err ); break; } @@ -406,17 +415,19 @@ pub async fn add( let result: Result<(), TransactionError> = db .transaction(|txn| { Box::pin(async move { - entity::info::seasons::ActiveModel { - show_id: Set(show.flix_id), - season_number: Set(season_number), - title: Set(season.title), - overview: Set(season.overview), - date: Set(season.air_date), - } - .insert(txn) - .await?; + drop( + entity::info::seasons::ActiveModel { + show_id: Set(show.flix_id), + season_number: Set(season_number), + title: Set(season.title), + overview: Set(season.overview), + date: Set(season.air_date), + } + .insert(txn) + .await?, + ); - entity::tmdb::seasons::ActiveModel { + _ = entity::tmdb::seasons::ActiveModel { tmdb_show: Set(show.tmdb_id), tmdb_season: Set(season_number), flix_show: Set(show.flix_id), @@ -427,18 +438,20 @@ pub async fn add( .await?; for episode in episodes { - entity::info::episodes::ActiveModel { - show_id: Set(show.flix_id), - season_number: Set(season_number), - episode_number: Set(episode.episode_number), - title: Set(episode.title), - overview: Set(episode.overview), - date: Set(episode.air_date), - } - .insert(txn) - .await?; + drop( + entity::info::episodes::ActiveModel { + show_id: Set(show.flix_id), + season_number: Set(season_number), + episode_number: Set(episode.episode_number), + title: Set(episode.title), + overview: Set(episode.overview), + date: Set(episode.air_date), + } + .insert(txn) + .await?, + ); - entity::tmdb::episodes::ActiveModel { + _ = entity::tmdb::episodes::ActiveModel { tmdb_show: Set(show.tmdb_id), tmdb_season: Set(season_number), tmdb_episode: Set(episode.episode_number), @@ -458,14 +471,14 @@ pub async fn add( .await; match result { - Ok(_) => {} - Err(TransactionError::Connection(err)) => Err(err)?, - Err(TransactionError::Transaction(err)) => Err(err)?, - }; + Ok(()) => {} + Err(TransactionError::Connection(err) | TransactionError::Transaction(err)) => { + return Err(err.into()); + } + } println!( - "Created Season: {} S{}", + "Created Season: {} S{season_number}", show.flix_id.into_raw(), - season_number ); Ok(()) @@ -476,19 +489,10 @@ pub async fn add( episode, episodes, } => { - let id = TmdbShowId::from_raw(id); - let season_number = season; - - let Some(show) = entity::tmdb::shows::Entity::find_by_id(id).one(db).await? else { - bail!("show does not exists"); - }; - let Some(_) = entity::tmdb::seasons::Entity::find_by_id((id, season)) - .one(db) - .await? - else { - bail!("season does not exists"); - }; - + /// Fetch and store episode information. + /// + /// # Errors + /// Forwards any database transaction failures or TMDB API failures. async fn fetch_episode( client: &Client, db: &DatabaseConnection, @@ -512,24 +516,26 @@ pub async fn add( .get_details(id, season, episode_number, None) .await .with_context(|| { - format!("episodes().get_details({}, {})", id.into_raw(), season) + format!("episodes().get_details({}, {season})", id.into_raw()) })?; let result: Result<(), TransactionError> = db .transaction(|txn| { Box::pin(async move { - entity::info::episodes::ActiveModel { - show_id: Set(flix_id), - season_number: Set(season), - episode_number: Set(episode_number), - title: Set(episode.title), - overview: Set(episode.overview), - date: Set(episode.air_date), - } - .insert(txn) - .await?; + drop( + entity::info::episodes::ActiveModel { + show_id: Set(flix_id), + season_number: Set(season), + episode_number: Set(episode_number), + title: Set(episode.title), + overview: Set(episode.overview), + date: Set(episode.air_date), + } + .insert(txn) + .await?, + ); - entity::tmdb::episodes::ActiveModel { + _ = entity::tmdb::episodes::ActiveModel { tmdb_show: Set(tmdb_id), tmdb_season: Set(season), tmdb_episode: Set(episode_number), @@ -548,20 +554,32 @@ pub async fn add( .await; match result { - Ok(_) => {} - Err(TransactionError::Connection(err)) => Err(err)?, - Err(TransactionError::Transaction(err)) => Err(err)?, - }; + Ok(()) => {} + Err(TransactionError::Connection(err) | TransactionError::Transaction(err)) => { + return Err(err.into()); + } + } println!( - "Created Episode: {} S{}E{}", + "Created Episode: {} S{season}E{episode_number}", flix_id.into_raw(), - season, - episode_number ); Ok(()) } + let id = TmdbShowId::from_raw(id); + let season_number = season; + + let Some(show) = entity::tmdb::shows::Entity::find_by_id(id).one(db).await? else { + bail!("show does not exists"); + }; + let Some(_) = entity::tmdb::seasons::Entity::find_by_id((id, season)) + .one(db) + .await? + else { + bail!("season does not exists"); + }; + let flix_id = show.flix_id; let tmdb_id = show.tmdb_id; @@ -574,552 +592,3 @@ pub async fn add( } } } - -pub async fn update(client: Client, db: &DatabaseConnection, command: Command) -> Result<()> { - _ = client; - _ = db; - _ = command; - unimplemented!("updates") - - // match command { - // Command::Collection { id } => { - // let id = TmdbCollectionId::from_raw(id); - - // let collection = entity::tmdb::collections::Entity::find_by_id(id) - // .one(db) - // .await?; - // if collection.is_some() { - // bail!("collection already exists"); - // } - - // let collection = client - // .collections() - // .get_details(id, None) - // .await - // .with_context(|| format!("collections().get_details({})", id.into_raw()))?; - - // let title = overrides.title.unwrap_or(collection.title); - - // let sort_title = overrides - // .sort_title - // .unwrap_or_else(|| text::make_sortable_title(&title)); - // let fs_slug = overrides - // .fs_slug - // .unwrap_or_else(|| text::make_fs_slug(&title)); - // let web_slug = overrides - // .web_slug - // .unwrap_or_else(|| text::make_web_slug(&title)); - - // let result: Result> = db - // .transaction(|txn| { - // let title = title.clone(); - // Box::pin(async move { - // let flix = entity::info::collections::ActiveModel { - // id: NotSet, - // title: Set(title), - // overview: Set(collection.overview), - // sort_title: Set(sort_title), - // fs_slug: Set(fs_slug), - // web_slug: Set(web_slug), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::collections::ActiveModel { - // tmdb_id: Set(id), - // flix_id: Set(flix.id), - // last_update: Set(Utc::now()), - // movie_count: Set(collection.movies.len().try_into().unwrap_or(0)), - // } - // .insert(txn) - // .await?; - - // Ok(flix.id) - // }) - // }) - // .await; - - // let flix_id = match result { - // Ok(id) => id, - // Err(TransactionError::Connection(err)) => Err(err)?, - // Err(TransactionError::Transaction(err)) => Err(err)?, - // }; - // println!("Created Collection: {}", title, flix_id.into_raw()); - - // Ok(()) - // } - // Command::Movie { id } => { - // let id = TmdbMovieId::from_raw(id); - - // let movie = entity::tmdb::movies::Entity::find_by_id(id).one(db).await?; - // if movie.is_some() { - // bail!("movie already exists"); - // } - - // let movie = client - // .movies() - // .get_details(id, None) - // .await - // .with_context(|| format!("movies().get_details({})", id.into_raw()))?; - - // let title = overrides.title.unwrap_or(movie.title); - // let year = movie.release_date.year(); - - // let sort_title = overrides - // .sort_title - // .unwrap_or_else(|| text::make_sortable_title(&title)); - // let fs_slug = overrides - // .fs_slug - // .unwrap_or_else(|| text::make_fs_slug_year(&title, year)); - // let web_slug = overrides - // .web_slug - // .unwrap_or_else(|| text::make_web_slug_year(&title, year)); - - // let result: Result> = db - // .transaction(|txn| { - // let title = title.clone(); - // Box::pin(async move { - // let flix = entity::info::movies::ActiveModel { - // id: NotSet, - // title: Set(title), - // tagline: Set(movie.tagline), - // overview: Set(movie.overview), - // date: Set(movie.release_date), - // sort_title: Set(sort_title), - // fs_slug: Set(fs_slug), - // web_slug: Set(web_slug), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::movies::ActiveModel { - // tmdb_id: Set(id), - // flix_id: Set(flix.id), - // last_update: Set(Utc::now()), - // runtime: Set(movie.runtime.into()), - // collection_id: Set(movie.collection.map(|c| c.id)), - // } - // .insert(txn) - // .await?; - - // Ok(flix.id) - // }) - // }) - // .await; - - // let flix_id = match result { - // Ok(id) => id, - // Err(TransactionError::Connection(err)) => Err(err)?, - // Err(TransactionError::Transaction(err)) => Err(err)?, - // }; - // println!( - // "Created Movie: {} ({})", - // title, - // year, - // flix_id.into_raw(), - // ); - - // Ok(()) - // } - // Command::Show { id } => { - // let id = TmdbShowId::from_raw(id); - - // let show = entity::tmdb::shows::Entity::find_by_id(id).one(db).await?; - // if show.is_some() { - // bail!("show already exists"); - // } - - // let show = client - // .shows() - // .get_details(id, None) - // .await - // .with_context(|| format!("shows().get_details({})", id.into_raw()))?; - // let mut seasons = Vec::new(); - // let mut episodes = HashMap::new(); - - // for season in 1..=show.number_of_seasons { - // let season = SeasonNumber::new(season); - // let season = match client - // .seasons() - // .get_details(id, season, None) - // .await - // .with_context(|| { - // format!("seasons().get_details({}, {})", id.into_raw(), season) - // }) { - // Ok(season) => season, - // Err(err) => { - // eprintln!("{err:?}"); - // continue; - // } - // }; - // if season.air_date > Utc::now().naive_utc().date() { - // eprintln!( - // "skipping season ({}, {})", - // id.into_raw(), - // season.season_number - // ); - // break; - // } - - // let Ok(number_of_episodes) = u32::try_from(season.episodes.len()) else { - // bail!( - // "could not convert {} to an EpisodeNumber", - // season.episodes.len() - // ) - // }; - - // let mut season_episodes = Vec::new(); - // for episode in 1..=number_of_episodes { - // let episode = EpisodeNumber::new(episode); - // let Ok(episode) = client - // .episodes() - // .get_details(id, season.season_number, episode, None) - // .await - // else { - // eprintln!( - // "skipping episode ({}, {}, {})", - // id.into_raw(), - // season.season_number, - // episode - // ); - // break; - // }; - // season_episodes.push(episode); - // } - - // episodes.insert(season.season_number, season_episodes); - // seasons.push(season); - // } - - // let title = overrides.title.unwrap_or(show.title); - // let year = show.first_air_date.year(); - - // let sort_title = overrides - // .sort_title - // .unwrap_or_else(|| text::make_sortable_title(&title)); - // let fs_slug = overrides - // .fs_slug - // .unwrap_or_else(|| text::make_fs_slug_year(&title, year)); - // let web_slug = overrides - // .web_slug - // .unwrap_or_else(|| text::make_web_slug_year(&title, year)); - - // let result: Result> = db - // .transaction(|txn| { - // let title = title.clone(); - // Box::pin(async move { - // let flix = entity::info::shows::ActiveModel { - // id: NotSet, - // title: Set(title), - // tagline: Set(show.tagline), - // overview: Set(show.overview), - // date: Set(show.first_air_date), - // sort_title: Set(sort_title), - // fs_slug: Set(fs_slug), - // web_slug: Set(web_slug), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::shows::ActiveModel { - // tmdb_id: Set(id), - // flix_id: Set(flix.id), - // last_update: Set(Utc::now()), - // number_of_seasons: Set(show.number_of_seasons), - // } - // .insert(txn) - // .await?; - - // for season in seasons { - // entity::info::seasons::ActiveModel { - // show_id: Set(flix.id), - // season_number: Set(season.season_number), - // title: Set(season.title), - // overview: Set(season.overview), - // date: Set(season.air_date), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::seasons::ActiveModel { - // tmdb_show: Set(id), - // tmdb_season: Set(season.season_number), - // flix_show: Set(flix.id), - // flix_season: Set(season.season_number), - // last_update: Set(Utc::now()), - // } - // .insert(txn) - // .await?; - // } - - // for (season, episodes) in episodes { - // for episode in episodes { - // entity::info::episodes::ActiveModel { - // show_id: Set(flix.id), - // season_number: Set(season), - // episode_number: Set(episode.episode_number), - // title: Set(episode.title), - // overview: Set(episode.overview), - // date: Set(episode.air_date), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::episodes::ActiveModel { - // tmdb_show: Set(id), - // tmdb_season: Set(season), - // tmdb_episode: Set(episode.episode_number), - // flix_show: Set(flix.id), - // flix_season: Set(season), - // flix_episode: Set(episode.episode_number), - // last_update: Set(Utc::now()), - // runtime: Set(episode.runtime.into()), - // } - // .insert(txn) - // .await?; - // } - // } - - // Ok(flix.id) - // }) - // }) - // .await; - - // let flix_id = match result { - // Ok(id) => id, - // Err(TransactionError::Connection(err)) => Err(err)?, - // Err(TransactionError::Transaction(err)) => Err(err)?, - // }; - // println!( - // "Created Show: {} ({})", - // title, - // year, - // flix_id.into_raw() - // ); - - // Ok(()) - // } - // Command::Season { id, season } => { - // let id = TmdbShowId::from_raw(id); - // let season_number = season; - - // let Some(show) = entity::tmdb::shows::Entity::find_by_id(id).one(db).await? else { - // bail!("show does not exists"); - // }; - - // let season = entity::tmdb::seasons::Entity::find_by_id((id, season)) - // .one(db) - // .await?; - // if season.is_some() { - // bail!("season already exists"); - // } - - // let season = client - // .seasons() - // .get_details(id, season_number, None) - // .await - // .with_context(|| { - // format!( - // "seasons().get_details({}, {})", - // id.into_raw(), - // season_number - // ) - // })?; - // let mut episodes = Vec::new(); - - // let Ok(number_of_episodes) = u32::try_from(season.episodes.len()) else { - // bail!( - // "could not convert {} to an EpisodeNumber", - // season.episodes.len() - // ) - // }; - - // for episode in 1..=number_of_episodes { - // let episode = EpisodeNumber::new(episode); - // let Ok(episode) = client - // .episodes() - // .get_details(id, season.season_number, episode, None) - // .await - // else { - // eprintln!( - // "skipping episode ({}, {}, {})", - // id.into_raw(), - // season.season_number, - // episode - // ); - // break; - // }; - // episodes.push(episode); - // } - - // let result: Result<(), TransactionError> = db - // .transaction(|txn| { - // Box::pin(async move { - // entity::info::seasons::ActiveModel { - // show_id: Set(show.flix_id), - // season_number: Set(season_number), - // title: Set(season.title), - // overview: Set(season.overview), - // date: Set(season.air_date), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::seasons::ActiveModel { - // tmdb_show: Set(show.tmdb_id), - // tmdb_season: Set(season_number), - // flix_show: Set(show.flix_id), - // flix_season: Set(season_number), - // last_update: Set(Utc::now()), - // } - // .insert(txn) - // .await?; - - // for episode in episodes { - // entity::info::episodes::ActiveModel { - // show_id: Set(show.flix_id), - // season_number: Set(season_number), - // episode_number: Set(episode.episode_number), - // title: Set(episode.title), - // overview: Set(episode.overview), - // date: Set(episode.air_date), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::episodes::ActiveModel { - // tmdb_show: Set(show.tmdb_id), - // tmdb_season: Set(season_number), - // tmdb_episode: Set(episode.episode_number), - // flix_show: Set(show.flix_id), - // flix_season: Set(season_number), - // flix_episode: Set(episode.episode_number), - // last_update: Set(Utc::now()), - // runtime: Set(episode.runtime.into()), - // } - // .insert(txn) - // .await?; - // } - - // Ok(()) - // }) - // }) - // .await; - - // match result { - // Ok(_) => (), - // Err(TransactionError::Connection(err)) => Err(err)?, - // Err(TransactionError::Transaction(err)) => Err(err)?, - // }; - // println!( - // "Created Season: {} S{}", - // show.flix_id.into_raw(), - // season_number - // ); - - // Ok(()) - // } - // Command::Episode { - // id, - // season, - // episode, - // episodes, - // } => { - // let id = TmdbShowId::from_raw(id); - // let season_number = season; - - // let Some(show) = entity::tmdb::shows::Entity::find_by_id(id).one(db).await? else { - // bail!("show does not exists"); - // }; - // let Some(_) = entity::tmdb::seasons::Entity::find_by_id((id, season)) - // .one(db) - // .await? - // else { - // bail!("season does not exists"); - // }; - - // async fn fetch_episode( - // client: &Client, - // db: &DatabaseConnection, - // flix_id: ShowId, - // tmdb_id: TmdbShowId, - // id: TmdbShowId, - // season: SeasonNumber, - // episode: EpisodeNumber, - // ) -> Result<()> { - // let episode_number = episode; - - // let episode = entity::tmdb::episodes::Entity::find_by_id((id, season, episode)) - // .one(db) - // .await?; - // if episode.is_some() { - // bail!("episode already exists"); - // } - - // let episode = client - // .episodes() - // .get_details(id, season, episode_number, None) - // .await - // .with_context(|| { - // format!("episodes().get_details({}, {})", id.into_raw(), season) - // })?; - - // let result: Result<(), TransactionError> = db - // .transaction(|txn| { - // Box::pin(async move { - // entity::info::episodes::ActiveModel { - // show_id: Set(flix_id), - // season_number: Set(season), - // episode_number: Set(episode_number), - // title: Set(episode.title), - // overview: Set(episode.overview), - // date: Set(episode.air_date), - // } - // .insert(txn) - // .await?; - - // entity::tmdb::episodes::ActiveModel { - // tmdb_show: Set(tmdb_id), - // tmdb_season: Set(season), - // tmdb_episode: Set(episode_number), - // flix_show: Set(flix_id), - // flix_season: Set(season), - // flix_episode: Set(episode_number), - // last_update: Set(Utc::now()), - // runtime: Set(episode.runtime.into()), - // } - // .insert(txn) - // .await?; - - // Ok(()) - // }) - // }) - // .await; - - // match result { - // Ok(_) => (), - // Err(TransactionError::Connection(err)) => Err(err)?, - // Err(TransactionError::Transaction(err)) => Err(err)?, - // }; - // println!( - // "Created Episode: {} S{}E{}", - // flix_id.into_raw(), - // season, - // episode_number - // ); - - // Ok(()) - // } - - // let flix_id = show.flix_id; - // let tmdb_id = show.tmdb_id; - - // fetch_episode(&client, db, flix_id, tmdb_id, id, season_number, episode).await?; - // for episode in episodes { - // fetch_episode(&client, db, flix_id, tmdb_id, id, season_number, episode).await?; - // } - - // Ok(()) - // } - // } -} diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index ccad881..2d6b933 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "flix-db" -version = "0.0.20" -license-file.workspace = true - +version = "0.1.0" description = "Types for storing persistent data about media" -repository = "https://github.com/QuantumShade/flix" -categories = [] +repository = "https://git.skrundz.dev/quantumshade/flix" +keywords = ["flix"] +categories = ["multimedia"] +include = ["/src"] +publish = true edition.workspace = true rust-version.workspace = true +license-file.workspace = true [package.metadata.docs.rs] all-features = true @@ -29,7 +31,7 @@ flix-tmdb = { workspace = true, features = ["sea-orm"], optional = true } [dev-dependencies] sea-orm-migration = { workspace = true, features = ["runtime-tokio-rustls"] } -tokio = { version = "^1", default-features = false, features = [ +tokio = { workspace = true, features = [ "macros", "rt", ] } diff --git a/crates/db/src/connection.rs b/crates/db/src/connection.rs index 6ed8c97..8e17f2e 100644 --- a/crates/db/src/connection.rs +++ b/crates/db/src/connection.rs @@ -1,15 +1,26 @@ -//! Types and functions related to [DatabaseConnection]s +//! Types and functions related to [`DatabaseConnection`]s. use sea_orm::{DatabaseConnection, DbErr}; use sea_orm_migration::MigratorTrait as _; -/// A newtype wrapping a [DatabaseConnection] +/// A newtype wrapping a [`DatabaseConnection`]. +#[derive(Debug)] pub struct Connection(DatabaseConnection); impl Connection { /// Helper function for applying database migrations while wrapping a - /// [DatabaseConnection] in a newtype + /// [`DatabaseConnection`] in a newtype. + /// + /// # Errors + /// Fails if connecting or applying migrations fail. + #[inline] pub async fn try_from(db: DatabaseConnection) -> Result { + // The migrations only create views which store no data, so it is + // important to down before up since modifications are impossible. + // + // Syncing the schema registry allows all real tables to be upgraded. + // It is important to sync twice to ensure internal consistency so that + // the views can be recreated on the latest schema. crate::migration::Migrator::down(&db, None).await?; db.get_schema_registry("flix_db::*").sync(&db).await?; db.get_schema_registry("flix_db::*").sync(&db).await?; @@ -19,6 +30,7 @@ impl Connection { } impl AsRef for Connection { + #[inline] fn as_ref(&self) -> &DatabaseConnection { &self.0 } diff --git a/crates/db/src/entity/content.rs b/crates/db/src/entity/content.rs index a0da637..ebdb032 100644 --- a/crates/db/src/entity/content.rs +++ b/crates/db/src/entity/content.rs @@ -1,6 +1,6 @@ -//! This module contains entities for storing media file information +//! This module contains entities for storing media file information. -/// Library entity +/// Library entity. pub mod libraries { use flix_model::id::LibraryId; @@ -10,42 +10,43 @@ pub mod libraries { use chrono::{DateTime, Utc}; use sea_orm::entity::prelude::*; - /// The database representation of a library media folder + /// The database representation of a library media folder. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_libraries")] pub struct Model { - /// The library's ID + /// The library's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: LibraryId, - /// The library's directory + /// The library's directory. pub directory: PathBytes, - /// The library's last scan data + /// The library's last scan data. pub last_scan_date: Option>, - /// The library's last scan duration + /// The library's last scan duration. pub last_scan_duration: Option, - /// Collections that are part of this library + /// Collections that are part of this library. #[sea_orm(has_many)] pub collections: HasMany, - /// Movies that are part of this library + /// Movies that are part of this library. #[sea_orm(has_many)] pub movies: HasMany, - /// Shows that are part of this library + /// Shows that are part of this library. #[sea_orm(has_many)] pub shows: HasMany, - /// Seasons that are part of this library + /// Seasons that are part of this library. #[sea_orm(has_many)] pub seasons: HasMany, - /// Episodes that are part of this library + /// Episodes that are part of this library. #[sea_orm(has_many)] pub episodes: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Collection entity +/// Collection entity. pub mod collections { use flix_model::id::{CollectionId, LibraryId}; @@ -55,25 +56,25 @@ pub mod collections { use crate::entity; - /// The database representation of a collection media folder + /// The database representation of a collection media folder. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_collections")] pub struct Model { - /// The collection's ID + /// The collection's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: CollectionId, - /// The collection's parent + /// The collection's parent. #[sea_orm(indexed)] pub parent_id: Option, - /// The collection's library ID + /// The collection's library ID. pub library_id: LibraryId, - /// The collection's directory + /// The collection's directory. pub directory: PathBytes, - /// The collection's poster path + /// The collection's poster path. pub relative_poster_path: Option, - /// This collection's parent + /// This collection's parent. #[sea_orm( self_ref, relation_enum = "Parent", @@ -83,7 +84,7 @@ pub mod collections { on_delete = "Cascade" )] pub parent: HasOne, - /// The library this collection belongs to + /// The library this collection belongs to. #[sea_orm( belongs_to, from = "library_id", @@ -92,7 +93,7 @@ pub mod collections { on_delete = "Cascade" )] pub library: HasOne, - /// The info for this collection + /// The info for this collection. #[sea_orm( belongs_to, relation_enum = "Info", @@ -103,15 +104,16 @@ pub mod collections { )] pub info: HasOne, - /// The watched info for this collection + /// The watched info for this collection. #[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")] pub watched: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Movie entity +/// Movie entity. pub mod movies { use flix_model::id::{CollectionId, LibraryId, MovieId}; @@ -121,27 +123,27 @@ pub mod movies { use crate::entity; - /// The database representation of a movie media folder + /// The database representation of a movie media folder. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_movies")] pub struct Model { - /// The movie's ID + /// The movie's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: MovieId, - /// The movie's parent + /// The movie's parent. #[sea_orm(indexed)] pub parent_id: Option, - /// The movie's library + /// The movie's library. pub library_id: LibraryId, - /// The movie's directory + /// The movie's directory. pub directory: PathBytes, - /// The movie's media path + /// The movie's media path. pub relative_media_path: String, - /// The movie's poster path + /// The movie's poster path. pub relative_poster_path: Option, - /// This movie's parent + /// This movie's parent. #[sea_orm( belongs_to, from = "parent_id", @@ -150,7 +152,7 @@ pub mod movies { on_delete = "Cascade" )] pub parent: HasOne, - /// The library this movie belongs to + /// The library this movie belongs to. #[sea_orm( belongs_to, from = "library_id", @@ -159,7 +161,7 @@ pub mod movies { on_delete = "Cascade" )] pub library: HasOne, - /// The info for this movie + /// The info for this movie. #[sea_orm( belongs_to, relation_enum = "Info", @@ -170,15 +172,16 @@ pub mod movies { )] pub info: HasOne, - /// The watched info for this movie + /// The watched info for this movie. #[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")] pub watched: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Show entity +/// Show entity. pub mod shows { use flix_model::id::{CollectionId, LibraryId, ShowId}; @@ -188,25 +191,25 @@ pub mod shows { use crate::entity; - /// The database representation of a show media folder + /// The database representation of a show media folder. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_shows")] pub struct Model { - /// The show's ID + /// The show's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: ShowId, - /// The show's parent + /// The show's parent. #[sea_orm(indexed)] pub parent_id: Option, - /// The show's library + /// The show's library. pub library_id: LibraryId, - /// The show's directory + /// The show's directory. pub directory: PathBytes, - /// The show's poster path + /// The show's poster path. pub relative_poster_path: Option, - /// This show's parent + /// This show's parent. #[sea_orm( belongs_to, from = "parent_id", @@ -215,7 +218,7 @@ pub mod shows { on_delete = "Cascade" )] pub parent: HasOne, - /// The library this show belongs to + /// The library this show belongs to. #[sea_orm( belongs_to, from = "library_id", @@ -224,7 +227,7 @@ pub mod shows { on_delete = "Cascade" )] pub library: HasOne, - /// The info for this show + /// The info for this show. #[sea_orm( belongs_to, relation_enum = "Info", @@ -235,21 +238,22 @@ pub mod shows { )] pub info: HasOne, - /// Seasons that are part of this show + /// Seasons that are part of this show. #[sea_orm(has_many)] pub seasons: HasMany, - /// Episodes that are part of this show + /// Episodes that are part of this show. #[sea_orm(has_many)] pub episodes: HasMany, - /// The watched info for this show + /// The watched info for this show. #[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")] pub watched: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Season entity +/// Season entity. pub mod seasons { use flix_model::id::{LibraryId, ShowId}; use flix_model::numbers::SeasonNumber; @@ -260,25 +264,25 @@ pub mod seasons { use crate::entity; - /// The database representation of a season media folder + /// The database representation of a season media folder. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_seasons")] pub struct Model { - /// The season's show's ID + /// The season's show's ID. #[sea_orm(primary_key, auto_increment = false)] pub show_id: ShowId, - /// The season's number + /// The season's number. #[sea_orm(primary_key, auto_increment = false)] pub season_number: SeasonNumber, - /// The season's library + /// The season's library. pub library_id: LibraryId, - /// The season's directory + /// The season's directory. pub directory: PathBytes, - /// The season's poster path + /// The season's poster path. pub relative_poster_path: Option, - /// This season's show + /// This season's show. #[sea_orm( belongs_to, from = "show_id", @@ -287,7 +291,7 @@ pub mod seasons { on_delete = "Cascade" )] pub show: HasOne, - /// The library this season belongs to + /// The library this season belongs to. #[sea_orm( belongs_to, from = "library_id", @@ -296,7 +300,7 @@ pub mod seasons { on_delete = "Cascade" )] pub library: HasOne, - /// The info for this season + /// The info for this season. #[sea_orm( belongs_to, relation_enum = "Info", @@ -307,10 +311,10 @@ pub mod seasons { )] pub info: HasOne, - /// Episodes that are part of this show + /// Episodes that are part of this show. #[sea_orm(has_many)] pub episodes: HasMany, - /// The watched info for this season + /// The watched info for this season. #[sea_orm( has_many, relation_enum = "Watched", @@ -320,10 +324,11 @@ pub mod seasons { pub watched: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Episode entity +/// Episode entity. pub mod episodes { use flix_model::id::{LibraryId, ShowId}; use flix_model::numbers::{EpisodeNumber, SeasonNumber}; @@ -334,32 +339,32 @@ pub mod episodes { use crate::entity; - /// The database representation of a episode media folder + /// The database representation of a episode media folder. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_episodes")] pub struct Model { - /// The episode's show's ID + /// The episode's show's ID. #[sea_orm(primary_key, auto_increment = false)] pub show_id: ShowId, - /// The episode's season's number + /// The episode's season's number. #[sea_orm(primary_key, auto_increment = false)] pub season_number: SeasonNumber, - /// The episode's number + /// The episode's number. #[sea_orm(primary_key, auto_increment = false)] pub episode_number: EpisodeNumber, - /// The number of additional contained episodes + /// The number of additional contained episodes. pub count: u8, - /// The episode's library + /// The episode's library. pub library_id: LibraryId, - /// The episode's directory + /// The episode's directory. pub directory: PathBytes, - /// The episode's media path + /// The episode's media path. pub relative_media_path: String, - /// The episode's poster path + /// The episode's poster path. pub relative_poster_path: Option, - /// This episode's show + /// This episode's show. #[sea_orm( belongs_to, from = "show_id", @@ -368,7 +373,7 @@ pub mod episodes { on_delete = "Cascade" )] pub show: HasOne, - /// This episode's season + /// This episode's season. #[sea_orm( belongs_to, from = "(show_id, season_number)", @@ -377,7 +382,7 @@ pub mod episodes { on_delete = "Cascade" )] pub season: HasOne, - /// The library this episode belongs to + /// The library this episode belongs to. #[sea_orm( belongs_to, from = "library_id", @@ -386,7 +391,7 @@ pub mod episodes { on_delete = "Cascade" )] pub library: HasOne, - /// The info for this episode + /// The info for this episode. #[sea_orm( belongs_to, relation_enum = "Info", @@ -397,7 +402,7 @@ pub mod episodes { )] pub info: HasOne, - /// The watched info for this episode + /// The watched info for this episode. #[sea_orm( has_many, relation_enum = "Watched", @@ -407,23 +412,26 @@ pub mod episodes { pub watched: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Macros for creating content entities +/// Macros for creating content entities. #[cfg(test)] pub mod test { macro_rules! make_content_library { ($db:expr, $id:expr) => { - $crate::entity::content::libraries::ActiveModel { - id: Set(::flix_model::id::LibraryId::from_raw($id)), - directory: Set(::std::path::PathBuf::new().into()), - last_scan_date: Set(None), - last_scan_duration: Set(None), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::content::libraries::ActiveModel { + id: Set(::flix_model::id::LibraryId::from_raw($id)), + directory: Set(::std::path::PathBuf::new().into()), + last_scan_date: Set(None), + last_scan_duration: Set(None), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_content_library; @@ -431,16 +439,18 @@ pub mod test { macro_rules! make_content_collection { ($db:expr, $lid:expr, $id:expr, $pid:expr) => { $crate::entity::info::test::make_info_collection!($db, $id); - $crate::entity::content::collections::ActiveModel { - id: Set(::flix_model::id::CollectionId::from_raw($id)), - parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)), - library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), - directory: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(::core::option::Option::None), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::content::collections::ActiveModel { + id: Set(::flix_model::id::CollectionId::from_raw($id)), + parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)), + library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), + directory: Set(::std::path::PathBuf::new().into()), + relative_poster_path: Set(::core::option::Option::None), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_content_collection; @@ -448,17 +458,19 @@ pub mod test { macro_rules! make_content_movie { ($db:expr, $lid:expr, $id:expr, $pid:expr) => { $crate::entity::info::test::make_info_movie!($db, $id); - $crate::entity::content::movies::ActiveModel { - id: Set(::flix_model::id::MovieId::from_raw($id)), - parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)), - library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), - directory: Set(::std::path::PathBuf::new().into()), - relative_media_path: Set(::std::string::String::new()), - relative_poster_path: Set(::core::option::Option::None), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::content::movies::ActiveModel { + id: Set(::flix_model::id::MovieId::from_raw($id)), + parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)), + library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), + directory: Set(::std::path::PathBuf::new().into()), + relative_media_path: Set(::alloc::string::String::new()), + relative_poster_path: Set(::core::option::Option::None), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_content_movie; @@ -466,16 +478,18 @@ pub mod test { macro_rules! make_content_show { ($db:expr, $lid:expr, $id:expr, $pid:expr) => { $crate::entity::info::test::make_info_show!($db, $id); - $crate::entity::content::shows::ActiveModel { - id: Set(::flix_model::id::ShowId::from_raw($id)), - parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)), - library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), - directory: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(::core::option::Option::None), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::content::shows::ActiveModel { + id: Set(::flix_model::id::ShowId::from_raw($id)), + parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)), + library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), + directory: Set(::std::path::PathBuf::new().into()), + relative_poster_path: Set(::core::option::Option::None), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_content_show; @@ -483,16 +497,18 @@ pub mod test { macro_rules! make_content_season { ($db:expr, $lid:expr, $show:expr, $season:expr) => { $crate::entity::info::test::make_info_season!($db, $show, $season); - $crate::entity::content::seasons::ActiveModel { - show_id: Set(::flix_model::id::ShowId::from_raw($show)), - season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), - library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), - directory: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(::core::option::Option::None), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::content::seasons::ActiveModel { + show_id: Set(::flix_model::id::ShowId::from_raw($show)), + season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), + library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), + directory: Set(::std::path::PathBuf::new().into()), + relative_poster_path: Set(::core::option::Option::None), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_content_season; @@ -506,19 +522,21 @@ pub mod test { }; (@make, $db:expr, $lid:expr, $show:expr, $season:expr, $episode:expr, $count:literal) => { $crate::entity::info::test::make_info_episode!($db, $show, $season, $episode); - $crate::entity::content::episodes::ActiveModel { - show_id: Set(::flix_model::id::ShowId::from_raw($show)), - season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), - episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)), - count: Set($count), - library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), - directory: Set(::std::path::PathBuf::new().into()), - relative_media_path: Set(::std::string::String::new()), - relative_poster_path: Set(::core::option::Option::None), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::content::episodes::ActiveModel { + show_id: Set(::flix_model::id::ShowId::from_raw($show)), + season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), + episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)), + count: Set($count), + library_id: Set(::flix_model::id::LibraryId::from_raw($lid)), + directory: Set(::std::path::PathBuf::new().into()), + relative_media_path: Set(::alloc::string::String::new()), + relative_poster_path: Set(::core::option::Option::None), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_content_episode; @@ -550,7 +568,10 @@ mod tests { use super::super::tests::get_error_kind; use super::super::tests::{noneable, notsettable}; + #[cfg(not(miri))] #[tokio::test] + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] async fn use_test_macros() { let db = new_initialized_memory_db().await; @@ -562,8 +583,10 @@ mod tests { make_content_episode!(&db, 1, 1, 1, 1); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_libraries() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn round_trip_libraries() { let db = new_initialized_memory_db().await; macro_rules! assert_library { @@ -601,8 +624,11 @@ mod tests { assert_library!(&db, 6, Success; last_scan_duration); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_collections() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_collections() { let db = new_initialized_memory_db().await; macro_rules! assert_collection { @@ -656,8 +682,11 @@ mod tests { assert_collection!(&db, 7, None, 1, Success; relative_poster_path); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_movies() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_movies() { let db = new_initialized_memory_db().await; macro_rules! assert_movie { @@ -718,8 +747,11 @@ mod tests { assert_movie!(&db, 8, None, 1, Success; relative_poster_path); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_shows() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_shows() { let db = new_initialized_memory_db().await; macro_rules! assert_show { @@ -776,8 +808,11 @@ mod tests { assert_show!(&db, 7, None, 1, Success; relative_poster_path); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_seasons() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_seasons() { let db = new_initialized_memory_db().await; macro_rules! assert_season { @@ -828,8 +863,11 @@ mod tests { assert_season!(&db, 1, 7, 1, Success; relative_poster_path); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_episodes() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_episodes() { let db = new_initialized_memory_db().await; macro_rules! assert_episode { diff --git a/crates/db/src/entity/info.rs b/crates/db/src/entity/info.rs index 6d50107..7ebb904 100644 --- a/crates/db/src/entity/info.rs +++ b/crates/db/src/entity/info.rs @@ -1,7 +1,7 @@ //! This module contains entities for storing media information such as -//! titles and overviews +//! titles and overviews. -/// Collection entity +/// Collection entity. pub mod collections { use flix_model::id::CollectionId; @@ -9,38 +9,39 @@ pub mod collections { use crate::entity; - /// The database representation of a flix collection + /// The database representation of a flix collection. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_info_collections")] pub struct Model { - /// The collection's ID + /// The collection's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: CollectionId, - /// The collection's title + /// The collection's title. pub title: String, - /// The collection's overview + /// The collection's overview. pub overview: String, - /// The sortable title + /// The sortable title. #[sea_orm(indexed)] pub sort_title: String, - /// The filesystem-safe slug + /// The filesystem-safe slug. #[sea_orm(indexed, unique)] pub fs_slug: String, - /// The url-safe slug + /// The url-safe slug. #[sea_orm(indexed, unique)] pub web_slug: String, - /// Potential content for this collection + /// Potential content for this collection. #[sea_orm(has_one)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Movie entity +/// Movie entity. pub mod movies { use flix_model::id::MovieId; @@ -49,43 +50,44 @@ pub mod movies { use crate::entity; - /// The database representation of a flix movie + /// The database representation of a flix movie. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_info_movies")] pub struct Model { - /// The movie's ID + /// The movie's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: MovieId, - /// The movie's title + /// The movie's title. pub title: String, - /// The movie's tagline + /// The movie's tagline. pub tagline: String, - /// The movie's overview + /// The movie's overview. pub overview: String, - /// The movie's release date + /// The movie's release date. #[sea_orm(indexed)] pub date: NaiveDate, - /// The sortable title + /// The sortable title. #[sea_orm(indexed)] pub sort_title: String, - /// The filesystem-safe slug + /// The filesystem-safe slug. #[sea_orm(indexed, unique)] pub fs_slug: String, - /// The url-safe slug + /// The url-safe slug. #[sea_orm(indexed, unique)] pub web_slug: String, - /// Potential content for this movie + /// Potential content for this movie. #[sea_orm(has_one)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Show entity +/// Show entity. pub mod shows { use flix_model::id::ShowId; @@ -94,50 +96,51 @@ pub mod shows { use crate::entity; - /// The database representation of a flix show + /// The database representation of a flix show. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_info_shows")] pub struct Model { - /// The show's ID + /// The show's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: ShowId, - /// The show's title + /// The show's title. pub title: String, - /// The show's tagline + /// The show's tagline. pub tagline: String, - /// The show's overview + /// The show's overview. pub overview: String, - /// The show's air date + /// The show's air date. #[sea_orm(indexed)] pub date: NaiveDate, - /// The sortable title + /// The sortable title. #[sea_orm(indexed)] pub sort_title: String, - /// The filesystem-safe slug + /// The filesystem-safe slug. #[sea_orm(indexed, unique)] pub fs_slug: String, - /// The url-safe slug + /// The url-safe slug. #[sea_orm(indexed, unique)] pub web_slug: String, - /// Seasons that are part of this show + /// Seasons that are part of this show. #[sea_orm(has_many)] pub seasons: HasMany, - /// Episodes that are part of this show + /// Episodes that are part of this show. #[sea_orm(has_many)] pub episodes: HasMany, - /// Potential content for this show + /// Potential content for this show. #[sea_orm(has_one)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Season entity +/// Season entity. pub mod seasons { use flix_model::id::ShowId; use flix_model::numbers::SeasonNumber; @@ -147,26 +150,26 @@ pub mod seasons { use crate::entity; - /// The database representation of a flix season + /// The database representation of a flix season. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_info_seasons")] pub struct Model { - /// The season's show's ID + /// The season's show's ID. #[sea_orm(primary_key, auto_increment = false)] pub show_id: ShowId, - /// The season's number + /// The season's number. #[sea_orm(primary_key, auto_increment = false)] pub season_number: SeasonNumber, - /// The season's title + /// The season's title. pub title: String, - /// The season's overview + /// The season's overview. pub overview: String, - /// The season's air date + /// The season's air date. #[sea_orm(indexed)] pub date: NaiveDate, - /// The show this season belongs to + /// The show this season belongs to. #[sea_orm( belongs_to, from = "show_id", @@ -175,19 +178,20 @@ pub mod seasons { on_delete = "Cascade" )] pub show: HasOne, - /// Episodes that are part of this season + /// Episodes that are part of this season. #[sea_orm(has_many)] pub episodes: HasMany, - /// Potential content for this season + /// Potential content for this season. #[sea_orm(has_one)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Episode entity +/// Episode entity. pub mod episodes { use flix_model::id::ShowId; use flix_model::numbers::{EpisodeNumber, SeasonNumber}; @@ -197,29 +201,29 @@ pub mod episodes { use crate::entity; - /// The database representation of a flix episode + /// The database representation of a flix episode. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_info_episodes")] pub struct Model { - /// The episode's show's ID + /// The episode's show's ID. #[sea_orm(primary_key, auto_increment = false)] pub show_id: ShowId, - /// The episode's season's number + /// The episode's season's number. #[sea_orm(primary_key, auto_increment = false)] pub season_number: SeasonNumber, - /// The episode's number + /// The episode's number. #[sea_orm(primary_key, auto_increment = false)] pub episode_number: EpisodeNumber, - /// The episode's title + /// The episode's title. pub title: String, - /// The episode's overview + /// The episode's overview. pub overview: String, - /// The episode's air date + /// The episode's air date. #[sea_orm(indexed)] pub date: NaiveDate, - /// The show this episode belongs to + /// The show this episode belongs to. #[sea_orm( belongs_to, from = "show_id", @@ -228,7 +232,7 @@ pub mod episodes { on_delete = "Cascade" )] pub show: HasOne, - /// The season this episode belongs to + /// The season this episode belongs to. #[sea_orm( belongs_to, from = "(show_id, season_number)", @@ -238,101 +242,112 @@ pub mod episodes { )] pub season: HasOne, - /// Potential content for this episode + /// Potential content for this episode. #[sea_orm(has_one)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Macros for creating info entities +/// Macros for creating info entities. #[cfg(test)] pub mod test { macro_rules! make_info_collection { ($db:expr, $id:expr) => { - $crate::entity::info::collections::ActiveModel { - id: Set(::flix_model::id::CollectionId::from_raw($id)), - title: Set(::std::string::String::new()), - overview: Set(::std::string::String::new()), - sort_title: Set(::std::string::String::new()), - fs_slug: Set(format!("C FS {}", $id)), - web_slug: Set(format!("C Web {}", $id)), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::info::collections::ActiveModel { + id: Set(::flix_model::id::CollectionId::from_raw($id)), + title: Set(::alloc::string::String::new()), + overview: Set(::alloc::string::String::new()), + sort_title: Set(::alloc::string::String::new()), + fs_slug: Set(format!("C FS {}", $id)), + web_slug: Set(format!("C Web {}", $id)), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_info_collection; macro_rules! make_info_movie { ($db:expr, $id:expr) => { - $crate::entity::info::movies::ActiveModel { - id: Set(::flix_model::id::MovieId::from_raw($id)), - title: Set(::std::string::String::new()), - tagline: Set(::std::string::String::new()), - overview: Set(::std::string::String::new()), - date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), - sort_title: Set(::std::string::String::new()), - fs_slug: Set(format!("M FS {}", $id)), - web_slug: Set(format!("M Web {}", $id)), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::info::movies::ActiveModel { + id: Set(::flix_model::id::MovieId::from_raw($id)), + title: Set(::alloc::string::String::new()), + tagline: Set(::alloc::string::String::new()), + overview: Set(::alloc::string::String::new()), + date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), + sort_title: Set(::alloc::string::String::new()), + fs_slug: Set(format!("M FS {}", $id)), + web_slug: Set(format!("M Web {}", $id)), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_info_movie; macro_rules! make_info_show { ($db:expr, $id:expr) => { - $crate::entity::info::shows::ActiveModel { - id: Set(::flix_model::id::ShowId::from_raw($id)), - title: Set(::std::string::String::new()), - tagline: Set(::std::string::String::new()), - overview: Set(::std::string::String::new()), - date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), - sort_title: Set(::std::string::String::new()), - fs_slug: Set(format!("S FS {}", $id)), - web_slug: Set(format!("S Web {}", $id)), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::info::shows::ActiveModel { + id: Set(::flix_model::id::ShowId::from_raw($id)), + title: Set(::alloc::string::String::new()), + tagline: Set(::alloc::string::String::new()), + overview: Set(::alloc::string::String::new()), + date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), + sort_title: Set(::alloc::string::String::new()), + fs_slug: Set(format!("S FS {}", $id)), + web_slug: Set(format!("S Web {}", $id)), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_info_show; macro_rules! make_info_season { ($db:expr, $show:expr, $season:expr) => { - $crate::entity::info::seasons::ActiveModel { - show_id: Set(::flix_model::id::ShowId::from_raw($show)), - season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), - title: Set(::std::string::String::new()), - overview: Set(::std::string::String::new()), - date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::info::seasons::ActiveModel { + show_id: Set(::flix_model::id::ShowId::from_raw($show)), + season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), + title: Set(::alloc::string::String::new()), + overview: Set(::alloc::string::String::new()), + date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_info_season; macro_rules! make_info_episode { ($db:expr, $show:expr, $season:expr, $episode:expr) => { - $crate::entity::info::episodes::ActiveModel { - show_id: Set(::flix_model::id::ShowId::from_raw($show)), - season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), - episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)), - title: Set(::std::string::String::new()), - overview: Set(::std::string::String::new()), - date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), - } - .insert($db) - .await - .expect("insert"); + drop( + $crate::entity::info::episodes::ActiveModel { + show_id: Set(::flix_model::id::ShowId::from_raw($show)), + season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), + episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)), + title: Set(::alloc::string::String::new()), + overview: Set(::alloc::string::String::new()), + date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")), + } + .insert($db) + .await + .expect("insert"), + ); }; } pub(crate) use make_info_episode; @@ -355,7 +370,10 @@ mod tests { make_info_collection, make_info_episode, make_info_movie, make_info_season, make_info_show, }; + #[cfg(not(miri))] #[tokio::test] + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] async fn use_test_macros() { let db = new_initialized_memory_db().await; @@ -366,8 +384,10 @@ mod tests { make_info_episode!(&db, 1, 1, 1); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_collections() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn round_trip_collections() { let db = new_initialized_memory_db().await; macro_rules! assert_collection { @@ -409,8 +429,10 @@ mod tests { assert_collection!(&db, 8, NotNullViolation; web_slug); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_movies() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn round_trip_movies() { let db = new_initialized_memory_db().await; macro_rules! assert_movie { @@ -458,8 +480,10 @@ mod tests { assert_movie!(&db, 10, NotNullViolation; web_slug); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_shows() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn round_trip_shows() { let db = new_initialized_memory_db().await; macro_rules! assert_show { @@ -510,8 +534,11 @@ mod tests { assert_show!(&db, 10, NotNullViolation; web_slug); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_seasons() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_seasons() { let db = new_initialized_memory_db().await; macro_rules! assert_season { @@ -561,8 +588,11 @@ mod tests { assert_season!(&db, 1, 7, NotNullViolation; date); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_episodes() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_episodes() { let db = new_initialized_memory_db().await; macro_rules! assert_episode { diff --git a/crates/db/src/entity/mod.rs b/crates/db/src/entity/mod.rs index 6bdc903..5a40e8b 100644 --- a/crates/db/src/entity/mod.rs +++ b/crates/db/src/entity/mod.rs @@ -1,4 +1,10 @@ -//! Entity structs for interacting with the database +//! Entity structs for interacting with the database. + +#![expect(clippy::exhaustive_enums, reason = "reflects the database schema")] +#![expect(clippy::exhaustive_structs, reason = "reflects the database schema")] +#![expect(clippy::derive_partial_eq_without_eq, reason = "#[sea_orm::model]")] +#![expect(clippy::impl_trait_in_params, reason = "#[sea_orm::model]")] +#![expect(clippy::same_name_method, reason = "#[sea_orm::model]")] pub mod content; pub mod info; @@ -13,8 +19,6 @@ mod tests { use flix_tmdb::model::id::TmdbRepr as TmdbReprId; use sea_orm::ActiveValue::Set; - use sea_orm::DatabaseConnection; - use sea_orm::DbErr; use sea_orm::entity::prelude::*; use sea_orm::sqlx::error::ErrorKind; use seamantic::model::id::SeaOrmRepr as SeaOrmReprId; @@ -32,35 +36,36 @@ mod tests { use crate::tests::new_initialized_memory_db; #[derive(Debug)] - pub enum ErrorKindError { - NotRuntimeError, - NotSqlxError, - NotDatabaseError, + pub(crate) enum ErrorKindNot { + Runtime, + Sqlx, + Database, } - pub fn get_error_kind(error: DbErr) -> Result { - let runtime_err = match error { - DbErr::Conn(runtime_err) => runtime_err, - DbErr::Exec(runtime_err) => runtime_err, - DbErr::Query(runtime_err) => runtime_err, - _ => return Err(ErrorKindError::NotRuntimeError), + /// Extract the error kind from a [`DbErr`]. + /// + /// # Errors + /// If the [`DbErr`] internals diverge from [`SqlxError::Database`]. + pub(crate) fn get_error_kind(error: DbErr) -> Result { + let (DbErr::Conn(runtime_err) | DbErr::Exec(runtime_err) | DbErr::Query(runtime_err)) = + error + else { + return Err(ErrorKindNot::Runtime); }; - let sqlx_err = match runtime_err { - sea_orm::RuntimeErr::SqlxError(sqlx_err) => sqlx_err, - _ => return Err(ErrorKindError::NotSqlxError), + let RuntimeErr::SqlxError(sqlx_err) = runtime_err else { + return Err(ErrorKindNot::Sqlx); }; - let database_err = match sqlx_err.as_ref() { - sea_orm::SqlxError::Database(database_err) => database_err, - _ => return Err(ErrorKindError::NotDatabaseError), + let SqlxError::Database(database_err) = sqlx_err.as_ref() else { + return Err(ErrorKindNot::Database); }; Ok(database_err.kind()) } /// Helper macro for writing tests for `ActiveModel` structs where - /// toggling [sea_orm::ActiveValue] is needed + /// toggling [`sea_orm::ActiveValue`] is needed. macro_rules! notsettable { ($field:ident, $value:expr $(, $($skip:ident),+)?) => { if notsettable!(@skip, $field $(, $($skip),+)?) { @@ -76,7 +81,7 @@ mod tests { pub(super) use notsettable; /// Helper macro for writing tests for `ActiveModel` structs where - /// toggling [sea_orm::ActiveValue] is needed + /// toggling [`sea_orm::ActiveValue`] is needed. macro_rules! noneable { ($field:ident, $value:expr $(, $($skip:ident),+)?) => { if noneable!(@skip, $field $(, $($skip),+)?) { @@ -148,6 +153,11 @@ mod tests { } } + /// Initialize a database with data to be deleted. + /// + /// # Panics + /// If any database operation fails, since this is only for testing. + #[expect(clippy::default_numeric_fallback, reason = "unit test")] async fn initialize_deletion_test_database(id: &DbId) -> DatabaseConnection { let db = new_initialized_memory_db().await; @@ -284,12 +294,14 @@ mod tests { db } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_info_collection() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_info_collection() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::info::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw( + _ = entity::info::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw( id.flix.collection, )) .exec(&db) @@ -368,12 +380,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_info_movie() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_info_movie() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::info::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw( + _ = entity::info::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw( id.flix.movie, )) .exec(&db) @@ -452,15 +466,19 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_info_show() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_info_show() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::info::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw(id.flix.show)) - .exec(&db) - .await - .expect("Entity::delete_by_id"); + _ = entity::info::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw( + id.flix.show, + )) + .exec(&db) + .await + .expect("Entity::delete_by_id"); assert_eq!( Ok(1), @@ -534,12 +552,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_info_season() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_info_season() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::info::seasons::Entity::delete_by_id(( + _ = entity::info::seasons::Entity::delete_by_id(( seamantic::model::id::Id::from_raw(id.flix.show), SeasonNumber::new(id.flix.season), )) @@ -619,12 +639,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_info_episodes() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_info_episodes() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::info::episodes::Entity::delete_by_id(( + _ = entity::info::episodes::Entity::delete_by_id(( seamantic::model::id::Id::from_raw(id.flix.show), SeasonNumber::new(id.flix.season), EpisodeNumber::new(id.flix.episode), @@ -705,13 +727,15 @@ mod tests { ); } + #[cfg(not(miri))] #[cfg(feature = "tmdb")] #[tokio::test] - async fn test_delete_tmdb_collection() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_tmdb_collection() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::tmdb::collections::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw( + _ = entity::tmdb::collections::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw( id.tmdb.collection, )) .exec(&db) @@ -790,13 +814,15 @@ mod tests { ); } + #[cfg(not(miri))] #[cfg(feature = "tmdb")] #[tokio::test] - async fn test_delete_tmdb_movie() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_tmdb_movie() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::tmdb::movies::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw( + _ = entity::tmdb::movies::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw( id.tmdb.movie, )) .exec(&db) @@ -875,16 +901,20 @@ mod tests { ); } + #[cfg(not(miri))] #[cfg(feature = "tmdb")] #[tokio::test] - async fn test_delete_tmdb_show() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_tmdb_show() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::tmdb::shows::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw(id.tmdb.show)) - .exec(&db) - .await - .expect("Entity::delete_by_id"); + _ = entity::tmdb::shows::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw( + id.tmdb.show, + )) + .exec(&db) + .await + .expect("Entity::delete_by_id"); assert_eq!( Ok(1), @@ -958,13 +988,15 @@ mod tests { ); } + #[cfg(not(miri))] #[cfg(feature = "tmdb")] #[tokio::test] - async fn test_delete_tmdb_season() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_tmdb_season() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::tmdb::seasons::Entity::delete_by_id(( + _ = entity::tmdb::seasons::Entity::delete_by_id(( flix_tmdb::model::id::Id::from_raw(id.tmdb.show), SeasonNumber::new(id.tmdb.season), )) @@ -1044,13 +1076,15 @@ mod tests { ); } + #[cfg(not(miri))] #[cfg(feature = "tmdb")] #[tokio::test] - async fn test_delete_tmdb_episode() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_tmdb_episode() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::tmdb::episodes::Entity::delete_by_id(( + _ = entity::tmdb::episodes::Entity::delete_by_id(( flix_tmdb::model::id::Id::from_raw(id.tmdb.show), SeasonNumber::new(id.tmdb.season), EpisodeNumber::new(id.tmdb.episode), @@ -1131,12 +1165,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_content_library() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_content_library() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::content::libraries::Entity::delete_by_id(seamantic::model::id::Id::from_raw( + _ = entity::content::libraries::Entity::delete_by_id(seamantic::model::id::Id::from_raw( id.content.library, )) .exec(&db) @@ -1215,12 +1251,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_content_collection() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_content_collection() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::content::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw( + _ = entity::content::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw( id.flix.collection, )) .exec(&db) @@ -1299,12 +1337,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_content_movie() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_content_movie() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::content::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw( + _ = entity::content::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw( id.flix.movie, )) .exec(&db) @@ -1383,12 +1423,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_content_show() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_content_show() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::content::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw( + _ = entity::content::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw( id.flix.show, )) .exec(&db) @@ -1467,12 +1509,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_content_season() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_content_season() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::content::seasons::Entity::delete_by_id(( + _ = entity::content::seasons::Entity::delete_by_id(( seamantic::model::id::Id::from_raw(id.flix.show), SeasonNumber::new(id.flix.season), )) @@ -1552,12 +1596,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_content_episode() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_content_episode() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::content::episodes::Entity::delete_by_id(( + _ = entity::content::episodes::Entity::delete_by_id(( seamantic::model::id::Id::from_raw(id.flix.show), SeasonNumber::new(id.flix.season), EpisodeNumber::new(id.flix.episode), @@ -1638,12 +1684,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_watched_movie() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_watched_movie() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::watched::movies::Entity::delete_by_id(( + _ = entity::watched::movies::Entity::delete_by_id(( seamantic::model::id::Id::from_raw(id.flix.movie), id.watch.user, )) @@ -1723,12 +1771,14 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] - async fn test_delete_watched_episode() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + async fn delete_watched_episode() { let id = DbId::default(); let db = initialize_deletion_test_database(&id).await; - entity::watched::episodes::Entity::delete_by_id(( + _ = entity::watched::episodes::Entity::delete_by_id(( seamantic::model::id::Id::from_raw(id.flix.show), SeasonNumber::new(id.flix.season), EpisodeNumber::new(id.flix.episode), diff --git a/crates/db/src/entity/tmdb.rs b/crates/db/src/entity/tmdb.rs index d562780..c3a25cd 100644 --- a/crates/db/src/entity/tmdb.rs +++ b/crates/db/src/entity/tmdb.rs @@ -1,6 +1,6 @@ -//! This module contains entities for storing dynamic data from TMDB +//! This module contains entities for storing dynamic data from TMDB. -/// Collection entity +/// Collection entity. pub mod collections { use flix_model::id::CollectionId as FlixId; use flix_tmdb::model::id::CollectionId; @@ -10,23 +10,23 @@ pub mod collections { use crate::entity; - /// The database representation of a tmdb collection + /// The database representation of a tmdb collection. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_tmdb_collections")] pub struct Model { - /// The collection's TMDB ID + /// The collection's TMDB ID. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_id: CollectionId, - /// The collection's ID + /// The collection's ID. #[sea_orm(unique)] pub flix_id: FlixId, - /// The date of the last update + /// The date of the last update. pub last_update: DateTime, - /// The number of movies in the collection + /// The number of movies in the collection. pub movie_count: u16, - /// The info for this collection + /// The info for this collection. #[sea_orm( belongs_to, from = "flix_id", @@ -36,15 +36,16 @@ pub mod collections { )] pub info: HasOne, - /// Movies that are in this collection + /// Movies that are in this collection. #[sea_orm(has_many)] pub movies: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Movie entity +/// Movie entity. pub mod movies { use flix_model::id::MovieId as FlixId; use flix_tmdb::model::id::{CollectionId, MovieId}; @@ -56,26 +57,26 @@ pub mod movies { use crate::entity; - /// The database representation of a tmdb movie + /// The database representation of a tmdb movie. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_tmdb_movies")] pub struct Model { - /// The movie's TMDB ID + /// The movie's TMDB ID. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_id: MovieId, - /// The movie's ID + /// The movie's ID. #[sea_orm(unique)] pub flix_id: FlixId, - /// The date of the last update + /// The date of the last update. pub last_update: DateTime, - /// The movie's runtime in seconds + /// The movie's runtime in seconds. pub runtime: Seconds, - /// The TMDB ID of the collection this movie belongs to + /// The TMDB ID of the collection this movie belongs to. #[sea_orm(indexed)] pub collection_id: Option, - /// The collection this movie belongs to + /// The collection this movie belongs to. #[sea_orm( belongs_to, from = "collection_id", @@ -84,7 +85,7 @@ pub mod movies { on_delete = "Cascade" )] pub collection: HasOne, - /// The info for this movie + /// The info for this movie. #[sea_orm( belongs_to, from = "flix_id", @@ -95,10 +96,11 @@ pub mod movies { pub info: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Show entity +/// Show entity. pub mod shows { use flix_model::id::ShowId as FlixId; use flix_tmdb::model::id::ShowId; @@ -108,23 +110,23 @@ pub mod shows { use crate::entity; - /// The database representation of a tmdb show + /// The database representation of a tmdb show. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_tmdb_shows")] pub struct Model { - /// The show's TMDB ID + /// The show's TMDB ID. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_id: ShowId, - /// The show's ID + /// The show's ID. #[sea_orm(unique)] pub flix_id: FlixId, - /// The movie's runtime in seconds + /// The movie's runtime in seconds. pub last_update: DateTime, - /// The number of seasons the show has + /// The number of seasons the show has. pub number_of_seasons: u32, - /// The info for this show + /// The info for this show. #[sea_orm( belongs_to, from = "flix_id", @@ -134,18 +136,19 @@ pub mod shows { )] pub info: HasOne, - /// Seasons that are part of this show + /// Seasons that are part of this show. #[sea_orm(has_many)] pub seasons: HasMany, - /// Episodes that are part of this show + /// Episodes that are part of this show. #[sea_orm(has_many)] pub episodes: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Season entity +/// Season entity. pub mod seasons { use flix_model::id::ShowId as FlixId; use flix_model::numbers::SeasonNumber; @@ -156,27 +159,27 @@ pub mod seasons { use crate::entity; - /// The database representation of a tmdb season + /// The database representation of a tmdb season. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_tmdb_seasons")] pub struct Model { - /// The season's show's TMDB ID + /// The season's show's TMDB ID. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_show: ShowId, - /// The season's TMDB season number + /// The season's TMDB season number. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_season: SeasonNumber, - /// The season's show's ID + /// The season's show's ID. #[sea_orm(unique_key = "flix")] pub flix_show: FlixId, - /// The season's number + /// The season's number. #[sea_orm(unique_key = "flix")] pub flix_season: SeasonNumber, - /// The date of the last update + /// The date of the last update. pub last_update: DateTime, - /// The show this season belongs to + /// The show this season belongs to. #[sea_orm( belongs_to, from = "tmdb_show", @@ -185,7 +188,7 @@ pub mod seasons { on_delete = "Cascade" )] pub show: HasOne, - /// The info for this season + /// The info for this season. #[sea_orm( belongs_to, from = "(flix_show, flix_season)", @@ -195,15 +198,16 @@ pub mod seasons { )] pub info: HasOne, - /// Episodes that are part of this season + /// Episodes that are part of this season. #[sea_orm(has_many)] pub episodes: HasMany, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Season entity +/// Season entity. pub mod episodes { use flix_model::id::ShowId as FlixId; use flix_model::numbers::{EpisodeNumber, SeasonNumber}; @@ -215,35 +219,35 @@ pub mod episodes { use crate::entity; - /// The database representation of a tmdb episode + /// The database representation of a tmdb episode. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_tmdb_episodes")] pub struct Model { - /// The episode's show's TMDB ID + /// The episode's show's TMDB ID. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_show: ShowId, - /// The episode's season's TMDB season number + /// The episode's season's TMDB season number. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_season: SeasonNumber, - /// The episode's TMDB episode number + /// The episode's TMDB episode number. #[sea_orm(primary_key, auto_increment = false)] pub tmdb_episode: EpisodeNumber, - /// The episode's show's ID + /// The episode's show's ID. #[sea_orm(unique_key = "flix")] pub flix_show: FlixId, - /// The episode's season's number + /// The episode's season's number. #[sea_orm(unique_key = "flix")] pub flix_season: SeasonNumber, - /// The episode's number + /// The episode's number. #[sea_orm(unique_key = "flix")] pub flix_episode: EpisodeNumber, - /// The date of the last update + /// The date of the last update. pub last_update: DateTime, - /// The episode's runtime in seconds + /// The episode's runtime in seconds. pub runtime: Seconds, - /// The show this episode belongs to + /// The show this episode belongs to. #[sea_orm( belongs_to, from = "tmdb_show", @@ -252,7 +256,7 @@ pub mod episodes { on_delete = "Cascade" )] pub show: HasOne, - /// The season this episode belongs to + /// The season this episode belongs to. #[sea_orm( belongs_to, from = "(tmdb_show, tmdb_season)", @@ -261,7 +265,7 @@ pub mod episodes { on_delete = "Cascade" )] pub season: HasOne, - /// The info for this episode + /// The info for this episode. #[sea_orm( belongs_to, from = "(flix_show, flix_season, flix_episode)", @@ -272,15 +276,16 @@ pub mod episodes { pub info: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Macros for creating tmdb entities +/// Macros for creating tmdb entities. #[cfg(test)] pub mod test { macro_rules! make_tmdb_collection { ($db:expr, $id:expr, $flix_id:expr) => { - $crate::entity::tmdb::collections::ActiveModel { + _ = $crate::entity::tmdb::collections::ActiveModel { tmdb_id: Set(::flix_tmdb::model::id::CollectionId::from_raw($id)), flix_id: Set(::flix_model::id::CollectionId::from_raw($flix_id)), last_update: Set(::chrono::Utc::now()), @@ -295,7 +300,7 @@ pub mod test { macro_rules! make_tmdb_movie { ($db:expr, $id:expr, $flix_id:expr) => { - $crate::entity::tmdb::movies::ActiveModel { + _ = $crate::entity::tmdb::movies::ActiveModel { tmdb_id: Set(::flix_tmdb::model::id::MovieId::from_raw($id)), flix_id: Set(::flix_model::id::MovieId::from_raw($flix_id)), last_update: Set(::chrono::Utc::now()), @@ -311,7 +316,7 @@ pub mod test { macro_rules! make_tmdb_show { ($db:expr, $id:expr, $flix_id:expr) => { - $crate::entity::tmdb::shows::ActiveModel { + _ = $crate::entity::tmdb::shows::ActiveModel { tmdb_id: Set(::flix_tmdb::model::id::ShowId::from_raw($id)), flix_id: Set(::flix_model::id::ShowId::from_raw($flix_id)), last_update: Set(::chrono::Utc::now()), @@ -326,7 +331,7 @@ pub mod test { macro_rules! make_tmdb_season { ($db:expr, $show:expr, $season:expr, $flix_show:expr, $flix_season:expr) => { - $crate::entity::tmdb::seasons::ActiveModel { + _ = $crate::entity::tmdb::seasons::ActiveModel { tmdb_show: Set(::flix_tmdb::model::id::ShowId::from_raw($show)), tmdb_season: Set(::flix_model::numbers::SeasonNumber::new($season)), flix_show: Set(::flix_model::id::ShowId::from_raw($flix_show)), @@ -342,7 +347,7 @@ pub mod test { macro_rules! make_tmdb_episode { ($db:expr, $show:expr, $season:expr, $episode:expr, $flix_show:expr, $flix_season:expr, $flix_episode:expr) => { - $crate::entity::tmdb::episodes::ActiveModel { + _ = $crate::entity::tmdb::episodes::ActiveModel { tmdb_show: Set(::flix_tmdb::model::id::ShowId::from_raw($show)), tmdb_season: Set(::flix_model::numbers::SeasonNumber::new($season)), tmdb_episode: Set(::flix_model::numbers::EpisodeNumber::new($episode)), @@ -385,7 +390,10 @@ mod tests { make_tmdb_collection, make_tmdb_episode, make_tmdb_movie, make_tmdb_season, make_tmdb_show, }; + #[cfg(not(miri))] #[tokio::test] + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] async fn use_test_macros() { let db = new_initialized_memory_db().await; @@ -402,8 +410,11 @@ mod tests { make_tmdb_episode!(&db, 1, 1, 1, 1, 1, 1); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_collections() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_collections() { let db = new_initialized_memory_db().await; macro_rules! assert_collection { @@ -449,8 +460,11 @@ mod tests { assert_collection!(&db, 6, 6, NotNullViolation; movie_count); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_movies() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_movies() { let db = new_initialized_memory_db().await; macro_rules! assert_movie { @@ -502,8 +516,11 @@ mod tests { assert_movie!(&db, 7, 7, None, ForeignKeyViolation; collection_id); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_shows() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_shows() { let db = new_initialized_memory_db().await; macro_rules! assert_show { @@ -552,8 +569,11 @@ mod tests { assert_show!(&db, 6, 6, NotNullViolation; number_of_seasons); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_seasons() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_seasons() { let db = new_initialized_memory_db().await; macro_rules! assert_season { @@ -607,8 +627,11 @@ mod tests { assert_season!(&db, 1, 7, 1, 7, NotNullViolation; last_update); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_episodes() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_episodes() { let db = new_initialized_memory_db().await; macro_rules! assert_episode { diff --git a/crates/db/src/entity/watched.rs b/crates/db/src/entity/watched.rs index c64bafe..ac26748 100644 --- a/crates/db/src/entity/watched.rs +++ b/crates/db/src/entity/watched.rs @@ -1,6 +1,6 @@ -//! This module contains entities for storing watched information +//! This module contains entities for storing watched information. -/// Collection entity +/// Collection entity. pub mod collections { use flix_model::id::{CollectionId, RawId}; @@ -9,21 +9,21 @@ pub mod collections { use crate::entity; - /// The database representation of a watched movie + /// The database representation of a watched movie. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_watched_collections")] pub struct Model { - /// The collection's ID + /// The collection's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: CollectionId, - /// The user's ID + /// The user's ID. #[sea_orm(primary_key, auto_increment = false)] pub user_id: RawId, - /// The date this collection was watched + /// The date this collection was watched. pub watched_date: DateTime, - /// The info for this collection + /// The info for this collection. #[sea_orm( belongs_to, relation_enum = "Info", @@ -33,15 +33,16 @@ pub mod collections { on_delete = "Cascade" )] pub info: HasOne, - /// The content for this collection + /// The content for this collection. #[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Movie entity +/// Movie entity. pub mod movies { use flix_model::id::{MovieId, RawId}; @@ -50,21 +51,21 @@ pub mod movies { use crate::entity; - /// The database representation of a watched movie + /// The database representation of a watched movie. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_watched_movies")] pub struct Model { - /// The movie's ID + /// The movie's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: MovieId, - /// The user's ID + /// The user's ID. #[sea_orm(primary_key, auto_increment = false)] pub user_id: RawId, - /// The date this movie was watched + /// The date this movie was watched. pub watched_date: DateTime, - /// The info for this movie + /// The info for this movie. #[sea_orm( belongs_to, from = "id", @@ -73,15 +74,16 @@ pub mod movies { on_delete = "Cascade" )] pub info: HasOne, - /// The content for this movie + /// The content for this movie. #[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Show entity +/// Show entity. pub mod shows { use flix_model::id::{RawId, ShowId}; @@ -90,21 +92,21 @@ pub mod shows { use crate::entity; - /// The database representation of a watched movie + /// The database representation of a watched movie. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_watched_shows")] pub struct Model { - /// The show's ID + /// The show's ID. #[sea_orm(primary_key, auto_increment = false)] pub id: ShowId, - /// The user's ID + /// The user's ID. #[sea_orm(primary_key, auto_increment = false)] pub user_id: RawId, - /// The date this show was watched + /// The date this show was watched. pub watched_date: DateTime, - /// The info for this show + /// The info for this show. #[sea_orm( belongs_to, relation_enum = "Info", @@ -114,15 +116,16 @@ pub mod shows { on_delete = "Cascade" )] pub info: HasOne, - /// The content for this show + /// The content for this show. #[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)] pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Season entity +/// Season entity. pub mod seasons { use flix_model::id::{RawId, ShowId}; use flix_model::numbers::SeasonNumber; @@ -132,24 +135,24 @@ pub mod seasons { use crate::entity; - /// The database representation of a watched movie + /// The database representation of a watched movie. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_watched_seasons")] pub struct Model { - /// The season's show's ID + /// The season's show's ID. #[sea_orm(primary_key, auto_increment = false)] pub show_id: ShowId, - /// The season's number + /// The season's number. #[sea_orm(primary_key, auto_increment = false)] pub season_number: SeasonNumber, - /// The user's ID + /// The user's ID. #[sea_orm(primary_key, auto_increment = false)] pub user_id: RawId, - /// The date this season was watched + /// The date this season was watched. pub watched_date: DateTime, - /// The info for this season + /// The info for this season. #[sea_orm( belongs_to, relation_enum = "Info", @@ -159,7 +162,7 @@ pub mod seasons { on_delete = "Cascade" )] pub info: HasOne, - /// The content for this season + /// The content for this season. #[sea_orm( belongs_to, relation_enum = "Content", @@ -170,10 +173,11 @@ pub mod seasons { pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Episode entity +/// Episode entity. pub mod episodes { use flix_model::id::{RawId, ShowId}; use flix_model::numbers::{EpisodeNumber, SeasonNumber}; @@ -183,27 +187,27 @@ pub mod episodes { use crate::entity; - /// The database representation of a watched movie + /// The database representation of a watched movie. #[sea_orm::model] #[derive(Debug, Clone, DeriveEntityModel)] #[sea_orm(table_name = "flix_watched_episodes")] pub struct Model { - /// The episode's show's ID + /// The episode's show's ID. #[sea_orm(primary_key, auto_increment = false)] pub show_id: ShowId, - /// The episode's season's number + /// The episode's season's number. #[sea_orm(primary_key, auto_increment = false)] pub season_number: SeasonNumber, - /// The episode's number + /// The episode's number. #[sea_orm(primary_key, auto_increment = false)] pub episode_number: EpisodeNumber, - /// The user's ID + /// The user's ID. #[sea_orm(primary_key, auto_increment = false)] pub user_id: RawId, - /// The date this episode was watched + /// The date this episode was watched. pub watched_date: DateTime, - /// The info for this episode + /// The info for this episode. #[sea_orm( belongs_to, relation_enum = "Info", @@ -213,7 +217,7 @@ pub mod episodes { on_delete = "Cascade" )] pub info: HasOne, - /// The content for this episode + /// The content for this episode. #[sea_orm( belongs_to, relation_enum = "Content", @@ -224,15 +228,16 @@ pub mod episodes { pub content: HasOne, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} } -/// Macros for creating watched entities +/// Macros for creating watched entities. #[cfg(test)] pub mod test { macro_rules! make_watched_movie { ($db:expr, $id:expr, $user:expr) => { - $crate::entity::watched::movies::ActiveModel { + _ = $crate::entity::watched::movies::ActiveModel { id: Set(::flix_model::id::MovieId::from_raw($id)), user_id: Set($user), watched_date: Set(::chrono::Utc::now()), @@ -246,7 +251,7 @@ pub mod test { macro_rules! make_watched_episode { ($db:expr, $show:expr, $season:expr, $episode:expr, $user:expr) => { - $crate::entity::watched::episodes::ActiveModel { + _ = $crate::entity::watched::episodes::ActiveModel { show_id: Set(::flix_model::id::ShowId::from_raw($show)), season_number: Set(::flix_model::numbers::SeasonNumber::new($season)), episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)), @@ -284,7 +289,10 @@ mod tests { use super::super::tests::notsettable; use super::test::{make_watched_episode, make_watched_movie}; + #[cfg(not(miri))] #[tokio::test] + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] async fn use_test_macros() { let db = new_initialized_memory_db().await; @@ -297,8 +305,11 @@ mod tests { make_watched_episode!(&db, 1, 1, 1, 1); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_movies() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_movies() { let db = new_initialized_memory_db().await; macro_rules! assert_movie { @@ -340,8 +351,11 @@ mod tests { assert_movie!(&db, 5, 1, NotNullViolation; watched_date); } + #[cfg(not(miri))] #[tokio::test] - async fn test_round_trip_episodes() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn round_trip_episodes() { let db = new_initialized_memory_db().await; macro_rules! assert_episode { @@ -394,13 +408,16 @@ mod tests { assert_episode!(&db, 7, 1, 1, 1, NotNullViolation; watched_date); } + #[cfg(not(miri))] #[tokio::test] - async fn test_query_seasons() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn query_seasons() { let db = new_initialized_memory_db().await; macro_rules! assert_season { ($db:expr, $show:literal, $season:literal, $uid:literal, Watched) => { - assert_season!(@find, $db, $show, $season, $uid) + _ = assert_season!(@find, $db, $show, $season, $uid) .ok_or(()) .expect("is none"); }; @@ -462,13 +479,16 @@ mod tests { assert_season!(&db, 1, 2, 3, Unwatched); } + #[cfg(not(miri))] #[tokio::test] - async fn test_query_shows() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn query_shows() { let db = new_initialized_memory_db().await; macro_rules! assert_show { ($db:expr, $show:literal, $uid:literal, Watched) => { - assert_show!(@find, $db, $show, $uid) + _ = assert_show!(@find, $db, $show, $uid) .ok_or(()) .expect("is none"); }; @@ -517,13 +537,16 @@ mod tests { assert_show!(&db, 1, 2, Unwatched); } + #[cfg(not(miri))] #[tokio::test] - async fn test_query_collections() { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + #[expect(clippy::default_numeric_fallback, reason = "unit test")] + async fn query_collections() { let db = new_initialized_memory_db().await; macro_rules! assert_collection { ($db:expr, $id:literal, $uid:literal, Watched) => { - assert_collection!(@find, $db, $id, $uid) + _ = assert_collection!(@find, $db, $id, $uid) .ok_or(()) .expect("is none"); }; diff --git a/crates/db/src/lib.rs b/crates/db/src/lib.rs index 369c769..5d64ff1 100644 --- a/crates/db/src/lib.rs +++ b/crates/db/src/lib.rs @@ -1,7 +1,10 @@ -//! flix-db provides types for storing persistent data about media +//! flix-db provides types for storing persistent data about media. #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(test)] +extern crate alloc; + pub mod connection; pub mod entity; pub mod migration; @@ -12,7 +15,8 @@ mod tests { use crate::connection::Connection; - pub async fn new_initialized_memory_db() -> DatabaseConnection { + #[expect(clippy::missing_panics_doc, reason = "unit test")] + pub(crate) async fn new_initialized_memory_db() -> DatabaseConnection { let options = ConnectOptions::new("sqlite::memory:"); let db = Database::connect(options) diff --git a/crates/db/src/migration/m_000001/collections.rs b/crates/db/src/migration/m_000001/collections.rs index 54e25a4..8d222fc 100644 --- a/crates/db/src/migration/m_000001/collections.rs +++ b/crates/db/src/migration/m_000001/collections.rs @@ -1,18 +1,22 @@ +//! Custom views for collections. + use sea_orm::prelude::*; use sea_orm::sea_query::Table; -use sea_orm::{ConnectionTrait, DbBackend, Statement}; +use sea_orm::{DbBackend, Statement}; use sea_orm_migration::prelude::*; -pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { +/// # Errors +/// Database operations can fail. +pub(crate) async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("flix_watched_collections").to_owned()) .await?; - manager + _ = manager .get_connection() .execute_raw(Statement::from_string( DbBackend::Sqlite, - r#" + " CREATE VIEW flix_watched_collections AS WITH RECURSIVE watched_items AS ( @@ -81,22 +85,24 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { ) GROUP BY ci.parent_id, wi.user_id ; - "#, + ", )) .await?; Ok(()) } -pub async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> { - manager +/// # Errors +/// Database operations can fail. +pub(crate) async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> { + _ = manager .get_connection() .execute_raw(Statement::from_string( DbBackend::Sqlite, - r#" + " DROP VIEW flix_watched_collections ; - "#, + ", )) .await?; diff --git a/crates/db/src/migration/m_000001.rs b/crates/db/src/migration/m_000001/mod.rs similarity index 78% rename from crates/db/src/migration/m_000001.rs rename to crates/db/src/migration/m_000001/mod.rs index 70cc3ea..26d1b24 100644 --- a/crates/db/src/migration/m_000001.rs +++ b/crates/db/src/migration/m_000001/mod.rs @@ -11,10 +11,16 @@ mod collections; mod seasons; mod shows; +/// The migration entry point. #[derive(DeriveMigrationName)] pub(super) struct Migration; #[async_trait::async_trait] +#[expect( + elided_lifetimes_in_paths, + reason = "async_trait causes lifetimes to be strange" +)] +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { seasons::up(manager).await?; diff --git a/crates/db/src/migration/m_000001/seasons.rs b/crates/db/src/migration/m_000001/seasons.rs index 046c814..7144e90 100644 --- a/crates/db/src/migration/m_000001/seasons.rs +++ b/crates/db/src/migration/m_000001/seasons.rs @@ -1,18 +1,22 @@ +//! Custom views for seasons. + use sea_orm::prelude::*; use sea_orm::sea_query::Table; -use sea_orm::{ConnectionTrait, DbBackend, Statement}; +use sea_orm::{DbBackend, Statement}; use sea_orm_migration::prelude::*; -pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { +/// # Errors +/// Database operations can fail. +pub(crate) async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("flix_watched_seasons").to_owned()) .await?; - manager + _ = manager .get_connection() .execute_raw(Statement::from_string( DbBackend::Sqlite, - r#" + " CREATE VIEW flix_watched_seasons AS SELECT w.show_id, @@ -36,22 +40,24 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { ) GROUP BY w.show_id, w.season_number, w.user_id ; - "#, + ", )) .await?; Ok(()) } -pub async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> { - manager +/// # Errors +/// Database operations can fail. +pub(crate) async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> { + _ = manager .get_connection() .execute_raw(Statement::from_string( DbBackend::Sqlite, - r#" + " DROP VIEW flix_watched_seasons ; - "#, + ", )) .await?; diff --git a/crates/db/src/migration/m_000001/shows.rs b/crates/db/src/migration/m_000001/shows.rs index b1fcd7f..dae6514 100644 --- a/crates/db/src/migration/m_000001/shows.rs +++ b/crates/db/src/migration/m_000001/shows.rs @@ -1,18 +1,22 @@ +//! Custom views for shows. + use sea_orm::prelude::*; use sea_orm::sea_query::Table; -use sea_orm::{ConnectionTrait, DbBackend, Statement}; +use sea_orm::{DbBackend, Statement}; use sea_orm_migration::prelude::*; -pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { +/// # Errors +/// Database operations can fail. +pub(crate) async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("flix_watched_shows").to_owned()) .await?; - manager + _ = manager .get_connection() .execute_raw(Statement::from_string( DbBackend::Sqlite, - r#" + " CREATE VIEW flix_watched_shows AS SELECT w.show_id as id, @@ -33,22 +37,24 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { ) GROUP BY w.show_id, w.user_id ; - "#, + ", )) .await?; Ok(()) } -pub async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> { - manager +/// # Errors +/// Database operations can fail. +pub(crate) async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> { + _ = manager .get_connection() .execute_raw(Statement::from_string( DbBackend::Sqlite, - r#" + " DROP VIEW flix_watched_shows ; - "#, + ", )) .await?; diff --git a/crates/db/src/migration/mod.rs b/crates/db/src/migration/mod.rs index f0221d7..c618dad 100644 --- a/crates/db/src/migration/mod.rs +++ b/crates/db/src/migration/mod.rs @@ -1,4 +1,4 @@ -//! Migrations for maintaining the database schema +//! Migrations for maintaining the database schema. seamantic::migrations! { "seaql_migrations_flix"; diff --git a/crates/flix/Cargo.toml b/crates/flix/Cargo.toml index e9b4466..6d4421f 100644 --- a/crates/flix/Cargo.toml +++ b/crates/flix/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "flix" -version = "0.0.20" -license-file.workspace = true - +version = "0.1.0" description = "Mechanisms for interacting with flix media" -repository = "https://github.com/QuantumShade/flix" -categories = [] +repository = "https://git.skrundz.dev/quantumshade/flix" +keywords = ["flix"] +categories = ["multimedia"] +include = ["/src"] +publish = true edition.workspace = true rust-version.workspace = true +license-file.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/crates/flix/src/lib.rs b/crates/flix/src/lib.rs index bb29afd..7662060 100644 --- a/crates/flix/src/lib.rs +++ b/crates/flix/src/lib.rs @@ -1,4 +1,4 @@ -//! flix provides mechanisms for interacting with flix media +//! flix provides mechanisms for interacting with flix media. #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index b54655a..8be2127 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "flix-fs" -version = "0.0.20" -license-file.workspace = true - +version = "0.1.0" description = "Filesystem scanner for flix media" -repository = "https://github.com/QuantumShade/flix" -categories = [] +repository = "https://git.skrundz.dev/quantumshade/flix" +keywords = ["flix"] +categories = ["multimedia"] +include = ["/src"] +publish = true edition.workspace = true rust-version.workspace = true +license-file.workspace = true [package.metadata.docs.rs] all-features = true @@ -23,5 +25,8 @@ thiserror = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true, features = ["fs"] } +[dev-dependencies] +tempfile = { workspace = true } + [lints] workspace = true diff --git a/crates/fs/src/error.rs b/crates/fs/src/error.rs index 8f5989e..8fe845a 100644 --- a/crates/fs/src/error.rs +++ b/crates/fs/src/error.rs @@ -1,39 +1,43 @@ +//! Filesystem errors. + use std::io; /// The error type for filesystem scanning operations. #[derive(Debug, thiserror::Error)] +#[expect(clippy::error_impl_error, reason = "Error is a good name here")] +#[expect(clippy::exhaustive_enums, reason = "unlikely to add more variants")] pub enum Error { - /// fs::read_dir failed + /// `fs::read_dir` failed. #[error("fs::read_dir: {0}")] ReadDir(io::Error), - /// fs::read_dir::next_entry failed + /// `fs::read_dir::next_entry` failed. #[error("fs::read_dir::next_entry: {0}")] ReadDirEntry(io::Error), - /// fs::read_dir::file_type failed + /// `fs::read_dir::file_type` failed. #[error("fs::read_dir::file_type: {0}")] FileType(io::Error), - /// There is an unexpected file in the directory + /// There is an unexpected file in the directory. #[error("unexpected file")] UnexpectedFile, - /// There is an unexpected folder in the directory + /// There is an unexpected folder in the directory. #[error("unexpected folder")] UnexpectedFolder, - /// There is an unexpected non-file item in the directory + /// There is an unexpected non-file item in the directory. #[error("unexpected non-file")] UnexpectedNonFile, - /// There are multiple media files in the directory + /// There are multiple media files in the directory. #[error("duplicate media file")] DuplicateMediaFile, - /// There are multiple poster files in the directory + /// There are multiple poster files in the directory. #[error("duplicate poster file")] DuplicatePosterFile, - /// The directory contains incomplete flix media + /// The directory contains incomplete flix media. #[error("incomplete")] Incomplete, - /// Some data is inconsistent with the folder structure + /// Some data is inconsistent with the folder structure. #[error("inconsistent")] Inconsistent, } diff --git a/crates/fs/src/item.rs b/crates/fs/src/item.rs index 2f75868..53f3636 100644 --- a/crates/fs/src/item.rs +++ b/crates/fs/src/item.rs @@ -1,18 +1,21 @@ +//! Filesystem scan result items. + use std::path::PathBuf; use crate::Error; -/// An item returned by scanner streams +/// An item returned by scanner streams. #[derive(Debug)] +#[expect(clippy::exhaustive_structs, reason = "unlikely to change")] pub struct Item { - /// The path of the item + /// The path of the item. pub path: PathBuf, - /// The event relating to the item + /// The event relating to the item. pub event: Result, } impl Item { - /// Helper function for mapping the inner event [Result] + /// Helper function for mapping the inner event [Result]. #[inline] pub fn map U>(self, op: F) -> Item { Item { diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index cd412d6..cf70f1d 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,4 +1,4 @@ -//! flix-fs provides filesystem scanners for flix media +//! flix-fs provides filesystem scanners for flix media. #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/crates/fs/src/macros.rs b/crates/fs/src/macros.rs index 09feef5..96d8712 100644 --- a/crates/fs/src/macros.rs +++ b/crates/fs/src/macros.rs @@ -1,3 +1,6 @@ +//! Helper macros. + +/// Defines media file extensions. macro_rules! is_media_extension { () => { Some("mp4" | "mkv") @@ -5,6 +8,7 @@ macro_rules! is_media_extension { } pub(super) use is_media_extension; +/// Defines image file expensions. macro_rules! is_image_extension { () => { Some("png" | "jpg") diff --git a/crates/fs/src/scanner/collection.rs b/crates/fs/src/scanner/collection.rs index e9f922b..763208a 100644 --- a/crates/fs/src/scanner/collection.rs +++ b/crates/fs/src/scanner/collection.rs @@ -1,4 +1,4 @@ -//! The collection scanner will scan a folder and its children +//! The collection scanner will scan a folder and its children. use core::pin::Pin; use std::ffi::OsStr; @@ -17,24 +17,27 @@ use crate::scanner::{ CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, generic, movie, show, }; -/// A collection item +/// A collection item. pub type Item = crate::Item; -/// The scanner for collections +/// The scanner for collections. +#[derive(Debug)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] pub enum Scanner { - /// A scanned collection + /// A scanned collection. Collection(CollectionScan), - /// A scanned movie + /// A scanned movie. Movie(MovieScan), - /// A scanned show + /// A scanned show. Show(ShowScan), - /// A scanned episode + /// A scanned episode. Season(SeasonScan), - /// A scanned episode + /// A scanned episode. Episode(EpisodeScan), } impl From for Scanner { + #[inline] fn from(value: movie::Scanner) -> Self { match value { movie::Scanner::Movie(m) => Self::Movie(m), @@ -43,6 +46,7 @@ impl From for Scanner { } impl From for Scanner { + #[inline] fn from(value: show::Scanner) -> Self { match value { show::Scanner::Show(s) => Self::Show(s), @@ -53,6 +57,7 @@ impl From for Scanner { } impl From for Scanner { + #[inline] fn from(value: generic::Scanner) -> Self { match value { generic::Scanner::Collection(c) => Self::Collection(c), @@ -65,7 +70,12 @@ impl From for Scanner { } impl Scanner { - /// Scan a folder for a collection + /// Scan a folder for a collection. + #[inline] + #[must_use] + #[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")] + #[allow(tail_expr_drop_order, reason = "bug in stream! macro")] + #[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")] pub fn scan_collection( path: &Path, parent_ref: Option>, @@ -151,7 +161,7 @@ impl Scanner { for await event in generic::Scanner::scan_detect_folder(&subdir, Some(id_ref.clone())) { - yield event.map(|e| e.into()); + yield event.map(Into::into); } } })) diff --git a/crates/fs/src/scanner/episode.rs b/crates/fs/src/scanner/episode.rs index 837e9f8..6ddfa2a 100644 --- a/crates/fs/src/scanner/episode.rs +++ b/crates/fs/src/scanner/episode.rs @@ -1,4 +1,4 @@ -//! The episode scanner will scan a folder and exit +//! The episode scanner will scan a folder and exit. use std::ffi::OsStr; use std::path::Path; @@ -15,17 +15,23 @@ use crate::Error; use crate::macros::{is_image_extension, is_media_extension}; use crate::scanner::{EpisodeScan, MediaRef}; -/// An episode item +/// An episode item. pub type Item = crate::Item; -/// The scanner for epispdes +/// The scanner for epispdes. +#[derive(Debug)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] pub enum Scanner { - /// A scanned episode + /// A scanned episode. Episode(EpisodeScan), } impl Scanner { - /// Scan a folder for an episode + /// Scan a folder for an episode. + #[inline] + #[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")] + #[allow(tail_expr_drop_order, reason = "bug in stream! macro")] + #[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")] pub fn scan_episode( path: &Path, show_ref: MediaRef, @@ -62,6 +68,7 @@ impl Scanner { continue; } }; + #[expect(clippy::filetype_is_file, reason = "we only want regular files")] if !filetype.is_file() { yield Item { path, diff --git a/crates/fs/src/scanner/generic.rs b/crates/fs/src/scanner/generic.rs index c114300..c7d5451 100644 --- a/crates/fs/src/scanner/generic.rs +++ b/crates/fs/src/scanner/generic.rs @@ -19,28 +19,32 @@ use crate::scanner::{ CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, collection, movie, show, }; +/// Regex for detecting a media folder. static MEDIA_FOLDER_REGEX: OnceLock = OnceLock::new(); +/// Regex for detecting a season folder. static SEASON_FOLDER_REGEX: OnceLock = OnceLock::new(); -/// A collection item +/// A collection item. pub type Item = crate::Item; -/// The scanner for collections +/// The scanner for collections. #[derive(Debug)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] pub enum Scanner { - /// A scanned collection + /// A scanned collection. Collection(CollectionScan), - /// A scanned movie + /// A scanned movie. Movie(MovieScan), - /// A scanned show + /// A scanned show. Show(ShowScan), - /// A scanned episode + /// A scanned episode. Season(SeasonScan), - /// A scanned episode + /// A scanned episode. Episode(EpisodeScan), } impl From for Scanner { + #[inline] fn from(value: collection::Scanner) -> Self { match value { collection::Scanner::Collection(c) => Self::Collection(c), @@ -53,6 +57,7 @@ impl From for Scanner { } impl From for Scanner { + #[inline] fn from(value: movie::Scanner) -> Self { match value { movie::Scanner::Movie(m) => Self::Movie(m), @@ -61,6 +66,7 @@ impl From for Scanner { } impl From for Scanner { + #[inline] fn from(value: show::Scanner) -> Self { match value { show::Scanner::Show(s) => Self::Show(s), @@ -71,13 +77,13 @@ impl From for Scanner { } impl Scanner { - /// Helper function for stripping allowed numerical prefixes for sorting ("01 - ") - fn strip_numeric_prefix(original: &str) -> &str { - let mut s = original; - while let Some('0'..='9') = s.chars().next() { - s = &s[1..] - } - s.strip_prefix(" - ").unwrap_or(original) + /// Helper function for stripping allowed numerical prefixes for sorting ("01 - "). + #[inline] + fn strip_numeric_prefix(string: &str) -> &str { + string + .trim_start_matches(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) + .strip_prefix(" - ") + .unwrap_or(string) } /// Detect the type of a folder and call the correct scanner. Use @@ -85,6 +91,13 @@ impl Scanner { /// - Collections /// - Movies /// - Shows + /// + /// # Panics + /// If a static regex is invalid. + #[inline] + #[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")] + #[allow(tail_expr_drop_order, reason = "bug in stream! macro")] + #[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")] pub fn scan_detect_folder( path: &Path, parent: Option>, @@ -95,12 +108,14 @@ impl Scanner { Show, } + #[expect(clippy::panic, reason = "static regex string")] let media_folder_re = MEDIA_FOLDER_REGEX.get_or_init(|| { Regex::new(r"^[[[:alnum:]]' -]+ \([[:digit:]]+\)( \[[[:digit:]]+\])?$") .unwrap_or_else(|err| panic!("regex is invalid: {err}")) }); + #[expect(clippy::panic, reason = "static regex string")] let season_folder_re = SEASON_FOLDER_REGEX.get_or_init(|| { - Regex::new(r"^S[[:digit:]]+$").unwrap_or_else(|err| panic!("regex is invalid: {err}")) + Regex::new("^S[[:digit:]]+$").unwrap_or_else(|err| panic!("regex is invalid: {err}")) }); stream!({ @@ -204,7 +219,7 @@ impl Scanner { }; for await event in collection::Scanner::scan_collection(path, parent, id) { - yield event.map(|e| e.into()); + yield event.map(Into::into); } } MediaType::Movie => { @@ -214,7 +229,7 @@ impl Scanner { }; for await event in movie::Scanner::scan_movie(path, parent, id) { - yield event.map(|e| e.into()); + yield event.map(Into::into); } } MediaType::Show => { @@ -224,7 +239,7 @@ impl Scanner { }; for await event in show::Scanner::scan_show(path, parent, id) { - yield event.map(|e| e.into()); + yield event.map(Into::into); } } } diff --git a/crates/fs/src/scanner/library.rs b/crates/fs/src/scanner/library.rs index 37fa74d..c1d8012 100644 --- a/crates/fs/src/scanner/library.rs +++ b/crates/fs/src/scanner/library.rs @@ -1,5 +1,5 @@ //! The library scanner will scan an entire directory using the generic -//! scanner +//! scanner. use std::ffi::OsStr; use std::path::Path; @@ -12,14 +12,20 @@ use tokio_stream::wrappers::ReadDirStream; use crate::Error; use crate::scanner::generic; -/// A library item +/// A library item. pub type Item = crate::Item; -/// The scanner for collections +/// The scanner for collections. +#[derive(Debug, Clone, Copy)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] pub enum Scanner {} impl Scanner { - /// Scan a folder for a library + /// Scan a folder for a library. + #[inline] + #[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")] + #[allow(tail_expr_drop_order, reason = "bug in stream! macro")] + #[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")] pub fn scan_library(path: &Path) -> impl Stream { stream!({ let dirs = match fs::read_dir(path).await { @@ -58,7 +64,7 @@ impl Scanner { match path.extension().and_then(OsStr::to_str) { Some(_) | None => { yield Item { - path: path.to_owned(), + path: path.clone(), event: Err(Error::UnexpectedFile), }; } diff --git a/crates/fs/src/scanner/mod.rs b/crates/fs/src/scanner/mod.rs index aeddde4..d1d49fa 100644 --- a/crates/fs/src/scanner/mod.rs +++ b/crates/fs/src/scanner/mod.rs @@ -1,6 +1,6 @@ -//! This module contains all of the filesystem scanner modules +//! This module contains all of the filesystem scanner modules. //! -//! The most common scanner to use is [generic::Scanner] which will +//! The most common scanner to use is [`generic::Scanner`] which will //! automatically detect and use the appropriate scanner. use flix_model::id::{CollectionId, MovieId, ShowId}; @@ -18,82 +18,118 @@ pub mod episode; pub mod season; pub mod show; -/// A reference to a piece of media +/// A reference to a piece of media. +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] #[derive(Debug, Clone)] pub enum MediaRef { - /// An explicit ID + /// An explicit ID. Id(ID), - /// A filesystem slug + /// A filesystem slug. Slug(String), } impl MediaRef { - /// Get the slug if it exists + /// Get the slug if it exists. + #[inline] pub fn into_slug(self) -> Option { match self { - MediaRef::Id(_) => None, - MediaRef::Slug(slug) => Some(slug), + Self::Id(_) => None, + Self::Slug(slug) => Some(slug), } } } -/// A scanned collection +/// A scanned collection. #[derive(Debug)] +#[expect( + clippy::exhaustive_structs, + reason = "it will be a breaking change when more information is scanned" +)] pub struct CollectionScan { - /// The ID of the parent collection (if any) + /// The ID of the parent collection (if any). pub parent_ref: Option>, - /// The ID of the collection + /// The ID of the collection. pub id_ref: MediaRef, - /// The file name of the poster file + /// The file name of the poster file. pub poster_file_name: Option, } -/// A scanned movie +/// A scanned movie. #[derive(Debug)] +#[expect( + clippy::exhaustive_structs, + reason = "it will be a breaking change when more information is scanned" +)] pub struct MovieScan { - /// The ID of the parent collection (if any) + /// The ID of the parent collection (if any). pub parent_ref: Option>, - /// The ID of the movie + /// The ID of the movie. pub id_ref: MediaRef, - /// The file name of the media file + /// The file name of the media file. pub media_file_name: String, - /// The file name of the poster file + /// The file name of the poster file. pub poster_file_name: Option, } -/// A scanned show +/// A scanned show. #[derive(Debug)] +#[expect( + clippy::exhaustive_structs, + reason = "it will be a breaking change when more information is scanned" +)] pub struct ShowScan { - /// The ID of the parent collection (if any) + /// The ID of the parent collection (if any). pub parent_ref: Option>, - /// The ID of the show + /// The ID of the show. pub id_ref: MediaRef, - /// The file name of the poster file + /// The file name of the poster file. pub poster_file_name: Option, } -/// A scanned season +/// A scanned season. #[derive(Debug)] +#[expect( + clippy::exhaustive_structs, + reason = "it will be a breaking change when more information is scanned" +)] pub struct SeasonScan { - /// The ID of the show this season belongs to + /// The ID of the show this season belongs to. pub show_ref: MediaRef, - /// The season this episode belongs to + /// The season this episode belongs to. pub season: SeasonNumber, - /// The file name of the poster file + /// The file name of the poster file. pub poster_file_name: Option, } -/// A scanned episode +/// A scanned episode. #[derive(Debug)] +#[expect( + clippy::exhaustive_structs, + reason = "it will be a breaking change when more information is scanned" +)] pub struct EpisodeScan { - /// The ID of the show this episode belongs to + /// The ID of the show this episode belongs to. pub show_ref: MediaRef, - /// The season this episode belongs to + /// The season this episode belongs to. pub season: SeasonNumber, - /// The number(s) of this episode + /// The number(s) of this episode. pub episode: EpisodeNumbers, - /// The file name of the media file + /// The file name of the media file. pub media_file_name: String, - /// The file name of the poster file + /// The file name of the poster file. pub poster_file_name: Option, } + +#[cfg(test)] +mod tests { + use super::{MediaRef, MovieId, library}; + + #[cfg(not(miri))] + #[test] + #[expect(clippy::missing_panics_doc, reason = "unit test")] + fn use_fn() { + let tmp = tempfile::tempdir().expect("temporary directory exists"); + drop(library::Scanner::scan_library(tmp.path())); + drop(MediaRef::Id(MovieId::from_raw(0)).into_slug()); + } +} diff --git a/crates/fs/src/scanner/movie.rs b/crates/fs/src/scanner/movie.rs index 16d4717..2b9997b 100644 --- a/crates/fs/src/scanner/movie.rs +++ b/crates/fs/src/scanner/movie.rs @@ -1,4 +1,4 @@ -//! The movie scanner will scan a folder and exit +//! The movie scanner will scan a folder and exit. use std::ffi::OsStr; use std::path::Path; @@ -14,17 +14,23 @@ use crate::Error; use crate::macros::{is_image_extension, is_media_extension}; use crate::scanner::{MediaRef, MovieScan}; -/// An movie item +/// An movie item. pub type Item = crate::Item; -/// The scanner for movies +/// The scanner for movies. +#[derive(Debug)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] pub enum Scanner { - /// A scanned movie + /// A scanned movie. Movie(MovieScan), } impl Scanner { - /// Scan a folder for a movie + /// Scan a folder for a movie. + #[inline] + #[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")] + #[allow(tail_expr_drop_order, reason = "bug in stream! macro")] + #[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")] pub fn scan_movie( path: &Path, parent_ref: Option>, @@ -60,6 +66,7 @@ impl Scanner { continue; } }; + #[expect(clippy::filetype_is_file, reason = "we only want regular files")] if !filetype.is_file() { yield Item { path, diff --git a/crates/fs/src/scanner/season.rs b/crates/fs/src/scanner/season.rs index e411d86..7f818af 100644 --- a/crates/fs/src/scanner/season.rs +++ b/crates/fs/src/scanner/season.rs @@ -1,10 +1,10 @@ -//! The episode scanner will scan a folder and its children +//! The episode scanner will scan a folder and its children. use std::ffi::OsStr; use std::path::Path; use flix_model::id::ShowId; -use flix_model::numbers::{EpisodeNumber, EpisodeNumbers, SeasonNumber}; +use flix_model::numbers::{EpisodeNumbers, SeasonNumber}; use async_stream::stream; use tokio::fs; @@ -15,18 +15,21 @@ use crate::Error; use crate::macros::is_image_extension; use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, episode}; -/// A season item +/// A season item. pub type Item = crate::Item; -/// The scanner for seasons +/// The scanner for seasons. +#[derive(Debug)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] pub enum Scanner { - /// A scanned season + /// A scanned season. Season(SeasonScan), - /// A scanned episode + /// A scanned episode. Episode(EpisodeScan), } impl From for Scanner { + #[inline] fn from(value: episode::Scanner) -> Self { match value { episode::Scanner::Episode(e) => Self::Episode(e), @@ -35,7 +38,11 @@ impl From for Scanner { } impl Scanner { - /// Scan a folder for a season and its episodes + /// Scan a folder for a season and its episodes. + #[inline] + #[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")] + #[allow(tail_expr_drop_order, reason = "bug in stream! macro")] + #[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")] pub fn scan_season( path: &Path, show_ref: MediaRef, @@ -158,7 +165,7 @@ impl Scanner { let Ok(episode_numbers) = e_str .split('E') - .map(|s| s.parse::()) + .map(str::parse) .collect::, _>>() else { yield Item { @@ -181,7 +188,7 @@ impl Scanner { season_number, episode_numbers, ) { - yield event.map(|e| e.into()); + yield event.map(Into::into); } } }) diff --git a/crates/fs/src/scanner/show.rs b/crates/fs/src/scanner/show.rs index 355324e..de15a4f 100644 --- a/crates/fs/src/scanner/show.rs +++ b/crates/fs/src/scanner/show.rs @@ -1,4 +1,4 @@ -//! The show scanner will scan a folder and its children +//! The show scanner will scan a folder and its children. use std::ffi::OsStr; use std::path::Path; @@ -15,20 +15,23 @@ use crate::Error; use crate::macros::is_image_extension; use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, ShowScan, season}; -/// A show item +/// A show item. pub type Item = crate::Item; -/// The scanner for shows +/// The scanner for shows. +#[derive(Debug)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to change")] pub enum Scanner { - /// A scanned show + /// A scanned show. Show(ShowScan), - /// A scanned season + /// A scanned season. Season(SeasonScan), - /// A scanned episode + /// A scanned episode. Episode(EpisodeScan), } impl From for Scanner { + #[inline] fn from(value: season::Scanner) -> Self { match value { season::Scanner::Season(s) => Self::Season(s), @@ -38,7 +41,11 @@ impl From for Scanner { } impl Scanner { - /// Scan a folder for a show and its seasons/episodes + /// Scan a folder for a show and its seasons/episodes. + #[inline] + #[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")] + #[allow(tail_expr_drop_order, reason = "bug in stream! macro")] + #[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")] pub fn scan_show( path: &Path, parent_ref: Option>, @@ -143,7 +150,7 @@ impl Scanner { for await event in season::Scanner::scan_season(&season_dir, id_ref.clone(), season_number) { - yield event.map(|e| e.into()); + yield event.map(Into::into); } } }) diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml index d50225c..c8bfaf0 100644 --- a/crates/model/Cargo.toml +++ b/crates/model/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "flix-model" -version = "0.0.20" -license-file.workspace = true - +version = "0.1.0" description = "Core types for flix data" -repository = "https://github.com/QuantumShade/flix" -categories = [] +repository = "https://git.skrundz.dev/quantumshade/flix" +keywords = ["flix"] +categories = ["multimedia"] +include = ["/src"] +publish = true edition.workspace = true rust-version.workspace = true +license-file.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/crates/model/src/id.rs b/crates/model/src/id.rs index ccd36da..a490df0 100644 --- a/crates/model/src/id.rs +++ b/crates/model/src/id.rs @@ -1,26 +1,49 @@ -//! This module contains types relating to flix media IDs +//! This module contains types relating to flix media IDs. + +#![expect(clippy::module_name_repetitions, reason = "ID types end with Id")] use seamantic::model::id::Id; -/// Type alias for the raw ID representation +/// Type alias for the raw ID representation. pub use seamantic::model::id::SeaOrmRepr as RawId; #[doc(hidden)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy)] pub enum Library {} -/// Type alias for a library ID +/// Type alias for a library ID. pub type LibraryId = Id; #[doc(hidden)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy)] pub enum Collection {} -/// Type alias for a collection ID +/// Type alias for a collection ID. pub type CollectionId = Id; #[doc(hidden)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy)] pub enum Movie {} -/// Type alias for a movie ID +/// Type alias for a movie ID. pub type MovieId = Id; #[doc(hidden)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy)] pub enum Show {} -/// Type alias for a show ID +/// Type alias for a show ID. pub type ShowId = Id; + +#[cfg(test)] +mod tests { + use super::{CollectionId, LibraryId, MovieId, ShowId}; + + #[test] + fn use_types() { + _ = CollectionId::from_raw(0); + _ = LibraryId::from_raw(0); + _ = MovieId::from_raw(0); + _ = ShowId::from_raw(0); + } +} diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index 7fbd2df..e9718b4 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -1,4 +1,4 @@ -//! flix-model provides core types for flix data +//! flix-model provides core types for flix data. #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/crates/model/src/numbers.rs b/crates/model/src/numbers.rs index b2942ef..dd9c18c 100644 --- a/crates/model/src/numbers.rs +++ b/crates/model/src/numbers.rs @@ -1,4 +1,4 @@ -//! This module contains season and episode numbers and related errors +//! This module contains season and episode numbers and related errors. use core::fmt; use core::ops::RangeInclusive; @@ -7,7 +7,7 @@ use std::collections::HashSet; use seamantic::sea_orm; -/// Newtype for representing season numbers +/// Newtype for representing season numbers. #[derive( Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, sea_orm::DeriveValueType, )] @@ -17,13 +17,16 @@ use seamantic::sea_orm; pub struct SeasonNumber(u32); impl SeasonNumber { - /// Create a `SeasonNumber` from an integer - pub fn new(value: u32) -> Self { + /// Create a [`SeasonNumber`] from an integer. + #[inline] + #[must_use] + pub const fn new(value: u32) -> Self { Self(value) } } impl fmt::Display for SeasonNumber { + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } @@ -32,12 +35,13 @@ impl fmt::Display for SeasonNumber { impl FromStr for SeasonNumber { type Err = ::Err; + #[inline] fn from_str(s: &str) -> Result { u32::from_str(s).map(Self) } } -/// Newtype for representing episode numbers +/// Newtype for representing episode numbers. #[derive( Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, sea_orm::DeriveValueType, )] @@ -47,18 +51,23 @@ impl FromStr for SeasonNumber { pub struct EpisodeNumber(u32); impl EpisodeNumber { - /// Create an `EpisodeNumber` from an integer - pub fn new(value: u32) -> Self { + /// Create an [`EpisodeNumber`] from an integer. + #[inline] + #[must_use] + pub const fn new(value: u32) -> Self { Self(value) } - /// Get the underlying value - pub fn into_inner(self) -> u32 { + /// Get the underlying value. + #[inline] + #[must_use] + pub const fn into_inner(self) -> u32 { self.0 } } impl fmt::Display for EpisodeNumber { + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } @@ -67,33 +76,40 @@ impl fmt::Display for EpisodeNumber { impl FromStr for EpisodeNumber { type Err = ::Err; + #[inline] fn from_str(s: &str) -> Result { u32::from_str(s).map(Self) } } -/// Potential errors when building EpisodeNumbers -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// There are no episodes +/// Potential errors when building [`EpisodeNumbers`]. +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[expect(clippy::exhaustive_enums, reason = "unlikely to add new variants")] +pub enum EpisodeNumbersError { + /// There are no episodes. #[error("zero episodes")] Zero, - /// There are gaps in the episodes + /// There are gaps in the episodes. #[error("noncontiguous episodes")] Noncontiguous, } -/// A wrapper for handling single and multi-episode entries +/// A wrapper for handling single and multi-episode entries. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[expect( + clippy::module_name_repetitions, + reason = "Episodes is not a good name" +)] pub struct EpisodeNumbers(RangeInclusive); impl TryFrom<&[EpisodeNumber]> for EpisodeNumbers { - type Error = Error; + type Error = EpisodeNumbersError; + #[inline] fn try_from(value: &[EpisodeNumber]) -> Result { match value { - [] => Err(Error::Zero), + [] => Err(EpisodeNumbersError::Zero), [n] => Ok(Self(*n..=*n)), _ => { // min and max will always exist @@ -102,12 +118,12 @@ impl TryFrom<&[EpisodeNumber]> for EpisodeNumbers { let len = value.len(); if usize::try_from(max.0.saturating_sub(min.0).saturating_add(1)) != Ok(len) { - return Err(Error::Noncontiguous); + return Err(EpisodeNumbersError::Noncontiguous); } let set: HashSet<_> = value.iter().copied().collect(); if set.len() != len { - return Err(Error::Noncontiguous); + return Err(EpisodeNumbersError::Noncontiguous); } Ok(Self(min..=max)) @@ -117,27 +133,45 @@ impl TryFrom<&[EpisodeNumber]> for EpisodeNumbers { } impl EpisodeNumbers { - /// Create an [EpisodeNumbers] from a starting number and a count. + /// Create an [`EpisodeNumbers`] from a starting number and a count. /// `count` should be zero for single episodes. + #[inline] + #[must_use] pub fn new(start: EpisodeNumber, count: u8) -> Self { Self(start..=EpisodeNumber(start.0.saturating_add(count.into()))) } - /// Get the range of episodes - pub fn as_range(&self) -> &RangeInclusive { + /// Get the range of episodes. + #[inline] + #[must_use] + pub const fn as_range(&self) -> &RangeInclusive { &self.0 } - /// Render this [EpisodeNumbers] as a range. If only one episode is - /// is present it renders as `01`, if multiple it renders as `01-02` + /// Render this [`EpisodeNumbers`] as a range. If only one episode is + /// is present it renders as `01`, if multiple it renders as `01-02`. + #[inline] + #[must_use] pub fn range_string(&self) -> String { let start = self.0.start(); let end = self.0.end(); if start == end { - format!("{:02}", start) + format!("{start:02}") } else { - format!("{:02}-{:02}", start, end) + format!("{start:02}-{end:02}") } } } + +#[cfg(test)] +mod tests { + use super::{EpisodeNumber, EpisodeNumbers, SeasonNumber}; + + #[test] + fn use_fn() { + _ = SeasonNumber::new(0); + let e = EpisodeNumber::new(0); + _ = EpisodeNumbers::new(e, 1); + } +} diff --git a/crates/model/src/text.rs b/crates/model/src/text.rs index 4b5cd90..5699656 100644 --- a/crates/model/src/text.rs +++ b/crates/model/src/text.rs @@ -1,22 +1,26 @@ -//! This module contains helper functions for normalizing media titles +//! This module contains helper functions for normalizing media titles. use core::iter::Peekable; use itertools::Itertools; +/// Return an iterator over the normalized words of a string. +/// - Alphanumeric +/// - Lowercase +/// - Replace `&` with `and` +/// - Collapse acronyms +/// /// # Panics /// /// Panics if `input` is not ASCII. fn split_normalized_words(input: &str) -> impl Iterator { - if !input.is_ascii() { - panic!("Input is not ASCII: {input}"); - } + assert!(input.is_ascii(), "input is not ASCII: {input}"); input .split_ascii_whitespace() .map(|s| { if s == "&" { - return "and".to_string(); + return "and".to_owned(); } let chars = s @@ -37,6 +41,7 @@ fn split_normalized_words(input: &str) -> impl Iterator { .filter(|part: &String| !part.is_empty() && part != "-") } +/// Split out `a`, `an`, and `the` from the start of the string if it exists. fn split_leading_article>(iter: I) -> (Option, Peekable) { let mut iter = iter.peekable(); match iter.peek().map(String::as_str) { @@ -57,13 +62,15 @@ fn split_leading_article>(iter: I) -> (Option /// # Panics /// /// Panics if `input` is not ASCII. +#[inline] +#[must_use] pub fn make_sortable_title(title: &str) -> String { let words = split_normalized_words(title); let (article, words) = split_leading_article(words); - let output = Itertools::intersperse(words, " ".to_string()); + let output = Itertools::intersperse(words, " ".to_owned()); if let Some(article) = article { - output.chain([", ".to_string(), article]).collect() + output.chain([", ".to_owned(), article]).collect() } else { output.collect() } @@ -81,11 +88,13 @@ pub fn make_sortable_title(title: &str) -> String { /// # Panics /// /// Panics if `input` is not ASCII. +#[inline] +#[must_use] pub fn make_fs_slug(title: &str) -> String { let words = split_normalized_words(title); let (_, words) = split_leading_article(words); - Itertools::intersperse(words, " ".to_string()).collect() + Itertools::intersperse(words, " ".to_owned()).collect() } /// Convert a media title and year to a folder name representable on filesystems @@ -100,11 +109,13 @@ pub fn make_fs_slug(title: &str) -> String { /// # Panics /// /// Panics if `input` is not ASCII. +#[inline] +#[must_use] pub fn make_fs_slug_year(title: &str, year: i32) -> String { let words = split_normalized_words(title); let (_, words) = split_leading_article(words); - Itertools::intersperse(words, " ".to_string()) + Itertools::intersperse(words, " ".to_owned()) .chain([format!(" ({year})")]) .collect() } @@ -117,10 +128,12 @@ pub fn make_fs_slug_year(title: &str, year: i32) -> String { /// assert_eq!(normalize_fs_name("Marvel's Agents of SHIELD (2013)"), "marvels agents of shield (2013)"); /// assert_eq!(normalize_fs_name("Avatar The Last Airbender (2005)"), "avatar the last airbender (2005)"); /// assert_eq!(normalize_fs_name("Cloak & Dagger (2018)"), "cloak and dagger (2018)"); +#[inline] +#[must_use] pub fn normalize_fs_name(input: &str) -> String { let chars = input.split_ascii_whitespace().map(|s| { if s == "&" { - return "and".to_string(); + return "and".to_owned(); } let chars = s @@ -138,7 +151,7 @@ pub fn normalize_fs_name(input: &str) -> String { chars.collect() } }); - Itertools::intersperse(chars, " ".to_string()).collect() + Itertools::intersperse(chars, " ".to_owned()).collect() } /// Convert a media title to a url compatible string @@ -153,11 +166,13 @@ pub fn normalize_fs_name(input: &str) -> String { /// # Panics /// /// Panics if `input` is not ASCII. +#[inline] +#[must_use] pub fn make_web_slug(title: &str) -> String { let words = split_normalized_words(title); let (_, words) = split_leading_article(words); - Itertools::intersperse(words, "-".to_string()).collect() + Itertools::intersperse(words, "-".to_owned()).collect() } /// Convert a media title and year to a url compatible string @@ -172,11 +187,31 @@ pub fn make_web_slug(title: &str) -> String { /// # Panics /// /// Panics if `input` is not ASCII. +#[inline] +#[must_use] pub fn make_web_slug_year(title: &str, year: i32) -> String { let words = split_normalized_words(title); let (_, words) = split_leading_article(words); - Itertools::intersperse(words, "-".to_string()) + Itertools::intersperse(words, "-".to_owned()) .chain([format!("-{year}")]) .collect() } + +#[cfg(test)] +mod tests { + use super::{ + make_fs_slug, make_fs_slug_year, make_sortable_title, make_web_slug, make_web_slug_year, + normalize_fs_name, + }; + + #[test] + fn use_fn() { + drop(make_sortable_title("")); + drop(make_fs_slug("")); + drop(make_fs_slug_year("", 0)); + drop(normalize_fs_name("")); + drop(make_web_slug("")); + drop(make_web_slug_year("", 0)); + } +} diff --git a/crates/tmdb/Cargo.toml b/crates/tmdb/Cargo.toml index 1b870e5..a6bd9c4 100644 --- a/crates/tmdb/Cargo.toml +++ b/crates/tmdb/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "flix-tmdb" -version = "0.0.20" -license-file.workspace = true - +version = "0.1.0" description = "Clients and models for fetching data from TMDB" -repository = "https://github.com/QuantumShade/flix" -categories = [] +repository = "https://git.skrundz.dev/quantumshade/flix" +keywords = ["flix"] +categories = ["multimedia"] +include = ["/src"] +publish = true edition.workspace = true rust-version.workspace = true +license-file.workspace = true [package.metadata.docs.rs] all-features = true @@ -32,6 +34,7 @@ sea-orm = { workspace = true, optional = true } [dev-dependencies] serde_test = { workspace = true } +tempfile = { workspace = true } [features] default = [] diff --git a/crates/tmdb/src/api/collections.rs b/crates/tmdb/src/api/collections.rs index b885dee..cc60311 100644 --- a/crates/tmdb/src/api/collections.rs +++ b/crates/tmdb/src/api/collections.rs @@ -1,6 +1,6 @@ -//! Collections API +//! Collections API. -use std::rc::Rc; +use alloc::sync::Arc; use std::sync::RwLock; use crate::api::exec_request; @@ -10,26 +10,38 @@ use crate::{Cache, CachePolicy, Config}; use super::{Error, make_request}; -/// TMDB Collections API client +/// TMDB Collections API client. +#[derive(Debug)] pub struct Client { - config: Rc, - cache: Rc, - policy: Rc>, + /// The client configuration. + config: Arc, + /// The request cache. + cache: Arc, + /// The cache policy to use for requests. + policy: Arc>, } impl Client { - /// Create a new client with the given configuration - pub fn new(config: Rc, cache: Rc, policy: Rc>) -> Self { + /// Create a new client with the given configuration. + #[inline] + pub fn new( + config: Arc, + cache: Arc, + policy: Arc>, + ) -> Self { Self { config, cache, policy, } } -} -impl Client { - /// Fetch the details of the collection refered to by ID + /// Fetch the details of the collection refered to by ID. + /// + /// # Errors + /// See [Error] for failure modes. + #[inline] + #[expect(clippy::impl_trait_in_params, reason = "turbofish is not expected")] pub async fn get_details( &self, id: impl Into, diff --git a/crates/tmdb/src/api/episodes.rs b/crates/tmdb/src/api/episodes.rs index c867ce3..0ee785b 100644 --- a/crates/tmdb/src/api/episodes.rs +++ b/crates/tmdb/src/api/episodes.rs @@ -1,6 +1,6 @@ -//! Episodes API +//! Episodes API. -use std::rc::Rc; +use alloc::sync::Arc; use std::sync::RwLock; use flix_model::numbers::{EpisodeNumber, SeasonNumber}; @@ -12,26 +12,38 @@ use crate::{Cache, CachePolicy, Config}; use super::{Error, make_request}; -/// TMDB Episodes API client +/// TMDB Episodes API client. +#[derive(Debug)] pub struct Client { - config: Rc, - cache: Rc, - policy: Rc>, + /// The client configuration. + config: Arc, + /// The request cache. + cache: Arc, + /// The cache policy to use for requests. + policy: Arc>, } impl Client { - /// Create a new client with the given configuration - pub fn new(config: Rc, cache: Rc, policy: Rc>) -> Self { + /// Create a new client with the given configuration. + #[inline] + pub fn new( + config: Arc, + cache: Arc, + policy: Arc>, + ) -> Self { Self { config, cache, policy, } } -} -impl Client { - /// Fetch the details of the episode refered to by ID, season number and episode number + /// Fetch the details of the episode refered to by ID, season number and episode number. + /// + /// # Errors + /// See [Error] for failure modes. + #[inline] + #[expect(clippy::impl_trait_in_params, reason = "turbofish is not expected")] pub async fn get_details( &self, id: impl Into, diff --git a/crates/tmdb/src/api/mod.rs b/crates/tmdb/src/api/mod.rs index 724f847..bd02c9b 100644 --- a/crates/tmdb/src/api/mod.rs +++ b/crates/tmdb/src/api/mod.rs @@ -1,6 +1,5 @@ -//! TMDB API clients +//! TMDB API clients. -use core::ops::Deref; use core::time::Duration; use std::sync::RwLock; @@ -17,20 +16,26 @@ pub mod movies; pub mod seasons; pub mod shows; -/// A generic error wrapping Url and Reqwest errors +/// A generic error wrapping Url and Reqwest errors. #[derive(Debug, thiserror::Error)] +#[expect(clippy::error_impl_error, reason = "Error is a good name here")] +#[expect(clippy::exhaustive_enums, reason = "unlikely to add new variants")] pub enum Error { - /// Url error wrapper + /// Url error wrapper. #[error("url parse error: {0}")] Url(#[from] url::ParseError), - /// Reqwest error wrapper + /// Reqwest error wrapper. #[error("reqwest error: {0}")] Reqwest(#[from] reqwest::Error), - /// Json error wrapper + /// Json error wrapper. #[error("json error: {0}")] Json(#[from] serde_json::Error), } +/// Turn a [Config] and `path` into a [Request]. +/// +/// # Errors +/// See [Error] for failure modes. fn make_request(config: &Config, path: &str, language: Option<&str>) -> Result { let url = config.base_url.join(path)?; @@ -38,7 +43,7 @@ fn make_request(config: &Config, path: &str, language: Option<&str>) -> Result) -> Result( config: &Config, cache: &dyn Cache, policy: &RwLock, request: Request, ) -> Result { - let (read_cache, write_cache) = if let Ok(guard) = policy.read() { - match guard.deref() { - CachePolicy::None => (None, None), - CachePolicy::Full => (Some(cache), Some(cache)), - CachePolicy::Read => (Some(cache), None), - CachePolicy::Update => (None, Some(cache)), - } - } else { - (None, None) - }; + let (read_cache, write_cache) = policy.read().map_or((None, None), |guard| match *guard { + CachePolicy::None => (None, None), + CachePolicy::Full => (Some(cache), Some(cache)), + CachePolicy::Read => (Some(cache), None), + CachePolicy::Update => (None, Some(cache)), + }); let path = request.url().path().to_owned(); @@ -73,24 +78,23 @@ async fn exec_request( response = cache.get(&path); } let needs_cache_write = response.is_none(); - let response = match response { - Some(response) => response, - None => { - config - .limiter - .until_ready_with_jitter(Jitter::new( - Duration::from_millis(0), - Duration::from_millis(50), - )) - .await; - config - .client - .execute(request) - .await? - .error_for_status()? - .bytes() - .await? - } + let response = if let Some(response) = response { + response + } else { + config + .limiter + .until_ready_with_jitter(Jitter::new( + Duration::from_millis(0), + Duration::from_millis(50), + )) + .await; + config + .client + .execute(request) + .await? + .error_for_status()? + .bytes() + .await? }; // write to the cache if needed @@ -102,3 +106,13 @@ async fn exec_request( Ok(serde_json::from_slice(&response)?) } + +#[cfg(test)] +mod tests { + use super::Error; + + #[test] + fn use_types() { + drop(Error::Url(url::ParseError::Overflow)); + } +} diff --git a/crates/tmdb/src/api/movies.rs b/crates/tmdb/src/api/movies.rs index 9f3c2e2..7351536 100644 --- a/crates/tmdb/src/api/movies.rs +++ b/crates/tmdb/src/api/movies.rs @@ -1,6 +1,6 @@ -//! Movies API +//! Movies API. -use std::rc::Rc; +use alloc::sync::Arc; use std::sync::RwLock; use crate::api::exec_request; @@ -10,26 +10,38 @@ use crate::{Cache, CachePolicy, Config}; use super::{Error, make_request}; -/// TMDB Movies API client +/// TMDB Movies API client. +#[derive(Debug)] pub struct Client { - config: Rc, - cache: Rc, - policy: Rc>, + /// The client configuration. + config: Arc, + /// The request cache. + cache: Arc, + /// The cache policy to use for requests. + policy: Arc>, } impl Client { - /// Create a new client with the given configuration - pub fn new(config: Rc, cache: Rc, policy: Rc>) -> Self { + /// Create a new client with the given configuration. + #[inline] + pub fn new( + config: Arc, + cache: Arc, + policy: Arc>, + ) -> Self { Self { config, cache, policy, } } -} -impl Client { - /// Fetch the details of the movie refered to by ID + /// Fetch the details of the movie refered to by ID. + /// + /// # Errors + /// See [Error] for failure modes. + #[inline] + #[expect(clippy::impl_trait_in_params, reason = "turbofish is not expected")] pub async fn get_details( &self, id: impl Into, diff --git a/crates/tmdb/src/api/seasons.rs b/crates/tmdb/src/api/seasons.rs index 8586d7f..b700728 100644 --- a/crates/tmdb/src/api/seasons.rs +++ b/crates/tmdb/src/api/seasons.rs @@ -1,6 +1,6 @@ -//! Seasons API +//! Seasons API. -use std::rc::Rc; +use alloc::sync::Arc; use std::sync::RwLock; use flix_model::numbers::SeasonNumber; @@ -12,26 +12,38 @@ use crate::{Cache, CachePolicy, Config}; use super::{Error, make_request}; -/// TMDB Seasons API client +/// TMDB Seasons API client. +#[derive(Debug)] pub struct Client { - config: Rc, - cache: Rc, - policy: Rc>, + /// The client configuration. + config: Arc, + /// The request cache. + cache: Arc, + /// The cache policy to use for requests. + policy: Arc>, } impl Client { - /// Create a new client with the given configuration - pub fn new(config: Rc, cache: Rc, policy: Rc>) -> Self { + /// Create a new client with the given configuration. + #[inline] + pub fn new( + config: Arc, + cache: Arc, + policy: Arc>, + ) -> Self { Self { config, cache, policy, } } -} -impl Client { - /// Fetch the details of the season refered to by ID and season number + /// Fetch the details of the season refered to by ID and season number. + /// + /// # Errors + /// See [Error] for failure modes. + #[inline] + #[expect(clippy::impl_trait_in_params, reason = "turbofish is not expected")] pub async fn get_details( &self, id: impl Into, diff --git a/crates/tmdb/src/api/shows.rs b/crates/tmdb/src/api/shows.rs index e312924..8bff83b 100644 --- a/crates/tmdb/src/api/shows.rs +++ b/crates/tmdb/src/api/shows.rs @@ -1,6 +1,6 @@ -//! Shows API +//! Shows API. -use std::rc::Rc; +use alloc::sync::Arc; use std::sync::RwLock; use crate::api::exec_request; @@ -10,26 +10,38 @@ use crate::{Cache, CachePolicy, Config}; use super::{Error, make_request}; -/// TMDB Shows API client +/// TMDB Shows API client. +#[derive(Debug)] pub struct Client { - config: Rc, - cache: Rc, - policy: Rc>, + /// The client configuration. + config: Arc, + /// The request cache. + cache: Arc, + /// The cache policy to use for requests. + policy: Arc>, } impl Client { - /// Create a new client with the given configuration - pub fn new(config: Rc, cache: Rc, policy: Rc>) -> Self { + /// Create a new client with the given configuration. + #[inline] + pub fn new( + config: Arc, + cache: Arc, + policy: Arc>, + ) -> Self { Self { config, cache, policy, } } -} -impl Client { - /// Fetch the details of the show refered to by ID + /// Fetch the details of the show refered to by ID. + /// + /// # Errors + /// See [Error] for failure modes. + #[inline] + #[expect(clippy::impl_trait_in_params, reason = "turbofish is not expected")] pub async fn get_details( &self, id: impl Into, diff --git a/crates/tmdb/src/cache.rs b/crates/tmdb/src/cache.rs index 9651095..6d23cf2 100644 --- a/crates/tmdb/src/cache.rs +++ b/crates/tmdb/src/cache.rs @@ -1,58 +1,72 @@ +//! Caching related types. + use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; use bytes::Bytes; -use redb::{Database, DatabaseError, ReadableDatabase, TableDefinition}; +use redb::{Database, DatabaseError, ReadableDatabase as _, TableDefinition}; -/// The client cache policy +/// The client cache policy. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum CachePolicy { - /// Do not use a cache + /// Do not use a cache. None, - /// Use and update the cache + /// Use and update the cache. Full, - /// Use the cache but don't update it + /// Use the cache but don't update it. Read, - /// Ignore the cache but update it + /// Ignore the cache but update it. Update, } -/// The trait representing a caching backend -pub trait Cache { - /// Get a cached value, or None +/// The trait representing a caching backend. +pub trait Cache: core::fmt::Debug + Send + Sync { + /// Get a cached value, or None. fn get(&self, query: &str) -> Option; - /// Set a value in the cache + /// Set a value in the cache. fn set(&self, query: &str, response: &Bytes); } -const TABLE: TableDefinition<&str, (u64, &[u8])> = TableDefinition::new("tmdb_responses"); +/// The [`TableDefinition`] for TMDB response data. +const TABLE: TableDefinition<'_, &str, (u64, &[u8])> = TableDefinition::new("tmdb_responses"); -/// A [Cache] implementation using [redb] as the backend +/// A [Cache] implementation using [redb] as the backend. +#[derive(Debug)] pub struct RedbCache { + /// The underlying database. db: Database, } impl RedbCache { - /// Create/open a [redb] database at the path + /// Create/open a [redb] database at the path. + /// + /// # Errors + /// Fails if creating/opening the database returns an error. + #[inline] pub fn new(path: &Path) -> Result { Ok(Self { db: Database::create(path)?, }) } - /// Helper function allowing for `.ok()?` + /// Helper function allowing for `.ok()?`. fn write(&self, timestamp: u64, query: &str, response: &Bytes) -> Option<()> { let write_txn = self.db.begin_write().ok()?; { let mut table = write_txn.open_table(TABLE).ok()?; - table - .insert(query, (timestamp, response.iter().as_slice())) - .ok()?; + drop( + table + .insert(query, (timestamp, response.iter().as_slice())) + .ok()?, + ); } write_txn.commit().ok() } } impl Cache for RedbCache { + #[inline] fn get(&self, query: &str) -> Option { let read_txn = self.db.begin_read().ok()?; let table = read_txn.open_table(TABLE).ok()?; @@ -62,8 +76,7 @@ impl Cache for RedbCache { let now = SystemTime::now() .duration_since(UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0); + .map_or(0, |d| d.as_secs()); if now.saturating_sub(timestamp) >= 60 * 60 * 24 * 30 * 6 { None @@ -72,12 +85,12 @@ impl Cache for RedbCache { } } + #[inline] fn set(&self, query: &str, response: &Bytes) { let now = SystemTime::now() .duration_since(UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0); + .map_or(0, |d| d.as_secs()); - self.write(now, query, response); + _ = self.write(now, query, response); } } diff --git a/crates/tmdb/src/client.rs b/crates/tmdb/src/client.rs index 408165c..f09a3bc 100644 --- a/crates/tmdb/src/client.rs +++ b/crates/tmdb/src/client.rs @@ -1,44 +1,63 @@ -use std::rc::Rc; +//! TMDB client. + +use alloc::sync::Arc; use std::sync::RwLock; use crate::{Cache, CachePolicy, Config, api}; -/// The primary client that references all other clients +/// The primary client that references all other clients. +#[derive(Debug)] pub struct Client { + /// The collection API client. collections: api::collections::Client, + /// The movie API client. movies: api::movies::Client, + /// The show API client. shows: api::shows::Client, + /// The season API client. seasons: api::seasons::Client, + /// The episode API client. episodes: api::episodes::Client, - cache_policy: Rc>, + /// The cache policy to use for all clients. + cache_policy: Arc>, } impl Client { - /// Create a new client with the given configuration - pub fn new(config: Config, cache: Rc, cache_policy: CachePolicy) -> Self { - let config = Rc::new(config); - let cache_policy = Rc::new(RwLock::new(cache_policy)); + /// Create a new client with the given configuration. + #[inline] + pub fn new(config: Config, cache: Arc, cache_policy: CachePolicy) -> Self { + let config = Arc::new(config); + let cache_policy = Arc::new(RwLock::new(cache_policy)); Self { collections: api::collections::Client::new( - config.clone(), - cache.clone(), - cache_policy.clone(), + Arc::clone(&config), + Arc::clone(&cache), + Arc::clone(&cache_policy), ), - movies: api::movies::Client::new(config.clone(), cache.clone(), cache_policy.clone()), - shows: api::shows::Client::new(config.clone(), cache.clone(), cache_policy.clone()), - seasons: api::seasons::Client::new(config.clone(), cache.clone(), cache_policy.clone()), - episodes: api::episodes::Client::new( - config.clone(), - cache.clone(), - cache_policy.clone(), + movies: api::movies::Client::new( + Arc::clone(&config), + Arc::clone(&cache), + Arc::clone(&cache_policy), ), + shows: api::shows::Client::new( + Arc::clone(&config), + Arc::clone(&cache), + Arc::clone(&cache_policy), + ), + seasons: api::seasons::Client::new( + Arc::clone(&config), + Arc::clone(&cache), + Arc::clone(&cache_policy), + ), + episodes: api::episodes::Client::new(config, cache, Arc::clone(&cache_policy)), cache_policy, } } - /// Modify the [CachePolicy] + /// Modify the [`CachePolicy`]. + #[inline] pub fn set_cache_policy(&self, new_policy: CachePolicy) { match self.cache_policy.write() { Ok(mut policy) => *policy = new_policy, @@ -48,31 +67,39 @@ impl Client { } } } -} -impl Client { - /// Access the Collections API - pub fn collections(&self) -> &api::collections::Client { + /// Access the Collections API. + #[inline] + #[must_use] + pub const fn collections(&self) -> &api::collections::Client { &self.collections } - /// Access the Movies API - pub fn movies(&self) -> &api::movies::Client { + /// Access the Movies API. + #[inline] + #[must_use] + pub const fn movies(&self) -> &api::movies::Client { &self.movies } - /// Access the Shows API - pub fn shows(&self) -> &api::shows::Client { + /// Access the Shows API. + #[inline] + #[must_use] + pub const fn shows(&self) -> &api::shows::Client { &self.shows } - /// Access the Seasons API - pub fn seasons(&self) -> &api::seasons::Client { + /// Access the Seasons API. + #[inline] + #[must_use] + pub const fn seasons(&self) -> &api::seasons::Client { &self.seasons } - /// Access the Episodes API - pub fn episodes(&self) -> &api::episodes::Client { + /// Access the Episodes API. + #[inline] + #[must_use] + pub const fn episodes(&self) -> &api::episodes::Client { &self.episodes } } diff --git a/crates/tmdb/src/config.rs b/crates/tmdb/src/config.rs index 048149e..69081ab 100644 --- a/crates/tmdb/src/config.rs +++ b/crates/tmdb/src/config.rs @@ -1,3 +1,5 @@ +//! Client configuration. + use governor::clock::MonotonicClock; use governor::state::{InMemoryState, NotKeyed}; use governor::{Quota, RateLimiter}; @@ -5,27 +7,31 @@ use nonzero_ext::nonzero; use url::Url; use url_macro::url; -/// The client configuration +/// The client configuration. +#[derive(Debug)] +#[expect(clippy::exhaustive_structs, reason = "must be constructed in full")] pub struct Config { - /// The base URL of the API + /// The base URL of the API. pub base_url: Url, - /// The reqwest client that is used for every request + /// The reqwest client that is used for every request. pub client: reqwest::Client, - /// The rate limiter to use for the client + /// The rate limiter to use for the client. pub limiter: RateLimiter, - /// The bearer token for readonly access to the API + /// The bearer token for readonly access to the API. pub bearer_token: String, - /// An optional user agent string to provide to the API + /// An optional user agent string to provide to the API. pub user_agent: Option, } impl Config { - /// Create a new configuration using the provided bearer token + /// Create a new configuration using the provided bearer token. + #[inline] + #[must_use] pub fn new(bearer_token: String) -> Self { Self { base_url: url!("https://api.themoviedb.org"), client: reqwest::Client::new(), - limiter: RateLimiter::direct(Quota::per_second(nonzero!(30u32))), + limiter: RateLimiter::direct(Quota::per_second(nonzero!(30_u32))), bearer_token, user_agent: None, } diff --git a/crates/tmdb/src/lib.rs b/crates/tmdb/src/lib.rs index e701cb2..a9ffa54 100644 --- a/crates/tmdb/src/lib.rs +++ b/crates/tmdb/src/lib.rs @@ -1,7 +1,9 @@ -//! flix-tmdb provides clients and models for fetching data from TMDB +//! flix-tmdb provides clients and models for fetching data from TMDB. #![cfg_attr(docsrs, feature(doc_cfg))] +extern crate alloc; + pub mod api; pub mod model; @@ -13,3 +15,20 @@ pub use client::Client; mod config; pub use config::Config; + +#[cfg(test)] +mod tests { + use alloc::sync::Arc; + + use super::{CachePolicy, Client, Config, RedbCache}; + + #[cfg(not(miri))] + #[test] + #[expect(clippy::missing_panics_doc, reason = "unit test")] + fn use_types() { + let tmp = tempfile::tempdir().expect("temporary directory exists"); + let config = Config::new(String::new()); + let cache = Arc::new(RedbCache::new(&tmp.path().join("cache.redb")).expect("redb")); + drop(Client::new(config, cache, CachePolicy::Full)); + } +} diff --git a/crates/tmdb/src/model/collection.rs b/crates/tmdb/src/model/collection.rs index e55abe2..66c21d8 100644 --- a/crates/tmdb/src/model/collection.rs +++ b/crates/tmdb/src/model/collection.rs @@ -1,25 +1,48 @@ +//! TMDB collection model. + use super::id::{CollectionId, MovieId}; -/// A deserialized Collection from the TMDB API +/// A deserialized Collection from the TMDB API. #[derive(Debug, Clone, serde::Deserialize)] +#[non_exhaustive] pub struct Collection { - /// The collection's TMDB ID + /// The collection's TMDB ID. pub id: CollectionId, - /// The collection's title + /// The collection's title. #[serde(rename = "name")] pub title: String, - /// The collection's overview + /// The collection's overview. pub overview: String, - /// The list of movies that are part of this collection + /// The list of movies that are part of this collection. #[serde(rename = "parts")] pub movies: Vec, } -/// A deserialized collection item from the TMDB API +/// A deserialized collection item from the TMDB API. #[derive(Debug, Clone, serde::Deserialize)] +#[non_exhaustive] pub struct Item { - /// The movie's TMDB ID + /// The movie's TMDB ID. pub id: MovieId, - /// The movie's title + /// The movie's title. pub title: String, } + +#[cfg(test)] +mod tests { + use super::{Collection, CollectionId, Item, MovieId}; + + #[test] + fn use_types() { + drop(Collection { + id: CollectionId::from_raw(0), + title: String::new(), + overview: String::new(), + movies: Vec::new(), + }); + drop(Item { + id: MovieId::from_raw(0), + title: String::new(), + }); + } +} diff --git a/crates/tmdb/src/model/episode.rs b/crates/tmdb/src/model/episode.rs index 40a95b4..39287b2 100644 --- a/crates/tmdb/src/model/episode.rs +++ b/crates/tmdb/src/model/episode.rs @@ -1,3 +1,5 @@ +//! TMDB episode model. + use core::time::Duration; use flix_model::numbers::EpisodeNumber; @@ -7,22 +9,44 @@ use url::Url; use super::{duration_from_minutes, still_url_from_path}; -/// A deserialized Episode from the TMDB API +/// A deserialized Episode from the TMDB API. #[derive(Debug, Clone, serde::Deserialize)] +#[non_exhaustive] pub struct Episode { - /// The episode's number + /// The episode's number. pub episode_number: EpisodeNumber, - /// The episode's title + /// The episode's title. #[serde(rename = "name")] pub title: String, - /// The episode's overview + /// The episode's overview. pub overview: String, - /// The episode's air date + /// The episode's air date. pub air_date: NaiveDate, - /// The episode's runtime + /// The episode's runtime. #[serde(deserialize_with = "duration_from_minutes")] pub runtime: Duration, - /// The episode's still path + /// The episode's still path. #[serde(deserialize_with = "still_url_from_path")] pub still_path: Option, } + +#[cfg(test)] +mod tests { + use core::time::Duration; + + use chrono::NaiveDate; + + use super::{Episode, EpisodeNumber}; + + #[test] + fn use_types() { + drop(Episode { + episode_number: EpisodeNumber::new(0), + title: String::new(), + overview: String::new(), + air_date: NaiveDate::default(), + runtime: Duration::default(), + still_path: None, + }); + } +} diff --git a/crates/tmdb/src/model/id.rs b/crates/tmdb/src/model/id.rs index ee9b67a..2c018c7 100644 --- a/crates/tmdb/src/model/id.rs +++ b/crates/tmdb/src/model/id.rs @@ -1,4 +1,6 @@ -//! Typed TMDB IDs +//! Typed TMDB IDs. + +#![expect(clippy::module_name_repetitions, reason = "ID types end with Id")] use core::cmp::Ordering; use core::fmt; @@ -10,21 +12,25 @@ use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr}; #[cfg(feature = "sea-orm")] use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value}; -/// The internal representation used by TMDB +/// The internal representation used by TMDB. pub type TmdbRepr = u32; -/// An opaque type representing a TMDB ID +/// An opaque type representing a TMDB ID. #[derive(serde::Serialize, serde::Deserialize)] #[serde(transparent)] #[repr(transparent)] pub struct Id { + /// Internal ID representation. id: TmdbRepr, + /// Marker to indicate an invariant T. #[serde(skip_serializing, default)] - _phantom: PhantomData, + _phantom: PhantomData T>, } // Manual implementation since `T: Clone` is not required +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl Clone for Id { + #[inline] fn clone(&self) -> Self { *self } @@ -34,31 +40,40 @@ impl Clone for Id { impl Copy for Id {} // Manual implementation since `T: PartialEq` is not required +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl PartialEq for Id { + #[inline] fn eq(&self, other: &Self) -> bool { self.id == other.id } } // Manual implementation since `T: Eq` is not required +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl Eq for Id {} // Manual implementation since `T: PartialOrd` is not required +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl PartialOrd for Id { + #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } // Manual implementation since `T: Ord` is not required +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl Ord for Id { + #[inline] fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } // Manual implementation since `T: Hash` is not required +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl Hash for Id { + #[inline] fn hash(&self, state: &mut H) { self.id.hash(state); } @@ -66,6 +81,8 @@ impl Hash for Id { impl Id { /// Allows the conversion from a raw value to [Id], though the use is discouraged. + #[inline] + #[must_use] pub fn from_raw(raw: TmdbRepr) -> Self { Self { id: raw, @@ -74,13 +91,16 @@ impl Id { } /// Allows extracting the raw value, though the use is discouraged. - pub fn into_raw(self) -> TmdbRepr { + #[inline] + #[must_use] + pub const fn into_raw(self) -> TmdbRepr { self.id } } impl fmt::Debug for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Id") .field("T", &core::any::type_name::()) .field("id", &self.id) @@ -89,7 +109,9 @@ impl fmt::Debug for Id { } #[cfg(feature = "sea-orm")] +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ValueType for Id { + #[inline] fn try_from(v: Value) -> Result { ::try_from(v).map(|id| Self { id, @@ -97,14 +119,17 @@ impl ValueType for Id { }) } + #[inline] fn type_name() -> String { - format!("Id<{}>", &core::any::type_name::()) + format!("Id<{}>", core::any::type_name::()) } + #[inline] fn array_type() -> ArrayType { TmdbRepr::array_type() } + #[inline] fn column_type() -> ColumnType { TmdbRepr::column_type() } @@ -112,13 +137,16 @@ impl ValueType for Id { #[cfg(feature = "sea-orm")] impl From> for Value { + #[inline] fn from(value: Id) -> Self { value.id.into() } } #[cfg(feature = "sea-orm")] +#[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl TryGetable for Id { + #[inline] fn try_get_by(res: &QueryResult, index: I) -> Result { TmdbRepr::try_get_by(res, index).map(|id| Self { id, @@ -129,6 +157,7 @@ impl TryGetable for Id { #[cfg(feature = "sea-orm")] impl TryFromU64 for Id { + #[inline] fn try_from_u64(n: u64) -> Result { TmdbRepr::try_from_u64(n).map(|id| Self { id, @@ -139,6 +168,7 @@ impl TryFromU64 for Id { #[cfg(feature = "sea-orm")] impl Nullable for Id { + #[inline] fn null() -> Value { TmdbRepr::null() } @@ -146,34 +176,34 @@ impl Nullable for Id { #[cfg(test)] mod tests { - #[test] #[cfg(feature = "sea-orm")] - fn test_sea_orm() { + #[expect(dead_code, reason = "structs test derive macros")] + mod test_seaorm { use sea_orm::{ ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait, EnumIter, PrimaryKeyTrait, }; - use super::Id; + use super::super::Id; - #[allow(dead_code)] #[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "ids")] - pub struct Model { + #[expect(clippy::use_self, reason = "derive macros cause Self to be invalid")] + struct Model { #[sea_orm(primary_key, auto_increment = false)] id: Id, nullable: Option>, } + #[expect(clippy::missing_trait_methods, reason = "accept default methods")] impl ActiveModelBehavior for ActiveModel {} - #[allow(dead_code)] #[derive(Debug, EnumIter, DeriveRelation)] - pub enum Relation {} + enum Relation {} } #[test] - fn test_serde() { + fn serde() { use super::Id; let id: Id<()> = Id::from_raw(1234); @@ -181,61 +211,70 @@ mod tests { } } -/// Type alias for the raw ID representation -pub use self::TmdbRepr as RawId; - #[doc(hidden)] +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum Collection {} -/// Type alias for a collection ID +/// Type alias for a collection ID. pub type CollectionId = Id; impl From for flix_model::id::CollectionId { + #[inline] fn from(value: CollectionId) -> Self { Self::from_raw(value.into_raw().into()) } } impl TryFrom for CollectionId { - type Error = >::Error; + type Error = >::Error; + #[inline] fn try_from(value: flix_model::id::CollectionId) -> Result { value.into_raw().try_into().map(Self::from_raw) } } #[doc(hidden)] +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum Movie {} -/// Type alias for a movie ID +/// Type alias for a movie ID. pub type MovieId = Id; impl From for flix_model::id::MovieId { + #[inline] fn from(value: MovieId) -> Self { Self::from_raw(value.into_raw().into()) } } impl TryFrom for MovieId { - type Error = >::Error; + type Error = >::Error; + #[inline] fn try_from(value: flix_model::id::MovieId) -> Result { value.into_raw().try_into().map(Self::from_raw) } } #[doc(hidden)] +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum Show {} -/// Type alias for a show ID +/// Type alias for a show ID. pub type ShowId = Id; impl From for flix_model::id::ShowId { + #[inline] fn from(value: ShowId) -> Self { Self::from_raw(value.into_raw().into()) } } impl TryFrom for ShowId { - type Error = >::Error; + type Error = >::Error; + #[inline] fn try_from(value: flix_model::id::ShowId) -> Result { value.into_raw().try_into().map(Self::from_raw) } diff --git a/crates/tmdb/src/model/mod.rs b/crates/tmdb/src/model/mod.rs index d7f2bc5..c14cf39 100644 --- a/crates/tmdb/src/model/mod.rs +++ b/crates/tmdb/src/model/mod.rs @@ -1,9 +1,9 @@ -//! Deserializable types from the TMDB API +//! Deserializable types from the TMDB API. -use core::str::FromStr; +use core::str::FromStr as _; use core::time::Duration; -use serde::{Deserialize, Deserializer}; +use serde::{Deserialize as _, Deserializer}; use url::Url; pub mod id; @@ -20,14 +20,22 @@ pub use movie::*; pub use season::*; pub use show::*; +/// Deserializer for converting integer minutes to a [Duration]. +/// +/// # Errors +/// Fails if the value being deserialized isn't a [u64]. fn duration_from_minutes<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { - let minutes = u64::deserialize(deserializer).unwrap_or(0); + let minutes = u64::deserialize(deserializer)?; Ok(Duration::from_secs(minutes.saturating_mul(60))) } +/// Deserializer for converting a string to a [Url]. +/// +/// # Errors +/// Fails if the value being deserialized isn't a [`&str`]. fn still_url_from_path<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -38,10 +46,6 @@ where let path = Option::<&str>::deserialize(deserializer)?; Ok(path.and_then(|path| { - Url::from_str(&format!( - "{}{}{}", - TMDB_IMAGE_BASE, TMDB_IMAGE_QUALITY, path - )) - .ok() + Url::from_str(&format!("{TMDB_IMAGE_BASE}{TMDB_IMAGE_QUALITY}{path}")).ok() })) } diff --git a/crates/tmdb/src/model/movie.rs b/crates/tmdb/src/model/movie.rs index dd476f1..2ea93d3 100644 --- a/crates/tmdb/src/model/movie.rs +++ b/crates/tmdb/src/model/movie.rs @@ -1,3 +1,5 @@ +//! TMDB movie model. + use core::time::Duration; use chrono::NaiveDate; @@ -5,33 +7,35 @@ use chrono::NaiveDate; use super::duration_from_minutes; use super::id::{CollectionId, MovieId}; -/// A deserialized Movie from the TMDB API +/// A deserialized Movie from the TMDB API. #[derive(Debug, Clone, serde::Deserialize)] +#[non_exhaustive] pub struct Movie { - /// The movie's TMDB ID + /// The movie's TMDB ID. pub id: MovieId, - /// The movie's collection, if it exists + /// The movie's collection, if it exists. #[serde(rename = "belongs_to_collection")] pub collection: Option, - /// The movie's title + /// The movie's title. pub title: String, - /// The movie's tagline + /// The movie's tagline. pub tagline: String, - /// The movie's overview + /// The movie's overview. pub overview: String, - /// The movie's release date + /// The movie's release date. pub release_date: NaiveDate, - /// The movie's runtime + /// The movie's runtime. #[serde(deserialize_with = "duration_from_minutes")] pub runtime: Duration, } -/// A deserialized movie's collection from the TMDB API +/// A deserialized movie's collection from the TMDB API. #[derive(Debug, Clone, serde::Deserialize)] +#[non_exhaustive] pub struct InCollection { - /// The collection's TMDB ID + /// The collection's TMDB ID. pub id: CollectionId, - /// The collection's title + /// The collection's title. #[serde(rename = "name")] pub title: String, } @@ -43,3 +47,28 @@ pub struct InCollection { // TODO: Company // pub companies: Vec // where: struct Company { id, name } + +#[cfg(test)] +mod tests { + use core::time::Duration; + + use chrono::NaiveDate; + + use super::{CollectionId, InCollection, Movie, MovieId}; + + #[test] + fn use_types() { + drop(Movie { + id: MovieId::from_raw(0), + collection: Some(InCollection { + id: CollectionId::from_raw(0), + title: String::new(), + }), + title: String::new(), + tagline: String::new(), + overview: String::new(), + release_date: NaiveDate::default(), + runtime: Duration::default(), + }); + } +} diff --git a/crates/tmdb/src/model/season.rs b/crates/tmdb/src/model/season.rs index c413a02..451cabf 100644 --- a/crates/tmdb/src/model/season.rs +++ b/crates/tmdb/src/model/season.rs @@ -1,23 +1,46 @@ +//! TMDB season model. + use chrono::NaiveDate; use flix_model::numbers::SeasonNumber; -/// A deserialized Season from the TMDB API +/// A deserialized Season from the TMDB API. #[derive(Debug, Clone, serde::Deserialize)] +#[non_exhaustive] pub struct Season { - /// The season's number + /// The season's number. pub season_number: SeasonNumber, - /// The season's title + /// The season's title. #[serde(rename = "name")] pub title: String, - /// The season's overview + /// The season's overview. pub overview: String, - /// The season's air date + /// The season's air date. pub air_date: NaiveDate, - /// The number of episodes in this season + /// The number of episodes in this season. pub episodes: Vec, } -/// A placeholder struct for parsing the episodes list for a season -#[derive(Debug, Clone, serde::Deserialize)] +/// A placeholder struct for parsing the episodes list for a season. +#[derive(Debug, Clone, Copy, serde::Deserialize)] +#[non_exhaustive] +#[expect(clippy::empty_structs_with_brackets, reason = "might add fields later")] pub struct FakeEpisode {} + +#[cfg(test)] +mod tests { + use chrono::NaiveDate; + + use super::{Season, SeasonNumber}; + + #[test] + fn use_types() { + drop(Season { + season_number: SeasonNumber::new(0), + title: String::new(), + overview: String::new(), + air_date: NaiveDate::default(), + episodes: Vec::new(), + }); + } +} diff --git a/crates/tmdb/src/model/show.rs b/crates/tmdb/src/model/show.rs index 2f198f5..64c4e6d 100644 --- a/crates/tmdb/src/model/show.rs +++ b/crates/tmdb/src/model/show.rs @@ -1,26 +1,29 @@ +//! TMDB show model. + use chrono::NaiveDate; use super::id::ShowId; -/// A deserialized Show from the TMDB API +/// A deserialized Show from the TMDB API. #[derive(Debug, Clone, serde::Deserialize)] +#[non_exhaustive] pub struct Show { - /// The show's TMDB ID + /// The show's TMDB ID. pub id: ShowId, - /// The show's title + /// The show's title. #[serde(rename = "name")] pub title: String, - /// The show's tagline + /// The show's tagline. pub tagline: String, - /// The show's overview + /// The show's overview. pub overview: String, - /// The show's first air date + /// The show's first air date. pub first_air_date: NaiveDate, - /// The show's last air date + /// The show's last air date. pub last_air_date: NaiveDate, - /// The total number of episodes in this show + /// The total number of episodes in this show. pub number_of_episodes: u32, - /// The number of seasons in this show + /// The number of seasons in this show. pub number_of_seasons: u32, } @@ -35,3 +38,24 @@ pub struct Show { // TODO: Company // pub companies: Vec // where: struct Company { id, name } + +#[cfg(test)] +mod tests { + use chrono::NaiveDate; + + use super::{Show, ShowId}; + + #[test] + fn use_types() { + drop(Show { + id: ShowId::from_raw(0), + title: String::new(), + tagline: String::new(), + overview: String::new(), + first_air_date: NaiveDate::default(), + last_air_date: NaiveDate::default(), + number_of_episodes: 0, + number_of_seasons: 0, + }); + } +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..55d4309 --- /dev/null +++ b/justfile @@ -0,0 +1,44 @@ +set minimum-version := '1.55.0' +set default-list + +alias update := update-min + +all: + just all-min + just all-max +all-min: update-min build lint test example doc format +all-max: update-max build lint test example doc format + +update-min: + cargo +nightly update -Z direct-minimal-versions + +update-max: + cargo update + +build: + cargo hack --feature-powerset build + +lint: + cargo hack --feature-powerset clippy -- -D warnings + cargo hack --feature-powerset clippy --tests -- -D warnings + +test: + cargo hack --feature-powerset test + cargo +1.94 hack --feature-powerset test + cargo +nightly hack --feature-powerset miri test + +example: + # cargo run --example= --features= + +doc *args: + RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo +nightly doc \ + --workspace --all-features --document-private-items \ + --lib --bins --no-deps --locked \ + {{ args }} + +format: + cargo fmt --check + +install: + cargo install --path crates/cli + cargo install --path crates/cli-mux diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 292fe49..e000fd2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "stable" +channel = "1.98"