From 42d073c3b8b008cbbc69bbd7fe5d369f38d7093f Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Tue, 6 Jan 2026 13:37:49 +0200 Subject: [PATCH 1/2] feat: add exam utils --- .gitignore | 1 + Cargo.lock | 468 +++++++++--------- Cargo.toml | 3 +- exam-utils/Cargo.toml | 16 + exam-utils/src/attempt.rs | 184 +++++++ exam-utils/src/error.rs | 18 + exam-utils/src/generation.rs | 89 ++++ exam-utils/src/lib.rs | 13 + exam-utils/src/misc.rs | 772 ++++++++++++++++++++++++++++++ moderation-service/Cargo.toml | 1 + moderation-service/src/attempt.rs | 118 ----- moderation-service/src/db.rs | 3 +- moderation-service/src/lib.rs | 1 - 13 files changed, 1323 insertions(+), 364 deletions(-) create mode 100644 exam-utils/Cargo.toml create mode 100644 exam-utils/src/attempt.rs create mode 100644 exam-utils/src/error.rs create mode 100644 exam-utils/src/generation.rs create mode 100644 exam-utils/src/lib.rs create mode 100644 exam-utils/src/misc.rs delete mode 100644 moderation-service/src/attempt.rs diff --git a/.gitignore b/.gitignore index 5fe154d..267559c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env* target/ +.vscode/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 640a1f5..b2ffa29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,11 +29,11 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "base64 0.22.1", + "base64", "bitflags 2.10.0", "bytes", "bytestring", - "derive_more 2.0.1", + "derive_more", "encoding_rs", "foldhash", "futures-core", @@ -46,7 +46,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.1", + "rand", "sha1", "smallvec", "tokio", @@ -131,7 +131,7 @@ dependencies = [ "bytes", "bytestring", "cfg-if", - "derive_more 2.0.1", + "derive_more", "encoding_rs", "foldhash", "futures-core", @@ -250,12 +250,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.22.1" @@ -319,7 +313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969a9ba84b0ff843813e7249eed1678d9b6607ce5a3b8f0a47af3fcf7978e6e" dependencies = [ "ahash", - "base64 0.22.1", + "base64", "bitvec", "getrandom 0.2.16", "getrandom 0.3.3", @@ -327,7 +321,7 @@ dependencies = [ "indexmap 2.10.0", "js-sys", "once_cell", - "rand 0.9.1", + "rand", "serde", "serde_bytes", "serde_json", @@ -337,24 +331,23 @@ dependencies = [ [[package]] name = "bson" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c1c1044eb5689370b9da714b543a4a8d15601f4a68538dcadcf9c41b916aff" +checksum = "b3f109694c4f45353972af96bf97d8a057f82e2d6e496457f4d135b9867a518c" dependencies = [ "ahash", - "base64 0.22.1", + "base64", "bitvec", - "getrandom 0.2.16", "getrandom 0.3.3", "hex", "indexmap 2.10.0", "js-sys", - "once_cell", - "rand 0.9.1", + "rand", "serde", "serde_bytes", + "serde_with", "simdutf8", - "thiserror 2.0.12", + "thiserror", "time", "uuid", ] @@ -445,12 +438,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.4" @@ -476,6 +463,36 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crunchy" version = "0.2.4" @@ -494,9 +511,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -504,9 +521,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", @@ -518,9 +535,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", @@ -585,19 +602,6 @@ dependencies = [ "syn", ] -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - [[package]] name = "derive_more" version = "2.0.1" @@ -659,9 +663,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" @@ -726,6 +730,22 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "exam-utils" +version = "0.1.0" +dependencies = [ + "bson 3.1.0", + "mongodb", + "prisma", + "rand", + "serde", + "serde_json", + "serde_with", + "thiserror", + "tracing", + "tracing-subscriber", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -802,17 +822,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.31" @@ -947,9 +956,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-proto" -version = "0.24.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ "async-trait", "cfg-if", @@ -961,8 +970,9 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.8.5", - "thiserror 1.0.69", + "rand", + "ring", + "thiserror", "tinyvec", "tokio", "tracing", @@ -971,21 +981,21 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", - "lru-cache", + "moka", "once_cell", "parking_lot", - "rand 0.8.5", + "rand", "resolv-conf", "smallvec", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing", ] @@ -1125,7 +1135,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", @@ -1361,9 +1371,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -1387,12 +1397,6 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1438,15 +1442,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "lsp-types" version = "0.95.1" @@ -1566,6 +1561,7 @@ version = "2.0.0" dependencies = [ "anyhow", "dotenvy", + "exam-utils", "futures-util", "mongodb", "prisma", @@ -1578,14 +1574,31 @@ dependencies = [ "tracing-test", ] +[[package]] +name = "moka" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + [[package]] name = "mongocrypt" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22426d6318d19c5c0773f783f85375265d6a8f0fa76a733da8dc4355516ec63d" +checksum = "8da0cd419a51a5fb44819e290fbdb0665a54f21dead8923446a799c7f4d26ad9" dependencies = [ "bson 2.15.0", - "bson 3.0.0", + "bson 3.1.0", "mongocrypt-sys", "once_cell", "serde", @@ -1593,26 +1606,23 @@ dependencies = [ [[package]] name = "mongocrypt-sys" -version = "0.1.4+1.12.0" +version = "0.1.5+1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda42df21d035f88030aad8e877492fac814680e1d7336a57b2a091b989ae388" +checksum = "224484c5d09285a7b8cb0a0c117e847ebd14cb6e4470ecf68cdb89c503b0edb9" [[package]] name = "mongodb" -version = "3.3.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622f272c59e54a3c85f5902c6b8e7b1653a6b6681f45e4c42d6581301119a4b8" +checksum = "12f5c20217413bed97c613714e6d6dfe39ef59dd79a68999f1043b0566192975" dependencies = [ - "async-trait", - "base64 0.13.1", - "bitflags 1.3.2", + "base64", + "bitflags 2.10.0", "bson 2.15.0", - "bson 3.0.0", - "chrono", + "bson 3.1.0", "derive-where", - "derive_more 0.99.20", + "derive_more", "futures-core", - "futures-executor", "futures-io", "futures-util", "hex", @@ -1623,10 +1633,9 @@ dependencies = [ "md-5", "mongocrypt", "mongodb-internal-macros", - "once_cell", "pbkdf2", "percent-encoding", - "rand 0.8.5", + "rand", "rustc_version_runtime", "rustls", "rustversion", @@ -1635,24 +1644,24 @@ dependencies = [ "serde_with", "sha1", "sha2", - "socket2 0.5.10", + "socket2 0.6.1", "stringprep", "strsim", "take_mut", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-rustls", "tokio-util", "typed-builder", "uuid", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] name = "mongodb-internal-macros" -version = "3.3.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63981427a0f26b89632fd2574280e069d09fb2912a3138da15de0174d11dd077" +checksum = "20033442aa13664e70bc9f8be1bacabebf6a31b6d4bb5608ceb99c4ec96e9951" dependencies = [ "macro_magic", "proc-macro2", @@ -1734,6 +1743,10 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "openssl" @@ -1830,9 +1843,9 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.11.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", ] @@ -1859,7 +1872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror", "ucd-trie", ] @@ -1920,13 +1933,19 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" dependencies = [ - "base64 0.22.1", + "base64", "indexmap 2.10.0", "quick-xml", "serde", "time", ] +[[package]] +name = "portable-atomic" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" + [[package]] name = "potential_utf" version = "0.1.2" @@ -1955,7 +1974,7 @@ dependencies = [ name = "prisma" version = "0.1.0" dependencies = [ - "bson 3.0.0", + "bson 3.1.0", "prisma-rust-schema", "serde", "serde_json", @@ -1981,7 +2000,7 @@ name = "prisma-value" version = "0.1.0" source = "git+https://github.com/prisma/prisma-engines?tag=6.16.2#1c57fdcd7e44b29b9313256c76699e91c3ac3c43" dependencies = [ - "base64 0.22.1", + "base64", "bigdecimal", "chrono", "regex", @@ -2064,33 +2083,12 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "rand_chacha", + "rand_core", ] [[package]] @@ -2100,16 +2098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", + "rand_core", ] [[package]] @@ -2132,18 +2121,18 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -2191,7 +2180,7 @@ version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", @@ -2380,6 +2369,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2480,7 +2481,7 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3b6729c8e71ac968edbe9bf2dd4109c162e552b52bacd2b07e24ede1aba84a5" dependencies = [ - "rand 0.9.1", + "rand", "sentry-types", "serde", "serde_json", @@ -2539,10 +2540,10 @@ checksum = "2c19d1d1967b55659c358886d0f1aa3076488d445f84c7d727d384c675adaec1" dependencies = [ "debugid", "hex", - "rand 0.9.1", + "rand", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror", "time", "url", "uuid", @@ -2550,10 +2551,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -2566,11 +2568,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2579,15 +2590,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "indexmap 2.10.0", "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -2627,18 +2639,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.13.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.10.0", - "schemars", - "serde", - "serde_derive", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -2646,9 +2658,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.13.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", @@ -2821,6 +2833,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "take_mut" version = "0.2.2" @@ -2848,38 +2866,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -3069,9 +3067,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3081,9 +3079,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -3092,9 +3090,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3113,9 +3111,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -3158,18 +3156,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typed-builder" -version = "0.20.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9d30e3a08026c78f246b173243cf07b3696d274debd26680773b6773c2afc7" +checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.20.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" +checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" dependencies = [ "proc-macro2", "quote", @@ -3242,7 +3240,7 @@ version = "3.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39" dependencies = [ - "base64 0.22.1", + "base64", "der", "log", "native-tls", @@ -3260,7 +3258,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7" dependencies = [ - "base64 0.22.1", + "base64", "http 1.3.1", "httparse", "log", @@ -3346,35 +3344,22 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -3385,9 +3370,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3395,31 +3380,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -3443,15 +3428,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.2", -] - [[package]] name = "webpki-roots" version = "1.0.2" @@ -3932,3 +3908,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9211a9f64b825911bdf0240f58b7a8dac217fe260fc61f080a07f61372fbd5" diff --git a/Cargo.toml b/Cargo.toml index 9d3a20a..59d484f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,4 @@ [workspace] -members = ["moderation-service", "prisma"] +members = ["exam-utils", "moderation-service", "prisma"] +exclude = ["script", "js-api"] resolver = "3" diff --git a/exam-utils/Cargo.toml b/exam-utils/Cargo.toml new file mode 100644 index 0000000..851c86d --- /dev/null +++ b/exam-utils/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "exam-utils" +version = "0.1.0" +edition = "2024" + +[dependencies] +bson = { version = "3.1.0", features = ["serde", "serde_with-3"] } +mongodb = { version = "3.4.1", features = ["bson-3"] } +prisma = { path = "../prisma" } +rand = "0.9.2" +serde = "1.0.219" +serde_json = "1" +serde_with = "3.16.1" +thiserror = "2.0.17" +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } diff --git a/exam-utils/src/attempt.rs b/exam-utils/src/attempt.rs new file mode 100644 index 0000000..9df348c --- /dev/null +++ b/exam-utils/src/attempt.rs @@ -0,0 +1,184 @@ +use mongodb::bson::oid::ObjectId; +use prisma; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +pub fn get_time_between_submissions(_attempt: prisma::ExamEnvironmentExamAttempt) -> Vec { + todo!() +} + +#[serde_with::serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Attempt { + pub id: ObjectId, + #[serde(rename = "examId")] + pub exam_id: ObjectId, + #[serde(rename = "userId")] + pub user_id: ObjectId, + pub prerequisites: Vec, + pub deprecated: bool, + #[serde(rename = "questionSets")] + pub question_sets: Vec, + pub config: prisma::ExamEnvironmentConfig, + #[serde(rename = "startTime")] + #[serde_as(as = "bson::serde_helpers::datetime::AsRfc3339String")] + pub start_time: mongodb::bson::DateTime, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AttemptQuestionSet { + pub id: ObjectId, + #[serde(rename = "type")] + pub _type: prisma::ExamEnvironmentQuestionType, + pub context: Option, + pub questions: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AttemptQuestionSetQuestion { + pub id: ObjectId, + pub text: String, + pub tags: Vec, + pub deprecated: bool, + pub audio: Option, + /// Includes all answers available in the exam + pub answers: Vec, + /// Includes only answers submitted in the attempt + pub selected: Vec, + /// Includes only answers shown from the generation + pub generated: Vec, + /// If question was submitted, time it was submitted + #[serde(rename = "submissionTime")] + pub submission_time: Option, +} + +/// Constructs an `Attempt`: +/// - Filters questions from exam based on generated exam +/// - Adds submission time from attempt questions +/// - Adds selected answers from attempt +/// +/// NOTE: Generated exam is assumed to not be needed, +/// because API ensures attempt only includes answers from assigned generation. +pub fn construct_attempt( + exam: &prisma::ExamEnvironmentExam, + generation: &prisma::ExamEnvironmentGeneratedExam, + exam_attempt: &prisma::ExamEnvironmentExamAttempt, +) -> Attempt { + let prisma::ExamEnvironmentExam { + id: _id, + question_sets, + config, + prerequisites, + deprecated, + version: _version, + } = exam; + // TODO: Can caluclate allocation size from exam + let mut attempt_question_sets = vec![]; + + for question_set in question_sets { + let prisma::ExamEnvironmentQuestionSet { + id, + _type, + context, + questions, + } = question_set; + + // Attempt might not have question set, if related question(s) not answered + let attempt_question_set = exam_attempt + .question_sets + .iter() + .find(|qs| qs.id == question_set.id); + let generation_question_set = generation + .question_sets + .iter() + .find(|qs| qs.id == question_set.id); + + let mut attempt_questions = vec![]; + + for question in questions { + let prisma::ExamEnvironmentMultipleChoiceQuestion { + id, + text, + tags, + audio, + answers, + deprecated, + } = question; + + let mut selected = vec![]; + let mut generated = vec![]; + let mut submission_time = None; + // Attempt question might not exist if not answered + if let Some(aqs) = attempt_question_set { + if let Some(aq) = aqs.questions.iter().find(|q| q.id == *id) { + selected.extend_from_slice(&aq.answers); + submission_time = Some(aq.submission_time); + }; + } + + // TODO: It should be impossible for the generation question set to not exist if the attempt encountered it + if let Some(gqs) = generation_question_set { + if let Some(gq) = gqs.questions.iter().find(|q| q.id == *id) { + generated.extend_from_slice(&gq.answers); + } + } + + let attempt_question_set_question = AttemptQuestionSetQuestion { + id: id.clone(), + text: text.clone(), + tags: tags.clone(), + deprecated: deprecated.clone(), + audio: audio.clone(), + answers: answers.clone(), + selected, + generated, + submission_time, + }; + + attempt_questions.push(attempt_question_set_question); + } + + let attempt_question_set = AttemptQuestionSet { + id: id.clone(), + _type: _type.clone(), + context: context.clone(), + questions: attempt_questions, + }; + + attempt_question_sets.push(attempt_question_set); + } + + let start_time = exam_attempt.start_time; + + let attempt = Attempt { + id: exam_attempt.id, + exam_id: exam_attempt.exam_id, + user_id: exam_attempt.user_id, + prerequisites: prerequisites.clone(), + deprecated: *deprecated, + question_sets: attempt_question_sets, + config: config.clone(), + start_time, + }; + + attempt +} + +pub struct AttemptStats { + pub time_to_answers: Vec, + pub total_questions: usize, + pub answered: usize, + pub correct: usize, + pub time_to_complete: f64, + pub average_time_per_question: f64, +} + +pub struct TimeToAnswer { + pub name: usize, + pub value: f64, + pub is_correct: bool, +} + +pub fn get_attempt_stats(_attempt: Attempt) -> AttemptStats { + todo!() +} diff --git a/exam-utils/src/error.rs b/exam-utils/src/error.rs new file mode 100644 index 0000000..e0381e2 --- /dev/null +++ b/exam-utils/src/error.rs @@ -0,0 +1,18 @@ +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("{0}")] + Generation(String), + #[error("{0}")] + InvalidConfig(String), + // Froms + #[error("{0}")] + MongoDB(#[from] mongodb::error::Error), + #[error("{0}")] + SystemTimeError(#[from] std::time::SystemTimeError), + #[error("{0}")] + BsonAccess(#[from] mongodb::bson::error::ValueAccessErrorKind), + #[error("{0}")] + BsonSerialization(#[from] mongodb::bson::error::Error), + // #[error("{0}")] + // TimeConversion(#[from] time::error::ConversionRange), +} diff --git a/exam-utils/src/generation.rs b/exam-utils/src/generation.rs new file mode 100644 index 0000000..3a7751d --- /dev/null +++ b/exam-utils/src/generation.rs @@ -0,0 +1,89 @@ +use crate::error::Error; + +/// Given an exam, use config to create generations. +/// +/// Can fail if config is invalid, or if algorithm gets stuck. +pub fn try_generate(_exam: E) -> Result +where + E: Into, +{ + unimplemented!() +} + +/// Given a generation, validate it for basic properties: +/// 1) No duplicates +pub fn validate_generation(generation: &prisma::ExamEnvironmentGeneratedExam) -> Result<(), Error> { + let question_sets = &generation.question_sets; + + let mut qs_ids = vec![]; + let mut q_ids = vec![]; + let mut a_ids = vec![]; + for qs in question_sets { + if qs_ids.contains(&qs.id) { + return Err(Error::Generation(format!( + "question set id {} duplicate of question set id", + qs.id + ))); + } + if q_ids.contains(&qs.id) { + return Err(Error::Generation(format!( + "question set id {} duplicate of question id", + qs.id + ))); + } + if a_ids.contains(&qs.id) { + return Err(Error::Generation(format!( + "question set id {} duplicate of answer id", + qs.id + ))); + } + qs_ids.push(qs.id); + + for q in &qs.questions { + if qs_ids.contains(&q.id) { + return Err(Error::Generation(format!( + "question id {} duplicate of question set id", + q.id + ))); + } + if q_ids.contains(&q.id) { + return Err(Error::Generation(format!( + "question id {} duplicate of question id", + q.id + ))); + } + if a_ids.contains(&q.id) { + return Err(Error::Generation(format!( + "question id {} duplicate of answer id", + q.id + ))); + } + q_ids.push(q.id); + + for a in &q.answers { + if qs_ids.contains(&a) { + return Err(Error::Generation(format!( + "answer id {} duplicate of question set id", + a + ))); + } + if q_ids.contains(&a) { + return Err(Error::Generation(format!( + "answer id {} duplicate of question id", + a + ))); + } + if a_ids.contains(&a) { + return Err(Error::Generation(format!( + "answer id {} duplicate of answer id", + a + ))); + } + + a_ids.push(*a); + } + } + } + + Ok(()) +} diff --git a/exam-utils/src/lib.rs b/exam-utils/src/lib.rs new file mode 100644 index 0000000..218504e --- /dev/null +++ b/exam-utils/src/lib.rs @@ -0,0 +1,13 @@ +//! Exam Environment Utility Functions +//! +//! ## Current API +//! +//! - Calculate attempt score +//! - Validate exam config +//! - Generate exams +//! - Validate generated exams +//! +pub mod attempt; +pub mod error; +pub mod generation; +pub mod misc; diff --git a/exam-utils/src/misc.rs b/exam-utils/src/misc.rs new file mode 100644 index 0000000..a31e3be --- /dev/null +++ b/exam-utils/src/misc.rs @@ -0,0 +1,772 @@ +use mongodb::bson::oid::ObjectId; +use prisma; +use rand::seq::SliceRandom; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, Instant}; +use tracing::trace; + +use crate::error::Error; + +/// Calculates the attempt score, and compares score >= pass_score +pub fn check_attempt_pass( + exam: &prisma::ExamEnvironmentExam, + generated_exam: &prisma::ExamEnvironmentGeneratedExam, + attempt: &prisma::ExamEnvironmentExamAttempt, +) -> bool { + let passing_percent = exam.config.passing_percent; + if let Ok(score) = calculate_score(exam, generated_exam, attempt) { + return score >= passing_percent; + } + false +} + +pub fn calculate_score( + exam: &prisma::ExamEnvironmentExam, + generated_exam: &prisma::ExamEnvironmentGeneratedExam, + attempt: &prisma::ExamEnvironmentExamAttempt, +) -> Result { + let attempt_question_sets = &attempt.question_sets; + let generated_question_sets = &generated_exam.question_sets; + + let total_questions: usize = generated_question_sets + .iter() + .map(|qs| qs.questions.len()) + .sum(); + let mut correct_questions: usize = 0; + + for attempt_question_set in attempt_question_sets { + let exam_question_set = exam + .question_sets + .iter() + .find(|qs| &qs.id == &attempt_question_set.id) + .ok_or_else(|| { + format!( + "Attempt question set {} must exist in exam {}", + attempt_question_set.id, exam.id + ) + })?; + + let generated_question_set = generated_question_sets + .iter() + .find(|qs| &qs.id == &attempt_question_set.id) + .ok_or_else(|| { + format!( + "Generated question set {} must exist in generated exam {}", + attempt_question_set.id, generated_exam.id + ) + })?; + + for attempt_question in &attempt_question_set.questions { + let exam_question = exam_question_set + .questions + .iter() + .find(|q| &q.id == &attempt_question.id) + .ok_or_else(|| { + format!( + "Attempt question {} must exist in exam {}", + attempt_question.id, exam.id + ) + })?; + + let generated_question = generated_question_set + .questions + .iter() + .find(|q| &q.id == &attempt_question.id) + .ok_or_else(|| { + format!( + "Generated question {} must exist in generated exam {}", + attempt_question.id, generated_exam.id + ) + })?; + + if compare_answers( + &exam_question.answers, + &generated_question.answers, + &attempt_question.answers, + ) { + let correct_exam_answer_ids: Vec = exam_question + .answers + .iter() + .filter_map(|a| if a.is_correct { Some(a.id) } else { None }) + .collect(); + let selected_answer_ids = attempt_question.answers.clone(); + assert!(correct_exam_answer_ids.contains(selected_answer_ids.get(0).unwrap())); + correct_questions += 1; + } + } + } + + Ok((correct_questions as f64 / total_questions as f64) * 100.0) +} + +pub fn compare_answers( + exam_answers: &[prisma::ExamEnvironmentAnswer], + generated_answers: &[ObjectId], + attempt_answers: &[ObjectId], +) -> bool { + let correct_generated_answers: Vec<&ObjectId> = generated_answers + .iter() + .filter(|gen_ans| { + exam_answers + .iter() + .any(|exam_ans| exam_ans.is_correct && &exam_ans.id == *gen_ans) + }) + .collect(); + + let answers_equal = correct_generated_answers + .iter() + .all(|&correct_answer| attempt_answers.contains(correct_answer)); + + answers_equal && correct_generated_answers.len() == attempt_answers.len() +} + +/// Validate Exam Config: +/// - `config.name` is not empty +/// - `config.passing_percent` is between 0 and 100 +/// - `config.tags` is solvable +/// - `config.question_sets` is solvable +/// - `question_sets.questions.text` is not empty +/// - `question_sets.questions.answers` has at least one correct answer +/// - `question_sets.questions.answers.text` is not empty +/// +/// A "solvable" config means that there are enough sets, questions, and answers to satisfy the constraints +pub fn validate_config(exam: &prisma::ExamEnvironmentExam) -> Result<(), String> { + let config = &exam.config; + let question_sets = &exam.question_sets; + + if config.name.is_empty() { + return Err("Config name is empty".into()); + } + + if config.passing_percent < 0.0 || config.passing_percent > 100.0 { + return Err("Config passing percent must be between 0.0 and 100.0".into()); + } + + // For each tag config, generate a map of (tag config, number of questions satisfying tag) + // If any tag config `number_of_questions` > available questions with that tag, return error + for tag_config in &config.tags { + let mut available_questions = 0; + for question_set in question_sets { + for question in &question_set.questions { + let group = &tag_config.group; + // if `question.tags` includes all of `group`, then it satisfies the tag config + if group.iter().all(|tag| question.tags.contains(tag)) { + available_questions += 1; + } + } + } + if available_questions < tag_config.number_of_questions as usize { + return Err(format!( + "Not enough questions for tag config: {:?}. Available: {}, Required: {}", + tag_config, available_questions, tag_config.number_of_questions + )); + } + } + + // For each question set config, ensure there are enough question sets of that type + for qs_config in &config.question_sets { + let available_question_sets = question_sets + .iter() + .filter(|qs| qs._type == qs_config._type) + .count(); + if available_question_sets < qs_config.number_of_set as usize { + return Err(format!( + "Not enough question sets for question set config: {:?}. Available: {}, Required: {}", + qs_config, available_question_sets, qs_config.number_of_set + )); + } + } + + // For each `config.question_sets.number_of_questions`, ensure there are enough questions in the question sets of that type + // Tally the total number of questions for a given type + // Also, ensure for each question_set config, there exists a question set of that type with enough questions + for qs_config in &config.question_sets { + let mut total_questions = 0; + let mut has_enough_in_single_set = false; + for question_set in question_sets + .iter() + .filter(|qs| qs._type == qs_config._type) + { + let num_questions_in_set = question_set.questions.len(); + total_questions += num_questions_in_set; + if num_questions_in_set >= qs_config.number_of_questions as usize { + has_enough_in_single_set = true; + } + } + if total_questions + < qs_config.number_of_set as usize * qs_config.number_of_questions as usize + { + return Err(format!( + "Not enough questions overall for question set config: {:?}. Available: {}, Required: {}", + qs_config, + total_questions, + qs_config.number_of_set * qs_config.number_of_questions + )); + } + if !has_enough_in_single_set { + return Err(format!( + "No single question set has enough questions for question set config: {:?}", + qs_config + )); + } + } + + // For each `config.question_sets.number_of_correct_answers` and `number_of_incorrect_answers`, ensure there are enough answers in the question sets of that type + for qs_config in &config.question_sets { + for question_set in question_sets + .iter() + .filter(|qs| qs._type == qs_config._type) + { + for question in &question_set.questions { + let num_correct_answers = question.answers.iter().filter(|a| a.is_correct).count(); + let num_incorrect_answers = + question.answers.iter().filter(|a| !a.is_correct).count(); + if num_correct_answers < qs_config.number_of_correct_answers as usize { + return Err(format!( + "Not enough correct answers for question {:?} in question set {:?}. Available: {}, Required: {}", + question.id, + question_set.id, + num_correct_answers, + qs_config.number_of_correct_answers + )); + } + if num_incorrect_answers < qs_config.number_of_incorrect_answers as usize { + return Err(format!( + "Not enough incorrect answers for question {:?} in question set {:?}. Available: {}, Required: {}", + question.id, + question_set.id, + num_incorrect_answers, + qs_config.number_of_incorrect_answers + )); + } + } + } + } + + for qs in question_sets { + for question in &qs.questions { + if question.text.trim().is_empty() { + return Err(format!("Question {:?} has empty text", question.id)); + } + let has_correct_answer = question.answers.iter().any(|a| a.is_correct); + if !has_correct_answer { + return Err(format!("Question {:?} has no correct answers", question.id)); + } + for answer in &question.answers { + if answer.text.trim().is_empty() { + return Err(format!( + "Answer {:?} in question {:?} has empty text", + answer.id, question.id + )); + } + } + } + } + + Ok(()) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExamInput { + pub id: ObjectId, + #[serde(rename = "questionSets")] + pub question_sets: Vec, + pub config: prisma::ExamEnvironmentConfig, +} + +#[derive(Debug, Clone)] +struct QuestionSetConfigWithQuestions { + config: prisma::ExamEnvironmentQuestionSetConfig, + question_sets: Vec, +} + +const TIMEOUT_IN_MS: u64 = 5_000; + +/// Generates an exam for the user, based on the exam configuration. +pub fn generate_exam(exam: ExamInput) -> Result { + let start_time = Instant::now(); + let timeout = Duration::from_millis(TIMEOUT_IN_MS); + + let mut rng = rand::rng(); + + // Shuffle question sets and their questions/answers + let mut shuffled_question_sets: Vec = exam + .question_sets + .into_iter() + .map(|mut qs| { + let mut shuffled_questions: Vec = qs + .questions + .into_iter() + .filter(|q| !q.deprecated) + .map(|mut q| { + q.answers.shuffle(&mut rng); + q + }) + .collect(); + shuffled_questions.shuffle(&mut rng); + qs.questions = shuffled_questions; + qs + }) + .collect(); + shuffled_question_sets.shuffle(&mut rng); + + if exam.config.question_sets.is_empty() { + return Err(Error::Generation(format!( + "{}: Invalid exam config - no question sets config.", + exam.id + ))); + } + + // Convert question set config by type: [[all question sets of type], [another type], ...] + let mut type_converted_question_sets_config: Vec< + Vec, + > = Vec::new(); + for config in exam.config.question_sets.iter() { + if let Some(type_group) = type_converted_question_sets_config + .iter_mut() + .find(|group| group.first().map(|c| &c._type) == Some(&config._type)) + { + type_group.push(config.clone()); + } else { + type_converted_question_sets_config.push(vec![config.clone()]); + } + } + + // Sort each type group randomly (heuristic for retry) + for group in type_converted_question_sets_config.iter_mut() { + group.shuffle(&mut rng); + } + + let sorted_question_sets_config: Vec = + type_converted_question_sets_config + .into_iter() + .flatten() + .collect(); + + // Move all questions from set that are used to fulfill tag config. + let mut question_sets_config_with_questions: Vec = + sorted_question_sets_config + .into_iter() + .map(|config| QuestionSetConfigWithQuestions { + config, + question_sets: Vec::new(), + }) + .collect(); + + // Sort tag config by number of tags in descending order. + let mut sorted_tag_config = exam.config.tags.clone(); + sorted_tag_config.sort_by(|a, b| b.group.len().cmp(&a.group.len())); + + // Main allocation loop + 'question_sets_config_loop: for qsc_with_qs in question_sets_config_with_questions.iter_mut() { + 'sorted_tag_config_loop: for tag_config in sorted_tag_config.iter_mut() { + // Collect questions to remove (question_set_id, question_id) + let mut questions_to_remove: Vec<(ObjectId, ObjectId)> = Vec::new(); + + for question_set in shuffled_question_sets + .iter_mut() + .filter(|sqs| sqs._type == qsc_with_qs.config._type) + { + // If questionSet does not have enough questions for config, do not consider. + if qsc_with_qs.config.number_of_questions > question_set.questions.len() as i64 { + trace!( + number_of_questions = question_set.questions.len(), + "skipping. not enough questions in question set" + ); + continue; + } + // If tagConfig is finished, skip. + if tag_config.number_of_questions == 0 { + trace!(?tag_config.group, "skipping. tag config fulfilled"); + continue 'sorted_tag_config_loop; + } + // If questionSetConfig has been fulfilled, skip. + if is_question_set_config_fulfilled(qsc_with_qs) { + trace!(?qsc_with_qs, "skipping. question set config fulfilled"); + continue 'question_sets_config_loop; + } + + // Store question_set id and metadata before mutable borrow + let question_set_id = question_set.id; + let question_set_type = question_set._type.clone(); + let question_set_context = question_set.context.clone(); + + // Find question with at least all tags in the set. + let questions: Vec<&mut prisma::ExamEnvironmentMultipleChoiceQuestion> = + question_set + .questions + .iter_mut() + .filter(|q| tag_config.group.iter().all(|t| q.tags.contains(t))) + .collect(); + + // trace!( + // number_of_questions = questions.len(), + // ?tag_config, + // "questions fulfilling tags" + // ); + + for question in questions { + // Does question fulfill criteria for questionSetConfig: + let number_of_correct_answers = + question.answers.iter().filter(|a| a.is_correct).count() as i64; + let number_of_incorrect_answers = + question.answers.iter().filter(|a| !a.is_correct).count() as i64; + + if qsc_with_qs.config.number_of_correct_answers <= number_of_correct_answers + && qsc_with_qs.config.number_of_incorrect_answers + <= number_of_incorrect_answers + { + if is_question_set_config_fulfilled(qsc_with_qs) { + continue 'question_sets_config_loop; + } + + // Push questionSet if it does not exist. Otherwise, just push question + let qscqs = qsc_with_qs + .question_sets + .iter_mut() + .find(|qs| qs.id == question_set_id); + + let question_with_correct_number_of_answers = + get_question_with_random_answers(question, &qsc_with_qs.config)?; + + if let Some(existing_qs) = qscqs { + if existing_qs.questions.len() + == qsc_with_qs.config.number_of_questions as usize + { + break; + } + existing_qs + .questions + .push(question_with_correct_number_of_answers); + } else { + if qsc_with_qs.question_sets.len() + == qsc_with_qs.config.number_of_set as usize + { + break; + } + // Create new question set from stored metadata + let new_question_set = prisma::ExamEnvironmentQuestionSet { + id: question_set_id, + _type: question_set_type.clone(), + context: question_set_context.clone(), + questions: vec![question_with_correct_number_of_answers], + }; + qsc_with_qs.question_sets.push(new_question_set); + } + + // Mark question for removal + questions_to_remove.push((question_set_id, question.id)); + + tag_config.number_of_questions -= 1; + } + } + } + + // Remove marked questions after iteration + for (qs_id, q_id) in questions_to_remove { + if let Some(qs) = shuffled_question_sets.iter_mut().find(|qs| qs.id == qs_id) { + qs.questions.retain(|q| q.id != q_id); + } + } + } + + // Add questions to questionSetsConfigWithQuestions until fulfilled. + while !is_question_set_config_fulfilled(qsc_with_qs) { + if start_time.elapsed() > timeout { + return Err(Error::Generation(format!( + "Unable to generate exam within {}ms", + TIMEOUT_IN_MS + ))); + } + + // Ensure all questionSets ARE FULL + if (qsc_with_qs.config.number_of_set as usize) > qsc_with_qs.question_sets.len() { + // Find a question set with enough questions and answers to fulfill config + let question_set = shuffled_question_sets + .iter() + .find(|qs| { + if qs._type == qsc_with_qs.config._type && qs.questions.len() >= qsc_with_qs.config.number_of_questions as usize + { + let questions: Vec<&prisma::ExamEnvironmentMultipleChoiceQuestion> = qs + .questions + .iter() + .filter(|q| { + let number_of_correct_answers = + q.answers.iter().filter(|a| a.is_correct).count() + as i64; + let number_of_incorrect_answers = + q.answers.iter().filter(|a| !a.is_correct).count() + as i64; + number_of_correct_answers + >= qsc_with_qs.config.number_of_correct_answers + && number_of_incorrect_answers + >= qsc_with_qs.config.number_of_incorrect_answers + }) + .collect(); + + return questions.len() + >= qsc_with_qs.config.number_of_questions as usize; + } + + false + }) + .cloned() + .ok_or_else(|| { + Error::Generation(format!( + "Invalid Exam Configuration for {}. Not enough questions for question type {:?}.", + exam.id, qsc_with_qs.config._type + )) + })?; + + trace!("question set found to fulfill"); + + // Remove questionSet from shuffledQuestionSets + let question_set_id = question_set.id; + shuffled_question_sets.retain(|qs| qs.id != question_set_id); + + // Find question with enough answers to fulfill config + let questions: Vec = question_set + .questions + .iter() + .filter(|q| { + let number_of_correct_answers = + q.answers.iter().filter(|a| a.is_correct).count() as i64; + let number_of_incorrect_answers = + q.answers.iter().filter(|a| !a.is_correct).count() as i64; + number_of_correct_answers >= qsc_with_qs.config.number_of_correct_answers + && number_of_incorrect_answers + >= qsc_with_qs.config.number_of_incorrect_answers + }) + .cloned() + .collect(); + + let num_to_add = qsc_with_qs.config.number_of_questions as usize; + let questions_with_correct_answers: Result< + Vec, + Error, + > = questions + .iter() + .take(num_to_add) + .map(|q| get_question_with_random_answers(q, &qsc_with_qs.config)) + .collect(); + + let mut question_set_with_correct_number_of_answers = question_set.clone(); + question_set_with_correct_number_of_answers.questions = + questions_with_correct_answers?; + + trace!( + number_of_questions = + question_set_with_correct_number_of_answers.questions.len(), + "pushing question set to question set with config" + ); + qsc_with_qs + .question_sets + .push(question_set_with_correct_number_of_answers); + } + + // Ensure all existing questionSets have correct number of questions + for question_set in qsc_with_qs.question_sets.iter_mut() { + if (question_set.questions.len() as i64) < qsc_with_qs.config.number_of_questions { + let questions: Vec = + shuffled_question_sets + .iter() + .find(|qs| qs.id == question_set.id) + .ok_or_else(|| { + Error::Generation( + format!( + "Invalid Exam Configuration for {}. Not enough questions for question type {:?}.", + exam.id, qsc_with_qs.config._type + )) + })? + .questions + .iter() + .filter(|q| !question_set.questions.iter().any(|qsq| qsq.id == q.id)) + .cloned() + .collect(); + + let questions_with_enough_answers: Vec< + prisma::ExamEnvironmentMultipleChoiceQuestion, + > = questions + .into_iter() + .filter(|q| { + let number_of_correct_answers = + q.answers.iter().filter(|a| a.is_correct).count() as i64; + let number_of_incorrect_answers = + q.answers.iter().filter(|a| !a.is_correct).count() as i64; + number_of_correct_answers + >= qsc_with_qs.config.number_of_correct_answers + && number_of_incorrect_answers + >= qsc_with_qs.config.number_of_incorrect_answers + }) + .collect(); + + // Push as many questions as needed to fulfill questionSetConfig + let num_to_add = (qsc_with_qs.config.number_of_questions as usize) + - question_set.questions.len(); + let questions_to_add: Vec = + questions_with_enough_answers + .into_iter() + .take(num_to_add) + .collect(); + + let questions_with_correct_answers: Result< + Vec, + Error, + > = questions_to_add + .iter() + .map(|q| get_question_with_random_answers(q, &qsc_with_qs.config)) + .collect(); + + question_set + .questions + .extend(questions_with_correct_answers?); + + // Remove questions from shuffledQuestionSets + for q in questions_to_add.iter() { + if let Some(qs) = shuffled_question_sets + .iter_mut() + .find(|qs| qs.id == question_set.id) + { + qs.questions.retain(|qs_q| qs_q.id != q.id); + } + } + } + } + } + } + + trace!("finished question set config"); + + // Update tag config for cases where one question fulfills multiple tag configs + for qsc_with_qs in question_sets_config_with_questions.iter() { + for question_set in qsc_with_qs.question_sets.iter() { + for question in question_set.questions.iter() { + for tag_config in sorted_tag_config.iter_mut() { + if tag_config.number_of_questions <= 0 { + continue; + } + if tag_config.group.iter().all(|t| question.tags.contains(t)) { + tag_config.number_of_questions -= 1; + } + } + } + } + } + + trace!("finished updating tag config post question set config"); + + // Verify all tag configs are fulfilled + for tag_config in sorted_tag_config.iter() { + if tag_config.number_of_questions > 0 { + return Err(Error::Generation(format!( + "Invalid Exam Configuration for exam \"{}\". Not enough questions for tag group \"{}\".", + exam.id, + tag_config.group.join(",") + ))); + } + } + + trace!("all tag configs are fulfilled"); + + // Build the final generated exam structure + let question_sets: Vec = + question_sets_config_with_questions + .into_iter() + .flat_map(|qsc| { + qsc.question_sets.into_iter().map(|qs| { + let questions: Vec = qs + .questions + .into_iter() + .map(|q| { + let answers: Vec = + q.answers.into_iter().map(|a| a.id).collect(); + prisma::ExamEnvironmentGeneratedMultipleChoiceQuestion { + id: q.id, + answers, + } + }) + .collect(); + prisma::ExamEnvironmentGeneratedQuestionSet { + id: qs.id, + questions, + } + }) + }) + .collect(); + + Ok(prisma::ExamEnvironmentGeneratedExam { + id: ObjectId::new(), + exam_id: exam.id, + question_sets, + deprecated: false, + version: 1, + }) +} + +/// Returns the configs that are not fulfilled +fn is_question_set_config_fulfilled(qsc_with_qs: &QuestionSetConfigWithQuestions) -> bool { + let enough_of_set = + qsc_with_qs.config.number_of_set as usize == qsc_with_qs.question_sets.len(); + + if !enough_of_set { + trace!( + expected = qsc_with_qs.config.number_of_set, + actual = qsc_with_qs.question_sets.len(), + "not enough of set" + ); + } + + let enough_questions = qsc_with_qs.question_sets.iter().all(|qs| { + let num_questions = qs.questions.len(); + let expected_num_questions = qsc_with_qs.config.number_of_questions as usize; + if num_questions != expected_num_questions { + trace!( + expected = expected_num_questions, + actual = num_questions, + "number of questions required in config does not match number of questions" + ); + } + + num_questions == expected_num_questions + }); + + enough_of_set && enough_questions +} + +/// Gets random answers for a question. +fn get_question_with_random_answers( + question: &prisma::ExamEnvironmentMultipleChoiceQuestion, + question_set_config: &prisma::ExamEnvironmentQuestionSetConfig, +) -> Result { + let mut rng = rand::rng(); + let mut random_answers = question.answers.clone(); + random_answers.shuffle(&mut rng); + + let incorrect_answers: Vec = random_answers + .iter() + .filter(|a| !a.is_correct) + .take(question_set_config.number_of_incorrect_answers as usize) + .cloned() + .collect(); + + let correct_answers: Vec = random_answers + .iter() + .filter(|a| a.is_correct) + .take(question_set_config.number_of_correct_answers as usize) + .cloned() + .collect(); + + if incorrect_answers.is_empty() || correct_answers.is_empty() { + return Err(Error::Generation(format!( + "Question {} does not have enough correct/incorrect answers.", + question.id + ))); + } + + let mut answers = incorrect_answers; + answers.extend(correct_answers); + + let mut result = question.clone(); + result.answers = answers; + Ok(result) +} diff --git a/moderation-service/Cargo.toml b/moderation-service/Cargo.toml index 368ed83..11aed8d 100644 --- a/moderation-service/Cargo.toml +++ b/moderation-service/Cargo.toml @@ -9,6 +9,7 @@ dotenvy = "0.15.7" futures-util = "0.3.31" mongodb = { version = "3.3.0", features = ["bson-3"] } prisma = { path = "../prisma" } +exam-utils = { path = "../exam-utils" } sentry = { version = "0.45.0", features = ["tracing", "logs"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" diff --git a/moderation-service/src/attempt.rs b/moderation-service/src/attempt.rs deleted file mode 100644 index 1a2cd29..0000000 --- a/moderation-service/src/attempt.rs +++ /dev/null @@ -1,118 +0,0 @@ -use mongodb::bson::oid::ObjectId; -use prisma::{ - ExamEnvironmentAnswer, ExamEnvironmentExam, ExamEnvironmentExamAttempt, - ExamEnvironmentGeneratedExam, -}; - -/// Calculates the attempt score, and compares score >= pass_score -pub fn check_attempt_pass( - exam: &ExamEnvironmentExam, - generated_exam: &ExamEnvironmentGeneratedExam, - attempt: &ExamEnvironmentExamAttempt, -) -> bool { - let passing_percent = exam.config.passing_percent; - if let Ok(score) = calculate_score(exam, generated_exam, attempt) { - return score >= passing_percent; - } - false -} - -pub fn calculate_score( - exam: &ExamEnvironmentExam, - generated_exam: &ExamEnvironmentGeneratedExam, - attempt: &ExamEnvironmentExamAttempt, -) -> Result { - let attempt_question_sets = &attempt.question_sets; - let generated_question_sets = &generated_exam.question_sets; - - let total_questions: usize = generated_question_sets - .iter() - .map(|qs| qs.questions.len()) - .sum(); - let mut correct_questions: usize = 0; - - for attempt_question_set in attempt_question_sets { - let exam_question_set = exam - .question_sets - .iter() - .find(|qs| &qs.id == &attempt_question_set.id) - .ok_or_else(|| { - format!( - "Attempt question set {} must exist in exam {}", - attempt_question_set.id, exam.id - ) - })?; - - let generated_question_set = generated_question_sets - .iter() - .find(|qs| &qs.id == &attempt_question_set.id) - .ok_or_else(|| { - format!( - "Generated question set {} must exist in generated exam {}", - attempt_question_set.id, generated_exam.id - ) - })?; - - for attempt_question in &attempt_question_set.questions { - let exam_question = exam_question_set - .questions - .iter() - .find(|q| &q.id == &attempt_question.id) - .ok_or_else(|| { - format!( - "Attempt question {} must exist in exam {}", - attempt_question.id, exam.id - ) - })?; - - let generated_question = generated_question_set - .questions - .iter() - .find(|q| &q.id == &attempt_question.id) - .ok_or_else(|| { - format!( - "Generated question {} must exist in generated exam {}", - attempt_question.id, generated_exam.id - ) - })?; - - if compare_answers( - &exam_question.answers, - &generated_question.answers, - &attempt_question.answers, - ) { - let correct_exam_answer_ids: Vec = exam_question - .answers - .iter() - .filter_map(|a| if a.is_correct { Some(a.id) } else { None }) - .collect(); - let selected_answer_ids = attempt_question.answers.clone(); - assert!(correct_exam_answer_ids.contains(selected_answer_ids.get(0).unwrap())); - correct_questions += 1; - } - } - } - - Ok((correct_questions as f64 / total_questions as f64) * 100.0) -} - -pub fn compare_answers( - exam_answers: &[ExamEnvironmentAnswer], - generated_answers: &[ObjectId], - attempt_answers: &[ObjectId], -) -> bool { - let correct_generated_answers: Vec<&ObjectId> = generated_answers - .iter() - .filter(|gen_ans| { - exam_answers - .iter() - .any(|exam_ans| exam_ans.is_correct && &exam_ans.id == *gen_ans) - }) - .collect(); - - let answers_equal = correct_generated_answers - .iter() - .all(|&correct_answer| attempt_answers.contains(correct_answer)); - - answers_equal && correct_generated_answers.len() == attempt_answers.len() -} diff --git a/moderation-service/src/db.rs b/moderation-service/src/db.rs index f9ede1d..2101c75 100644 --- a/moderation-service/src/db.rs +++ b/moderation-service/src/db.rs @@ -15,8 +15,9 @@ use prisma::{ ExamEnvironmentGeneratedExam, }; use serde_json::json; +use exam_utils::misc::check_attempt_pass; -use crate::{attempt::check_attempt_pass, config::EnvVars}; +use crate::{config::EnvVars}; const PRACTICE_EXAM_ID: &str = "674819431ed2e8ac8d170f5e"; diff --git a/moderation-service/src/lib.rs b/moderation-service/src/lib.rs index 8fd2933..7cb3263 100644 --- a/moderation-service/src/lib.rs +++ b/moderation-service/src/lib.rs @@ -1,3 +1,2 @@ -pub mod attempt; pub mod config; pub mod db; From 0ea6c80b23f1e4c31369af422dc1d8d64a2696f5 Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Tue, 6 Jan 2026 13:38:31 +0200 Subject: [PATCH 2/2] feat: add script and boilerplate js-api --- js-api/Cargo.toml | 13 + js-api/src/lib.rs | 14 + script/Cargo.lock | 3363 ++++++++++++++++++++++ script/Cargo.toml | 17 + script/src/ensure_awarded_challenges.rs | 275 ++ script/src/main.rs | 26 + script/src/update_python_gen_attempts.rs | 37 + script/src/util.rs | 81 + script/src/validate_all_generations.rs | 35 + 9 files changed, 3861 insertions(+) create mode 100644 js-api/Cargo.toml create mode 100644 js-api/src/lib.rs create mode 100644 script/Cargo.lock create mode 100644 script/Cargo.toml create mode 100644 script/src/ensure_awarded_challenges.rs create mode 100644 script/src/main.rs create mode 100644 script/src/update_python_gen_attempts.rs create mode 100644 script/src/util.rs create mode 100644 script/src/validate_all_generations.rs diff --git a/js-api/Cargo.toml b/js-api/Cargo.toml new file mode 100644 index 0000000..9170239 --- /dev/null +++ b/js-api/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "js-api" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +serde-wasm-bindgen = "0.6.5" +wasm-bindgen = "0.2.106" +exam-utils = { path = "../exam-utils" } +prisma = { path = "../prisma" } diff --git a/js-api/src/lib.rs b/js-api/src/lib.rs new file mode 100644 index 0000000..1824837 --- /dev/null +++ b/js-api/src/lib.rs @@ -0,0 +1,14 @@ +use serde_wasm_bindgen::{from_value, to_value}; +use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; + +#[wasm_bindgen] +pub fn validate_generation(val: JsValue) -> JsValue { + let generation: prisma::ExamEnvironmentGeneratedExam = from_value(val).unwrap(); + let res = exam_utils::generation::validate_generation(&generation); + + if let Err(e) = res { + return to_value(&e.to_string()).unwrap(); + } + + JsValue::null() +} diff --git a/script/Cargo.lock b/script/Cargo.lock new file mode 100644 index 0000000..f17403f --- /dev/null +++ b/script/Cargo.lock @@ -0,0 +1,3363 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bson" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969a9ba84b0ff843813e7249eed1678d9b6607ce5a3b8f0a47af3fcf7978e6e" +dependencies = [ + "ahash", + "base64", + "bitvec", + "getrandom 0.2.16", + "getrandom 0.3.4", + "hex", + "indexmap 2.12.1", + "js-sys", + "once_cell", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "bson" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3f109694c4f45353972af96bf97d8a057f82e2d6e496457f4d135b9867a518c" +dependencies = [ + "ahash", + "base64", + "bitvec", + "getrandom 0.3.4", + "hex", + "indexmap 2.12.1", + "js-sys", + "rand", + "serde", + "serde_bytes", + "serde_with", + "simdutf8", + "thiserror", + "time", + "uuid", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "connection-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3" + +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive-where" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "diagnostics" +version = "0.1.0" +source = "git+https://github.com/prisma/prisma-engines?tag=6.16.3#bb420e667c1820a8c05a38023385f6cc7ef8e83a" +dependencies = [ + "colored", + "indoc", + "pest", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "exam-utils" +version = "0.1.0" +dependencies = [ + "bson 3.1.0", + "mongodb", + "prisma", + "rand", + "serde", + "serde_json", + "serde_with", + "thiserror", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.12.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand", + "ring", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.1", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.179" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lsp-types" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "macro_magic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn", +] + +[[package]] +name = "macro_magic_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150" +dependencies = [ + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "macro_magic_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" +dependencies = [ + "macro_magic_core", + "quote", + "syn", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "mongocrypt" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da0cd419a51a5fb44819e290fbdb0665a54f21dead8923446a799c7f4d26ad9" +dependencies = [ + "bson 2.15.0", + "bson 3.1.0", + "mongocrypt-sys", + "once_cell", + "serde", +] + +[[package]] +name = "mongocrypt-sys" +version = "0.1.5+1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224484c5d09285a7b8cb0a0c117e847ebd14cb6e4470ecf68cdb89c503b0edb9" + +[[package]] +name = "mongodb" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f5c20217413bed97c613714e6d6dfe39ef59dd79a68999f1043b0566192975" +dependencies = [ + "base64", + "bitflags 2.10.0", + "bson 2.15.0", + "bson 3.1.0", + "derive-where", + "derive_more", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hickory-proto", + "hickory-resolver", + "hmac", + "macro_magic", + "md-5", + "mongocrypt", + "mongodb-internal-macros", + "pbkdf2", + "percent-encoding", + "rand", + "rustc_version_runtime", + "rustls", + "rustversion", + "serde", + "serde_bytes", + "serde_with", + "sha1", + "sha2", + "socket2 0.6.1", + "stringprep", + "strsim", + "take_mut", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "typed-builder", + "uuid", + "webpki-roots", +] + +[[package]] +name = "mongodb-internal-macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20033442aa13664e70bc9f8be1bacabebf6a31b6d4bb5608ceb99c4ec96e9951" +dependencies = [ + "macro_magic", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "parser-database" +version = "0.1.0" +source = "git+https://github.com/prisma/prisma-engines?tag=6.16.3#bb420e667c1820a8c05a38023385f6cc7ef8e83a" +dependencies = [ + "diagnostics", + "either", + "enumflags2", + "indexmap 2.12.1", + "itertools", + "rustc-hash", + "schema-ast", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prisma" +version = "0.1.0" +dependencies = [ + "bson 3.1.0", + "prisma-rust-schema", + "serde", + "serde_json", +] + +[[package]] +name = "prisma-rust-schema" +version = "2.1.1" +source = "git+https://github.com/ShaunSHamilton/prisma-rust-schema.git#0bb2707d41115872b2f348071e317ee5fcac1da3" +dependencies = [ + "proc-macro2", + "psl", + "quote", + "reqwest", + "serde", + "serde_json", + "serde_tokenstream", + "syn", +] + +[[package]] +name = "prisma-value" +version = "0.1.0" +source = "git+https://github.com/prisma/prisma-engines?tag=6.16.3#bb420e667c1820a8c05a38023385f6cc7ef8e83a" +dependencies = [ + "base64", + "bigdecimal", + "chrono", + "regex", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl" +version = "0.1.0" +source = "git+https://github.com/prisma/prisma-engines?tag=6.16.3#bb420e667c1820a8c05a38023385f6cc7ef8e83a" +dependencies = [ + "psl-core", +] + +[[package]] +name = "psl-core" +version = "0.1.0" +source = "git+https://github.com/prisma/prisma-engines?tag=6.16.3#bb420e667c1820a8c05a38023385f6cc7ef8e83a" +dependencies = [ + "bigdecimal", + "cfg-if", + "chrono", + "connection-string", + "diagnostics", + "either", + "enumflags2", + "hex", + "indoc", + "itertools", + "lsp-types", + "parser-database", + "prisma-value", + "regex", + "schema-ast", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schema-ast" +version = "0.1.0" +source = "git+https://github.com/prisma/prisma-engines?tag=6.16.3#bb420e667c1820a8c05a38023385f6cc7ef8e83a" +dependencies = [ + "diagnostics", + "pest", + "pest_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "script" +version = "0.1.0" +dependencies = [ + "dotenvy", + "exam-utils", + "futures-util", + "indicatif", + "mongodb", + "prisma", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +dependencies = [ + "indexmap 2.12.1", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_tokenstream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-builder" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +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", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +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", +] + +[[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 = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/script/Cargo.toml b/script/Cargo.toml new file mode 100644 index 0000000..f695192 --- /dev/null +++ b/script/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "script" +version = "0.1.0" +edition = "2024" + +[dependencies] +mongodb = { version = "3.4.1", features = ["bson-3"] } +serde = "1.0.228" +tokio = { version = "1.48.0", features = ["full"] } +prisma = { path = "../prisma" } +exam-utils = { path = "../exam-utils" } +dotenvy = "0.15.7" +futures-util = "0.3.31" +indicatif = "0.18.3" +serde_json = "1.0.148" +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] } diff --git a/script/src/ensure_awarded_challenges.rs b/script/src/ensure_awarded_challenges.rs new file mode 100644 index 0000000..8a18a9c --- /dev/null +++ b/script/src/ensure_awarded_challenges.rs @@ -0,0 +1,275 @@ +use std::{collections::HashMap, str::FromStr}; + +use exam_utils::misc::check_attempt_pass; +use futures_util::{StreamExt, TryStreamExt}; +use indicatif::ProgressBar; +use mongodb::{ + Client, + bson::{Bson, Document, deserialize_from_document, doc, oid::ObjectId, serialize_to_bson}, +}; +use serde_json::{Value, json}; +use tracing::{debug, error, info, trace, warn}; + +use crate::util::{err, get_collection, get_from_cache_or_collection}; + +/// Finds all approved moderations with `challengesAwarded: true` +/// Gets matching attempt +/// Checks attempt passes +/// Gets user +/// Pushes completed challenge, if not already present +pub async fn ensure_awarded_challenges(client: Client) -> Result<(), String> { + let moderation_col = get_collection::( + &client, + "ExamEnvironmentExamModeration", + ) + .await; + let exam_col = + get_collection::(&client, "ExamEnvironmentExam").await; + let generation_col = get_collection::( + &client, + "ExamEnvironmentGeneratedExam", + ) + .await; + let challenge_col = + get_collection::(&client, "ExamEnvironmentChallenge") + .await; + let user_col = get_collection::(&client, "user").await; + + let moderation_filter = doc! {"status": prisma::ExamEnvironmentExamModerationStatus::Approved, "challengesAwarded": true}; + + let moderation_count = moderation_col + .count_documents(moderation_filter.clone()) + .await + .map_err(err("unable to query moderation count"))?; + + let pb: ProgressBar = ProgressBar::new(moderation_count); + let mut num_attempts_need_fixing = 0; + let mut num_attempts_not_need_fixing = 0; + + let mut exams: HashMap = HashMap::new(); + let mut generations = HashMap::::new(); + let mut challenges = HashMap::>::new(); + + let pipeline = vec![ + doc! { "$match": moderation_filter }, + doc! { "$lookup": { + "from": "ExamEnvironmentExamAttempt", + "localField": "examAttemptId", + "foreignField": "_id", + "as": "attempt" + }}, + doc! { "$unwind": "$attempt" }, + ]; + + let mut cursor = moderation_col + .aggregate(pipeline) + .await + .map_err(err("unable to run lookup aggregation"))?; + + while let Some(mod_attempt) = cursor.next().await { + pb.inc(1); + let attempt = match mod_attempt { + Ok(mod_attempt) => { + let attempt: prisma::ExamEnvironmentExamAttempt = match mod_attempt + .get_document("attempt") + { + Ok(a) => match deserialize_from_document(a.clone()) { + Ok(a) => a, + Err(e) => { + error!(error = ?e, "unable to deserialize attempt"); + continue; + } + }, + Err(e) => { + error!( + error = ?e, "'attempt' field not attached as document to moderation attempt" + ); + continue; + } + }; + attempt + } + Err(e) => { + error!(error = ?e, "unable to access moderation attempt"); + continue; + } + }; + + // Check attempt passed + let exam = match get_from_cache_or_collection( + &exam_col, + doc! {"_id": attempt.exam_id}, + &mut exams, + attempt.exam_id, + ) + .await + { + Some(e) => e, + None => continue, + }; + + let generation = match get_from_cache_or_collection( + &generation_col, + doc! {"_id": attempt.generated_exam_id}, + &mut generations, + attempt.generated_exam_id, + ) + .await + { + Some(g) => g, + None => continue, + }; + + let passed = check_attempt_pass(&exam, &generation, &attempt); + + if !passed { + debug!(attempt_id = %attempt.id, "attempt did not pass"); + continue; + } + debug!(attempt_id = %attempt.id, "attempt passed"); + + // Get user + let user = match user_col + .find_one(doc! {"_id": attempt.user_id}) + .projection(doc! {"_id": true,"completedChallenges": true}) + .await + { + Ok(user) => match user { + Some(user) => user, + None => { + error!( + user_id = %attempt.user_id, attempt_id = %attempt.id, "user does not exist for attempt", + ); + continue; + } + }, + Err(e) => { + error!( + error = ?e, user_id = %attempt.user_id, attempt_id = %attempt.id, "unable to find user for attempt" + ); + continue; + } + }; + + let challenges = if let Some(challenges) = challenges.get(&exam.id) { + challenges.to_owned() + } else { + let chals: mongodb::Cursor = + if let Ok(c) = challenge_col.find(doc! {"examId": exam.id}).await { + c + } else { + error!(exam_id = %exam.id, "unable to query challenges for exam id"); + continue; + }; + let cs: Vec = if let Ok(ids) = chals + .map(|c| match c { + Ok(c) => Ok(c.challenge_id), + Err(e) => Err(e), + }) + .try_collect() + .await + { + ids + } else { + error!( + exam_id = %exam.id, "unable to deserialize challenges from query for exam id" + ); + continue; + }; + challenges.insert(exam.id, cs.clone()); + cs + }; + + // Check if challenges are all awarded + let mut challenges_to_award = vec![]; + let mut awarded_challenges = vec![]; + match user.get_array("completedChallenges") { + Ok(completed_challenges) => { + for completed_challenge in completed_challenges { + if let Some(completed_challenge) = completed_challenge.as_document() { + let id = match completed_challenge.get("id") { + Some(Bson::ObjectId(id)) => *id, + Some(Bson::String(s)) => { + if let Ok(o) = ObjectId::from_str(s) { + o + } else { + warn!(id = s, "unable to parse completed challenge id"); + continue; + } + } + t => { + warn!( + field = ?t, user_id = %attempt.user_id, "unexpected completed challenge id type for user" + ); + continue; + } + }; + + awarded_challenges.push(id); + } else { + warn!(user_id = %attempt.user_id, "completed challenge is not document"); + } + } + } + Err(e) => { + error!(user_id = %attempt.user_id, error = ?e, "unexpected type for completed challenges field"); + } + } + + for challenge in challenges { + if !awarded_challenges.contains(&challenge) { + challenges_to_award.push(challenge); + trace!(%challenge, user_id = %attempt.user_id, "challenge needing awarding to user"); + } else { + trace!(%challenge, user_id = %attempt.user_id, "challenge already awarded to user"); + } + } + + if !challenges_to_award.is_empty() { + num_attempts_need_fixing += 1; + + let attempt_start_time = attempt.start_time.timestamp_millis(); + + let chals: Vec = challenges_to_award + .iter() + .map(|id| { + json!({ + "id": id.to_hex(), + "completedDate": attempt_start_time, + "challengeType": json!(30) + }) + }) + .collect(); + + info!(user_id = %attempt.user_id, num_challenges = challenges_to_award.len(), "awarding challenges"); + let completed_bson = match serialize_to_bson(&json!(chals)) { + Ok(b) => b, + Err(e) => { + error!(error = ?e, "unable to serialize challenges to bson"); + continue; + } + }; + info!(user_id = %attempt.user_id, "updating user with completed challenges"); + if let Err(e) = user_col + .update_one( + doc! {"_id": attempt.user_id}, + doc! {"$push": {"completedChallenges": {"$each": completed_bson}}}, + ) + .await + { + error!( + user_id = %attempt.user_id, ?challenges_to_award, error = ?e,"unable to update user with challenges", + ); + continue; + } + } else { + num_attempts_not_need_fixing += 1; + } + } + + pb.finish_with_message("challenges awarded"); + println!("Number of attempts fixed: {num_attempts_need_fixing}"); + println!("Number of attempts already handled: {num_attempts_not_need_fixing}"); + + Ok(()) +} diff --git a/script/src/main.rs b/script/src/main.rs new file mode 100644 index 0000000..3928e9c --- /dev/null +++ b/script/src/main.rs @@ -0,0 +1,26 @@ +use tracing::error; +use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; + +mod ensure_awarded_challenges; +mod util; +use ensure_awarded_challenges::ensure_awarded_challenges; +use util::client; + +#[tokio::main] +async fn main() { + let file = std::fs::File::create("logs.jsonl").expect("unable to create file"); + tracing_subscriber::registry() + .with( + EnvFilter::try_from_default_env() + .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME")).into()), + ) + // .with(tracing_subscriber::fmt::layer().pretty()) + .with(tracing_subscriber::fmt::layer().json().with_writer(file)) + .init(); + dotenvy::dotenv().ok(); + let mongo_uri = std::env::var("MONGODB_URI").unwrap(); + let client = client(&mongo_uri).await.unwrap(); + if let Err(e) = ensure_awarded_challenges(client).await { + error!("{e:?}"); + } +} diff --git a/script/src/update_python_gen_attempts.rs b/script/src/update_python_gen_attempts.rs new file mode 100644 index 0000000..5b100cd --- /dev/null +++ b/script/src/update_python_gen_attempts.rs @@ -0,0 +1,37 @@ +pub async fn _update_python_gen_attempts(client: Client) { + let attempt_collection = + get_collection::(&client, "ExamEnvironmentExamAttempt") + .await; + + // Get all attempts for generation + // Update with correct answer + let res = attempt_collection + .update_many( + doc! {"generatedExamId": ObjectId::from_str("69398ad739d7be3806660016").unwrap()}, + vec![doc! { + "$set": { + "questionSets": { + "$concatArrays": [ + "$questionSets", + [{ + "id": ObjectId::from_str("68e66c12f77abced35427f5a").unwrap(), + "questions": [ + { + "id": ObjectId::from_str("68e66c12f77abced35427f5b").unwrap(), + "answers": [ + ObjectId::from_str("68e66c3ef77abced35427f5d").unwrap() + ], + // Adds 1000ms (1 second) to the record's startTime + "submissionTime": { "$add": ["$startTime", 1000] } + } + ] + }] + ] + } + } + }], + ) + .await; + + println!("{res:?}"); +} diff --git a/script/src/util.rs b/script/src/util.rs new file mode 100644 index 0000000..bc1c97d --- /dev/null +++ b/script/src/util.rs @@ -0,0 +1,81 @@ +use std::collections::HashMap; + +use mongodb::{ + Client, Collection, + bson::{Document, doc, oid::ObjectId}, + options::ClientOptions, +}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use tracing::{error, instrument}; + +pub async fn get_collection<'d, T>(client: &Client, collection_name: &str) -> Collection +where + T: Send + Sync + Deserialize<'d> + Serialize, +{ + let db = client + .default_database() + .expect("database needs to be defined in the URI"); + + let collection = db.collection::(collection_name); + collection +} + +pub async fn client(uri: &str) -> mongodb::error::Result { + let mut client_options = ClientOptions::parse(uri).await?; + + client_options.app_name = Some("exam-services-script".to_string()); + + // Get a handle to the cluster + let client = Client::with_options(client_options)?; + + // Ping the server to see if you can connect to the cluster + client + .default_database() + .expect("database needs to be defined in the URI") + // .database("freecodecamp") + .run_command(doc! {"ping": 1}) + .await?; + + Ok(client) +} + +pub fn err(s: &str) -> impl FnOnce(E) -> String +where + E: ToString, +{ + return move |e: E| format!("{}: {}", s, e.to_string()); +} + +#[instrument(skip_all, fields(collection = collection.name(), query = query.to_string()))] +pub async fn get_from_cache_or_collection( + collection: &Collection, + query: Document, + hash: &mut HashMap, + id: ObjectId, +) -> Option +where + T: DeserializeOwned + Send + Sync + Clone, +{ + let item = if let Some(item) = hash.get(&id) { + item.to_owned() + } else { + let item = match collection.find_one(query).await { + Ok(i) => match i { + Some(i) => i, + None => { + error!("enoent record"); + return None; + } + }, + Err(e) => { + error!(error = ?e, "unable to query database"); + return None; + } + }; + + hash.insert(id, item.clone()); + item + }; + + Some(item) +} diff --git a/script/src/validate_all_generations.rs b/script/src/validate_all_generations.rs new file mode 100644 index 0000000..3c650c4 --- /dev/null +++ b/script/src/validate_all_generations.rs @@ -0,0 +1,35 @@ +pub async fn _validate_all_generations(client: Client) { + let generation_collection = get_collection::( + &client, + "ExamEnvironmentGeneratedExam", + ) + .await; + + let generation_count = generation_collection + .count_documents(doc! {}) + .await + .expect("unable to get generation count"); + + // Get generations + let mut generation_cursor = generation_collection + .find(doc! {}) + .batch_size(20) + .await + .unwrap(); + + let pb = ProgressBar::new(generation_count); + + while let Some(generation) = generation_cursor.next().await { + let generation = generation.expect("unable to deserialize generation"); + match exam_utils::generation::validate_generation(&generation) { + Ok(_) => {} + Err(e) => { + println!("Invalid Generation: {}", generation.id); + eprintln!("{e:?}"); + } + } + pb.inc(1) + } + + pb.finish_with_message("Generations checked"); +}