From 43f2addd94df9a0613c96cb7a4a418211e902746 Mon Sep 17 00:00:00 2001 From: gvsafronov Date: Sun, 16 Nov 2025 20:25:52 +0300 Subject: [PATCH] my commit --- Cargo.lock | 2237 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 27 + config/config.toml | 40 + lua-scripts/init.lua | 18 + lua-scripts/utils.lua | 50 + src/cli.rs | 129 +++ src/cli/parser.rs | 290 ++++++ src/config.rs | 56 ++ src/db.rs | 1294 ++++++++++++++++++++++++ src/http.rs | 453 +++++++++ src/lib.rs | 23 + src/lua.rs | 423 ++++++++ src/lua/bindings.rs | 55 + src/main.rs | 168 ++++ 14 files changed, 5263 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100755 config/config.toml create mode 100644 lua-scripts/init.lua create mode 100644 lua-scripts/utils.lua create mode 100644 src/cli.rs create mode 100644 src/cli/parser.rs create mode 100644 src/config.rs create mode 100644 src/db.rs create mode 100644 src/http.rs create mode 100644 src/lib.rs create mode 100644 src/lua.rs create mode 100644 src/lua/bindings.rs create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5ccb4a1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2237 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[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 = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "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.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[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 = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[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 = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[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.2", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fd-lock" +version = "3.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" +dependencies = [ + "cfg-if", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futriix" +version = "0.1.0" +dependencies = [ + "ansi_term", + "chrono", + "crossbeam", + "csv", + "env_logger", + "flate2", + "log", + "md-5", + "parking_lot", + "rlua", + "rmp-serde", + "rustyline", + "serde", + "serde_json", + "tokio", + "toml", + "warp", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-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-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +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", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[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 = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "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 = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[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 = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.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 = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[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", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.108", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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 = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[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", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "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 = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustyline" +version = "12.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +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 = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[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.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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", +] + +[[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.108", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[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 = "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 = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.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.108", + "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.108", + "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 1.1.2", + "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.2", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "synstructure", +] + +[[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.108", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9992612 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "futriix" +version = "0.1.0" +edition = "2024" # По состоянию на октябрь 2025 года наиболее стабильная и последняя редакция языка Rust (Rust Edition) + +[dependencies] +rlua = "0.20.1" # Библиотека для интеграции Rust с языком Lua. Позволяет выполнять Lua-скрипты из Rust, передавать данные между ними и использовать Lua в качестве скриптового движка (например, для плагинов или конфигураций). +ansi_term = "0.12.1" # Предоставляет инструменты для работы с ANSI-кодами в терминале, такими как цветной вывод текста, жирный шрифт и другие стили. Полезна для создания красивого CLI-интерфейса. +tokio = { version = "1.0", features = ["full"] } # Асинхронное рантайм для Rust, для написания неблокирующего кода (например, для сетевых запросов, многозадачности). С фичей "full" включает всё для полного асинхронного управления. +warp = "0.3" # Лёгкий веб-фреймворк для Rust, построенный на tokio. Упрощает создание HTTP-серверов, API и веб-приложений с поддержкой маршрутизации, middleware и т.д. +serde = { version = "1.0", features = ["derive"] } #Основная библиотека для сериализации и десериализации данных в Rust (преобразование структур в строки/байты и обратно). Фича "derive" генерирует код автоматически через макросы. +serde_json = "1.0" # Расширение для serde, для работы с JSON-форматом. Позволяет сериализовать/десериализовать данные в JSON. +toml = "0.5" # Библиотека для чтения и записи файлов в формате TOML (Tom's Obvious, Minimal Language). +rmp-serde = "1.1" # MessagePack +csv = "1.4.0" # Добавлена библиотека для CSV +chrono = { version = "0.4", features = ["serde"] } # Работа с датами, временем и периодами (например, парсинг ISO 8601). С фичей "serde" поддерживает сериализацию временных объектов. +log = "0.4" # Фрейморк для логирования в Rust, предоставляет трейты для записи сообщений (ошибки, предупреждения и т.д.) без привязки к конкретной реализации. +env_logger = "0.10" # Конкретная реализация логирования на основе переменных окружения. Работает с crate log, позволяет настраивать уровень логирования через переменные вроде RUST_LOG. +parking_lot = "0.12" #Более производительная альтернатива стандартным мьютексам и спинлокам в Rust, полезна для многопоточного кода с низкими оверхедами. +crossbeam = "0.8" #Инструменты для параллелизма, включая каналы для межпоточного общения, атомарные операции и структуры данных для конкурентного доступа. +rustyline = "12.0" # Библиотека для создания интерактивных REPL (read-eval-print loops) или терминалов с поддержкой истории команд, автодополнения и редактирования строк. +flate2 = "1.0" # Библиотека для сжатия и декомпрессии данных с помощью алгоритмов вроде gzip/deflate. В проекте нужнадля сжатия backup +md-5 = "0.10.6" # Библиотека для поддержки MD5 + +[dev-dependencies] # Блок для запуска тестов +tokio = { version = "1.0", features = ["full", "rt-multi-thread"] } +chrono = { version = "0.4", features = ["serde"] } diff --git a/config/config.toml b/config/config.toml new file mode 100755 index 0000000..6321a47 --- /dev/null +++ b/config/config.toml @@ -0,0 +1,40 @@ +# Конфигурация сервера приложений и СУБД + +http_port = 9080 +db_path = "./data" +log_level = "info" + +# Настройки кластера +cluster_enabled = false +node_id = "node_1" +cluster_nodes = [ + "http://node1.example.com:9040", + "http://node2.example.com:9040", + "http://node3.example.com:9040" +] + +# Настройки ACL (Access Control List) +acl_enabled = false +admin_users = [ + "admin", + "root" +] + +# Настройки репликации +master_master_replication = false +replication_nodes = [ + "http://replica1.example.com:8080", + "http://replica2.example.com:8080" +] + +# Настройки HTTPS +https_enabled = false +https_port = 8443 +cert_file = "./cert.pem" +key_file = "./key.pem" + +# Настройки HTTP2 +http2_enabled = false + +# Настройки хранимых процедур +stored_procedures_path = "./procedures" diff --git a/lua-scripts/init.lua b/lua-scripts/init.lua new file mode 100644 index 0000000..f006b59 --- /dev/null +++ b/lua-scripts/init.lua @@ -0,0 +1,18 @@ +-- Автоматически загружаемый скрипт при запуске сервера + +print("futriiX Lua environment initialized") + +-- Вспомогательные функции +function help() + print("Available commands:") + print(" inbox.start - Enter database mode") + print(" db_create(collection, key, json) - Create document") + print(" exit - Exit application") +end + +-- Глобальная функция для логирования +function log_info(message) + print("[INFO] " .. message) +end + +log_info("Lua scripts loaded successfully") diff --git a/lua-scripts/utils.lua b/lua-scripts/utils.lua new file mode 100644 index 0000000..a65998f --- /dev/null +++ b/lua-scripts/utils.lua @@ -0,0 +1,50 @@ +--utils.lua- является пользовательской библиотекой вспомогательных функций Lua для работы с СУБД futriix. + +-- Выполняет роль: +-- Загрузчика в других Lua скриптах проекта +-- Загрузчика при инициализации Lua интерпретатора +-- Основного инструмента в хранимых процедурах проекта + +function json_encode(tbl) + return require("serde").encode(tbl) +end + +function json_decode(str) + return require("serde").decode(str) +end + +function table_length(tbl) + local count = 0 + for _ in pairs(tbl) do + count = count + 1 + end + return count +end + +-- Функция для MD5 хеширования +function md5_hash(text) + return md5(text) +end + +-- Функция для создания пользователя с хешированным паролем +function create_user_with_password(space, username, password, user_data) + user_data = user_data or {} + user_data.password = md5_hash(password) + return insert(space, username, json_encode(user_data)) +end + +-- Функция для проверки пароля пользователя +function verify_user_password(space, username, password) + local user_json = get(space, username) + if user_json == "null" then + return false, "User not found" + end + + local user_data = json_decode(user_json) + if user_data.password == md5_hash(password) then + return true, "Password correct" + else + return false, "Password incorrect" + end +end + diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..879599e --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,129 @@ +use std::error::Error; +use ansi_term::Colour; + +/// Парсер команд для интерпретатора +pub struct CommandParser; + +impl CommandParser { + pub fn parse_command(line: &str) -> Result> { + let parts: Vec<&str> = line.trim().split_whitespace().collect(); + if parts.is_empty() { + return Ok(Command::Empty); + } + + match parts[0].to_lowercase().as_str() { + "exit" | "quit" => Ok(Command::Exit), + "help" => Ok(Command::Help), + "backup" => { + if parts.len() >= 2 { + Ok(Command::Backup { + path: parts[1].to_string(), + }) + } else { + Err("Usage: backup ".into()) + } + } + "restore" => { + if parts.len() >= 2 { + Ok(Command::Restore { + path: parts[1].to_string(), + }) + } else { + Err("Usage: restore ".into()) + } + } + "create" => { + if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" { + if parts.len() >= 4 { + Ok(Command::CreateProcedure { + name: parts[2].to_string(), + code: parts[3..].join(" "), + }) + } else { + Err("Usage: create procedure ".into()) + } + } else { + Ok(Command::LuaCode(line.to_string())) + } + } + "call" => { + if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" { + Ok(Command::CallProcedure { + name: parts[2].to_string(), + }) + } else { + Ok(Command::LuaCode(line.to_string())) + } + } + "drop" => { + if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" { + Ok(Command::DropProcedure { + name: parts[2].to_string(), + }) + } else { + Ok(Command::LuaCode(line.to_string())) + } + } + _ => Ok(Command::LuaCode(line.to_string())), + } + } + + pub fn print_help() { + println!("\n{}", Colour::Cyan.paint("Available commands:")); + println!("{}", Colour::Yellow.paint(" General:")); + println!(" exit, quit - Exit the interpreter"); + println!(" help - Show this help message"); + println!(); + println!("{}", Colour::Yellow.paint(" Database operations:")); + println!(" backup - Create database backup"); + println!(" restore - Restore database from backup"); + println!(); + println!("{}", Colour::Yellow.paint(" Stored procedures:")); + println!(" create procedure - Create stored procedure"); + println!(" call procedure - Execute stored procedure"); + println!(" drop procedure - Delete stored procedure"); + println!(); + println!("{}", Colour::Yellow.paint(" Lua operations:")); + println!(" Any valid Lua code - Execute Lua code with DB API"); + println!(); + println!("{}", Colour::Cyan.paint("Lua DB API functions:")); + println!(" create_space(name) - Create space"); + println!(" delete_space(name) - Delete space"); + println!(" insert(space, key, value) - Insert document"); + println!(" get(space, key) - Get document"); + println!(" update(space, key, value) - Update document"); + println!(" delete(space, key) - Delete document"); + println!(" create_procedure(name, code) - Create stored procedure"); + println!(" drop_procedure(name) - Drop stored procedure"); + println!(" call_procedure(name) - Call stored procedure"); + println!(" create_backup(path) - Create backup"); + println!(" restore_backup(path) - Restore backup"); + println!(" enable_replication() - Enable replication"); + println!(" disable_replication() - Disable replication"); + println!(" sync_replication() - Sync replication"); + } +} + +/// Команды интерпретатора +pub enum Command { + Exit, + Help, + Empty, + LuaCode(String), + Backup { + path: String, + }, + Restore { + path: String, + }, + CreateProcedure { + name: String, + code: String, + }, + CallProcedure { + name: String, + }, + DropProcedure { + name: String, + }, +} diff --git a/src/cli/parser.rs b/src/cli/parser.rs new file mode 100644 index 0000000..397a849 --- /dev/null +++ b/src/cli/parser.rs @@ -0,0 +1,290 @@ +use ansi_term::Colour; +use std::collections::VecDeque; + +/// Парсер команд для CLI интерфейса +pub struct CommandParser { + history: VecDeque, + max_history: usize, +} + +impl CommandParser { + pub fn new() -> Self { + Self { + history: VecDeque::new(), + max_history: 100, + } + } + + pub fn parse(&self, input: &str) -> Result { + let parts: Vec<&str> = input.trim().split_whitespace().collect(); + + if parts.is_empty() { + return Err("Empty command".to_string()); + } + + match parts[0] { + "create" => self.parse_create(&parts), + "read" => self.parse_read(&parts), + "update" => self.parse_update(&parts), + "delete" => self.parse_delete(&parts), + "create.space" => self.parse_create_space(&parts), + "delete.space" => self.parse_delete_space(&parts), + "create.tuple" => self.parse_create_tuple(&parts), + "read.tuple" => self.parse_read_tuple(&parts), + "delete.tuple" => self.parse_delete_tuple(&parts), + "create.primary.index" => self.parse_create_primary_index(&parts), + "create.secondary.index" => self.parse_create_secondary_index(&parts), + "drop.primary.index" => self.parse_drop_primary_index(&parts), + "drop.secondary.index" => self.parse_drop_secondary_index(&parts), + "search.all.index" => self.parse_search_all_index(&parts), + "search.index" => self.parse_search_index(&parts), + "begin.transaction" => self.parse_begin_transaction(&parts), + "commit.transaction" => self.parse_commit_transaction(&parts), + "rollback.transaction" => self.parse_rollback_transaction(&parts), + "mode.csv" => Ok(ParsedCommand::ModeCsv), + "export" => self.parse_export(&parts), + "import" => self.parse_import(&parts), + "exit" => Ok(ParsedCommand::Exit), + "inbox.start" => Ok(ParsedCommand::InboxStart), + _ => Err(format!("Unknown command: {}", parts[0])), + } + } + + fn parse_create(&self, parts: &[&str]) -> Result { + if parts.len() < 4 { + return Err("Usage: create ".to_string()); + } + Ok(ParsedCommand::Create { + collection: parts[1].to_string(), + key: parts[2].to_string(), + value: serde_json::from_str(parts[3]) + .map_err(|e| format!("Invalid JSON: {}", e))?, + }) + } + + fn parse_read(&self, parts: &[&str]) -> Result { + if parts.len() < 3 { + return Err("Usage: read ".to_string()); + } + Ok(ParsedCommand::Read { + collection: parts[1].to_string(), + key: parts[2].to_string(), + }) + } + + fn parse_update(&self, parts: &[&str]) -> Result { + if parts.len() < 4 { + return Err("Usage: update ".to_string()); + } + Ok(ParsedCommand::Update { + collection: parts[1].to_string(), + key: parts[2].to_string(), + value: serde_json::from_str(parts[3]) + .map_err(|e| format!("Invalid JSON: {}", e))?, + }) + } + + fn parse_delete(&self, parts: &[&str]) -> Result { + if parts.len() < 3 { + return Err("Usage: delete ".to_string()); + } + Ok(ParsedCommand::Delete { + collection: parts[1].to_string(), + key: parts[2].to_string(), + }) + } + + fn parse_create_space(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: create.space ".to_string()); + } + Ok(ParsedCommand::CreateSpace { + name: parts[1].to_string(), + }) + } + + fn parse_delete_space(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: delete.space ".to_string()); + } + Ok(ParsedCommand::DeleteSpace { + name: parts[1].to_string(), + }) + } + + fn parse_create_tuple(&self, parts: &[&str]) -> Result { + if parts.len() < 4 { + return Err("Usage: create.tuple ".to_string()); + } + Ok(ParsedCommand::CreateTuple { + space: parts[1].to_string(), + tuple_id: parts[2].to_string(), + value: serde_json::from_str(parts[3]) + .map_err(|e| format!("Invalid JSON: {}", e))?, + }) + } + + fn parse_read_tuple(&self, parts: &[&str]) -> Result { + if parts.len() < 3 { + return Err("Usage: read.tuple ".to_string()); + } + Ok(ParsedCommand::ReadTuple { + space: parts[1].to_string(), + tuple_id: parts[2].to_string(), + }) + } + + fn parse_delete_tuple(&self, parts: &[&str]) -> Result { + if parts.len() < 3 { + return Err("Usage: delete.tuple ".to_string()); + } + Ok(ParsedCommand::DeleteTuple { + space: parts[1].to_string(), + tuple_id: parts[2].to_string(), + }) + } + + fn parse_create_primary_index(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: create.primary.index ".to_string()); + } + Ok(ParsedCommand::CreatePrimaryIndex { + collection: parts[1].to_string(), + }) + } + + fn parse_create_secondary_index(&self, parts: &[&str]) -> Result { + if parts.len() < 4 { + return Err("Usage: create.secondary.index ".to_string()); + } + Ok(ParsedCommand::CreateSecondaryIndex { + collection: parts[1].to_string(), + field: parts[2].to_string(), + index_type: parts[3].to_string(), + }) + } + + fn parse_drop_primary_index(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: drop.primary.index ".to_string()); + } + Ok(ParsedCommand::DropPrimaryIndex { + collection: parts[1].to_string(), + }) + } + + fn parse_drop_secondary_index(&self, parts: &[&str]) -> Result { + if parts.len() < 3 { + return Err("Usage: drop.secondary.index ".to_string()); + } + Ok(ParsedCommand::DropSecondaryIndex { + collection: parts[1].to_string(), + field: parts[2].to_string(), + }) + } + + fn parse_search_all_index(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: search.all.index ".to_string()); + } + Ok(ParsedCommand::SearchAllIndex { + collection: parts[1].to_string(), + }) + } + + fn parse_search_index(&self, parts: &[&str]) -> Result { + if parts.len() < 4 { + return Err("Usage: search.index ".to_string()); + } + Ok(ParsedCommand::SearchIndex { + collection: parts[1].to_string(), + field: parts[2].to_string(), + value: parts[3].to_string(), + }) + } + + fn parse_begin_transaction(&self, parts: &[&str]) -> Result { + let tx_id = if parts.len() > 1 { + parts[1].to_string() + } else { + format!("tx_{}", chrono::Utc::now().timestamp_millis()) + }; + Ok(ParsedCommand::BeginTransaction { + transaction_id: tx_id, + }) + } + + fn parse_commit_transaction(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: commit.transaction ".to_string()); + } + Ok(ParsedCommand::CommitTransaction { + transaction_id: parts[1].to_string(), + }) + } + + fn parse_rollback_transaction(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: rollback.transaction ".to_string()); + } + Ok(ParsedCommand::RollbackTransaction { + transaction_id: parts[1].to_string(), + }) + } + + fn parse_export(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: export ".to_string()); + } + Ok(ParsedCommand::Export { + file_path: parts[1].to_string(), + }) + } + + fn parse_import(&self, parts: &[&str]) -> Result { + if parts.len() < 2 { + return Err("Usage: import ".to_string()); + } + Ok(ParsedCommand::Import { + file_path: parts[1].to_string(), + }) + } + + pub fn add_to_history(&mut self, command: String) { + if self.history.len() >= self.max_history { + self.history.pop_front(); + } + self.history.push_back(command); + } + + pub fn get_history(&self) -> &VecDeque { + &self.history + } +} + +#[derive(Debug)] +pub enum ParsedCommand { + Create { collection: String, key: String, value: serde_json::Value }, + Read { collection: String, key: String }, + Update { collection: String, key: String, value: serde_json::Value }, + Delete { collection: String, key: String }, + CreateSpace { name: String }, + DeleteSpace { name: String }, + CreateTuple { space: String, tuple_id: String, value: serde_json::Value }, + ReadTuple { space: String, tuple_id: String }, + DeleteTuple { space: String, tuple_id: String }, + CreatePrimaryIndex { collection: String }, + CreateSecondaryIndex { collection: String, field: String, index_type: String }, + DropPrimaryIndex { collection: String }, + DropSecondaryIndex { collection: String, field: String }, + SearchAllIndex { collection: String }, + SearchIndex { collection: String, field: String, value: String }, + BeginTransaction { transaction_id: String }, + CommitTransaction { transaction_id: String }, + RollbackTransaction { transaction_id: String }, + ModeCsv, + Export { file_path: String }, + Import { file_path: String }, + Exit, + InboxStart, +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..7b4edea --- /dev/null +++ b/src/config.rs @@ -0,0 +1,56 @@ +use serde::Deserialize; + +#[derive(Deserialize, Clone)] +pub struct Config { + pub http_port: u16, + pub db_path: String, + pub log_level: String, + + // Параметры кластера + pub cluster_enabled: bool, + pub node_id: String, + pub cluster_nodes: Vec, + + // Параметры ACL + pub acl_enabled: bool, + pub admin_users: Vec, + + // Параметры репликации + pub master_master_replication: bool, + pub replication_nodes: Vec, + + // Параметры HTTPS + pub https_enabled: bool, + pub https_port: u16, + pub cert_file: String, + pub key_file: String, + + // Параметры HTTP2 + pub http2_enabled: bool, + + // Настройки хранимых процедур + pub stored_procedures_path: String, +} + +impl Default for Config { + fn default() -> Self { + Self { + http_port: 8080, + db_path: "./data".to_string(), + log_level: "info".to_string(), + cluster_enabled: false, + node_id: "node_1".to_string(), + cluster_nodes: vec![], + acl_enabled: false, + admin_users: vec![], + master_master_replication: false, + replication_nodes: vec![], + https_enabled: false, + https_port: 8443, + cert_file: "./cert.pem".to_string(), + key_file: "./key.pem".to_string(), + http2_enabled: false, + stored_procedures_path: "./procedures".to_string(), + } + } +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..bd1c68d --- /dev/null +++ b/src/db.rs @@ -0,0 +1,1294 @@ +//[file name]: db.rs + +mod storage { + use std::collections::HashMap; + use std::sync::Arc; + use parking_lot::RwLock; + use serde_json::Value; + use chrono::Utc; + + /// Движок хранения данных с документной моделью + pub struct StorageEngine { + spaces: Arc>>, + path: String, + } + + struct Space { + documents: HashMap, + tuples: HashMap, + } + + struct Document { + value: Value, + created_at: String, + updated_at: String, + } + + struct Tuple { + value: Value, + created_at: String, + updated_at: String, + } + + impl StorageEngine { + pub fn new(path: &str) -> Result { + Ok(Self { + spaces: Arc::new(RwLock::new(HashMap::new())), + path: path.to_string(), + }) + } + + pub fn create_space(&self, name: &str) -> Result<(), String> { + let mut spaces = self.spaces.write(); + if spaces.contains_key(name) { + return Err("Space already exists".to_string()); + } + spaces.insert(name.to_string(), Space { + documents: HashMap::new(), + tuples: HashMap::new(), + }); + Ok(()) + } + + pub fn delete_space(&self, name: &str) -> Result<(), String> { + let mut spaces = self.spaces.write(); + spaces.remove(name) + .map(|_| ()) + .ok_or_else(|| "Space not found".to_string()) + } + + pub fn insert(&self, space: &str, key: &str, value: Value) -> Result<(), String> { + let mut spaces = self.spaces.write(); + let space_obj = spaces.get_mut(space) + .ok_or_else(|| "Space not found".to_string())?; + + if space_obj.documents.contains_key(key) { + return Err("Document already exists".to_string()); + } + + let timestamp = Utc::now().to_rfc3339(); + space_obj.documents.insert(key.to_string(), Document { + value, + created_at: timestamp.clone(), + updated_at: timestamp, + }); + Ok(()) + } + + pub fn get(&self, space: &str, key: &str) -> Result, String> { + let spaces = self.spaces.read(); + match spaces.get(space) { + Some(space_obj) => Ok(space_obj.documents.get(key).map(|doc| { + let mut value = doc.value.clone(); + if let Some(obj) = value.as_object_mut() { + obj.insert("_created_at".to_string(), Value::String(doc.created_at.clone())); + obj.insert("_updated_at".to_string(), Value::String(doc.updated_at.clone())); + } + value + })), + None => Err("Space not found".to_string()), + } + } + + pub fn update(&self, space: &str, key: &str, value: Value) -> Result<(), String> { + let mut spaces = self.spaces.write(); + let space_obj = spaces.get_mut(space) + .ok_or_else(|| "Space not found".to_string())?; + + if let Some(document) = space_obj.documents.get_mut(key) { + document.value = value; + document.updated_at = Utc::now().to_rfc3339(); + Ok(()) + } else { + Err("Document not found".to_string()) + } + } + + pub fn delete(&self, space: &str, key: &str) -> Result<(), String> { + let mut spaces = self.spaces.write(); + let space_obj = spaces.get_mut(space) + .ok_or_else(|| "Space not found".to_string())?; + + space_obj.documents.remove(key) + .map(|_| ()) + .ok_or_else(|| "Document not found".to_string()) + } + + pub fn create_tuple(&self, space: &str, tuple_id: &str, value: Value) -> Result<(), String> { + let mut spaces = self.spaces.write(); + let space_obj = spaces.get_mut(space) + .ok_or_else(|| "Space not found".to_string())?; + + let timestamp = Utc::now().to_rfc3339(); + space_obj.tuples.insert(tuple_id.to_string(), Tuple { + value, + created_at: timestamp.clone(), + updated_at: timestamp, + }); + Ok(()) + } + + pub fn read_tuple(&self, space: &str, tuple_id: &str) -> Result, String> { + let spaces = self.spaces.read(); + match spaces.get(space) { + Some(space_obj) => Ok(space_obj.tuples.get(tuple_id).map(|tuple| { + let mut value = tuple.value.clone(); + if let Some(obj) = value.as_object_mut() { + obj.insert("_created_at".to_string(), Value::String(tuple.created_at.clone())); + obj.insert("_updated_at".to_string(), Value::String(tuple.updated_at.clone())); + } + value + })), + None => Err("Space not found".to_string()), + } + } + + pub fn delete_tuple(&self, space: &str, tuple_id: &str) -> Result<(), String> { + let mut spaces = self.spaces.write(); + let space_obj = spaces.get_mut(space) + .ok_or_else(|| "Space not found".to_string())?; + + space_obj.tuples.remove(tuple_id) + .map(|_| ()) + .ok_or_else(|| "Tuple not found".to_string()) + } + + pub fn get_all_data(&self) -> HashMap> { + let spaces = self.spaces.read(); + let mut result = HashMap::new(); + + for (space_name, space) in spaces.iter() { + let mut space_data = HashMap::new(); + for (key, document) in &space.documents { + let mut value = document.value.clone(); + if let Some(obj) = value.as_object_mut() { + obj.insert("_created_at".to_string(), Value::String(document.created_at.clone())); + obj.insert("_updated_at".to_string(), Value::String(document.updated_at.clone())); + } + space_data.insert(key.clone(), value); + } + result.insert(space_name.clone(), space_data); + } + + result + } + + pub fn restore_from_data(&self, data: HashMap>) -> Result<(), String> { + let mut spaces = self.spaces.write(); + spaces.clear(); + + for (space_name, documents) in data { + let mut space = Space { + documents: HashMap::new(), + tuples: HashMap::new(), + }; + + for (key, value) in documents { + let timestamp = Utc::now().to_rfc3339(); + let created_at = if let Some(obj) = value.as_object() { + obj.get("_created_at") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| timestamp.clone()) + } else { + timestamp.clone() + }; + + let updated_at = if let Some(obj) = value.as_object() { + obj.get("_updated_at") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| timestamp.clone()) + } else { + timestamp.clone() + }; + + let mut clean_value = value.clone(); + if let Some(obj) = clean_value.as_object_mut() { + obj.remove("_created_at"); + obj.remove("_updated_at"); + } + + space.documents.insert(key, Document { + value: clean_value, + created_at, + updated_at, + }); + } + + spaces.insert(space_name, space); + } + + Ok(()) + } + } +} + +mod wal { + use std::fs::{OpenOptions, File}; + use std::io::{Write, BufWriter}; + use serde_json::Value; + use chrono::Utc; + + /// Write-Ahead Log для обеспечения durability + pub struct WriteAheadLog { + file: BufWriter, + } + + impl WriteAheadLog { + pub fn new(filename: &str) -> Result { + let file = OpenOptions::new() + .create(true) + .append(true) + .open(filename) + .map_err(|e| format!("Failed to open WAL file: {}", e))?; + + Ok(Self { + file: BufWriter::new(file), + }) + } + + pub fn log_operation(&mut self, operation: &str, collection: &str, key: &str, value: &Value) -> Result<(), String> { + let log_entry = LogEntry { + timestamp: Utc::now().to_rfc3339(), + operation: operation.to_string(), + collection: collection.to_string(), + key: key.to_string(), + value: value.clone(), + }; + + let json_str = serde_json::to_string(&log_entry) + .map_err(|e| format!("Failed to serialize log entry: {}", e))?; + + writeln!(self.file, "{}", json_str) + .map_err(|e| format!("Failed to write to WAL: {}", e))?; + + self.file.flush() + .map_err(|e| format!("Failed to flush WAL: {}", e))?; + + Ok(()) + } + + pub fn recover(&mut self) -> Result<(), String> { + Ok(()) + } + } + + #[derive(serde::Serialize, serde::Deserialize)] + struct LogEntry { + timestamp: String, + operation: String, + collection: String, + key: String, + value: Value, + } +} + +mod index { + use std::collections::{HashMap, BTreeMap}; + use parking_lot::RwLock; + use serde_json::Value; + + /// Менеджер индексов для ускорения поиска + pub struct IndexManager { + primary_indexes: RwLock>, + secondary_indexes: RwLock>>, + } + + struct PrimaryIndex { + index: HashMap, + } + + enum SecondaryIndex { + Hash(HashMap>), + BTree(BTreeMap>), + } + + impl IndexManager { + pub fn new() -> Self { + Self { + primary_indexes: RwLock::new(HashMap::new()), + secondary_indexes: RwLock::new(HashMap::new()), + } + } + + pub fn create_primary_index(&self, collection: &str) -> Result<(), String> { + let mut indexes = self.primary_indexes.write(); + if indexes.contains_key(collection) { + return Err("Primary index already exists".to_string()); + } + + indexes.insert(collection.to_string(), PrimaryIndex { + index: HashMap::new(), + }); + Ok(()) + } + + pub fn create_secondary_index(&self, collection: &str, field: &str, index_type: &str) -> Result<(), String> { + let mut indexes = self.secondary_indexes.write(); + let collection_indexes = indexes.entry(collection.to_string()) + .or_insert_with(HashMap::new); + + if collection_indexes.contains_key(field) { + return Err("Secondary index already exists".to_string()); + } + + let index = match index_type { + "hash" => SecondaryIndex::Hash(HashMap::new()), + "btree" => SecondaryIndex::BTree(BTreeMap::new()), + _ => return Err(format!("Unsupported index type: {}", index_type)), + }; + + collection_indexes.insert(field.to_string(), index); + Ok(()) + } + + pub fn drop_primary_index(&self, collection: &str) -> Result<(), String> { + let mut indexes = self.primary_indexes.write(); + indexes.remove(collection) + .map(|_| ()) + .ok_or_else(|| "Primary index not found".to_string()) + } + + pub fn drop_secondary_index(&self, collection: &str, field: &str) -> Result<(), String> { + let mut indexes = self.secondary_indexes.write(); + if let Some(collection_indexes) = indexes.get_mut(collection) { + collection_indexes.remove(field) + .map(|_| ()) + .ok_or_else(|| "Secondary index not found".to_string()) + } else { + Err("Collection not found".to_string()) + } + } + + pub fn search_index(&self, collection: &str, field: &str, value: &str) -> Result, String> { + let indexes = self.secondary_indexes.read(); + if let Some(collection_indexes) = indexes.get(collection) { + if let Some(index) = collection_indexes.get(field) { + match index { + SecondaryIndex::Hash(hash_map) => { + let json_value: serde_json::Value = serde_json::from_str(value) + .map_err(|e| format!("Invalid value: {}", e))?; + Ok(hash_map.get(&json_value) + .cloned() + .unwrap_or_default()) + }, + _ => Ok(vec![]), // Упрощённая реализация для других типов индексов + } + } else { + Err("Index not found".to_string()) + } + } else { + Err("Collection not found".to_string()) + } + } + + pub fn search_primary(&self, collection: &str, key: &str) -> Option { + let indexes = self.primary_indexes.read(); + indexes.get(collection) + .and_then(|index| index.index.get(key).cloned()) + } + } +} + +mod raft { + use std::collections::HashMap; + use parking_lot::RwLock; + + /// Raft консенсус для кластеризации + pub struct RaftCluster { + nodes: RwLock>, + state: RwLock, + current_node_id: String, + } + + struct RaftNode { + url: String, + role: NodeRole, + } + + #[derive(Clone, Debug, PartialEq)] + pub enum NodeRole { + Leader, + Follower, + Candidate, + } + + struct RaftState { + current_term: u64, + voted_for: Option, + log: Vec, + } + + struct LogEntry { + term: u64, + command: String, + } + + impl RaftCluster { + pub async fn new() -> Result { + let node_id = "node_1".to_string(); + let mut nodes = HashMap::new(); + nodes.insert(node_id.clone(), RaftNode { + url: "http://localhost:9080".to_string(), + role: NodeRole::Leader, // По умолчанию первый узел - лидер + }); + + Ok(Self { + nodes: RwLock::new(nodes), + state: RwLock::new(RaftState { + current_term: 0, + voted_for: None, + log: Vec::new(), + }), + current_node_id: node_id, + }) + } + + pub async fn add_node(&self, node_url: String) -> Result<(), String> { + let node_id = format!("node_{}", self.nodes.read().len() + 1); + let mut nodes = self.nodes.write(); + nodes.insert(node_id.clone(), RaftNode { + url: node_url, + role: NodeRole::Follower, + }); + Ok(()) + } + + pub async fn remove_node(&mut self, node_id: &str) -> Result<(), String> { + let mut nodes = self.nodes.write(); + nodes.remove(node_id) + .map(|_| ()) + .ok_or_else(|| "Node not found".to_string()) + } + + pub fn list_nodes(&self) -> Vec<(String, String)> { + let nodes = self.nodes.read(); + nodes.iter() + .map(|(id, node)| { + let role = match node.role { + NodeRole::Leader => "leader", + NodeRole::Follower => "follower", + NodeRole::Candidate => "candidate", + }; + (id.clone(), role.to_string()) + }) + .collect() + } + + pub fn get_cluster_status(&self) -> String { + let nodes = self.nodes.read(); + if nodes.values().any(|node| matches!(node.role, NodeRole::Leader)) { + "cluster_formed".to_string() + } else { + "cluster_not_formed".to_string() + } + } + + /// Получить роль текущего узла + pub fn get_current_node_role(&self) -> NodeRole { + let nodes = self.nodes.read(); + nodes.get(&self.current_node_id) + .map(|node| node.role.clone()) + .unwrap_or(NodeRole::Follower) + } + + /// Получить ID текущего узла + pub fn get_current_node_id(&self) -> String { + self.current_node_id.clone() + } + } +} + +mod acl { + use std::collections::HashMap; + use parking_lot::RwLock; + use serde_json::Value; + + /// Система контроля доступа (ACL) + pub struct AccessControl { + permissions: RwLock>, + } + + #[derive(Clone)] + pub struct UserPermissions { + pub can_read: bool, + pub can_write: bool, + pub can_create: bool, + pub can_delete: bool, + pub allowed_collections: Vec, + } + + impl AccessControl { + pub fn new() -> Self { + Self { + permissions: RwLock::new(HashMap::new()), + } + } + + pub fn add_user(&self, username: &str, permissions: UserPermissions) { + let mut perms = self.permissions.write(); + perms.insert(username.to_string(), permissions); + } + + pub fn can_read(&self, username: &str, collection: &str) -> bool { + let perms = self.permissions.read(); + if let Some(user_perms) = perms.get(username) { + user_perms.can_read && + (user_perms.allowed_collections.is_empty() || + user_perms.allowed_collections.contains(&collection.to_string())) + } else { + false + } + } + + pub fn can_write(&self, username: &str, collection: &str) -> bool { + let perms = self.permissions.read(); + if let Some(user_perms) = perms.get(username) { + user_perms.can_write && + (user_perms.allowed_collections.is_empty() || + user_perms.allowed_collections.contains(&collection.to_string())) + } else { + false + } + } + + pub fn can_create(&self, username: &str, collection: &str) -> bool { + let perms = self.permissions.read(); + if let Some(user_perms) = perms.get(username) { + user_perms.can_create && + (user_perms.allowed_collections.is_empty() || + user_perms.allowed_collections.contains(&collection.to_string())) + } else { + false + } + } + + pub fn can_delete(&self, username: &str, collection: &str) -> bool { + let perms = self.permissions.read(); + if let Some(user_perms) = perms.get(username) { + user_perms.can_delete && + (user_perms.allowed_collections.is_empty() || + user_perms.allowed_collections.contains(&collection.to_string())) + } else { + false + } + } + } + + impl Default for UserPermissions { + fn default() -> Self { + Self { + can_read: true, + can_write: true, + can_create: true, + can_delete: true, + allowed_collections: vec![], + } + } + } +} + +mod transaction { + use std::collections::HashMap; + use parking_lot::RwLock; + use serde_json::Value; + + #[derive(Debug, Clone)] + pub enum TransactionOperation { + Create { collection: String, key: String, value: Value }, + Update { collection: String, key: String, value: Value }, + Delete { collection: String, key: String }, + } + + #[derive(Debug, Clone)] + pub struct Transaction { + pub id: String, + pub operations: Vec, + pub state: TransactionState, + } + + #[derive(Debug, PartialEq, Clone)] + pub enum TransactionState { + Active, + Committed, + RolledBack, + } + + pub struct TransactionManager { + transactions: RwLock>, + } + + impl TransactionManager { + pub fn new() -> Self { + Self { + transactions: RwLock::new(HashMap::new()), + } + } + + pub fn begin_transaction(&self, transaction_id: String) -> Result<(), String> { + let mut transactions = self.transactions.write(); + if transactions.contains_key(&transaction_id) { + return Err("Transaction already exists".to_string()); + } + + transactions.insert(transaction_id.clone(), Transaction { + id: transaction_id, + operations: Vec::new(), + state: TransactionState::Active, + }); + Ok(()) + } + + pub fn add_operation(&self, transaction_id: &str, operation: TransactionOperation) -> Result<(), String> { + let mut transactions = self.transactions.write(); + if let Some(transaction) = transactions.get_mut(transaction_id) { + if transaction.state != TransactionState::Active { + return Err("Transaction is not active".to_string()); + } + transaction.operations.push(operation); + Ok(()) + } else { + Err("Transaction not found".to_string()) + } + } + + pub fn commit_transaction(&self, transaction_id: &str) -> Result, String> { + let mut transactions = self.transactions.write(); + if let Some(transaction) = transactions.get_mut(transaction_id) { + if transaction.state != TransactionState::Active { + return Err("Transaction is not active".to_string()); + } + transaction.state = TransactionState::Committed; + let operations = transaction.operations.clone(); + transactions.remove(transaction_id); + Ok(operations) + } else { + Err("Transaction not found".to_string()) + } + } + + pub fn rollback_transaction(&self, transaction_id: &str) -> Result<(), String> { + let mut transactions = self.transactions.write(); + if let Some(transaction) = transactions.get_mut(transaction_id) { + transaction.state = TransactionState::RolledBack; + transactions.remove(transaction_id); + Ok(()) + } else { + Err("Transaction not found".to_string()) + } + } + + pub fn get_transaction(&self, transaction_id: &str) -> Option { + let transactions = self.transactions.read(); + transactions.get(transaction_id).cloned() + } + } +} + +mod csv_export { + use std::fs::File; + use std::collections::HashMap; + use serde_json::Value; + use csv::Writer; + + pub struct CsvExporter; + + impl CsvExporter { + pub fn export_data(data: &HashMap>, file_path: &str) -> Result<(), String> { + let file = File::create(file_path) + .map_err(|e| format!("Failed to create file: {}", e))?; + + let mut wtr = Writer::from_writer(file); + + for (collection, documents) in data { + for (key, value) in documents { + if let Some(obj) = value.as_object() { + let mut record = vec![collection.clone(), key.clone()]; + + for (field, field_value) in obj { + record.push(field_value.to_string()); + } + + wtr.write_record(&record) + .map_err(|e| format!("Failed to write record: {}", e))?; + } + } + } + + wtr.flush() + .map_err(|e| format!("Failed to flush writer: {}", e))?; + + Ok(()) + } + + pub fn import_data(file_path: &str) -> Result>, String> { + let file = File::open(file_path) + .map_err(|e| format!("Failed to open file: {}", e))?; + + let mut rdr = csv::Reader::from_reader(file); + let mut result = HashMap::new(); + + for record in rdr.records() { + let record = record.map_err(|e| format!("Failed to read record: {}", e))?; + if record.len() >= 3 { + let collection = record[0].to_string(); + let key = record[1].to_string(); + + let mut document = HashMap::new(); + for (i, field) in record.iter().enumerate().skip(2) { + document.insert(format!("field_{}", i - 2), Value::String(field.to_string())); + } + + result.entry(collection) + .or_insert_with(HashMap::new) + .insert(key, Value::Object(serde_json::Map::from_iter(document))); + } + } + + Ok(result) + } + } +} + +mod replication { + use std::collections::HashMap; + use std::sync::Arc; + use parking_lot::RwLock; + use serde_json::Value; + use std::time::Duration; + + /// Мастер-мастер репликация + pub struct MasterMasterReplication { + enabled: bool, + nodes: Vec, + replication_queue: Arc>>, + } + + #[derive(Clone, Debug)] + pub struct ReplicationOperation { + pub operation: String, + pub collection: String, + pub key: String, + pub value: Value, + pub timestamp: u64, + } + + impl MasterMasterReplication { + pub fn new(enabled: bool, nodes: Vec) -> Self { + Self { + enabled, + nodes, + replication_queue: Arc::new(RwLock::new(Vec::new())), + } + } + + pub fn is_enabled(&self) -> bool { + self.enabled + } + + pub fn enable(&mut self) { + self.enabled = true; + } + + pub fn disable(&mut self) { + self.enabled = false; + } + + pub fn add_operation(&self, operation: ReplicationOperation) { + if self.enabled { + let mut queue = self.replication_queue.write(); + queue.push(operation); + } + } + + pub fn get_pending_operations(&self) -> Vec { + let queue = self.replication_queue.read(); + queue.clone() + } + + pub fn clear_operations(&self) { + let mut queue = self.replication_queue.write(); + queue.clear(); + } + + // Убираем async из этой функции, чтобы избежать проблем с Send + pub fn sync_with_nodes(&self) -> Result<(), String> { + if !self.enabled { + return Ok(()); + } + + let operations = self.get_pending_operations(); + if operations.is_empty() { + return Ok(()); + } + + // Упрощённая реализация синхронизации + // В реальной системе здесь был бы HTTP клиент для отправки операций на другие узлы + log::info!("Syncing {} operations with {} nodes", operations.len(), self.nodes.len()); + + // Очищаем очередь после успешной синхронизации + self.clear_operations(); + + Ok(()) + } + } +} + +mod backup { + use std::fs::File; + use std::collections::HashMap; + use serde_json::Value; + use flate2::write::GzEncoder; + use flate2::Compression; + use std::io::Write; + + /// Менеджер бэкапов + pub struct BackupManager; + + impl BackupManager { + pub fn create_backup(data: &HashMap>, backup_path: &str) -> Result<(), String> { + let file = File::create(backup_path) + .map_err(|e| format!("Failed to create backup file: {}", e))?; + + let mut encoder = GzEncoder::new(file, Compression::default()); + let json_data = serde_json::to_string(data) + .map_err(|e| format!("Failed to serialize backup data: {}", e))?; + + encoder.write_all(json_data.as_bytes()) + .map_err(|e| format!("Failed to write backup data: {}", e))?; + + encoder.finish() + .map_err(|e| format!("Failed to finish backup: {}", e))?; + + Ok(()) + } + + pub fn restore_backup(backup_path: &str) -> Result>, String> { + let file = File::open(backup_path) + .map_err(|e| format!("Failed to open backup file: {}", e))?; + + let decoder = flate2::read::GzDecoder::new(file); + let data: HashMap> = serde_json::from_reader(decoder) + .map_err(|e| format!("Failed to deserialize backup data: {}", e))?; + + Ok(data) + } + } +} + +mod procedures { + use std::collections::HashMap; + use std::fs; + use std::path::Path; + use parking_lot::RwLock; + use serde_json::Value; + + /// Менеджер хранимых процедур + pub struct StoredProceduresManager { + procedures: RwLock>, + procedures_path: String, + } + + impl StoredProceduresManager { + pub fn new(procedures_path: &str) -> Result { + let manager = Self { + procedures: RwLock::new(HashMap::new()), + procedures_path: procedures_path.to_string(), + }; + + // Загружаем существующие процедуры при инициализации + manager.load_procedures()?; + + Ok(manager) + } + + pub fn create_procedure(&self, name: &str, code: &str) -> Result<(), String> { + let mut procedures = self.procedures.write(); + + if procedures.contains_key(name) { + return Err(format!("Procedure '{}' already exists", name)); + } + + procedures.insert(name.to_string(), code.to_string()); + + // Сохраняем процедуру в файл + self.save_procedure(name, code)?; + + Ok(()) + } + + pub fn drop_procedure(&self, name: &str) -> Result<(), String> { + let mut procedures = self.procedures.write(); + + if !procedures.contains_key(name) { + return Err(format!("Procedure '{}' not found", name)); + } + + procedures.remove(name); + + // Удаляем файл процедуры + let file_path = format!("{}/{}.lua", self.procedures_path, name); + if let Err(e) = fs::remove_file(file_path) { + log::warn!("Failed to remove procedure file: {}", e); + } + + Ok(()) + } + + pub fn get_procedure(&self, name: &str) -> Option { + let procedures = self.procedures.read(); + procedures.get(name).cloned() + } + + pub fn list_procedures(&self) -> Vec { + let procedures = self.procedures.read(); + procedures.keys().cloned().collect() + } + + fn load_procedures(&self) -> Result<(), String> { + let path = Path::new(&self.procedures_path); + + // Создаем директорию если не существует + if !path.exists() { + fs::create_dir_all(path) + .map_err(|e| format!("Failed to create procedures directory: {}", e))?; + } + + let entries = fs::read_dir(path) + .map_err(|e| format!("Failed to read procedures directory: {}", e))?; + + let mut procedures = self.procedures.write(); + + for entry in entries { + let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?; + let path = entry.path(); + + if path.is_file() && path.extension().map_or(false, |ext| ext == "lua") { + if let Some(file_name) = path.file_stem().and_then(|s| s.to_str()) { + let code = fs::read_to_string(&path) + .map_err(|e| format!("Failed to read procedure file: {}", e))?; + + procedures.insert(file_name.to_string(), code); + } + } + } + + Ok(()) + } + + fn save_procedure(&self, name: &str, code: &str) -> Result<(), String> { + let file_path = format!("{}/{}.lua", self.procedures_path, name); + fs::write(&file_path, code) + .map_err(|e| format!("Failed to save procedure: {}", e))?; + + Ok(()) + } + } +} + +mod encryption { + use md5::{Digest, Md5}; + + /// Менеджер шифрования для СУБД + pub struct EncryptionManager; + + impl EncryptionManager { + pub fn md5_hash(text: &str) -> String { + let mut hasher = Md5::new(); + hasher.update(text.as_bytes()); + let result = hasher.finalize(); + format!("{:x}", result) + } + + pub fn verify_md5(text: &str, hash: &str) -> bool { + Self::md5_hash(text) == hash + } + } +} + +use std::collections::HashMap; +use std::sync::Arc; +use parking_lot::RwLock; +use serde_json::Value; +use crate::config::Config; + +// Re-export всех модулей +pub use self::storage::StorageEngine; +pub use self::wal::WriteAheadLog; +pub use self::index::IndexManager; +pub use self::raft::RaftCluster; +pub use self::acl::AccessControl; +pub use self::transaction::{TransactionManager, TransactionOperation, TransactionState}; +pub use self::csv_export::CsvExporter; +pub use self::replication::{MasterMasterReplication, ReplicationOperation}; +pub use self::backup::BackupManager; +pub use self::procedures::StoredProceduresManager; +pub use self::encryption::EncryptionManager; // Добавлен менеджер шифрования + +/// Основной класс СУБД +#[derive(Clone)] +pub struct FutriixDB { + storage: Arc, + wal: Arc>, + index_manager: Arc, + raft_cluster: Arc, + acl: Arc, + transaction_manager: Arc, + replication: Arc>, + procedures_manager: Arc, + config: Config, +} + +impl FutriixDB { + pub async fn new(config: &Config) -> Result { + // Создаем директорию для данных если не существует + std::fs::create_dir_all(&config.db_path) + .map_err(|e| format!("Failed to create data directory: {}", e))?; + + let storage = Arc::new(StorageEngine::new(&config.db_path)?); + let wal = Arc::new(RwLock::new(WriteAheadLog::new("wal.log")?)); + let index_manager = Arc::new(IndexManager::new()); + let raft_cluster = Arc::new(RaftCluster::new().await?); + let acl = Arc::new(AccessControl::new()); + let transaction_manager = Arc::new(TransactionManager::new()); + + let replication = Arc::new(RwLock::new(MasterMasterReplication::new( + config.master_master_replication, + config.replication_nodes.clone(), + ))); + + let procedures_manager = Arc::new(StoredProceduresManager::new(&config.stored_procedures_path)?); + + Ok(Self { + storage, + wal, + index_manager, + raft_cluster, + acl, + transaction_manager, + replication, + procedures_manager, + config: config.clone(), + }) + } + + // Методы для работы с хранимыми процедурами + pub fn create_procedure(&self, name: &str, code: &str) -> Result<(), String> { + self.procedures_manager.create_procedure(name, code) + } + + pub fn drop_procedure(&self, name: &str) -> Result<(), String> { + self.procedures_manager.drop_procedure(name) + } + + pub fn get_procedure(&self, name: &str) -> Option { + self.procedures_manager.get_procedure(name) + } + + pub fn list_procedures(&self) -> Vec { + self.procedures_manager.list_procedures() + } + + // Методы для работы с бэкапами + pub fn create_backup(&self, backup_path: &str) -> Result<(), String> { + let data = self.storage.get_all_data(); + BackupManager::create_backup(&data, backup_path) + } + + pub fn restore_backup(&self, backup_path: &str) -> Result<(), String> { + let data = BackupManager::restore_backup(backup_path)?; + self.storage.restore_from_data(data) + } + + // Методы для работы с репликацией + pub fn is_replication_enabled(&self) -> bool { + self.replication.read().is_enabled() + } + + pub fn enable_replication(&self) { + self.replication.write().enable(); + } + + pub fn disable_replication(&self) { + self.replication.write().disable(); + } + + // Убираем async и проблемы с Send + pub fn sync_replication(&self) -> Result<(), String> { + self.replication.read().sync_with_nodes() + } + + // Новые методы для работы с шифрованием + pub fn md5_hash(&self, text: &str) -> String { + EncryptionManager::md5_hash(text) + } + + pub fn verify_md5(&self, text: &str, hash: &str) -> bool { + EncryptionManager::verify_md5(text, hash) + } + + // Метод для создания документа с MD5 хешированием пароля + pub fn insert_with_password_hash(&self, space: &str, key: &str, value: Value, password_field: &str) -> Result<(), String> { + let mut value = value; + + if let Some(obj) = value.as_object_mut() { + if let Some(password_value) = obj.get_mut(password_field) { + if let Some(password) = password_value.as_str() { + let hashed_password = self.md5_hash(password); + *password_value = Value::String(hashed_password); + } + } + } + + self.insert(space, key, value) + } + + // Метод для проверки пароля + pub fn verify_password(&self, space: &str, key: &str, password: &str, password_field: &str) -> Result { + match self.get(space, key) { + Ok(Some(value)) => { + if let Some(obj) = value.as_object() { + if let Some(stored_hash) = obj.get(password_field).and_then(|v| v.as_str()) { + Ok(self.verify_md5(password, stored_hash)) + } else { + Ok(false) + } + } else { + Ok(false) + } + } + Ok(None) => Err("Document not found".to_string()), + Err(e) => Err(e), + } + } + + // Остальные методы СУБД остаются без изменений + pub fn create_space(&self, name: &str) -> Result<(), String> { + self.storage.create_space(name) + } + + pub fn delete_space(&self, name: &str) -> Result<(), String> { + self.storage.delete_space(name) + } + + pub fn insert(&self, space: &str, key: &str, value: Value) -> Result<(), String> { + self.storage.insert(space, key, value) + } + + pub fn get(&self, space: &str, key: &str) -> Result, String> { + self.storage.get(space, key) + } + + pub fn update(&self, space: &str, key: &str, value: Value) -> Result<(), String> { + self.storage.update(space, key, value) + } + + pub fn delete(&self, space: &str, key: &str) -> Result<(), String> { + self.storage.delete(space, key) + } + + pub fn create_tuple(&self, space: &str, tuple_id: &str, value: Value) -> Result<(), String> { + self.storage.create_tuple(space, tuple_id, value) + } + + pub fn read_tuple(&self, space: &str, tuple_id: &str) -> Result, String> { + self.storage.read_tuple(space, tuple_id) + } + + pub fn delete_tuple(&self, space: &str, tuple_id: &str) -> Result<(), String> { + self.storage.delete_tuple(space, tuple_id) + } + + pub fn begin_transaction(&self, transaction_id: String) -> Result<(), String> { + self.transaction_manager.begin_transaction(transaction_id) + } + + pub fn add_operation(&self, transaction_id: &str, operation: TransactionOperation) -> Result<(), String> { + self.transaction_manager.add_operation(transaction_id, operation) + } + + pub fn commit_transaction(&self, transaction_id: &str) -> Result, String> { + self.transaction_manager.commit_transaction(transaction_id) + } + + pub fn rollback_transaction(&self, transaction_id: &str) -> Result<(), String> { + self.transaction_manager.rollback_transaction(transaction_id) + } + + pub fn create_primary_index(&self, collection: &str) -> Result<(), String> { + self.index_manager.create_primary_index(collection) + } + + pub fn create_secondary_index(&self, collection: &str, field: &str, index_type: &str) -> Result<(), String> { + self.index_manager.create_secondary_index(collection, field, index_type) + } + + pub fn drop_primary_index(&self, collection: &str) -> Result<(), String> { + self.index_manager.drop_primary_index(collection) + } + + pub fn drop_secondary_index(&self, collection: &str, field: &str) -> Result<(), String> { + self.index_manager.drop_secondary_index(collection, field) + } + + pub fn search_index(&self, collection: &str, field: &str, value: &str) -> Result, String> { + self.index_manager.search_index(collection, field, value) + } + + pub fn add_user(&self, username: &str, permissions: acl::UserPermissions) { + self.acl.add_user(username, permissions); + } + + pub fn can_read(&self, username: &str, collection: &str) -> bool { + self.acl.can_read(username, collection) + } + + pub fn can_write(&self, username: &str, collection: &str) -> bool { + self.acl.can_write(username, collection) + } + + pub fn can_create(&self, username: &str, collection: &str) -> bool { + self.acl.can_create(username, collection) + } + + pub fn can_delete(&self, username: &str, collection: &str) -> bool { + self.acl.can_delete(username, collection) + } + + pub async fn add_cluster_node(&self, node_url: String) -> Result<(), String> { + self.raft_cluster.add_node(node_url).await + } + + pub async fn remove_cluster_node(&self, node_id: &str) -> Result<(), String> { + // Для удаления узла нам нужно &mut self + // В реальной реализации это потребовало бы изменения архитектуры + Err("Node removal not implemented in this version".to_string()) + } + + pub fn list_cluster_nodes(&self) -> Vec<(String, String)> { + self.raft_cluster.list_nodes() + } + + pub fn get_cluster_status(&self) -> String { + self.raft_cluster.get_cluster_status() + } + + pub fn get_current_node_role(&self) -> raft::NodeRole { + self.raft_cluster.get_current_node_role() + } + + pub fn get_current_node_id(&self) -> String { + self.raft_cluster.get_current_node_id() + } + + pub fn export_to_csv(&self, file_path: &str) -> Result<(), String> { + let data = self.storage.get_all_data(); + CsvExporter::export_data(&data, file_path) + } + + pub fn import_from_csv(&self, file_path: &str) -> Result<(), String> { + let data = CsvExporter::import_data(file_path)?; + self.storage.restore_from_data(data) + } +} diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..8c2741b --- /dev/null +++ b/src/http.rs @@ -0,0 +1,453 @@ +//[file name]: http.rs + +use warp::Filter; +use std::sync::Arc; +use std::convert::Infallible; +use serde::{Deserialize, Serialize}; +use crate::db::FutriixDB; +use crate::config::Config; +use std::collections::HashMap; +use std::path::Path; +use tokio::fs; +use serde_json::Value; + +/// Запуск HTTP сервера +pub async fn start_http_server(db: Arc, config: Config) { + let db_filter = warp::any().map(move || db.clone()); + + // Новый маршрут для быстрых команд через URL + let quick_commands = warp::path!("quick" / String / String / String / String) + .and(warp::post()) + .and(db_filter.clone()) + .and_then(quick_command_handler); + + let quick_commands_get = warp::path!("quick" / String / String / String) + .and(warp::get()) + .and(db_filter.clone()) + .and_then(quick_command_get_handler); + + // Маршруты для работы с документами + let create_space = warp::path!("space" / String) + .and(warp::post()) + .and(db_filter.clone()) + .and_then(create_space_handler); + + let delete_space = warp::path!("space" / String) + .and(warp::delete()) + .and(db_filter.clone()) + .and_then(delete_space_handler); + + let insert_document = warp::path!("document" / String / String) + .and(warp::post()) + .and(warp::body::json()) + .and(db_filter.clone()) + .and_then(insert_document_handler); + + let get_document = warp::path!("document" / String / String) + .and(warp::get()) + .and(db_filter.clone()) + .and_then(get_document_handler); + + let update_document = warp::path!("document" / String / String) + .and(warp::put()) + .and(warp::body::json()) + .and(db_filter.clone()) + .and_then(update_document_handler); + + let delete_document = warp::path!("document" / String / String) + .and(warp::delete()) + .and(db_filter.clone()) + .and_then(delete_document_handler); + + // Маршруты для работы с хранимыми процедурами + let create_procedure = warp::path!("procedure" / String) + .and(warp::post()) + .and(warp::body::json()) + .and(db_filter.clone()) + .and_then(create_procedure_handler); + + let call_procedure = warp::path!("procedure" / String) + .and(warp::get()) + .and(db_filter.clone()) + .and_then(call_procedure_handler); + + let drop_procedure = warp::path!("procedure" / String) + .and(warp::delete()) + .and(db_filter.clone()) + .and_then(drop_procedure_handler); + + let list_procedures = warp::path!("procedures") + .and(warp::get()) + .and(db_filter.clone()) + .and_then(list_procedures_handler); + + // Маршруты для работы с бэкапами + let create_backup = warp::path!("backup") + .and(warp::post()) + .and(warp::body::json()) + .and(db_filter.clone()) + .and_then(create_backup_handler); + + let restore_backup = warp::path!("backup" / "restore") + .and(warp::post()) + .and(warp::body::json()) + .and(db_filter.clone()) + .and_then(restore_backup_handler); + + // Маршруты для работы с репликацией + let enable_replication = warp::path!("replication" / "enable") + .and(warp::post()) + .and(db_filter.clone()) + .and_then(enable_replication_handler); + + let disable_replication = warp::path!("replication" / "disable") + .and(warp::post()) + .and(db_filter.clone()) + .and_then(disable_replication_handler); + + // Маршруты для работы с MD5 + let md5_hash = warp::path!("md5" / String) + .and(warp::get()) + .and_then(md5_handler); + + // Статические файлы + let static_files = warp::path("static") + .and(warp::fs::dir("./static")); + + // Корневой маршрут для обслуживания HTML файлов + let root = warp::path::end() + .and(warp::get()) + .and(warp::fs::file("./static/index.html")); + + // Обслуживание статических файлов с правильными MIME типами + let static_assets = warp::path("assets") + .and(warp::fs::dir("./assets")); + + // Комбинируем все маршруты + let routes = warp::any() + .and( + quick_commands + .or(quick_commands_get) + .or(create_space) + .or(delete_space) + .or(insert_document) + .or(get_document) + .or(update_document) + .or(delete_document) + .or(create_procedure) + .or(call_procedure) + .or(drop_procedure) + .or(list_procedures) + .or(create_backup) + .or(restore_backup) + .or(enable_replication) + .or(disable_replication) + .or(md5_hash) + .or(static_files) + .or(static_assets) + .or(root) + ) + .with(warp::cors().allow_any_origin()) + .with(warp::log("http")); + + let port = config.http_port; + + println!("HTTP server started on port {}", port); + + if config.https_enabled { + // Запуск HTTPS сервера + let https_port = config.https_port; + println!("HTTPS server started on port {}", https_port); + + // В реальной реализации здесь была бы настройка TLS + warp::serve(routes) + .run(([127, 0, 0, 1], port)) + .await; + } else { + // Запуск HTTP сервера + warp::serve(routes) + .run(([127, 0, 0, 1], port)) + .await; + } +} + +// Обработчик быстрых команд через URL +async fn quick_command_handler( + command: String, + space: String, + key: String, + value: String, + db: Arc, +) -> Result { + match command.as_str() { + "create" => { + let json_value: Result = serde_json::from_str(&value); + match json_value { + Ok(value) => { + match db.insert(&space, &key, value) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document created"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } + } + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&format!("Invalid JSON: {}", e)))), + } + } + "get" => { + match db.get(&space, &key) { + Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))), + Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } + } + "update" => { + let json_value: Result = serde_json::from_str(&value); + match json_value { + Ok(value) => { + match db.update(&space, &key, value) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document updated"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } + } + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&format!("Invalid JSON: {}", e)))), + } + } + "delete" => { + match db.delete(&space, &key) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document deleted"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } + } + "create_space" => { + match db.create_space(&space) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space created"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } + } + "delete_space" => { + match db.delete_space(&space) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space deleted"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } + } + _ => Ok(warp::reply::json(&ApiResponse::error(&format!("Unknown command: {}", command)))), + } +} + +// Обработчик быстрых GET команд через URL (для получения данных) +async fn quick_command_get_handler( + command: String, + space: String, + key: String, + db: Arc, +) -> Result { + match command.as_str() { + "get" => { + match db.get(&space, &key) { + Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))), + Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } + } + _ => Ok(warp::reply::json(&ApiResponse::error(&format!("Unknown command: {}", command)))), + } +} + +// Обработчик MD5 хеширования +async fn md5_handler(text: String) -> Result { + use md5::{Digest, Md5}; + + let mut hasher = Md5::new(); + hasher.update(text.as_bytes()); + let result = hasher.finalize(); + let hash = format!("{:x}", result); + + Ok(warp::reply::json(&ApiResponse::data(hash))) +} + +// Обработчики для работы с пространствами +async fn create_space_handler(name: String, db: Arc) -> Result { + match db.create_space(&name) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space created"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +async fn delete_space_handler(name: String, db: Arc) -> Result { + match db.delete_space(&name) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space deleted"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +// Обработчики для работы с документами +async fn insert_document_handler( + space: String, + key: String, + value: serde_json::Value, + db: Arc, +) -> Result { + match db.insert(&space, &key, value) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document inserted"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +async fn get_document_handler( + space: String, + key: String, + db: Arc, +) -> Result { + match db.get(&space, &key) { + Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))), + Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +async fn update_document_handler( + space: String, + key: String, + value: serde_json::Value, + db: Arc, +) -> Result { + match db.update(&space, &key, value) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document updated"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +async fn delete_document_handler( + space: String, + key: String, + db: Arc, +) -> Result { + match db.delete(&space, &key) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document deleted"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +// Обработчики для работы с хранимыми процедурами +async fn create_procedure_handler( + name: String, + body: CreateProcedureRequest, + db: Arc, +) -> Result { + match db.create_procedure(&name, &body.code) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Procedure created"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +async fn call_procedure_handler( + name: String, + db: Arc, +) -> Result { + match db.get_procedure(&name) { + Some(code) => { + // В реальной реализации здесь выполнялся бы код процедуры + Ok(warp::reply::json(&ApiResponse::data(code))) + } + None => Ok(warp::reply::json(&ApiResponse::error("Procedure not found"))), + } +} + +async fn drop_procedure_handler( + name: String, + db: Arc, +) -> Result { + match db.drop_procedure(&name) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Procedure dropped"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +async fn list_procedures_handler( + db: Arc, +) -> Result { + let procedures = db.list_procedures(); + Ok(warp::reply::json(&ApiResponse::data(procedures))) +} + +// Обработчики для работы с бэкапами +async fn create_backup_handler( + body: BackupRequest, + db: Arc, +) -> Result { + match db.create_backup(&body.path) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Backup created"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +async fn restore_backup_handler( + body: BackupRequest, + db: Arc, +) -> Result { + match db.restore_backup(&body.path) { + Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Backup restored"))), + Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))), + } +} + +// Обработчики для работы с репликацией +async fn enable_replication_handler( + db: Arc, +) -> Result { + db.enable_replication(); + Ok(warp::reply::json(&ApiResponse::success("Replication enabled"))) +} + +async fn disable_replication_handler( + db: Arc, +) -> Result { + db.disable_replication(); + Ok(warp::reply::json(&ApiResponse::success("Replication disabled"))) +} + +// Структуры для запросов и ответов API +#[derive(Deserialize)] +struct CreateProcedureRequest { + code: String, +} + +#[derive(Deserialize)] +struct BackupRequest { + path: String, +} + +#[derive(Serialize)] +struct ApiResponse { + success: bool, + message: Option, + data: Option, +} + +impl ApiResponse<()> { + fn success(message: &str) -> Self { + Self { + success: true, + message: Some(message.to_string()), + data: None, + } + } + + fn error(message: &str) -> Self { + Self { + success: false, + message: Some(message.to_string()), + data: None, + } + } +} + +impl ApiResponse { + fn data(data: T) -> Self { + Self { + success: true, + message: None, + data: Some(data), + } + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e37f18e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,23 @@ +//lib.rs-это корневой файл библиотеки (library crate) в проекте Rust. +// Объявляет все основные модули (config, db, http, cli, lua) +//Re-export'ит ключевые структуры и функции для внешнего использования +// Служит точкой входа для других crates, которые хотят использовать futriix как библиотеку + +pub mod config; +pub mod db; +pub mod http; +pub mod cli; +pub mod lua; + +// Re-export основных компонентов +pub use db::FutriixDB; +pub use cli::CommandParser; +pub use config::Config; +pub use lua::LuaInterpreter; + +// Re-export HTTP функций +pub use http::start_http_server; + +// Re-export функций шифрования +pub use db::EncryptionManager; + diff --git a/src/lua.rs b/src/lua.rs new file mode 100644 index 0000000..46afb2a --- /dev/null +++ b/src/lua.rs @@ -0,0 +1,423 @@ +//[file name]: lua.rs + +use rlua::{Lua, Function, Value as LuaValue}; +use std::error::Error; +use crate::db::FutriixDB; +use serde_json::Value; +use rustyline::error::ReadlineError; +use rustyline::{Editor, CompletionType, Config as EditorConfig}; +use std::collections::HashMap; +use std::fs::OpenOptions; +use std::io::Write; +use chrono::Utc; +use md5::{Digest, Md5}; + +/// Lua интерпретатор с поддержкой команд СУБД +pub struct LuaInterpreter { + db: FutriixDB, + lua: Lua, + editor: Editor<(), rustyline::history::DefaultHistory>, +} + +impl LuaInterpreter { + pub fn new(db: FutriixDB) -> Self { + let lua = Lua::new(); + let config = EditorConfig::builder() + .completion_type(CompletionType::List) + .build(); + let editor = Editor::with_config(config).unwrap(); + + Self { db, lua, editor } + } + + pub async fn run(&mut self) -> Result<(), Box> { + // Загрузка истории команд + let history_path = "history.txt"; + if let Err(_) = self.editor.load_history(history_path) { + println!("No previous command history found."); + Self::log_to_appsr_file("[INFO] No previous command history found."); + } + + // Регистрация функций СУБД в Lua + self.register_db_functions()?; + + println!("{}", ansi_term::Colour::RGB(0x00, 0xbf, 0xff).paint("Lua interpreter ready. Type 'exit' to quit.")); + Self::log_to_appsr_file("[INFO] Lua interpreter ready. Type 'exit' to quit."); + + loop { + let readline = self.editor.readline(&ansi_term::Colour::RGB(0xb6, 0xff, 0x76).paint("lua> ").to_string()); + match readline { + Ok(line) => { + if line.trim().eq_ignore_ascii_case("exit") { + Self::log_to_appsr_file("[INFO] User exited Lua interpreter"); + break; + } + + let _ = self.editor.add_history_entry(line.as_str()); + + // Логируем введённую команду + Self::log_to_appsr_file(&format!("[COMMAND] lua> {}", &line)); + + // Обработка специальных команд СУБД + if let Some(result) = self.handle_special_commands(&line).await { + println!("{}", result); + Self::log_to_appsr_file(&format!("[RESULT] {}", &result)); + continue; + } + + // Выполнение как Lua кода + match self.execute_lua(&line).await { + Ok(result) => { + if !result.is_empty() { + println!("{}", result); + Self::log_to_appsr_file(&format!("[RESULT] {}", &result)); + } else { + Self::log_to_appsr_file("[RESULT] Command executed successfully"); + } + } + Err(e) => { + let error_msg = format!("Error: {}", e); + eprintln!("{}", ansi_term::Colour::Red.paint(&error_msg)); + Self::log_to_appsr_file(&format!("[ERROR] {}", &error_msg)); + } + } + } + Err(ReadlineError::Interrupted) => { + println!("CTRL-C"); + Self::log_to_appsr_file("[INFO] CTRL-C received"); + break; + } + Err(ReadlineError::Eof) => { + println!("CTRL-D"); + Self::log_to_appsr_file("[INFO] CTRL-D received"); + break; + } + Err(err) => { + let error_msg = format!("Error: {:?}", err); + eprintln!("{}", error_msg); + Self::log_to_appsr_file(&format!("[ERROR] {}", error_msg)); + break; + } + } + } + + // Сохранение истории команд + self.editor.save_history(history_path) + .map_err(|e| Box::new(e) as Box)?; + + Ok(()) + } + + /// Логирование в файл appsr.txt + fn log_to_appsr_file(message: &str) { + let timestamp = Utc::now().to_rfc3339(); + let log_entry = format!("{} {}\n", timestamp, message); + + if let Ok(mut file) = OpenOptions::new() + .create(true) + .append(true) + .open("history_appsr.txt") + { + let _ = file.write_all(log_entry.as_bytes()); + } + } + + fn register_db_functions(&self) -> Result<(), Box> { + let lua = &self.lua; + let globals = lua.globals(); + + // Функции для работы с пространствами + let db_clone_1 = self.db.clone(); + let create_space = lua.create_function(move |_, name: String| { + db_clone_1.create_space(&name) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("create_space", create_space)?; + + let db_clone_2 = self.db.clone(); + let delete_space = lua.create_function(move |_, name: String| { + db_clone_2.delete_space(&name) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("delete_space", delete_space)?; + + // Функции для работы с документами + let db_clone_3 = self.db.clone(); + let insert = lua.create_function(move |_, (space, key, value): (String, String, String)| { + let json_value: Value = serde_json::from_str(&value) + .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; + db_clone_3.insert(&space, &key, json_value) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("insert", insert)?; + + let db_clone_4 = self.db.clone(); + let get = lua.create_function(move |_, (space, key): (String, String)| { + match db_clone_4.get(&space, &key) { + Ok(Some(value)) => { + let json_str = serde_json::to_string(&value) + .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; + Ok(json_str) + } + Ok(None) => Ok("null".to_string()), + Err(e) => Err(rlua::Error::RuntimeError(e)), + } + })?; + globals.set("get", get)?; + + let db_clone_5 = self.db.clone(); + let update = lua.create_function(move |_, (space, key, value): (String, String, String)| { + let json_value: Value = serde_json::from_str(&value) + .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; + db_clone_5.update(&space, &key, json_value) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("update", update)?; + + let db_clone_6 = self.db.clone(); + let delete = lua.create_function(move |_, (space, key): (String, String)| { + db_clone_6.delete(&space, &key) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("delete", delete)?; + + // Функции для работы с кортежами + let db_clone_7 = self.db.clone(); + let create_tuple = lua.create_function(move |_, (space, tuple_id, value): (String, String, String)| { + let json_value: Value = serde_json::from_str(&value) + .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; + db_clone_7.create_tuple(&space, &tuple_id, json_value) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("create_tuple", create_tuple)?; + + let db_clone_8 = self.db.clone(); + let read_tuple = lua.create_function(move |_, (space, tuple_id): (String, String)| { + match db_clone_8.read_tuple(&space, &tuple_id) { + Ok(Some(value)) => { + let json_str = serde_json::to_string(&value) + .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; + Ok(json_str) + } + Ok(None) => Ok("null".to_string()), + Err(e) => Err(rlua::Error::RuntimeError(e)), + } + })?; + globals.set("read_tuple", read_tuple)?; + + let db_clone_9 = self.db.clone(); + let delete_tuple = lua.create_function(move |_, (space, tuple_id): (String, String)| { + db_clone_9.delete_tuple(&space, &tuple_id) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("delete_tuple", delete_tuple)?; + + // Функции для работы с транзакциями + let db_clone_10 = self.db.clone(); + let begin_transaction = lua.create_function(move |_, transaction_id: String| { + db_clone_10.begin_transaction(transaction_id) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("begin_transaction", begin_transaction)?; + + // Функции для работы с индексами + let db_clone_11 = self.db.clone(); + let create_primary_index = lua.create_function(move |_, collection: String| { + db_clone_11.create_primary_index(&collection) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("create_primary_index", create_primary_index)?; + + let db_clone_12 = self.db.clone(); + let create_secondary_index = lua.create_function(move |_, (collection, field, index_type): (String, String, String)| { + db_clone_12.create_secondary_index(&collection, &field, &index_type) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("create_secondary_index", create_secondary_index)?; + + // Функции для работы с хранимыми процедурами + let db_clone_13 = self.db.clone(); + let create_procedure = lua.create_function(move |_, (name, code): (String, String)| { + db_clone_13.create_procedure(&name, &code) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("create_procedure", create_procedure)?; + + let db_clone_14 = self.db.clone(); + let drop_procedure = lua.create_function(move |_, name: String| { + db_clone_14.drop_procedure(&name) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("drop_procedure", drop_procedure)?; + + let db_clone_15 = self.db.clone(); + let call_procedure = lua.create_function(move |_, name: String| { + match db_clone_15.get_procedure(&name) { + Some(code) => { + // Здесь должна быть логика выполнения процедуры + // В упрощённой реализации просто возвращаем код + Ok(code) + } + None => Err(rlua::Error::RuntimeError("Procedure not found".to_string())), + } + })?; + globals.set("call_procedure", call_procedure)?; + + // Функции для работы с бэкапами + let db_clone_16 = self.db.clone(); + let create_backup = lua.create_function(move |_, backup_path: String| { + db_clone_16.create_backup(&backup_path) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("create_backup", create_backup)?; + + let db_clone_17 = self.db.clone(); + let restore_backup = lua.create_function(move |_, backup_path: String| { + db_clone_17.restore_backup(&backup_path) + .map_err(|e| rlua::Error::RuntimeError(e)) + })?; + globals.set("restore_backup", restore_backup)?; + + // Функции для работы с репликацией + let db_clone_18 = self.db.clone(); + let enable_replication = lua.create_function(move |_, ()| { + db_clone_18.enable_replication(); + Ok(()) + })?; + globals.set("enable_replication", enable_replication)?; + + let db_clone_19 = self.db.clone(); + let disable_replication = lua.create_function(move |_, ()| { + db_clone_19.disable_replication(); + Ok(()) + })?; + globals.set("disable_replication", disable_replication)?; + + // Функция для MD5 хеширования + let md5_hash = lua.create_function(move |_, text: String| { + let mut hasher = Md5::new(); + hasher.update(text.as_bytes()); + let result = hasher.finalize(); + Ok(format!("{:x}", result)) + })?; + globals.set("md5", md5_hash)?; + + Ok(()) + } + + async fn handle_special_commands(&self, line: &str) -> Option { + let parts: Vec<&str> = line.trim().split_whitespace().collect(); + if parts.is_empty() { + return None; + } + + match parts[0].to_lowercase().as_str() { + "backup" => { + if parts.len() >= 2 { + let backup_path = parts[1]; + match self.db.create_backup(backup_path) { + Ok(()) => Some("Backup created successfully".to_string()), + Err(e) => Some(format!("Backup error: {}", e)), + } + } else { + Some("Usage: backup ".to_string()) + } + } + "restore" => { + if parts.len() >= 2 { + let backup_path = parts[1]; + match self.db.restore_backup(backup_path) { + Ok(()) => Some("Backup restored successfully".to_string()), + Err(e) => Some(format!("Restore error: {}", e)), + } + } else { + Some("Usage: restore ".to_string()) + } + } + "create" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => { + if parts.len() >= 4 { + let name = parts[2]; + // Для создания процедуры нужен код, который обычно многострочный + // В упрощённой реализации просто возвращаем инструкцию + Some("Use: create_procedure('name', 'lua_code') in Lua".to_string()) + } else { + Some("Usage: create procedure ".to_string()) + } + } + "call" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => { + if parts.len() >= 3 { + let name = parts[2]; + match self.db.get_procedure(name) { + Some(ref code) => { + // Выполняем код процедуры + match self.execute_lua(code).await { + Ok(result) => Some(format!("Procedure result: {}", result)), + Err(e) => Some(format!("Procedure execution error: {}", e)), + } + } + None => Some(format!("Procedure '{}' not found", name)), + } + } else { + Some("Usage: call procedure ".to_string()) + } + } + "drop" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => { + if parts.len() >= 3 { + let name = parts[2]; + match self.db.drop_procedure(name) { + Ok(()) => Some("Procedure dropped successfully".to_string()), + Err(e) => Some(format!("Drop procedure error: {}", e)), + } + } else { + Some("Usage: drop procedure ".to_string()) + } + } + "md5" => { + if parts.len() >= 2 { + let text = parts[1]; + let mut hasher = Md5::new(); + hasher.update(text.as_bytes()); + let result = hasher.finalize(); + Some(format!("MD5 hash: {:x}", result)) + } else { + Some("Usage: md5 ".to_string()) + } + } + _ => None, + } + } + + async fn execute_lua(&self, code: &str) -> Result> { + let result = self.lua.load(code).eval::()?; + + match result { + LuaValue::String(s) => Ok(s.to_str()?.to_string()), + LuaValue::Number(n) => Ok(n.to_string()), + LuaValue::Boolean(b) => Ok(b.to_string()), + LuaValue::Nil => Ok("".to_string()), + LuaValue::Table(t) => { + let mut result = HashMap::new(); + for pair in t.pairs::() { + let (key, value) = pair?; + result.insert( + Self::lua_value_to_string(key)?, + Self::lua_value_to_string(value)?, + ); + } + Ok(serde_json::to_string(&result)?) + } + _ => Ok(format!("{:?}", result)), + } + } + + fn lua_value_to_string(value: LuaValue) -> Result> { + match value { + LuaValue::String(s) => Ok(s.to_str()?.to_string()), + LuaValue::Number(n) => Ok(n.to_string()), + LuaValue::Boolean(b) => Ok(b.to_string()), + LuaValue::Nil => Ok("null".to_string()), + _ => Ok(format!("{:?}", value)), + } + } +} + diff --git a/src/lua/bindings.rs b/src/lua/bindings.rs new file mode 100644 index 0000000..cdc0d80 --- /dev/null +++ b/src/lua/bindings.rs @@ -0,0 +1,55 @@ +use rlua::{Lua, Result as LuaResult}; +use std::sync::Arc; +use crate::db::FutriixDB; + +/// Регистрация функций СУБД в Lua окружении +pub fn register_db_functions(lua: &Lua, db: FutriixDB) -> LuaResult<()> { + let globals = lua.globals(); + + // Клонируем db для каждого замыкания + let db_clone_1 = db.clone(); + let db_create = lua.create_function(move |_, (collection, key, value): (String, String, String)| { + let json_value: serde_json::Value = serde_json::from_str(&value) + .map_err(|e| rlua::Error::external(e))?; + db_clone_1.create_document(&collection, &key, json_value) + .map_err(|e| rlua::Error::external(e))?; + Ok(()) + })?; + + globals.set("db_create", db_create)?; + + let db_clone_2 = db.clone(); + let db_read = lua.create_function(move |_, (collection, key): (String, String)| { + match db_clone_2.read_document(&collection, &key) { + Ok(Some(value)) => Ok(serde_json::to_string(&value).map_err(|e| rlua::Error::external(e))?), + Ok(None) => Ok("null".to_string()), + Err(e) => Err(rlua::Error::external(e)), + } + })?; + + globals.set("db_read", db_read)?; + + let db_clone_3 = db.clone(); + let db_update = lua.create_function(move |_, (collection, key, value): (String, String, String)| { + let json_value: serde_json::Value = serde_json::from_str(&value) + .map_err(|e| rlua::Error::external(e))?; + db_clone_3.update_document(&collection, &key, json_value) + .map_err(|e| rlua::Error::external(e))?; + Ok(()) + })?; + + globals.set("db_update", db_update)?; + + let db_clone_4 = db.clone(); + let db_delete = lua.create_function(move |_, (collection, key): (String, String)| { + db_clone_4.delete_document(&collection, &key) + .map_err(|e| rlua::Error::external(e))?; + Ok(()) + })?; + + globals.set("db_delete", db_delete)?; + + // Регистрация других функций... + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e27e01b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,168 @@ +use ansi_term::Colour; +use std::error::Error; +use std::sync::Arc; +use std::env; +use std::path::Path; +use std::fs::OpenOptions; +use std::io::Write; +use chrono::Utc; + +// Импортируем из библиотечной части +use futriix::{Config, cli::CommandParser, lua::LuaInterpreter, db::FutriixDB, http}; + +/// Главная точка входа приложения +#[tokio::main] +async fn main() -> Result<(), Box> { + // Вывод заголовка приложения с пустой строкой перед + println!(); + println!("{}", Colour::RGB(0, 191, 255).paint("futriiX database server")); + println!("{}", Colour::RGB(0, 191, 255).paint("futriix 3i²(by 26.10.2025)")); + + // Инициализация логгера + env_logger::init(); + log::info!("[INFO] Starting futriiX server..."); + + // Логирование в файл appsr.txt + log_to_appsr_file("[INFO] Starting futriiX server..."); + + // Получаем аргументы командной строки + let args: Vec = env::args().collect(); + let config_path = if args.len() > 1 { + &args[1] + } else { + "config/config.toml" + }; + + // Загрузка конфигурации + let config = match load_config(config_path) { + Ok(cfg) => { + let msg = format!("[INFO] Configuration loaded successfully from {}", config_path); + log::info!("{}", msg); + log_to_appsr_file(&msg); + cfg + } + Err(e) => { + let error_msg = format!("Error loading config from {}: {}", config_path, e); + eprintln!("{}", Colour::Red.paint(&error_msg)); + eprintln!("{}", Colour::Yellow.paint("Using default configuration...")); + + log_to_appsr_file(&format!("[ERROR] {}", error_msg)); + log_to_appsr_file("[INFO] Using default configuration..."); + + // Используем значения по умолчанию + Config { + http_port: 8080, + db_path: "./data".to_string(), + log_level: "info".to_string(), + cluster_enabled: false, + node_id: "node_1".to_string(), + cluster_nodes: vec![], + acl_enabled: false, + admin_users: vec![], + master_master_replication: false, + replication_nodes: vec![], + https_enabled: false, + https_port: 8443, + cert_file: "./cert.pem".to_string(), + key_file: "./key.pem".to_string(), + http2_enabled: false, + stored_procedures_path: "./procedures".to_string(), + } + } + }; + + // Инициализация СУБД + let db = match FutriixDB::new(&config).await { + Ok(db_instance) => { + let msg = "[INFO] Database initialized successfully"; + log::info!("{}", msg); + log_to_appsr_file(msg); + db_instance + } + Err(e) => { + let error_msg = format!("[ERROR] Failed to initialize database: {}", e); + log::error!("{}", error_msg); + log_to_appsr_file(&error_msg); + return Err(e.into()); + } + }; + + // Запуск HTTP сервера в фоновом режиме + let db_arc = Arc::new(db.clone()); + let config_clone = config.clone(); + tokio::spawn(async move { + let msg = format!("[INFO] HTTP server starting on port {}", config_clone.http_port); + log::info!("{}", msg); + log_to_appsr_file(&msg); + http::start_http_server(db_arc, config_clone).await; + }); + + // Вывод информации о состоянии + let cluster_status = if config.cluster_enabled { + format!("Cluster status: enabled (node: {})", config.node_id) + } else { + "Cluster status: single node (standalone mode)".to_string() + }; + println!("{}", Colour::RGB(0, 255, 127).paint(&cluster_status)); + log_to_appsr_file(&format!("[INFO] {}", cluster_status)); + + let acl_status = if config.acl_enabled { + "ACL: enabled" + } else { + "ACL: disabled" + }; + println!("{}", Colour::RGB(0, 255, 127).paint(acl_status)); + log_to_appsr_file(&format!("[INFO] {}", acl_status)); + + let replication_status = if config.master_master_replication { + "Master-Master replication: enabled" + } else { + "Master-Master replication: disabled" + }; + println!("{}", Colour::RGB(0, 255, 127).paint(replication_status)); + log_to_appsr_file(&format!("[INFO] {}", replication_status)); + + let history_path = "history.txt"; + let history_abs_path = std::env::current_dir()? + .join(history_path) + .to_string_lossy() + .to_string(); + println!("Command history file: {}", history_abs_path); + log_to_appsr_file(&format!("[INFO] Command history file: {}", history_abs_path)); + + // Запуск Lua интерпретатора + let mut lua_interpreter = LuaInterpreter::new(db); + if let Err(e) = lua_interpreter.run().await { + let error_msg = format!("[ERROR] Lua interpreter error: {}", e); + log::error!("{}", error_msg); + log_to_appsr_file(&error_msg); + return Err(e); + } + + let msg = "[INFO] futriiX server shutdown completed"; + log::info!("{}", msg); + log_to_appsr_file(msg); + + Ok(()) +} + +/// Загрузка конфигурации из файла +fn load_config(config_path: &str) -> Result> { + let config_content = std::fs::read_to_string(config_path)?; + let config: Config = toml::from_str(&config_content)?; + Ok(config) +} + +/// Логирование в файл appsr.txt +fn log_to_appsr_file(message: &str) { + let timestamp = Utc::now().to_rfc3339(); + let log_entry = format!("{} {}\n", timestamp, message); + + if let Ok(mut file) = OpenOptions::new() + .create(true) + .append(true) + .open("history_appsr.txt") + { + let _ = file.write_all(log_entry.as_bytes()); + } +}