diff --git a/Cargo.lock b/Cargo.lock index 41121c1..2af76b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -14,12 +38,58 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "derive_more" version = "1.0.0-beta.6" @@ -53,6 +123,158 @@ dependencies = [ "syn", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -62,6 +284,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -77,6 +318,37 @@ dependencies = [ "num-traits", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.83" @@ -95,16 +367,114 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "simple-redis" version = "0.1.0" dependencies = [ "anyhow", "bytes", + "dashmap", "derive_more", "enum_dispatch", + "futures", + "lazy_static", "ordered-float", "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", ] [[package]] @@ -138,6 +508,129 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -149,3 +642,176 @@ name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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.5", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index efc9a90..6758b93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,20 @@ publish = false edition = "2021" [dependencies] +anyhow = "1.0.86" bytes = "1.6.0" -derive_more = { version = "1.0.0-beta.6", features = ["deref", "display"] } +dashmap = "5.5.3" +derive_more = { version = "1.0.0-beta.6", features = ["deref", "display", "as_ref", "from"] } enum_dispatch = "0.3.13" +futures = { version = "0.3.30", default-features = false } +lazy_static = "1.4.0" ordered-float = "4.2.0" thiserror = "1.0.61" +tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread", "rt", "net", "io-util"] } +tokio-stream = "0.1.15" +tokio-util = { version = "0.7.11", features = ["codec"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [dev-dependencies] anyhow = "1.0.86" diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..eb23708 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,43 @@ +use std::sync::Arc; + +use crate::RespFrame; +use dashmap::DashMap; +use derive_more::Deref; + +#[derive(Debug, Clone, Deref, Default)] +pub struct Backend(Arc); + +#[derive(Debug, Default)] +pub struct BackendInner { + pub(crate) map: DashMap, + pub(crate) hmap: DashMap>, +} + +impl Backend { + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self, key: &str) -> Option { + self.map.get(key).map(|v| v.value().clone()) + } + + pub fn set(&self, key: String, value: RespFrame) { + self.map.insert(key, value); + } + + pub fn hget(&self, key: &str, field: &str) -> Option { + self.hmap + .get(key) + .and_then(|v| v.get(field).map(|v| v.value().clone())) + } + + pub fn hset(&self, key: String, field: String, value: RespFrame) { + let hmap = self.hmap.entry(key).or_default(); + hmap.insert(field, value); + } + + pub fn hgetall(&self, key: &str) -> Option> { + self.hmap.get(key).map(|v| v.clone()) + } +} diff --git a/src/cmd/hmap.rs b/src/cmd/hmap.rs new file mode 100644 index 0000000..d59840b --- /dev/null +++ b/src/cmd/hmap.rs @@ -0,0 +1,188 @@ +use super::{ + extract_args, validate_command, CommandError, CommandExecutor, HGet, HGetAll, HSet, RESP_OK, +}; +use crate::{resp::RespNull, Backend, RespArray, RespBulkString, RespFrame}; + +impl CommandExecutor for HSet { + fn execute(self, backend: &Backend) -> RespFrame { + backend.hset(self.key, self.field, self.value); + RESP_OK.clone() + } +} + +impl CommandExecutor for HGet { + fn execute(self, backend: &Backend) -> RespFrame { + match backend.hget(&self.key, &self.field) { + Some(value) => value, + None => RespFrame::Null(RespNull), + } + } +} + +impl CommandExecutor for HGetAll { + fn execute(self, backend: &Backend) -> RespFrame { + let hmap = backend.hmap.get(&self.key); + match hmap { + Some(hmap) => { + let mut data = Vec::with_capacity(hmap.len()); + for v in hmap.iter() { + let key = v.key().to_owned(); + data.push((key, v.value().clone())); + } + if self.sort { + data.sort_by(|a, b| a.0.cmp(&b.0)); + } + let ret = data + .into_iter() + .flat_map(|(k, v)| vec![RespBulkString::from(k).into(), v]) + .collect::>(); + + RespArray::new(ret).into() + } + None => RespArray::new([]).into(), + } + } +} + +impl TryFrom for HSet { + type Error = CommandError; + fn try_from(value: RespArray) -> Result { + validate_command(&value, &["hset"], 3)?; + + let mut args = extract_args(value, 1)?.into_iter(); + match (args.next(), args.next(), args.next()) { + (Some(RespFrame::BulkString(key)), Some(RespFrame::BulkString(field)), Some(value)) => { + Ok(HSet { + key: String::from_utf8(key.0)?, + field: String::from_utf8(field.0)?, + value, + }) + } + _ => Err(CommandError::InvalidCommandArguments( + "Invalid key, field or value".to_string(), + )), + } + } +} + +impl TryFrom for HGet { + type Error = CommandError; + fn try_from(value: RespArray) -> Result { + validate_command(&value, &["hget"], 2)?; + + let mut args = extract_args(value, 1)?.into_iter(); + match (args.next(), args.next()) { + (Some(RespFrame::BulkString(key)), Some(RespFrame::BulkString(field))) => Ok(HGet { + key: String::from_utf8(key.0)?, + field: String::from_utf8(field.0)?, + }), + _ => Err(CommandError::InvalidCommandArguments( + "Invalid key or field".to_string(), + )), + } + } +} + +impl TryFrom for HGetAll { + type Error = CommandError; + fn try_from(value: RespArray) -> Result { + validate_command(&value, &["hgetall"], 1)?; + + let mut args = extract_args(value, 1)?.into_iter(); + match args.next() { + Some(RespFrame::BulkString(key)) => Ok(HGetAll { + key: String::from_utf8(key.0)?, + sort: false, + }), + _ => Err(CommandError::InvalidCommandArguments( + "Invalid key".to_string(), + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{resp::RespDecoder, RespBulkString}; + use anyhow::Result; + use bytes::BytesMut; + + #[test] + fn test_hset_command() -> Result<()> { + let mut buf = BytesMut::new(); + buf.extend_from_slice( + b"*4\r\n$4\r\nhset\r\n$6\r\nmyhash\r\n$5\r\nfield\r\n$5\r\nvalue\r\n", + ); + let input = RespArray::decode(&mut buf)?; + + let cmd = HSet::try_from(input)?; + assert_eq!(cmd.key, "myhash"); + assert_eq!(cmd.field, "field"); + assert_eq!( + cmd.value, + RespFrame::BulkString(RespBulkString::new("value")) + ); + + Ok(()) + } + + #[test] + fn test_hget_command() -> Result<()> { + let mut buf = BytesMut::new(); + buf.extend_from_slice(b"*3\r\n$4\r\nhget\r\n$6\r\nmyhash\r\n$5\r\nfield\r\n"); + let input = RespArray::decode(&mut buf)?; + let cmd = HGet::try_from(input)?; + assert_eq!(cmd.key, "myhash"); + assert_eq!(cmd.field, "field"); + + Ok(()) + } + + #[test] + fn test_hgetall_command() -> Result<()> { + let mut buf = BytesMut::new(); + buf.extend_from_slice(b"*2\r\n$7\r\nhgetall\r\n$6\r\nmyhash\r\n"); + let input = RespArray::decode(&mut buf)?; + + let cmd = HGetAll::try_from(input)?; + assert_eq!(cmd.key, "myhash"); + Ok(()) + } + + #[test] + fn test_hgetall_cmd_execute() { + let backend = Backend::new(); + let cmd = HSet { + key: "family".to_string(), + field: "name".to_string(), + value: RespFrame::BulkString(RespBulkString::new("Vic")), + }; + let resp = cmd.execute(&backend); + assert_eq!(resp, RESP_OK.clone()); + + let cmd = HSet { + key: "family".to_string(), + field: "age".to_string(), + value: RespFrame::Integer(10.into()), + }; + let resp = cmd.execute(&backend); + assert_eq!(resp, RESP_OK.clone()); + + let cmd = HGetAll { + key: "family".to_string(), + sort: true, + }; + let resp = cmd.execute(&backend); + assert_eq!( + resp, + RespArray::new([ + RespBulkString::from("age").into(), + RespFrame::Integer(10), + RespBulkString::from("name").into(), + RespFrame::BulkString("Vic".into()), + ]) + .into() + ); + } +} diff --git a/src/cmd/map.rs b/src/cmd/map.rs new file mode 100644 index 0000000..4af4a08 --- /dev/null +++ b/src/cmd/map.rs @@ -0,0 +1,105 @@ +use super::{extract_args, validate_command, CommandError, CommandExecutor, Get, Set, RESP_OK}; +use crate::{ + resp::{RespArray, RespNull}, + Backend, RespFrame, +}; + +impl CommandExecutor for Get { + fn execute(self, backend: &Backend) -> RespFrame { + match backend.get(&self.key) { + Some(value) => value, + None => RespFrame::Null(RespNull), + } + } +} + +impl CommandExecutor for Set { + fn execute(self, backend: &Backend) -> RespFrame { + backend.set(self.key, self.value); + RESP_OK.clone() + } +} + +impl TryFrom for Set { + type Error = CommandError; + fn try_from(value: RespArray) -> Result { + validate_command(&value, &["set"], 2)?; + + let mut args = extract_args(value, 1)?.into_iter(); + match (args.next(), args.next()) { + (Some(RespFrame::BulkString(key)), Some(value)) => Ok(Set { + key: String::from_utf8(key.0)?, + value, + }), + _ => Err(CommandError::InvalidCommandArguments( + "Invalid key or value".to_string(), + )), + } + } +} + +impl TryFrom for Get { + type Error = CommandError; + fn try_from(value: RespArray) -> Result { + validate_command(&value, &["get"], 1)?; + + let mut args = extract_args(value, 1)?.into_iter(); + match args.next() { + Some(RespFrame::BulkString(key)) => Ok(Get { + key: String::from_utf8(key.0)?, + }), + _ => Err(CommandError::InvalidCommandArguments( + "Invalid key".to_string(), + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{resp::RespDecoder, RespBulkString}; + use anyhow::Result; + use bytes::BytesMut; + + #[test] + fn test_get_from_resp_array() -> Result<()> { + let mut buf = BytesMut::new(); + buf.extend_from_slice(b"*2\r\n$3\r\nget\r\n$4\r\nname\r\n"); + let frame = RespArray::decode(&mut buf)?; + let get = Get::try_from(frame)?; + assert_eq!(get.key, "name"); + Ok(()) + } + + #[test] + fn test_set_from_resp_array() -> Result<()> { + let mut buf = BytesMut::new(); + buf.extend_from_slice(b"*3\r\n$3\r\nset\r\n$4\r\nname\r\n$7\r\nvictory\r\n"); + let frame = RespArray::decode(&mut buf)?; + let set = Set::try_from(frame)?; + assert_eq!(set.key, "name"); + assert_eq!( + set.value, + RespFrame::BulkString(RespBulkString::new("victory")) + ); + Ok(()) + } + + #[test] + fn test_set_and_get_cmd_execute() { + let backend = Backend::new(); + let cmd = Set { + key: "name".to_string(), + value: RespFrame::BulkString("victory".into()), + }; + let resp = cmd.execute(&backend); + assert_eq!(resp, RESP_OK.clone()); + + let cmd = Get { + key: "name".to_string(), + }; + let resp = cmd.execute(&backend); + assert_eq!(resp, RespFrame::BulkString("victory".into())); + } +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs new file mode 100644 index 0000000..f838e6f --- /dev/null +++ b/src/cmd/mod.rs @@ -0,0 +1,156 @@ +mod hmap; +mod map; + +use crate::resp::{RespArray, RespSimpleString}; +use crate::{Backend, RespError, RespFrame}; +use enum_dispatch::enum_dispatch; +use lazy_static::lazy_static; +use thiserror::Error; + +lazy_static! { + static ref RESP_OK: RespFrame = RespSimpleString::new("OK").into(); +} + +// set hello world => "*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n" +// get hello => "*2\r\n$3\r\nget\r\n$5\r\nhello\r\n" +// hset map hello world => "*4\r\n$4\r\nhset\r\n$3\r\nmap\r\n$5\r\nhello\r\n$5\r\nworld\r\n" +// hget map hello => "*3\r\n$4\r\nhget\r\n$3\r\nmap\r\n$5\r\nhello\r\n" +// hgetall map => "*2\r\n$7\r\nhgetall\r\n$3\r\nmap\r\n" + +#[derive(Error, Debug)] +pub enum CommandError { + #[error("Invalid command: {0}")] + InvalidCommand(String), + #[error("Invalid command arguments: {0}")] + InvalidCommandArguments(String), + #[error("{0}")] + RespError(#[from] RespError), + #[error("Invalid UTF-8: {0}")] + Utf8Error(#[from] std::string::FromUtf8Error), +} + +#[enum_dispatch(CommandExecutor)] +#[derive(Debug)] +pub enum Command { + Set(Set), + Get(Get), + HSet(HSet), + HGet(HGet), + HGetAll(HGetAll), + + // unrecognized command + Unrecognized(Unrecognized), +} + +#[enum_dispatch] +pub trait CommandExecutor { + fn execute(self, backend: &Backend) -> RespFrame; +} + +#[derive(Debug)] +pub struct Set { + key: String, + value: RespFrame, +} + +#[derive(Debug)] +pub struct Get { + key: String, +} + +#[derive(Debug)] +pub struct HSet { + key: String, + field: String, + value: RespFrame, +} + +#[derive(Debug)] +pub struct HGet { + key: String, + field: String, +} + +#[derive(Debug)] +pub struct HGetAll { + key: String, + sort: bool, +} + +#[derive(Debug)] +pub struct Unrecognized; + +impl TryFrom for Command { + type Error = CommandError; + fn try_from(v: RespFrame) -> Result { + match v { + RespFrame::Array(array) => array.try_into(), + _ => Err(CommandError::InvalidCommand( + "Command must be an Array".to_string(), + )), + } + } +} + +impl TryFrom for Command { + type Error = CommandError; + fn try_from(v: RespArray) -> Result { + match v.first() { + Some(RespFrame::BulkString(ref cmd)) => match cmd.as_slice() { + b"get" => Ok(Get::try_from(v)?.into()), + b"set" => Ok(Set::try_from(v)?.into()), + b"hget" => Ok(HGet::try_from(v)?.into()), + b"hset" => Ok(HSet::try_from(v)?.into()), + b"hgetall" => Ok(HGetAll::try_from(v)?.into()), + _ => Ok(Unrecognized.into()), + }, + _ => Err(CommandError::InvalidCommand( + "Command must have a BulkString as the first argument".to_string(), + )), + } + } +} + +impl CommandExecutor for Unrecognized { + fn execute(self, _: &Backend) -> RespFrame { + RESP_OK.clone() + } +} + +fn validate_command( + value: &RespArray, + names: &[&'static str], + n_args: usize, +) -> Result<(), CommandError> { + if value.len() != n_args + names.len() { + return Err(CommandError::InvalidCommandArguments(format!( + "{} command must have exactly {} argument", + names.join(" "), + n_args + ))); + } + + for (i, name) in names.iter().enumerate() { + match value[i] { + RespFrame::BulkString(ref cmd) => { + if cmd.as_ref().to_ascii_lowercase() != name.as_bytes() { + return Err(CommandError::InvalidCommand(format!( + "Invalid command: expected {}, got {}", + name, + String::from_utf8_lossy(cmd.as_ref()) + ))); + } + } + _ => { + return Err(CommandError::InvalidCommand( + "Command must have a BulkString as the first argument".to_string(), + )) + } + } + } + Ok(()) +} + +fn extract_args(value: RespArray, start: usize) -> Result, CommandError> { + Ok(value.0.into_iter().skip(start).collect::>()) +} diff --git a/src/lib.rs b/src/lib.rs index 13db19e..2a13071 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,8 @@ +mod backend; mod resp; -pub use resp::RespFrame; +pub mod cmd; +pub mod network; + +pub use backend::Backend; +pub use resp::{RespArray, RespBulkString, RespDecoder, RespEncoder, RespError, RespFrame}; diff --git a/src/main.rs b/src/main.rs index e7a11a9..683a519 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,25 @@ -fn main() { - println!("Hello, world!"); +use anyhow::Result; +use simple_redis::{network, Backend}; +use tokio::net::TcpListener; +use tracing::{info, warn}; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let addr = "0.0.0.0:6379"; + let listener = TcpListener::bind(addr).await?; + info!("Simple Redis Server listening on {}", addr); + let backend = Backend::new(); + loop { + let (stream, s_addr) = listener.accept().await?; + info!("Accepted connection from: {}", s_addr); + let cloned_backend = backend.clone(); + tokio::spawn(async move { + match network::stream_handler(stream, cloned_backend).await { + Ok(_) => info!("Connection from {} exited", s_addr), + Err(e) => warn!("Error handling connection {}: {:?}", s_addr, e), + } + }); + } } diff --git a/src/network.rs b/src/network.rs new file mode 100644 index 0000000..f4a326d --- /dev/null +++ b/src/network.rs @@ -0,0 +1,77 @@ +use anyhow::Result; +use bytes::BytesMut; +use futures::SinkExt; +use tokio::net::TcpStream; +use tokio_stream::StreamExt; +use tokio_util::codec::{Decoder, Encoder, Framed}; +use tracing::info; + +use crate::{ + cmd::{Command, CommandExecutor}, + Backend, RespDecoder, RespEncoder, RespError, RespFrame, +}; + +#[derive(Debug)] +struct RespCodec; + +#[derive(Debug)] +struct RedisRequest { + frame: RespFrame, + backend: Backend, +} + +#[derive(Debug)] +struct RedisResponse { + frame: RespFrame, +} + +pub async fn stream_handler(stream: TcpStream, backend: Backend) -> Result<()> { + // how to get a frame from the stream + let mut framed = Framed::new(stream, RespCodec); + loop { + match framed.next().await { + Some(Ok(frame)) => { + info!("Received frame: {:?}", frame); + let req = RedisRequest { + frame, + backend: backend.clone(), + }; + let res = request_handler(req).await?; + framed.send(res.frame).await?; + } + Some(Err(e)) => return Err(e), + None => return Ok(()), + } + } +} + +async fn request_handler(req: RedisRequest) -> Result { + let (frame, backend) = (req.frame, req.backend); + let cmd = Command::try_from(frame)?; + info!("Executing command: {:?}", cmd); + let frame = cmd.execute(&backend); + Ok(RedisResponse { frame }) +} + +impl Encoder for RespCodec { + type Error = anyhow::Error; + + fn encode(&mut self, item: RespFrame, dst: &mut BytesMut) -> Result<()> { + let encoded = item.encode(); + dst.extend_from_slice(&encoded); + Ok(()) + } +} + +impl Decoder for RespCodec { + type Item = RespFrame; + type Error = anyhow::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result> { + match RespFrame::decode(src) { + Ok(frame) => Ok(Some(frame)), + Err(RespError::FrameNotComplete) => Ok(None), + Err(e) => Err(e.into()), + } + } +} diff --git a/src/resp/decode.rs b/src/resp/decode.rs index 15c8077..8dfe18a 100644 --- a/src/resp/decode.rs +++ b/src/resp/decode.rs @@ -13,19 +13,50 @@ const CRLF_LEN: usize = b"\r\n".len(); impl RespDecoder for RespFrame { const PREFIX: &'static str = ""; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let mut buf_iter = buf.iter().peekable(); match buf_iter.peek() { - Some(b'+') => RespSimpleString::decode(buf), - Some(b'-') => RespSimpleError::decode(buf), - Some(b':') => i64::decode(buf), - Some(b'$') => RespBulkString::decode(buf), - Some(b'*') => RespArray::decode(buf), - Some(b'_') => RespNull::decode(buf), - Some(b'#') => bool::decode(buf), - Some(b',') => RespDouble::decode(buf), - Some(b'%') => RespMap::decode(buf), - Some(b'~') => RespSet::decode(buf), + Some(b'+') => { + let frame = RespSimpleString::decode(buf)?; + Ok(frame.into()) + } + Some(b'-') => { + let frame = RespSimpleError::decode(buf)?; + Ok(frame.into()) + } + Some(b':') => { + let frame = i64::decode(buf)?; + Ok(frame.into()) + } + Some(b'$') => { + let frame = RespBulkString::decode(buf)?; + Ok(frame.into()) + } + Some(b'*') => { + let frame = RespArray::decode(buf)?; + Ok(frame.into()) + } + Some(b'_') => { + let frame = RespNull::decode(buf)?; + Ok(frame.into()) + } + Some(b'#') => { + let frame = bool::decode(buf)?; + Ok(frame.into()) + } + Some(b',') => { + let frame = RespDouble::decode(buf)?; + Ok(frame.into()) + } + Some(b'%') => { + let frame = RespMap::decode(buf)?; + Ok(frame.into()) + } + Some(b'~') => { + let frame = RespSet::decode(buf)?; + Ok(frame.into()) + } + None => Err(RespError::FrameNotComplete), _ => Err(super::RespError::InvalidFrame(format!("data: {:?}", buf))), } } @@ -51,11 +82,11 @@ impl RespDecoder for RespFrame { // Simple string "+\r\n" decode to RespSimpleString impl RespDecoder for RespSimpleString { const PREFIX: &'static str = "+"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let end = extract_simple_resp(buf, Self::PREFIX)?; let data = buf.split_to(end + 2); let s = String::from_utf8_lossy(&data[Self::PREFIX.len()..end]); - Ok(RespSimpleString::new(s.to_string()).into()) + Ok(RespSimpleString::new(s.to_string())) } fn expect_length(buf: &[u8]) -> Result { @@ -67,11 +98,11 @@ impl RespDecoder for RespSimpleString { // Simple error "-\r\n" decode to RespSimpleError impl RespDecoder for RespSimpleError { const PREFIX: &'static str = "-"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let end = extract_simple_resp(buf, Self::PREFIX)?; let data = buf.split_to(end + 2); let s = String::from_utf8_lossy(&data[Self::PREFIX.len()..end]); - Ok(RespSimpleError::new(s.to_string()).into()) + Ok(RespSimpleError::new(s.to_string())) } fn expect_length(buf: &[u8]) -> Result { @@ -83,12 +114,12 @@ impl RespDecoder for RespSimpleError { // integer ":[<+|->]\r\n" decode to i64 impl RespDecoder for i64 { const PREFIX: &'static str = ":"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let end = extract_simple_resp(buf, Self::PREFIX)?; let data = buf.split_to(end + 2); let s = String::from_utf8_lossy(&data[Self::PREFIX.len()..end]); let num = s.parse::()?; - Ok(num.into()) + Ok(num) } fn expect_length(buf: &[u8]) -> Result { @@ -100,27 +131,21 @@ impl RespDecoder for i64 { // Bulk string "$\r\n\r\n" decode to RespBulkString impl RespDecoder for RespBulkString { const PREFIX: &'static str = "$"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { if check_resp2_null(buf, Self::PREFIX) { buf.advance(Self::PREFIX.len() + RESP2_NULL.len()); - return Ok(RespNull.into()); + return Ok(RespBulkString::new(vec![])); } let (end, len) = parse_length(buf, Self::PREFIX)?; let act_len = buf[end + CRLF_LEN..].len(); - match len.cmp(&(act_len - CRLF_LEN)) { - std::cmp::Ordering::Less => Err(RespError::InvalidFrameLength(format!( - "expected length: {}, found: {}", - len, - act_len - CRLF_LEN - ))), - std::cmp::Ordering::Greater => Err(RespError::FrameNotComplete), - std::cmp::Ordering::Equal => { - buf.advance(end + CRLF_LEN); - let data = buf.split_to(len + CRLF_LEN); - Ok(RespBulkString::new(data[..len].to_vec()).into()) - } + if act_len < len + CRLF_LEN { + return Err(RespError::FrameNotComplete); } + + buf.advance(end + CRLF_LEN); + let data = buf.split_to(len + CRLF_LEN); + Ok(RespBulkString::new(data[..len].to_vec())) } fn expect_length(buf: &[u8]) -> Result { @@ -136,10 +161,10 @@ impl RespDecoder for RespBulkString { // Arrays "*\r\n..." decode to RespArray impl RespDecoder for RespArray { const PREFIX: &'static str = "*"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { if check_resp2_null(buf, Self::PREFIX) { buf.advance(Self::PREFIX.len() + RESP2_NULL.len()); - return Ok(RespNull.into()); + return Ok(RespArray::new(vec![])); } let (end, arr_len) = parse_length(buf, Self::PREFIX)?; @@ -150,15 +175,18 @@ impl RespDecoder for RespArray { } buf.advance(end + CRLF_LEN); + let mut frames = Vec::with_capacity(arr_len); + if arr_len == 0 { - return Ok(RespArray::new(frames).into()); + return Ok(RespArray::new(frames)); } + for _ in 0..arr_len { - let frame = RespFrame::decode(buf)?; - frames.push(frame); + frames.push(RespFrame::decode(buf)?); } - Ok(RespArray::new(frames).into()) + + Ok(RespArray::new(frames)) } fn expect_length(buf: &[u8]) -> Result { @@ -170,10 +198,10 @@ impl RespDecoder for RespArray { // Null "_\r\n" decode to RespNull impl RespDecoder for RespNull { const PREFIX: &'static str = "_"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let end = extract_simple_resp(buf, Self::PREFIX)?; buf.advance(end + CRLF_LEN); - Ok(RespNull.into()) + Ok(RespNull) } fn expect_length(_buf: &[u8]) -> Result { @@ -184,14 +212,14 @@ impl RespDecoder for RespNull { // Boolean "#\r\n" decode to bool impl RespDecoder for bool { const PREFIX: &'static str = "#"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let end = extract_simple_resp(buf, Self::PREFIX)?; let data = buf.split_to(end + 2); let s = &data[Self::PREFIX.len()..end]; match s { - b"t" => Ok(true.into()), - b"f" => Ok(false.into()), - _ => Err(RespError::InvalidFrameType(format!( + b"t" => Ok(true), + b"f" => Ok(false), + _ => Err(RespError::InvalidFrame(format!( "expected t or f, found: {}", String::from_utf8_lossy(s) ))), @@ -206,12 +234,12 @@ impl RespDecoder for bool { // Double ",[<+|->][.][[sign]]\r\n" decode to RespDouble impl RespDecoder for RespDouble { const PREFIX: &'static str = ","; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let end = extract_simple_resp(buf, Self::PREFIX)?; let data = buf.split_to(end + 2); let s = String::from_utf8_lossy(&data[Self::PREFIX.len()..end]); let num = s.parse()?; - Ok(RespDouble::new(num).into()) + Ok(RespDouble::new(num)) } fn expect_length(buf: &[u8]) -> Result { @@ -223,7 +251,7 @@ impl RespDecoder for RespDouble { // Map "%\r\n..." decode to RespMap impl RespDecoder for RespMap { const PREFIX: &'static str = "%"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let (end, len) = parse_length(buf, Self::PREFIX)?; let total_len = calc_total_length(buf, end, len, Self::PREFIX)?; @@ -234,14 +262,14 @@ impl RespDecoder for RespMap { buf.advance(end + CRLF_LEN); let mut map = HashMap::with_capacity(len); if len == 0 { - return Ok(RespMap::new(map).into()); + return Ok(RespMap::new(map)); } for _ in 0..len { let key = RespFrame::decode(buf)?; let value = RespFrame::decode(buf)?; map.insert(key, value); } - Ok(RespMap::new(map).into()) + Ok(RespMap::new(map)) } fn expect_length(buf: &[u8]) -> Result { @@ -253,7 +281,7 @@ impl RespDecoder for RespMap { // Set "~\r\n..." decode to RespSet impl RespDecoder for RespSet { const PREFIX: &'static str = "~"; - fn decode(buf: &mut BytesMut) -> Result { + fn decode(buf: &mut BytesMut) -> Result { let (end, len) = parse_length(buf, Self::PREFIX)?; let total_len = calc_total_length(buf, end, len, Self::PREFIX)?; @@ -264,13 +292,13 @@ impl RespDecoder for RespSet { buf.advance(end + CRLF_LEN); let mut set = HashSet::with_capacity(len); if len == 0 { - return Ok(RespSet::new(set).into()); + return Ok(RespSet::new(set)); } for _ in 0..len { let frame = RespFrame::decode(buf)?; set.insert(frame); } - Ok(RespSet::new(set).into()) + Ok(RespSet::new(set)) } fn expect_length(buf: &[u8]) -> Result { @@ -285,7 +313,7 @@ fn extract_simple_resp(buf: &[u8], prefix: &str) -> Result { } if !buf.starts_with(prefix.as_bytes()) { - return Err(RespError::InvalidFrameType(format!( + return Err(RespError::InvalidFrame(format!( "expected start with: {}, found: {:?}", prefix, buf ))); @@ -406,6 +434,17 @@ mod tests { "data: [115, 101, 116, 13, 10]".to_string() )) ); + + let mut buf = BytesMut::from("*3\r\n$6\r\nfoobar\r\n:100\r\n$3\r\nset\r\n"); + let frame = RespArray::decode(&mut buf)?; + assert_eq!( + frame, + RespArray::new(vec![ + RespBulkString::new(b"foobar".to_vec()).into(), + 100i64.into(), + RespBulkString::new(b"set".to_vec()).into() + ]) + ); Ok(()) } @@ -413,7 +452,7 @@ mod tests { fn test_decode_null_array() -> Result<()> { let mut buf = BytesMut::from("*-1\r\n"); let frame = RespFrame::decode(&mut buf)?; - assert_eq!(frame, RespNull.into()); + assert_eq!(frame, RespArray::new(vec![]).into()); Ok(()) } diff --git a/src/resp/mod.rs b/src/resp/mod.rs index 99d13d8..5d7fe78 100644 --- a/src/resp/mod.rs +++ b/src/resp/mod.rs @@ -7,7 +7,7 @@ use std::{ }; use bytes::BytesMut; -use derive_more::{Deref, Display}; +use derive_more::{AsRef, Deref, Display, From}; use enum_dispatch::enum_dispatch; use ordered_float::OrderedFloat; use thiserror::Error; @@ -17,12 +17,6 @@ pub enum RespError { #[error("Invalid frame: {0}")] InvalidFrame(String), - #[error("Invalid frame type: {0}")] - InvalidFrameType(String), - - #[error("Invalid frame length: {0}")] - InvalidFrameLength(String), - #[error("Frame is not complete")] FrameNotComplete, @@ -48,29 +42,30 @@ pub enum RespFrame { Set(RespSet), } -#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash)] -pub struct RespSimpleString(String); +#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash, From)] +pub struct RespSimpleString(pub(crate) String); -#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash)] -pub struct RespSimpleError(String); +#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash, From)] +pub struct RespSimpleError(pub(crate) String); -#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash)] -pub struct RespBulkString(Vec); +#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash, AsRef, From)] +#[from(String, &'static str, &[u8])] +pub struct RespBulkString(pub(crate) Vec); -#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash)] -pub struct RespArray(Vec); +#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash, From)] +pub struct RespArray(pub(crate) Vec); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RespNull; -#[derive(Debug, Clone, Deref, Display, PartialEq, Eq, Hash)] -pub struct RespDouble(OrderedFloat); +#[derive(Debug, Clone, Deref, Display, PartialEq, Eq, Hash, From)] +pub struct RespDouble(pub(crate) OrderedFloat); -#[derive(Debug, Clone, Deref, PartialEq, Eq)] -pub struct RespMap(HashMap); +#[derive(Debug, Clone, Deref, PartialEq, Eq, From)] +pub struct RespMap(pub(crate) HashMap); -#[derive(Debug, Clone, Deref, PartialEq, Eq)] -pub struct RespSet(HashSet); +#[derive(Debug, Clone, Deref, PartialEq, Eq, From)] +pub struct RespSet(pub(crate) HashSet); #[enum_dispatch] pub trait RespEncoder { @@ -79,7 +74,7 @@ pub trait RespEncoder { pub trait RespDecoder: Sized { const PREFIX: &'static str; - fn decode(buf: &mut BytesMut) -> Result; + fn decode(buf: &mut BytesMut) -> Result; fn expect_length(buf: &[u8]) -> Result; }