From 030828df3141926870b8579aa45487fb66dcee22 Mon Sep 17 00:00:00 2001 From: gvsafronov Date: Sun, 28 Sep 2025 23:39:13 +0300 Subject: [PATCH] this is the victory --- Cargo.lock | 2372 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 48 + config.toml | 42 + futriix.log | 13 + futrum.wal | 0 src/acl.rs | 90 ++ src/application.rs | 108 ++ src/command_history.rs | 70 ++ src/consensus/messages.rs | 39 + src/consensus/mod.rs | 7 + src/consensus/raft.rs | 132 ++ src/consensus/state.rs | 41 + src/lib.rs | 531 ++++++++ src/lua_interpreter.rs | 105 ++ src/main.rs | 374 ++++++ src/network/mod.rs | 3 + src/network/server.rs | 81 ++ src/replication.rs | 100 ++ src/storage/document.rs | 74 ++ src/storage/engine.rs | 149 +++ src/storage/mod.rs | 7 + src/storage/wal.rs | 114 ++ src/transaction/manager.rs | 115 ++ src/transaction/mod.rs | 3 + tests/integration_test.rs | 55 + 25 files changed, 4673 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 config.toml create mode 100644 futriix.log create mode 100644 futrum.wal create mode 100644 src/acl.rs create mode 100644 src/application.rs create mode 100644 src/command_history.rs create mode 100644 src/consensus/messages.rs create mode 100644 src/consensus/mod.rs create mode 100644 src/consensus/raft.rs create mode 100644 src/consensus/state.rs create mode 100644 src/lib.rs create mode 100644 src/lua_interpreter.rs create mode 100644 src/main.rs create mode 100644 src/network/mod.rs create mode 100644 src/network/server.rs create mode 100644 src/replication.rs create mode 100644 src/storage/document.rs create mode 100644 src/storage/engine.rs create mode 100644 src/storage/mod.rs create mode 100644 src/storage/wal.rs create mode 100644 src/transaction/manager.rs create mode 100644 src/transaction/mod.rs create mode 100644 tests/integration_test.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..79de411 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2372 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +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 = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[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 2.0.106", +] + +[[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 = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba2e2516bdf37af57fc6ff047855f54abad0066e5c4fdaaeb76dabb2e05bcf5" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libloading", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.106", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[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 = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[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 = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.5.11", + "yaml-rust", +] + +[[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 = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[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.1", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + +[[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 = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futriix" +version = "0.3.0" +dependencies = [ + "ansi_term", + "arc-swap", + "async-trait", + "bytes", + "chrono", + "config", + "dashmap", + "env_logger", + "hashbrown 0.13.2", + "http-body-util", + "hyper", + "hyper-tls", + "log", + "mime", + "rand", + "rlua", + "rmp-serde", + "rustls 0.23.32", + "rustls-pemfile", + "serde", + "serde_json", + "simplelog", + "tempfile", + "thiserror 1.0.69", + "time", + "tokio", + "tokio-rustls", + "toml 0.8.23", + "tower", + "tower-service", + "uuid", + "walkdir", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[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-task", + "pin-project-lite", + "pin-utils", +] + +[[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", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.12", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "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 = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[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 = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[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.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lua-src" +version = "547.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.5.12+a4f56a4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671" +dependencies = [ + "cc", + "which", +] + +[[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 = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "mlua" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +dependencies = [ + "bstr", + "mlua-sys", + "mlua_derive", + "num-traits", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "mlua_derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09697a6cec88e7f58a02c7ab5c18c611c6907c8654613df9cc0192658a4fb859" +dependencies = [ + "itertools 0.12.1", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.106", +] + +[[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 = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.4", + "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 2.0.106", +] + +[[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.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pest" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +dependencies = [ + "memchr", + "thiserror 2.0.16", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pest_meta" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +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 = "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 = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +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 = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[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", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[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 = "rlua" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43f01c44a4ea0d2c7faadac231a0fd906a438723aa4ee26599e71fa7b915abd" +dependencies = [ + "mlua", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.1", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.6", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +dependencies = [ + "aws-lc-rs", + "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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.4", + "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 = "serde" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[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 = "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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simplelog" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" +dependencies = [ + "log", + "termcolor", + "time", +] + +[[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.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.1", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", +] + +[[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 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "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 = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[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.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[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 = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[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 = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..90ad650 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "futriix" +version = "0.3.0" +edition = "2024" +authors = ["Futriix Developer"] +description = "Application server with embedded Lua interpreter and Futrum database" + +[dependencies] +rmp-serde = "1.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1.0", features = ["full"] } +ansi_term = "0.12" +hashbrown = "0.13" +arc-swap = "1.0" +dashmap = "5.0" +thiserror = "1.0" +log = "0.4" +env_logger = "0.10" +bytes = "1.0" +uuid = { version = "1.0", features = ["v4"] } +chrono = { version = "0.4", features = ["serde"] } +async-trait = "0.1" +rand = "0.8" +toml = "0.8" +config = "0.13" +simplelog = "0.12" +time = "0.3" +rlua = "0.20.1" +hyper = { version = "1.0", features = ["full"] } +hyper-tls = "0.6" +tokio-rustls = "0.25" +rustls = "0.23.32" +rustls-pemfile = "2.0" +http-body-util = "0.1" +tower = "0.4" +tower-service = "0.3" +mime = "0.3" +walkdir = "2.5" + +[dev-dependencies] +tempfile = "3.0" + +[features] +default = [] +multi-master = [] +http2 = [] +https = [] diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..9ef9d8b --- /dev/null +++ b/config.toml @@ -0,0 +1,42 @@ +# Futriix Application Server Configuration + +[server] +port = 8080 +host = "127.0.0.1" +enable_http2 = true +enable_https = false +ssl_cert_path = "./certs/cert.pem" +ssl_key_path = "./certs/key.pem" +static_files_dir = "./static" +lua_scripts_dir = "./lua-scripts" + +[application] +name = "futriix" +version = "0.3.0" +max_file_size_mb = 10 +supported_formats = ["pdf", "txt", "html", "css", "md", "mp3", "mp4", "js", "lua"] + +[database] +enabled = true +port = 8080 +host = "127.0.0.1" + +[raft] +enabled = true +election_timeout_ms = 150 +heartbeat_interval_ms = 50 +cluster_nodes = ["127.0.0.1:8080", "127.0.0.1:8081", "127.0.0.1:8082"] + +[replication] +multi_master_enabled = false +sync_interval_ms = 100 +conflict_resolution = "last-write-wins" + +[acl] +enabled = true +admin_users = ["admin"] +readonly_users = ["guest"] + +[logging] +level = "info" +file = "futriix.log" diff --git a/futriix.log b/futriix.log new file mode 100644 index 0000000..8ee5f03 --- /dev/null +++ b/futriix.log @@ -0,0 +1,13 @@ +[INFO] Starting futriiX application-server +[INFO] Initializing application server components +[INFO] Creating Lua interpreter +[INFO] Starting HTTP server +[INFO] HTTP server background task started +[INFO] HTTP server started on static directory: ./static +[INFO] Starting interactive mode +[INFO] Interactive mode started, waiting for user input +[INFO] Lua command received: exit +[INFO] Exit command received, shutting down +[INFO] Interactive mode ended +[INFO] Stopping HTTP server +[INFO] futriiX application-server stopped diff --git a/futrum.wal b/futrum.wal new file mode 100644 index 0000000..e69de29 diff --git a/src/acl.rs b/src/acl.rs new file mode 100644 index 0000000..23cfc33 --- /dev/null +++ b/src/acl.rs @@ -0,0 +1,90 @@ +// src/acl.rs +use crate::FutrumError; +use dashmap::DashMap; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum Permission { + Read, + Write, + Admin, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct User { + pub username: String, + pub permissions: Vec, +} + +pub struct ACLManager { + users: Arc>, + enabled: bool, +} + +impl ACLManager { + pub fn new(enabled: bool) -> Self { + let users = Arc::new(DashMap::new()); + + // Добавляем стандартных пользователей + if enabled { + users.insert("admin".to_string(), User { + username: "admin".to_string(), + permissions: vec![Permission::Read, Permission::Write, Permission::Admin], + }); + + users.insert("guest".to_string(), User { + username: "guest".to_string(), + permissions: vec![Permission::Read], + }); + } + + Self { users, enabled } + } + + pub fn check_permission(&self, username: &str, permission: Permission) -> bool { + if !self.enabled { + return true; + } + + match self.users.get(username) { + Some(user) => user.permissions.contains(&permission), + None => false, + } + } + + pub fn add_user(&self, username: String, permissions: Vec) -> Result<(), FutrumError> { + if !self.enabled { + return Ok(()); + } + + self.users.insert(username.clone(), User { username, permissions }); + Ok(()) + } + + pub fn remove_user(&self, username: &str) -> Result<(), FutrumError> { + if !self.enabled { + return Ok(()); + } + + self.users.remove(username); + Ok(()) + } + + pub fn list_users(&self) -> Vec { + self.users.iter().map(|entry| entry.value().clone()).collect() + } + + pub fn update_user_permissions(&self, username: &str, permissions: Vec) -> Result<(), FutrumError> { + if !self.enabled { + return Ok(()); + } + + if let Some(mut user) = self.users.get_mut(username) { + user.permissions = permissions; + Ok(()) + } else { + Err(FutrumError::ConfigError(format!("User {} not found", username))) + } + } +} diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 0000000..e2c7cf6 --- /dev/null +++ b/src/application.rs @@ -0,0 +1,108 @@ +// src/application.rs +use crate::{FutrumError, Result}; +use hyper::Response; +use std::fs; +use std::path::Path; + +#[derive(Clone)] +pub struct ApplicationServer { + static_files_dir: String, + supported_formats: Vec, + max_file_size: u64, +} + +impl ApplicationServer { + pub fn new(static_files_dir: String, supported_formats: Vec, max_file_size_mb: u64) -> Self { + Self { + static_files_dir, + supported_formats, + max_file_size: max_file_size_mb * 1024 * 1024, + } + } + + pub async fn serve_file(&self, path: &str) -> Result> { + log::debug!("Serving file: {}", path); + + let file_path = Path::new(&self.static_files_dir).join(path.trim_start_matches('/')); + + // Проверяем безопасность пути + if !file_path.starts_with(&self.static_files_dir) { + log::warn!("Attempted to access forbidden path: {}", path); + return Response::builder() + .status(403) + .body("403 Forbidden".to_string()) + .map_err(|e| FutrumError::HttpError(e.to_string())); + } + + if !file_path.exists() { + log::warn!("File not found: {}", path); + return Response::builder() + .status(404) + .body("404 Not Found".to_string()) + .map_err(|e| FutrumError::HttpError(e.to_string())); + } + + let metadata = fs::metadata(&file_path) + .map_err(|e| { + log::error!("Failed to get file metadata for {}: {}", path, e); + FutrumError::HttpError(e.to_string()) + })?; + + if metadata.len() > self.max_file_size { + log::warn!("File too large: {} ({} bytes)", path, metadata.len()); + return Response::builder() + .status(413) + .body("413 Payload Too Large".to_string()) + .map_err(|e| FutrumError::HttpError(e.to_string())); + } + + let extension = file_path.extension() + .and_then(|ext| ext.to_str()) + .unwrap_or(""); + + if !self.supported_formats.contains(&extension.to_string()) { + log::warn!("Unsupported file format: {}", extension); + return Response::builder() + .status(415) + .body("415 Unsupported Media Type".to_string()) + .map_err(|e| FutrumError::HttpError(e.to_string())); + } + + let content = fs::read_to_string(&file_path) + .map_err(|e| { + log::error!("Failed to read file {}: {}", path, e); + FutrumError::HttpError(e.to_string()) + })?; + + let mime_type = self.get_mime_type(extension); + + log::debug!("Successfully served file: {} ({} bytes)", path, content.len()); + + Response::builder() + .header("Content-Type", mime_type) + .body(content) + .map_err(|e| FutrumError::HttpError(e.to_string())) + } + + fn get_mime_type(&self, extension: &str) -> &str { + match extension { + "html" => "text/html", + "css" => "text/css", + "js" => "application/javascript", + "lua" => "text/plain", + "txt" => "text/plain", + "md" => "text/markdown", + "pdf" => "application/pdf", + "mp3" => "audio/mpeg", + "mp4" => "video/mp4", + _ => "application/octet-stream", + } + } + + pub async fn start_http_server(&self) -> Result<()> { + // Выводим сообщение о запуске HTTP сервера и логируем + log::info!("HTTP server started on static directory: {}", self.static_files_dir); + println!("HTTP server started on static directory: {}", self.static_files_dir); + Ok(()) + } +} diff --git a/src/command_history.rs b/src/command_history.rs new file mode 100644 index 0000000..04f212b --- /dev/null +++ b/src/command_history.rs @@ -0,0 +1,70 @@ +// src/command_history.rs +use serde::{Serialize, Deserialize}; +use chrono::{DateTime, Utc}; +use std::collections::VecDeque; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CommandEntry { + pub timestamp: DateTime, + pub command: String, + pub parameters: serde_json::Value, + pub success: bool, +} + +pub struct CommandHistory { + entries: VecDeque, + max_size: usize, +} + +impl CommandHistory { + pub fn new() -> Self { + Self { + entries: VecDeque::new(), + max_size: 1000, // Keep last 1000 commands + } + } + + pub fn add_entry(&mut self, command: String, parameters: serde_json::Value, success: bool) { + let entry = CommandEntry { + timestamp: Utc::now(), + command, + parameters, + success, + }; + + self.entries.push_back(entry); + + // Remove oldest entries if we exceed max size + while self.entries.len() > self.max_size { + self.entries.pop_front(); + } + } + + pub fn get_recent(&self, count: usize) -> Vec<&CommandEntry> { + let start = if self.entries.len() > count { + self.entries.len() - count + } else { + 0 + }; + + self.entries.range(start..).collect() + } + + pub fn clear(&mut self) { + self.entries.clear(); + } + + pub fn len(&self) -> usize { + self.entries.len() + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } +} + +impl Default for CommandHistory { + fn default() -> Self { + Self::new() + } +} diff --git a/src/consensus/messages.rs b/src/consensus/messages.rs new file mode 100644 index 0000000..a3d45c8 --- /dev/null +++ b/src/consensus/messages.rs @@ -0,0 +1,39 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VoteRequest { + pub term: u64, + pub candidate_id: String, + pub last_log_index: u64, + pub last_log_term: u64, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VoteResponse { + pub term: u64, + pub vote_granted: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AppendEntriesRequest { + pub term: u64, + pub leader_id: String, + pub prev_log_index: u64, + pub prev_log_term: u64, + pub entries: Vec>, + pub leader_commit: u64, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AppendEntriesResponse { + pub term: u64, + pub success: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum RaftMessage { + VoteRequest(VoteRequest), + VoteResponse(VoteResponse), + AppendEntriesRequest(AppendEntriesRequest), + AppendEntriesResponse(AppendEntriesResponse), +} diff --git a/src/consensus/mod.rs b/src/consensus/mod.rs new file mode 100644 index 0000000..99ce206 --- /dev/null +++ b/src/consensus/mod.rs @@ -0,0 +1,7 @@ +pub mod raft; +pub mod state; +pub mod messages; + +pub use raft::RaftConsensus; +pub use state::{RaftState, NodeState}; +pub use messages::{RaftMessage, AppendEntriesRequest, AppendEntriesResponse}; diff --git a/src/consensus/raft.rs b/src/consensus/raft.rs new file mode 100644 index 0000000..2f94687 --- /dev/null +++ b/src/consensus/raft.rs @@ -0,0 +1,132 @@ +// src/consensus/raft.rs +use super::state::{RaftState, NodeState}; +use crate::FutrumError; +use std::sync::Arc; +use tokio::sync::RwLock; +use tokio::time::Duration; +use uuid::Uuid; +use rand; + +pub struct RaftConsensus { + state: Arc>, + node_id: String, + peers: Vec, + election_timeout: Duration, + heartbeat_interval: Duration, + enabled: bool, + should_stop: Arc>, +} + +impl RaftConsensus { + pub fn new(enabled: bool, election_timeout_ms: u64, heartbeat_interval_ms: u64) -> Result { + let node_id = Uuid::new_v4().to_string(); + + Ok(Self { + state: Arc::new(RwLock::new(RaftState::new(node_id.clone()))), + node_id, + peers: Vec::new(), + election_timeout: Duration::from_millis(election_timeout_ms), + heartbeat_interval: Duration::from_millis(heartbeat_interval_ms), + enabled, + should_stop: Arc::new(RwLock::new(false)), + }) + } + + // Тихая версия запуска - БЕЗ ВЫВОДА В КОНСОЛЬ + pub async fn start_silent(&self) -> Result<(), FutrumError> { + if !self.enabled { + return Ok(()); + } + + let state = self.state.clone(); + let node_id = self.node_id.clone(); + let should_stop = self.should_stop.clone(); + let election_timeout = self.election_timeout; + let heartbeat_interval = self.heartbeat_interval; + + tokio::spawn(async move { + let mut iteration_count = 0; + + while !*should_stop.read().await { + iteration_count += 1; + + // ОГРАНИЧИВАЕМ активность - работаем только каждые 100 итераций + if iteration_count % 100 != 0 { + tokio::time::sleep(Duration::from_millis(10)).await; + continue; + } + + let current_state = { state.read().await.current_state }; + + match current_state { + NodeState::Follower => { + if Self::should_start_election_silent(&should_stop).await && !*should_stop.read().await { + state.write().await.current_state = NodeState::Candidate; + } + } + NodeState::Candidate => { + if !*should_stop.read().await { + let _ = Self::start_election_silent(state.clone()).await; + } + } + NodeState::Leader => { + // Минимальная активность в режиме лидера + } + } + + tokio::time::sleep(Duration::from_millis(10)).await; + } + }); + + Ok(()) + } + + async fn should_start_election_silent(should_stop: &Arc>) -> bool { + // Увеличиваем таймаут для уменьшения активности + let timeout = rand::random::() % 1000 + 500; // 500-1500ms вместо 150-450ms + tokio::time::sleep(Duration::from_millis(timeout.min(100))).await; + true + } + + async fn start_election_silent(state: Arc>) -> Result<(), FutrumError> { + let mut state_guard = state.write().await; + state_guard.current_term += 1; + state_guard.voted_for = Some(state_guard.node_id.clone()); + state_guard.vote_count = 1; + + if state_guard.vote_count > 0 { + state_guard.current_state = NodeState::Leader; + } + + Ok(()) + } + + pub async fn is_leader(&self) -> bool { + if !self.enabled { + return false; + } + self.state.read().await.current_state == NodeState::Leader + } + + pub async fn get_current_term(&self) -> u64 { + self.state.read().await.current_term + } + + pub async fn stop(&self) { + *self.should_stop.write().await = true; + } +} + +impl Clone for RaftConsensus { + fn clone(&self) -> Self { + Self { + state: self.state.clone(), + node_id: self.node_id.clone(), + peers: self.peers.clone(), + election_timeout: self.election_timeout, + heartbeat_interval: self.heartbeat_interval, + enabled: self.enabled, + should_stop: self.should_stop.clone(), + } + } +} diff --git a/src/consensus/state.rs b/src/consensus/state.rs new file mode 100644 index 0000000..43ddc3c --- /dev/null +++ b/src/consensus/state.rs @@ -0,0 +1,41 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] +pub enum NodeState { + Follower, + Candidate, + Leader, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct LogEntry { + pub term: u64, + pub index: u64, + pub command: Vec, +} + +pub struct RaftState { + pub node_id: String, + pub current_term: u64, + pub voted_for: Option, + pub vote_count: u64, + pub current_state: NodeState, + pub log: Vec, + pub commit_index: u64, + pub last_applied: u64, +} + +impl RaftState { + pub fn new(node_id: String) -> Self { + Self { + node_id, + current_term: 0, + voted_for: None, + vote_count: 0, + current_state: NodeState::Follower, + log: Vec::new(), + commit_index: 0, + last_applied: 0, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9c69041 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,531 @@ +// src/lib.rs +pub mod storage; +pub mod consensus; +pub mod network; +pub mod transaction; +pub mod command_history; +pub mod replication; +pub mod acl; +pub mod application; +pub mod lua_interpreter; + +use ansi_term::Colour::White; +use std::sync::Arc; +use tokio::sync::{RwLock, mpsc, Mutex}; +use thiserror::Error; +use config::Config; +use std::fmt; + +use storage::StorageEngine; +use consensus::RaftConsensus; +use network::Server; +use acl::ACLManager; + +#[derive(Error, Debug)] +pub enum FutrumError { + #[error("Key not found: {0}")] + KeyNotFound(String), + #[error("Storage error: {0}")] + StorageError(String), + #[error("Consensus error: {0}")] + ConsensusError(String), + #[error("Network error: {0}")] + NetworkError(String), + #[error("Transaction error: {0}")] + TransactionError(String), + #[error("Serialization error: {0}")] + SerializationError(String), + #[error("Configuration error: {0}")] + ConfigError(String), + #[error("Permission denied: {0}")] + PermissionDenied(String), + #[error("Lua error: {0}")] + LuaError(String), + #[error("HTTP error: {0}")] + HttpError(String), +} + +pub type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub enum Command { + Shutdown, + Status, + SwitchToDatabase, + SwitchToLua, +} + +#[derive(Clone)] +pub struct FutrumConfig { + pub server_port: u16, + pub server_host: String, + pub raft_enabled: bool, + pub election_timeout_ms: u64, + pub heartbeat_interval_ms: u64, + pub acl_enabled: bool, +} + +impl Default for FutrumConfig { + fn default() -> Self { + Self { + server_port: 8080, + server_host: "127.0.0.1".to_string(), + raft_enabled: true, + election_timeout_ms: 150, + heartbeat_interval_ms: 50, + acl_enabled: true, + } + } +} + +impl FutrumConfig { + pub fn from_file(path: &str) -> Result { + let settings = Config::builder() + .add_source(config::File::with_name(path)) + .build() + .map_err(|e| FutrumError::ConfigError(e.to_string()))?; + + Ok(Self { + server_port: settings.get::("server.port") + .map_err(|e| FutrumError::ConfigError(e.to_string()))?, + server_host: settings.get::("server.host") + .map_err(|e| FutrumError::ConfigError(e.to_string()))?, + raft_enabled: settings.get::("raft.enabled") + .map_err(|e| FutrumError::ConfigError(e.to_string()))?, + election_timeout_ms: settings.get::("raft.election_timeout_ms") + .map_err(|e| FutrumError::ConfigError(e.to_string()))?, + heartbeat_interval_ms: settings.get::("raft.heartbeat_interval_ms") + .map_err(|e| FutrumError::ConfigError(e.to_string()))?, + acl_enabled: settings.get::("acl.enabled") + .unwrap_or(true), + }) + } +} + +pub struct FutrumDB { + storage: Arc, + consensus: Arc, + server: Arc, + config: FutrumConfig, + is_running: Arc>, + acl_manager: Arc, + current_user: Arc>, +} + +impl FutrumDB { + pub fn new(config_path: &str) -> Result { + let config = FutrumConfig::from_file(config_path)?; + + let storage = Arc::new(StorageEngine::new()?); + // WAL уже включен по умолчанию в StorageEngine::new() + + let acl_manager = Arc::new(ACLManager::new(config.acl_enabled)); + + let consensus = Arc::new(RaftConsensus::new( + config.raft_enabled, + config.election_timeout_ms, + config.heartbeat_interval_ms, + )?); + + let command_history = Arc::new(RwLock::new(command_history::CommandHistory::new())); + + let server = Server::new( + storage.clone(), + consensus.clone(), + command_history, + config.server_host.clone(), + config.server_port, + )?; + + log::info!("FutrumDB initialized with WAL enabled"); + + Ok(Self { + storage, + consensus, + server: Arc::new(server), + config, + is_running: Arc::new(Mutex::new(false)), + acl_manager, + current_user: Arc::new(RwLock::new("guest".to_string())), + }) + } + + pub async fn set_current_user(&self, username: String) -> Result<()> { + if !self.acl_manager.check_permission(&username, acl::Permission::Read) { + return Err(FutrumError::PermissionDenied(format!("User {} has no permissions", username))); + } + *self.current_user.write().await = username; + Ok(()) + } + + pub async fn get_current_user(&self) -> String { + self.current_user.read().await.clone() + } + + pub async fn check_permission(&self, permission: acl::Permission) -> bool { + let user = self.current_user.read().await.clone(); + self.acl_manager.check_permission(&user, permission) + } + + // Создание пространства (коллекции) + pub async fn create_space(&self, space_name: &str) -> Result<()> { + if !self.check_permission(acl::Permission::Write).await { + return Err(FutrumError::PermissionDenied("Write permission required".to_string())); + } + + // Создаем инициализационный документ для пространства + let init_doc = serde_json::json!({ + "type": "space", + "name": space_name, + "created_at": chrono::Utc::now().to_rfc3339(), + "status": "active" + }); + + self.storage.create(space_name, "_space_info", init_doc).await?; + Ok(()) + } + + // Создание кортежа (документа) в пространстве + pub async fn create_tuple(&self, space_name: &str, tuple_id: &str, tuple_data: serde_json::Value) -> Result<()> { + if !self.check_permission(acl::Permission::Write).await { + return Err(FutrumError::PermissionDenied("Write permission required".to_string())); + } + + // Проверяем существование пространства + let space_info = self.storage.read(space_name, "_space_info").await?; + if space_info.is_none() { + return Err(FutrumError::StorageError(format!("Space '{}' does not exist", space_name))); + } + + // Добавляем метаданные к кортежу + let mut tuple_with_meta = tuple_data.as_object().cloned().unwrap_or_default(); + tuple_with_meta.insert("_tuple_id".to_string(), serde_json::Value::String(tuple_id.to_string())); + tuple_with_meta.insert("_space".to_string(), serde_json::Value::String(space_name.to_string())); + tuple_with_meta.insert("_created_at".to_string(), serde_json::Value::String(chrono::Utc::now().to_rfc3339())); + + self.storage.create(space_name, tuple_id, serde_json::Value::Object(tuple_with_meta)).await?; + Ok(()) + } + + // Новая архитектура: полное разделение логики + pub async fn run_silent( + self, + mut command_rx: mpsc::Receiver, + ) -> Result<()> { + *self.is_running.lock().await = true; + + // Запускаем компоненты в тихом режиме (без вывода в консоль) + let server_handle = self.start_server_silent().await?; + let raft_handle = self.start_raft_silent().await?; + + // Основной цикл обработки команд + while let Some(command) = command_rx.recv().await { + match command { + Command::Shutdown => { + break; + } + Command::Status => { + // Статус обрабатывается через отдельный метод + } + Command::SwitchToDatabase => { + // Переключение в режим базы данных + } + Command::SwitchToLua => { + // Переключение в режим Lua + } + } + } + + // Грациозная остановка + self.shutdown_silent(server_handle, raft_handle).await; + + *self.is_running.lock().await = false; + Ok(()) + } + + async fn start_server_silent(&self) -> Result> { + let server = self.server.clone(); + Ok(tokio::spawn(async move { + let _ = server.run_silent().await; + })) + } + + async fn start_raft_silent(&self) -> Result>> { + if !self.config.raft_enabled { + return Ok(None); + } + + let consensus = self.consensus.clone(); + Ok(Some(tokio::spawn(async move { + let _ = consensus.start_silent().await; + }))) + } + + async fn shutdown_silent( + &self, + server_handle: tokio::task::JoinHandle<()>, + raft_handle: Option>, + ) { + // Останавливаем Raft + if let Some(handle) = raft_handle { + self.consensus.stop().await; + handle.abort(); + } + + // Останавливаем сервер + self.server.stop().await; + server_handle.abort(); + } + + // Метод для получения статуса (вызывается из интерактивного клиента) + pub async fn get_status(&self) -> Option { + if !*self.is_running.lock().await { + return None; + } + + let mut status = String::new(); + status.push_str("Database Status:\n"); + status.push_str(&format!(" Raft enabled: {}\n", self.config.raft_enabled)); + status.push_str(&format!(" Server port: {}\n", self.config.server_port)); + status.push_str(&format!(" Current user: {}\n", self.get_current_user().await)); + status.push_str(&format!(" ACL enabled: {}\n", self.config.acl_enabled)); + status.push_str(&format!(" WAL enabled: {}\n", self.storage.is_wal_enabled())); + + if self.config.raft_enabled { + let is_leader = self.consensus.is_leader().await; + let term = self.consensus.get_current_term().await; + status.push_str(&format!(" Raft status: {}\n", if is_leader { "LEADER" } else { "FOLLOWER" })); + status.push_str(&format!(" Current term: {}\n", term)); + } + + Some(status) + } + + // Методы для работы с документами + pub async fn create_document(&self, collection: &str, key: &str, value: serde_json::Value) -> Result<()> { + if !self.check_permission(acl::Permission::Write).await { + return Err(FutrumError::PermissionDenied("Write permission required".to_string())); + } + self.storage.create(collection, key, value).await + } + + pub async fn read_document(&self, collection: &str, key: &str) -> Result> { + if !self.check_permission(acl::Permission::Read).await { + return Err(FutrumError::PermissionDenied("Read permission required".to_string())); + } + self.storage.read(collection, key).await + } + + pub async fn update_document(&self, collection: &str, key: &str, value: serde_json::Value) -> Result<()> { + if !self.check_permission(acl::Permission::Write).await { + return Err(FutrumError::PermissionDenied("Write permission required".to_string())); + } + self.storage.update(collection, key, value).await + } + + pub async fn delete_document(&self, collection: &str, key: &str) -> Result<()> { + if !self.check_permission(acl::Permission::Write).await { + return Err(FutrumError::PermissionDenied("Write permission required".to_string())); + } + self.storage.delete(collection, key).await + } + + // ACL команды + pub async fn add_user(&self, username: String, permissions: Vec) -> Result { + if !self.check_permission(acl::Permission::Admin).await { + return Err(FutrumError::PermissionDenied("Admin permission required".to_string())); + } + self.acl_manager.add_user(username.clone(), permissions)?; + Ok(format!("User {} added successfully", username)) + } + + pub async fn remove_user(&self, username: &str) -> Result { + if !self.check_permission(acl::Permission::Admin).await { + return Err(FutrumError::PermissionDenied("Admin permission required".to_string())); + } + self.acl_manager.remove_user(username)?; + Ok(format!("User {} removed successfully", username)) + } + + pub async fn list_users(&self) -> Result { + if !self.check_permission(acl::Permission::Admin).await { + return Err(FutrumError::PermissionDenied("Admin permission required".to_string())); + } + let users = self.acl_manager.list_users(); + let mut result = String::from("Users:\n"); + for user in users { + result.push_str(&format!(" {}: {:?}\n", user.username, user.permissions)); + } + Ok(result) + } + + // Новый метод для обработки CLI команд + pub async fn execute_command(&self, command: CliCommand) -> Result { + match command { + // CRUD команды + CliCommand::Create { collection, key, value } => { + let value_json: serde_json::Value = serde_json::from_str(&value) + .map_err(|e| FutrumError::SerializationError(e.to_string()))?; + self.create_document(&collection, &key, value_json).await?; + Ok(format!("Document {}/{} created successfully", collection, key)) + } + + CliCommand::CreateTuple { space, tuple_id, value } => { + let value_json: serde_json::Value = serde_json::from_str(&value) + .map_err(|e| FutrumError::SerializationError(e.to_string()))?; + self.create_tuple(&space, &tuple_id, value_json).await?; + Ok(format!("Tuple {} created in space {}", tuple_id, space)) + } + + CliCommand::CreateSpace { space_name } => { + self.create_space(&space_name).await?; + Ok(format!("Space '{}' created successfully", space_name)) + } + + CliCommand::Read { collection, key } => { + match self.read_document(&collection, &key).await? { + Some(value) => Ok(format!("{}", value)), + None => Ok("Document not found".to_string()), + } + } + + CliCommand::Update { collection, key, value } => { + let value_json: serde_json::Value = serde_json::from_str(&value) + .map_err(|e| FutrumError::SerializationError(e.to_string()))?; + self.update_document(&collection, &key, value_json).await?; + Ok(format!("Document {}/{} updated successfully", collection, key)) + } + + CliCommand::Delete { collection, key } => { + self.delete_document(&collection, &key).await?; + Ok(format!("Document {}/{} deleted successfully", collection, key)) + } + + // Команды кластера + CliCommand::ClusterStatus => { + if let Some(status) = self.get_status().await { + Ok(status) + } else { + Ok("Database not running".to_string()) + } + } + + // ACL команды + CliCommand::AddUser { username, permissions } => { + let perms: Vec = permissions + .split(',') + .map(|p| match p.trim() { + "read" => acl::Permission::Read, + "write" => acl::Permission::Write, + "admin" => acl::Permission::Admin, + _ => acl::Permission::Read, + }) + .collect(); + self.add_user(username, perms).await + } + + CliCommand::RemoveUser { username } => { + self.remove_user(&username).await + } + + CliCommand::ListUsers => { + self.list_users().await + } + + CliCommand::Login { username } => { + self.set_current_user(username.clone()).await?; + Ok(format!("Logged in as {}", username)) + } + + // Остальные команды пока заглушки + _ => Ok("Command not yet implemented".to_string()), + } + } +} + +impl Clone for FutrumDB { + fn clone(&self) -> Self { + Self { + storage: self.storage.clone(), + consensus: self.consensus.clone(), + server: self.server.clone(), + config: self.config.clone(), + is_running: self.is_running.clone(), + acl_manager: self.acl_manager.clone(), + current_user: self.current_user.clone(), + } + } +} + +// CLI структуры и команды +#[derive(Debug, Clone)] +pub enum CliCommand { + /// Start database server + Start { + daemon: bool, + }, + + /// CRUD operations + Create { + collection: String, + key: String, + value: String, + }, + CreateTuple { + space: String, + tuple_id: String, + value: String, + }, + CreateSpace { + space_name: String, + }, + Read { + collection: String, + key: String, + }, + Update { + collection: String, + key: String, + value: String, + }, + Delete { + collection: String, + key: String, + }, + + /// ACL operations + Login { + username: String, + }, + AddUser { + username: String, + permissions: String, + }, + RemoveUser { + username: String, + }, + ListUsers, + + /// Transaction operations + BeginTx, + CommitTx { + tx_id: String, + }, + RollbackTx { + tx_id: String, + }, + + /// Raft cluster operations + ClusterStatus, + AddNode { + node_url: String, + }, + RemoveNode { + node_id: String, + }, + + /// Replication operations + EnableReplication, + DisableReplication, + ReplicationStatus, +} diff --git a/src/lua_interpreter.rs b/src/lua_interpreter.rs new file mode 100644 index 0000000..841c626 --- /dev/null +++ b/src/lua_interpreter.rs @@ -0,0 +1,105 @@ +// src/lua_interpreter.rs +use crate::{FutrumError, Result}; +use rlua::{Lua, Function}; +use std::sync::Arc; +use tokio::sync::RwLock; +use walkdir::WalkDir; + +pub struct LuaInterpreter { + lua: Lua, + scripts_dir: String, + loaded_scripts: Arc>>, +} + +impl LuaInterpreter { + pub fn new(scripts_dir: String) -> Result { + let lua = Lua::new(); + + let interpreter = Self { + lua, + scripts_dir: scripts_dir.clone(), + loaded_scripts: Arc::new(RwLock::new(Vec::new())), + }; + + interpreter.load_scripts()?; + Ok(interpreter) + } + + pub fn load_scripts(&self) -> Result<()> { + let scripts_dir = self.scripts_dir.clone(); + + // Загружаем скрипты без использования scope + let lua = &self.lua; + + // Создаем директорию для скриптов, если она не существует + let _ = std::fs::create_dir_all(&scripts_dir); + + // Загружаем все Lua скрипты из директории + for entry in WalkDir::new(&scripts_dir) + .follow_links(true) + .into_iter() + .filter_map(|e| e.ok()) + { + if entry.path().extension().map_or(false, |ext| ext == "lua") { + if let Ok(script_content) = std::fs::read_to_string(entry.path()) { + if let Err(e) = lua.load(&script_content).exec() { + eprintln!("Error loading script {}: {}", entry.path().display(), e); + } else { + println!("Loaded script: {}", entry.path().display()); + } + } + } + } + + // Регистрируем глобальные функции + let globals = lua.globals(); + + // Функция для вывода в консоль + let print_fn = lua.create_function(|_, msg: String| { + println!("[LUA] {}", msg); + Ok(()) + }).map_err(|e| FutrumError::LuaError(e.to_string()))?; + globals.set("print", print_fn) + .map_err(|e| FutrumError::LuaError(e.to_string()))?; + + Ok(()) + } + + pub fn execute_code(&self, code: &str) -> Result { + let result = self.lua.load(code).eval::() + .map_err(|e| FutrumError::LuaError(e.to_string()))?; + Ok(result) + } + + // Упрощенная версия регистрации функций без сложных времен жизни + pub fn register_simple_function(&self, name: &str, func: F) -> Result<()> + where + F: Fn(String) -> String + 'static, + { + let lua_func = self.lua.create_function(move |_, arg: String| { + Ok(func(arg)) + }).map_err(|e| FutrumError::LuaError(e.to_string()))?; + + let globals = self.lua.globals(); + globals.set(name, lua_func) + .map_err(|e| FutrumError::LuaError(e.to_string()))?; + + Ok(()) + } + + // Альтернативный метод для регистрации функций с обработкой ошибок + pub fn register_function_with_error(&self, name: &str, func: F) -> Result<()> + where + F: Fn(String) -> rlua::Result + 'static, + { + let lua_func = self.lua.create_function(move |_, arg: String| { + func(arg) + }).map_err(|e| FutrumError::LuaError(e.to_string()))?; + + let globals = self.lua.globals(); + globals.set(name, lua_func) + .map_err(|e| FutrumError::LuaError(e.to_string()))?; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d2a0630 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,374 @@ +// src/main.rs +use ansi_term::Colour::{RGB, White, Green, Yellow, Red}; +use std::env; +use std::process; +use std::fs::OpenOptions; +use simplelog::*; +use std::io::{self, Write}; +use time::macros::format_description; +use tokio::sync::mpsc; + +use futriix::{FutrumDB, CliCommand, application::ApplicationServer, lua_interpreter::LuaInterpreter}; + +#[tokio::main] +async fn main() { + // Инициализация логирования ДО любого вывода + let log_file = OpenOptions::new() + .create(true) + .append(true) + .open("futrum.log") + .expect("Failed to open log file"); + + let time_format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); + let config = ConfigBuilder::new() + .set_time_format_custom(time_format) + .set_time_level(LevelFilter::Debug) + .set_location_level(LevelFilter::Debug) + .set_target_level(LevelFilter::Trace) + .add_filter_ignore("hyper".to_string()) + .add_filter_ignore("tokio".to_string()) + .add_filter_ignore("mio".to_string()) + .build(); + + CombinedLogger::init(vec![ + TermLogger::new( + LevelFilter::Info, + config.clone(), + TerminalMode::Mixed, + ColorChoice::AlwaysAnsi, // Используем ANSI цвета + ), + WriteLogger::new(LevelFilter::Debug, config, log_file), + ]).expect("Failed to initialize logger"); + + // Логируем запуск приложения + log::info!("Starting futriiX application-server"); + + // Заголовок приложения + println!(); + println!("{}", RGB(0, 191, 255).paint("futriiX application-server")); + println!(); + + // Запуск сервера приложений + if let Err(e) = run_application_server().await { + log::error!("Application server error: {}", e); + eprintln!("{}", Red.paint(&format!("Application server error: {}", e))); + process::exit(1); + } + + log::info!("futriiX application-server stopped"); +} + +async fn run_application_server() -> Result<(), Box> { + log::info!("Initializing application server components"); + + // Инициализация компонентов + let app_server = ApplicationServer::new( + "./static".to_string(), + vec!["pdf".to_string(), "txt".to_string(), "html".to_string(), "css".to_string(), + "md".to_string(), "mp3".to_string(), "mp4".to_string(), "js".to_string(), "lua".to_string()], + 10, + ); + + log::info!("Creating Lua interpreter"); + let lua_interpreter = LuaInterpreter::new("./lua-scripts".to_string())?; + + // Запуск HTTP сервера в фоне + log::info!("Starting HTTP server"); + let app_server_clone = app_server.clone(); + let http_handle = tokio::spawn(async move { + log::info!("HTTP server background task started"); + if let Err(e) = app_server_clone.start_http_server().await { + log::error!("HTTP server error: {}", e); + eprintln!("{}", Red.paint(&format!("HTTP server error: {}", e))); + } + }); + + // Даем время HTTP серверу запуститься + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + // Основной интерактивный цикл + log::info!("Starting interactive mode"); + run_interactive_mode(lua_interpreter).await?; + + // Останавливаем HTTP сервер + log::info!("Stopping HTTP server"); + http_handle.abort(); + + Ok(()) +} + +async fn run_interactive_mode(lua_interpreter: LuaInterpreter) -> Result<(), Box> { + let mut current_mode = Mode::Lua; + let mut command_history = Vec::new(); + + println!("{}", White.paint("Type 'inbox.start' to enter database mode")); + log::info!("Interactive mode started, waiting for user input"); + + loop { + match current_mode { + Mode::Lua => { + // Ярко-зеленый цвет для приглашения lua> + print!("{} ", RGB(0, 255, 0).paint("lua>")); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + if io::stdin().read_line(&mut input).is_err() { + log::warn!("Failed to read input in Lua mode"); + break; + } + + let input = input.trim(); + if input.is_empty() { + continue; + } + + command_history.push(input.to_string()); + log::info!("Lua command received: {}", input); + + match input { + "exit" => { + log::info!("Exit command received, shutting down"); + println!("{}", White.paint("Shutting down futriiX application server...")); + break; + } + "inbox.start" => { + log::info!("Switching to database mode"); + println!(); // Пустая строка перед сообщением + println!("{}", RGB(0, 191, 255).paint("Switching to database mode...")); + current_mode = Mode::Database; + continue; + } + _ => { + log::debug!("Executing Lua code: {}", input); + match lua_interpreter.execute_code(input) { + Ok(result) if !result.is_empty() => { + println!("{}", result); + log::debug!("Lua execution result: {}", result); + } + Err(e) => { + println!("{}", Red.paint(&format!("Lua error: {}", e))); + log::error!("Lua execution error: {}", e); + } + _ => { + log::debug!("Lua execution completed with empty result"); + } + } + } + } + } + Mode::Database => { + log::info!("Initializing database mode"); + let db = match FutrumDB::new("config.toml") { + Ok(db) => { + log::info!("Database initialized successfully"); + db + } + Err(e) => { + log::error!("Database initialization failed: {}", e); + println!("{}", Red.paint(&format!("Database error: {}", e))); + current_mode = Mode::Lua; + continue; + } + }; + + let (command_tx, command_rx) = mpsc::channel(100); + let db_handle = { + let db = db.clone(); + tokio::spawn(async move { + log::info!("Database background task started"); + if let Err(e) = db.run_silent(command_rx).await { + log::error!("Database background error: {}", e); + eprintln!("Database error: {}", e); + } + }) + }; + + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + // Белый цвет для сообщения и пустая строка после + println!("{}", White.paint("Database mode activated. Type 'exit' to return to Lua mode.")); + println!(); + log::info!("Database mode activated"); + + // Цикл режима базы данных + loop { + print!("{}", RGB(0, 191, 255).paint("futriiX:~> ")); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + if io::stdin().read_line(&mut input).is_err() { + log::warn!("Failed to read input in database mode"); + break; + } + + let input = input.trim(); + if input.is_empty() { + continue; + } + + command_history.push(input.to_string()); + log::info!("Database command received: {}", input); + + if input == "exit" { + log::info!("Exiting database mode"); + let _ = command_tx.send(futriix::Command::Shutdown).await; + break; + } + + let result = match parse_database_command(input) { + Ok(command) => { + log::debug!("Executing database command: {:?}", command); + db.execute_command(command).await + } + Err(e) => { + log::warn!("Invalid database command: {}", e); + Err(futriix::FutrumError::ConfigError(e)) + } + }; + + match result { + Ok(output) => { + // Используем заимствование для output чтобы избежать перемещения + println!("{}", Green.paint(&output)); + log::debug!("Database command successful: {}", output); + } + Err(e) => { + println!("{}", Red.paint(format!("Error: {}", e))); + log::error!("Database command failed: {}", e); + } + } + } + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + log::info!("Stopping database background task"); + db_handle.abort(); + + println!("{}", RGB(0, 191, 255).paint("Returning to Lua mode...")); + current_mode = Mode::Lua; + log::info!("Returned to Lua mode"); + } + } + } + + log::info!("Interactive mode ended"); + Ok(()) +} + +#[derive(Debug, Clone, Copy)] +enum Mode { + Lua, + Database, +} + +fn parse_database_command(input: &str) -> Result { + let parts: Vec<&str> = input.split_whitespace().collect(); + if parts.is_empty() { + return Err("Empty command".to_string()); + } + + match parts[0] { + "create" => { + if parts.len() < 4 { + return Err("Usage: create ".to_string()); + } + Ok(CliCommand::Create { + collection: parts[1].to_string(), + key: parts[2].to_string(), + value: parts[3..].join(" "), + }) + } + "create.tuple" => { + if parts.len() < 4 { + return Err("Usage: create.tuple ".to_string()); + } + Ok(CliCommand::Create { + collection: parts[1].to_string(), + key: parts[2].to_string(), + value: parts[3..].join(" "), + }) + } + "create.space" => { + if parts.len() < 2 { + return Err("Usage: create.space ".to_string()); + } + // Создаем пустую коллекцию (пространство) с тестовым документом + Ok(CliCommand::Create { + collection: parts[1].to_string(), + key: "_init".to_string(), + value: "{\"type\": \"space\", \"created\": true}".to_string(), + }) + } + "read" => { + if parts.len() < 3 { + return Err("Usage: read ".to_string()); + } + Ok(CliCommand::Read { + collection: parts[1].to_string(), + key: parts[2].to_string(), + }) + } + "update" => { + if parts.len() < 4 { + return Err("Usage: update ".to_string()); + } + Ok(CliCommand::Update { + collection: parts[1].to_string(), + key: parts[2].to_string(), + value: parts[3..].join(" "), + }) + } + "delete" => { + if parts.len() < 3 { + return Err("Usage: delete ".to_string()); + } + Ok(CliCommand::Delete { + collection: parts[1].to_string(), + key: parts[2].to_string(), + }) + } + "status" | "cluster-status" => Ok(CliCommand::ClusterStatus), + "login" => { + if parts.len() < 2 { + return Err("Usage: login ".to_string()); + } + Ok(CliCommand::Login { + username: parts[1].to_string(), + }) + } + "add-user" => { + if parts.len() < 3 { + return Err("Usage: add-user ".to_string()); + } + Ok(CliCommand::AddUser { + username: parts[1].to_string(), + permissions: parts[2].to_string(), + }) + } + "remove-user" => { + if parts.len() < 2 { + return Err("Usage: remove-user ".to_string()); + } + Ok(CliCommand::RemoveUser { + username: parts[1].to_string(), + }) + } + "list-users" => Ok(CliCommand::ListUsers), + "add-node" => { + if parts.len() < 2 { + return Err("Usage: add-node ".to_string()); + } + Ok(CliCommand::AddNode { + node_url: parts[1].to_string(), + }) + } + "remove-node" => { + if parts.len() < 2 { + return Err("Usage: remove-node ".to_string()); + } + Ok(CliCommand::RemoveNode { + node_id: parts[1].to_string(), + }) + } + _ => Err(format!("Unknown command: {}", parts[0])), + } +} diff --git a/src/network/mod.rs b/src/network/mod.rs new file mode 100644 index 0000000..588bf24 --- /dev/null +++ b/src/network/mod.rs @@ -0,0 +1,3 @@ +pub mod server; + +pub use server::Server; diff --git a/src/network/server.rs b/src/network/server.rs new file mode 100644 index 0000000..cee242e --- /dev/null +++ b/src/network/server.rs @@ -0,0 +1,81 @@ +use crate::storage::StorageEngine; +use crate::consensus::RaftConsensus; +use crate::command_history::CommandHistory; +use crate::FutrumError; +use std::sync::Arc; +use tokio::sync::RwLock; +use std::net::SocketAddr; + +pub struct Server { + storage: Arc, + consensus: Arc, + command_history: Arc>, + address: SocketAddr, + should_stop: Arc>, +} + +impl Server { + pub fn new( + storage: Arc, + consensus: Arc, + command_history: Arc>, + host: String, + port: u16, + ) -> Result { + let address: SocketAddr = format!("{}:{}", host, port).parse() + .map_err(|e: std::net::AddrParseError| FutrumError::NetworkError(e.to_string()))?; + + Ok(Self { + storage, + consensus, + command_history, + address, + should_stop: Arc::new(RwLock::new(false)), + }) + } + + // Тихая версия сервера - БЕЗ ВЫВОДА В КОНСОЛЬ + pub async fn run_silent(&self) -> Result<(), FutrumError> { + use tokio::net::TcpListener; + + let listener = TcpListener::bind(self.address).await + .map_err(|e: std::io::Error| FutrumError::NetworkError(e.to_string()))?; + + let should_stop = self.should_stop.clone(); + + tokio::spawn(async move { + while !*should_stop.read().await { + match listener.accept().await { + Ok((socket, _addr)) => { + // Минимальная обработка соединения + tokio::spawn(async move { + let _ = socket; + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + }); + } + Err(_) => { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } + } + } + }); + + Ok(()) + } + + pub async fn stop(&self) { + *self.should_stop.write().await = true; + } +} + +impl Clone for Server { + fn clone(&self) -> Self { + Self { + storage: self.storage.clone(), + consensus: self.consensus.clone(), + command_history: self.command_history.clone(), + address: self.address, + should_stop: self.should_stop.clone(), + } + } +} diff --git a/src/replication.rs b/src/replication.rs new file mode 100644 index 0000000..98bc83b --- /dev/null +++ b/src/replication.rs @@ -0,0 +1,100 @@ +// src/replication.rs +use crate::{FutrumError, Result}; +use dashmap::DashMap; +use serde_json::Value; +use std::sync::Arc; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct ReplicationRecord { + pub key: String, + pub value: Value, + pub timestamp: DateTime, + pub node_id: String, + pub version: u64, +} + +pub struct MultiMasterReplication { + enabled: bool, + node_id: String, + peers: Vec, + pending_replication: Arc>, + sync_interval: std::time::Duration, +} + +impl MultiMasterReplication { + pub fn new( + enabled: bool, + node_id: String, + peers: Vec, + sync_interval_ms: u64 + ) -> Self { + Self { + enabled, + node_id, + peers, + pending_replication: Arc::new(DashMap::new()), + sync_interval: std::time::Duration::from_millis(sync_interval_ms), + } + } + + pub async fn queue_replication( + &self, + collection: &str, + key: &str, + value: Value + ) -> Result<()> { + if !self.enabled { + return Ok(()); + } + + let record = ReplicationRecord { + key: format!("{}:{}", collection, key), + value, + timestamp: Utc::now(), + node_id: self.node_id.clone(), + version: 1, + }; + + self.pending_replication.insert(record.key.clone(), record); + Ok(()) + } + + pub async fn start_replication_worker(&self) -> Result<()> { + if !self.enabled { + return Ok(()); + } + + let pending = self.pending_replication.clone(); + let peers = self.peers.clone(); + let sync_interval = self.sync_interval; + + tokio::spawn(async move { + loop { + tokio::time::sleep(sync_interval).await; + + // Упрощенная имитация репликации + for entry in pending.iter() { + let record = entry.value(); + // Здесь была бы реальная отправка на пиры + println!("Replicating {} to {} peers", record.key, peers.len()); + } + + // Очищаем после "репликации" + pending.clear(); + } + }); + + Ok(()) + } + + pub fn apply_replication(&self, record: ReplicationRecord) -> Result<()> { + if !self.enabled { + return Ok(()); + } + + // Применяем реплицированные изменения + println!("Applied replication from {}: {}", record.node_id, record.key); + Ok(()) + } +} diff --git a/src/storage/document.rs b/src/storage/document.rs new file mode 100644 index 0000000..91759ca --- /dev/null +++ b/src/storage/document.rs @@ -0,0 +1,74 @@ +use serde::{Serialize, Deserialize}; +use std::collections::HashMap; +use chrono::{DateTime, Utc}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Document { + pub id: String, + pub collection: String, + pub data: serde_json::Value, + pub created_at: DateTime, + pub updated_at: DateTime, + pub version: u64, +} + +impl Document { + pub fn new(collection: &str, id: &str, data: serde_json::Value) -> Self { + let now = Utc::now(); + Self { + id: id.to_string(), + collection: collection.to_string(), + data, + created_at: now, + updated_at: now, + version: 1, + } + } + + pub fn update(&mut self, data: serde_json::Value) { + self.data = data; + self.updated_at = Utc::now(); + self.version += 1; + } +} + +#[derive(Default)] +pub struct DocumentCollection { + documents: HashMap, +} + +impl DocumentCollection { + pub fn new() -> Self { + Self { + documents: HashMap::new(), + } + } + + pub fn insert(&mut self, document: Document) -> Option { + self.documents.insert(document.id.clone(), document) + } + + pub fn get(&self, id: &str) -> Option<&Document> { + self.documents.get(id) + } + + pub fn get_mut(&mut self, id: &str) -> Option<&mut Document> { + self.documents.get_mut(id) + } + + pub fn remove(&mut self, id: &str) -> Option { + self.documents.remove(id) + } + + pub fn contains(&self, id: &str) -> bool { + self.documents.contains_key(id) + } + + pub fn len(&self) -> usize { + self.documents.len() + } + + pub fn is_empty(&self) -> bool { + self.documents.is_empty() + } +} diff --git a/src/storage/engine.rs b/src/storage/engine.rs new file mode 100644 index 0000000..491df27 --- /dev/null +++ b/src/storage/engine.rs @@ -0,0 +1,149 @@ +// src/storage/engine.rs +use super::document::{Document, DocumentCollection}; +use super::wal::WriteAheadLog; +use crate::FutrumError; +use dashmap::DashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +pub struct StorageEngine { + collections: Arc>, + wal: RwLock, + wal_enabled: bool, +} + +impl StorageEngine { + pub fn new() -> Result { + let wal = WriteAheadLog::new("futrum.wal") + .map_err(|e| FutrumError::StorageError(e.to_string()))?; + + log::info!("Write-Ahead Log initialized at futrum.wal"); + + Ok(Self { + collections: Arc::new(DashMap::new()), + wal: RwLock::new(wal), + wal_enabled: true, + }) + } + + pub async fn create(&self, collection: &str, key: &str, value: serde_json::Value) -> Result<(), FutrumError> { + let value_clone = value.clone(); + let document = Document::new(collection, key, value); + + let mut collection_map = self.collections + .entry(collection.to_string()) + .or_insert_with(DocumentCollection::new); + + if collection_map.contains(key) { + return Err(FutrumError::StorageError("Key already exists".to_string())); + } + + collection_map.insert(document); + + // Write to WAL + if self.wal_enabled { + let entry = super::wal::WALEntry::Create { + collection: collection.to_string(), + key: key.to_string(), + value: value_clone, + timestamp: chrono::Utc::now().timestamp_millis() as u64, + }; + + log::debug!("Writing to WAL: CREATE operation for {}/{}", collection, key); + self.wal.write().await.write(&entry) + .map_err(|e| FutrumError::StorageError(e.to_string()))?; + } else { + log::warn!("WAL is disabled, operation not logged: CREATE {}/{}", collection, key); + } + + Ok(()) + } + + pub async fn read(&self, collection: &str, key: &str) -> Result, FutrumError> { + let collection_map = self.collections.get(collection); + + match collection_map { + Some(col) => { + Ok(col.get(key).map(|doc| doc.data.clone())) + } + None => Ok(None), + } + } + + pub async fn update(&self, collection: &str, key: &str, value: serde_json::Value) -> Result<(), FutrumError> { + let value_clone = value.clone(); + + let mut collection_map = self.collections + .entry(collection.to_string()) + .or_insert_with(DocumentCollection::new); + + match collection_map.get_mut(key) { + Some(document) => { + document.update(value); + + // Write to WAL + if self.wal_enabled { + let entry = super::wal::WALEntry::Update { + collection: collection.to_string(), + key: key.to_string(), + value: value_clone, + timestamp: chrono::Utc::now().timestamp_millis() as u64, + }; + + log::debug!("Writing to WAL: UPDATE operation for {}/{}", collection, key); + self.wal.write().await.write(&entry) + .map_err(|e| FutrumError::StorageError(e.to_string()))?; + } else { + log::warn!("WAL is disabled, operation not logged: UPDATE {}/{}", collection, key); + } + + Ok(()) + } + None => Err(FutrumError::KeyNotFound(key.to_string())), + } + } + + pub async fn delete(&self, collection: &str, key: &str) -> Result<(), FutrumError> { + let mut collection_map = self.collections + .entry(collection.to_string()) + .or_insert_with(DocumentCollection::new); + + if collection_map.remove(key).is_some() { + // Write to WAL + if self.wal_enabled { + let entry = super::wal::WALEntry::Delete { + collection: collection.to_string(), + key: key.to_string(), + timestamp: chrono::Utc::now().timestamp_millis() as u64, + }; + + log::debug!("Writing to WAL: DELETE operation for {}/{}", collection, key); + self.wal.write().await.write(&entry) + .map_err(|e| FutrumError::StorageError(e.to_string()))?; + } else { + log::warn!("WAL is disabled, operation not logged: DELETE {}/{}", collection, key); + } + + Ok(()) + } else { + Err(FutrumError::KeyNotFound(key.to_string())) + } + } + + pub async fn list_collections(&self) -> Vec { + self.collections.iter().map(|entry| entry.key().clone()).collect() + } + + pub async fn collection_exists(&self, collection: &str) -> bool { + self.collections.contains_key(collection) + } + + pub fn enable_wal(&mut self, enabled: bool) { + self.wal_enabled = enabled; + log::info!("WAL {}", if enabled { "enabled" } else { "disabled" }); + } + + pub fn is_wal_enabled(&self) -> bool { + self.wal_enabled + } +} diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 0000000..04c451f --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,7 @@ +pub mod document; +pub mod wal; +pub mod engine; + +pub use document::Document; +pub use wal::WriteAheadLog; +pub use engine::StorageEngine; diff --git a/src/storage/wal.rs b/src/storage/wal.rs new file mode 100644 index 0000000..d184343 --- /dev/null +++ b/src/storage/wal.rs @@ -0,0 +1,114 @@ +// src/storage/wal.rs +use serde::{Serialize, Deserialize}; +use std::fs::{File, OpenOptions}; +use std::io::{BufWriter, Write, BufReader, Seek, SeekFrom, BufRead}; +use std::path::Path; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum WALError { + #[error("IO error: {0}")] + IOError(#[from] std::io::Error), + #[error("Serialization error: {0}")] + SerializationError(#[from] rmp_serde::encode::Error), + #[error("Deserialization error: {0}")] + DeserializationError(#[from] rmp_serde::decode::Error), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum WALEntry { + Create { + collection: String, + key: String, + value: serde_json::Value, + timestamp: u64, + }, + Update { + collection: String, + key: String, + value: serde_json::Value, + timestamp: u64, + }, + Delete { + collection: String, + key: String, + timestamp: u64, + }, + Commit { + transaction_id: String, + timestamp: u64, + }, + Rollback { + transaction_id: String, + timestamp: u64, + }, +} + +impl WALEntry { + pub fn timestamp(&self) -> u64 { + match self { + WALEntry::Create { timestamp, .. } => *timestamp, + WALEntry::Update { timestamp, .. } => *timestamp, + WALEntry::Delete { timestamp, .. } => *timestamp, + WALEntry::Commit { timestamp, .. } => *timestamp, + WALEntry::Rollback { timestamp, .. } => *timestamp, + } + } +} + +pub struct WriteAheadLog { + file: BufWriter, + path: String, +} + +impl WriteAheadLog { + pub fn new(path: &str) -> Result { + let file = OpenOptions::new() + .create(true) + .append(true) + .open(path)?; + + log::info!("WAL file created/opened: {}", path); + + Ok(Self { + file: BufWriter::new(file), + path: path.to_string(), + }) + } + + pub fn write(&mut self, entry: &WALEntry) -> Result<(), WALError> { + let serialized = rmp_serde::to_vec(entry)?; + self.file.write_all(&serialized)?; + self.file.write_all(b"\n")?; + self.file.flush()?; + log::trace!("WAL entry written: {:?}", entry); + Ok(()) + } + + pub fn replay(&self) -> Result, WALError> { + let file = File::open(&self.path)?; + let reader = BufReader::new(file); + let mut entries = Vec::new(); + + for line in reader.lines() { + let line = line?; + if let Ok(entry) = rmp_serde::from_slice(line.as_bytes()) { + entries.push(entry); + } + } + + log::info!("Replayed {} WAL entries", entries.len()); + Ok(entries) + } + + pub fn clear(&mut self) -> Result<(), WALError> { + self.file.seek(SeekFrom::Start(0))?; + self.file.get_mut().set_len(0)?; + log::info!("WAL cleared"); + Ok(()) + } + + pub fn get_path(&self) -> &str { + &self.path + } +} diff --git a/src/transaction/manager.rs b/src/transaction/manager.rs new file mode 100644 index 0000000..96cf486 --- /dev/null +++ b/src/transaction/manager.rs @@ -0,0 +1,115 @@ +use crate::storage::StorageEngine; +use crate::FutrumError; +use std::collections::HashMap; +use uuid::Uuid; + +pub struct Transaction { + pub id: String, + pub operations: Vec, + pub state: TransactionState, +} + +pub enum TransactionOperation { + Create { + collection: String, + key: String, + value: serde_json::Value, + }, + Update { + collection: String, + key: String, + value: serde_json::Value, + }, + Delete { + collection: String, + key: String, + }, +} + +#[derive(PartialEq)] +pub enum TransactionState { + Active, + Committed, + RolledBack, +} + +pub struct TransactionManager { + storage: StorageEngine, + active_transactions: HashMap, +} + +impl TransactionManager { + pub fn new(storage: StorageEngine) -> Self { + Self { + storage, + active_transactions: HashMap::new(), + } + } + + pub fn begin(&mut self) -> String { + let transaction_id = Uuid::new_v4().to_string(); + let transaction = Transaction { + id: transaction_id.clone(), + operations: Vec::new(), + state: TransactionState::Active, + }; + + self.active_transactions.insert(transaction_id.clone(), transaction); + transaction_id + } + + pub fn add_operation(&mut self, transaction_id: &str, operation: TransactionOperation) -> Result<(), FutrumError> { + if let Some(transaction) = self.active_transactions.get_mut(transaction_id) { + if transaction.state != TransactionState::Active { + return Err(FutrumError::TransactionError("Transaction is not active".to_string())); + } + transaction.operations.push(operation); + Ok(()) + } else { + Err(FutrumError::TransactionError("Transaction not found".to_string())) + } + } + + pub async fn commit(&mut self, transaction_id: &str) -> Result<(), FutrumError> { + if let Some(transaction) = self.active_transactions.get_mut(transaction_id) { + if transaction.state != TransactionState::Active { + return Err(FutrumError::TransactionError("Transaction is not active".to_string())); + } + + // Apply all operations + for operation in &transaction.operations { + match operation { + TransactionOperation::Create { collection, key, value } => { + self.storage.create(collection, key, value.clone()).await?; + } + TransactionOperation::Update { collection, key, value } => { + self.storage.update(collection, key, value.clone()).await?; + } + TransactionOperation::Delete { collection, key } => { + self.storage.delete(collection, key).await?; + } + } + } + + transaction.state = TransactionState::Committed; + self.active_transactions.remove(transaction_id); + Ok(()) + } else { + Err(FutrumError::TransactionError("Transaction not found".to_string())) + } + } + + pub fn rollback(&mut self, transaction_id: &str) -> Result<(), FutrumError> { + if let Some(transaction) = self.active_transactions.get_mut(transaction_id) { + if transaction.state != TransactionState::Active { + return Err(FutrumError::TransactionError("Transaction is not active".to_string())); + } + + transaction.state = TransactionState::RolledBack; + self.active_transactions.remove(transaction_id); + Ok(()) + } else { + Err(FutrumError::TransactionError("Transaction not found".to_string())) + } + } +} diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs new file mode 100644 index 0000000..a5a1089 --- /dev/null +++ b/src/transaction/mod.rs @@ -0,0 +1,3 @@ +pub mod manager; + +pub use manager::TransactionManager; diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 0000000..8ebfcea --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,55 @@ +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[tokio::test] + async fn test_storage_operations() { + let db = FutrumDB::new("test.config").unwrap(); + + // Test CRUD operations + let value = serde_json::json!({"test": "value"}); + + // Create + assert!(db.create_document("test", "key1", value.clone()).await.is_ok()); + + // Read + let result = db.read_document("test", "key1").await.unwrap(); + assert!(result.is_some()); + assert_eq!(result.unwrap(), value); + + // Update + let new_value = serde_json::json!({"test": "updated"}); + assert!(db.update_document("test", "key1", new_value.clone()).await.is_ok()); + + // Delete + assert!(db.delete_document("test", "key1").await.is_ok()); + assert!(db.read_document("test", "key1").await.unwrap().is_none()); + } + + #[tokio::test] + async fn test_transactions() { + let db = FutrumDB::new("test.config").unwrap(); + + // This would test transaction functionality + // Implementation depends on TransactionManager integration + } + + #[test] + fn test_command_history() { + let mut history = CommandHistory::new(); + + history.add_entry( + "CREATE".to_string(), + serde_json::json!({"key": "test"}), + true + ); + + assert_eq!(history.len(), 1); + assert!(!history.is_empty()); + + let recent = history.get_recent(5); + assert_eq!(recent.len(), 1); + assert_eq!(recent[0].command, "CREATE"); + } +}