├── .github └── workflows │ ├── compile_and_test.yml │ └── publish.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── generic_api.rs ├── multiplexer.rs ├── native_api.rs └── pipeline.rs ├── examples ├── actix_crud.rs ├── actix_long_polling_pubsub.rs ├── axum_crud.rs ├── axum_long_polling_pubsub.rs ├── cbor.rs └── simple.rs ├── redis ├── certs │ ├── ca.crt │ ├── redis.crt │ └── redis.key ├── cluster-entrypoint.sh ├── cluster.conf ├── cluster.dockerfile ├── docker-compose.yml ├── docker_down.cmd ├── docker_down.sh ├── docker_up.cmd ├── docker_up.sh ├── sentinel-entrypoint.sh ├── sentinel.conf ├── sentinel.dockerfile ├── set_host_ip.cmd ├── set_host_ip.sh └── standalone.conf ├── run_tests.cmd └── src ├── client ├── client.rs ├── client_state.rs ├── client_tracking_invalidation_stream.rs ├── config.rs ├── message.rs ├── mod.rs ├── monitor_stream.rs ├── pipeline.rs ├── pooled_client_manager.rs ├── prepared_command.rs ├── pub_sub_stream.rs └── transaction.rs ├── commands ├── bitmap_commands.rs ├── blocking_commands.rs ├── bloom_commands.rs ├── cluster_commands.rs ├── connection_commands.rs ├── count_min_sktech_commands.rs ├── cuckoo_commands.rs ├── debug_commands.rs ├── generic_commands.rs ├── geo_commands.rs ├── graph_cache.rs ├── graph_commands.rs ├── graph_value.rs ├── hash_commands.rs ├── hyper_log_log_commands.rs ├── internal_pub_sub_commands.rs ├── json_commands.rs ├── list_commands.rs ├── mod.rs ├── pub_sub_commands.rs ├── scripting_commands.rs ├── search_commands.rs ├── sentinel_commands.rs ├── server_commands.rs ├── set_commands.rs ├── sorted_set_commands.rs ├── stream_commands.rs ├── string_commands.rs ├── t_disgest_commands.rs ├── time_series_commands.rs ├── top_k_commands.rs └── transaction_commands.rs ├── error.rs ├── lib.rs ├── network ├── async_executor_strategy.rs ├── cluster_connection.rs ├── command_info_manager.rs ├── connection.rs ├── mod.rs ├── network_handler.rs ├── reconnection_state.rs ├── sentinel_connection.rs ├── standalone_connection.rs ├── util.rs └── version.rs ├── resp ├── buffer_decoder.rs ├── bulk_string.rs ├── command.rs ├── command_args.rs ├── command_encoder.rs ├── mod.rs ├── resp_batch_deserializer.rs ├── resp_buf.rs ├── resp_deserializer.rs ├── resp_serializer.rs ├── response.rs ├── to_args.rs ├── util.rs ├── value.rs ├── value_deserialize.rs ├── value_deserializer.rs └── value_serialize.rs └── tests ├── bitmap_commands.rs ├── bloom_commands.rs ├── buffer_decoder.rs ├── client.rs ├── cluster.rs ├── cluster_commands.rs ├── command_args.rs ├── command_info_manager.rs ├── config.rs ├── connection_commands.rs ├── count_min_sktech_commands.rs ├── cuckoo_commands.rs ├── debug_commands.rs ├── error.rs ├── from_value.rs ├── generic_commands.rs ├── geo_commands.rs ├── graph_commands.rs ├── hash_commands.rs ├── hyper_log_log_commands.rs ├── json_commands.rs ├── list_commands.rs ├── mod.rs ├── multiplexed_client.rs ├── pipeline.rs ├── pooled_client_manager.rs ├── pub_sub_commands.rs ├── resp3.rs ├── resp_deserializer.rs ├── resp_serializer.rs ├── scripting_commands.rs ├── search_commands.rs ├── sentinel.rs ├── server_commands.rs ├── set_commands.rs ├── sorted_set_commands.rs ├── stream_commands.rs ├── string_commands.rs ├── t_disgest_commands.rs ├── time_series_commands.rs ├── tls.rs ├── top_k_commands.rs ├── transaction.rs ├── util.rs ├── value.rs ├── value_deserialize.rs ├── value_deserializer.rs └── value_serialize.rs /.github/workflows/compile_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | paths: 6 | - "src/**" 7 | - "redis/**" 8 | - ".github/workflows/**" 9 | - "Cargo.toml" 10 | branches: 11 | - "**" 12 | tags-ignore: 13 | - "*.*.*" 14 | pull_request: 15 | paths: 16 | - "src/**" 17 | - ".github/workflows/**" 18 | - "Cargo.toml" 19 | branches: 20 | - "**" 21 | tags-ignore: 22 | - "*.*.*" 23 | 24 | jobs: 25 | check: 26 | name: Check 27 | runs-on: ubuntu-latest 28 | env: 29 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 30 | steps: 31 | - name: Checkout sources 32 | uses: actions/checkout@v4 33 | - name: Run cargo check 34 | run: cargo check 35 | - name: Checking style 36 | run: cargo fmt --all -- --check 37 | 38 | test: 39 | name: Test 40 | runs-on: ubuntu-latest 41 | env: 42 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 43 | steps: 44 | - name: Checkout sources 45 | uses: actions/checkout@v4 46 | - name: Create Redis containers 47 | run: | 48 | cd /home/runner/work/rustis/rustis/redis/ 49 | sh ./docker_up.sh 50 | - name: Run cargo test 51 | run: cargo test --features pool,tokio-tls,redis-stack 52 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish on crates.io 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | env: 11 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v4 15 | - name: Update version 16 | run: | 17 | VERSION=${{github.ref_name}} 18 | sed -i -e 's/^version = .*/version = "'$VERSION'"/' Cargo.toml 19 | - name: Publish crate 20 | run: cargo publish --allow-dirty --token ${{secrets.CRATES_IO_API_TOKEN}} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /.vscode 4 | /redis/.env 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustis" 3 | version = "0.1.0" 4 | keywords = ["redis", "database", "async", "cluster", "sentinel"] 5 | categories = ["database", "asynchronous"] 6 | description = "Redis async driver for Rust" 7 | homepage = "https://github.com/dahomey-technologies/rustis" 8 | repository = "https://github.com/dahomey-technologies/rustis" 9 | documentation = "https://docs.rs/rustis" 10 | readme = "README.md" 11 | license-file = "LICENSE" 12 | edition = "2021" 13 | 14 | [features] 15 | default = ["tokio-runtime"] 16 | tokio-runtime = [ 17 | "tokio/macros", 18 | "tokio/net", 19 | "tokio/rt", 20 | "tokio/io-util", 21 | ] 22 | tokio-tls = [ 23 | "tokio-native-tls", 24 | "tls" 25 | ] 26 | async-std-runtime = [ 27 | "async-std", 28 | "async-std/attributes", 29 | "tokio-util/compat", 30 | "async-native-tls", 31 | ] 32 | async-std-tls = [ 33 | "async-native-tls", 34 | "tls" 35 | ] 36 | pool = ["bb8"] 37 | tls = ["native-tls"] 38 | redis-json = [] 39 | redis-search = [] 40 | redis-graph = [] 41 | redis-bloom = [] 42 | redis-time-series = [] 43 | redis-stack = [ 44 | "redis-json", 45 | "redis-search", 46 | "redis-graph", 47 | "redis-bloom", 48 | "redis-time-series" 49 | ] 50 | 51 | [dependencies] 52 | async-std = { version = "1.13", features = ["attributes"], optional = true } 53 | futures-util = { version = "0.3", features = ["sink"] } 54 | futures-channel = { version = "0.3", features = ["sink"] } 55 | bytes = "1.10" 56 | tokio = { version = "1.44", features = ["time", "io-util", "sync"] } 57 | tokio-util = { version = "0.7", features = ["codec"] } 58 | atoi = "2.0" 59 | itoa = "1.0" 60 | fast-float = "0.2" 61 | dtoa = "1.0" 62 | smallvec = { version = "1.14", features = ["union", "serde"] } 63 | bb8 = { version = "0.9", optional = true } 64 | url = "2.5" 65 | native-tls = { version = "0.2", optional = true } 66 | tokio-native-tls = { version = "0.3", optional = true } 67 | async-native-tls = { version = "0.5", optional = true } 68 | log = "0.4" 69 | crc16 = "0.4" 70 | rand = "0.9" 71 | serde = { version = "1.0", features = ["derive"] } 72 | socket2 = "0.5" 73 | memchr = "2.7" 74 | 75 | [dev-dependencies] 76 | serial_test = "3.2" 77 | tokio = { version = "1.44", features = ["rt-multi-thread"] } 78 | rand = "0.9" 79 | env_logger = "0.11" 80 | smallvec = { version = "1.14", features = ["serde"] } 81 | criterion = "0.5" 82 | redis = { version = "0.29", features = ["aio", "tokio-comp"] } 83 | fred = "10.1" 84 | axum = "0.8" 85 | actix-web = "4.10" 86 | 87 | [package.metadata.docs.rs] 88 | features = ["tokio-runtime", "tokio-tls", "redis-stack", "pool"] 89 | rustdoc-args = ["--cfg", "docsrs"] 90 | 91 | [[bench]] 92 | name = "generic_api" 93 | harness = false 94 | 95 | [[bench]] 96 | name = "native_api" 97 | harness = false 98 | 99 | [[bench]] 100 | name = "pipeline" 101 | harness = false 102 | 103 | [[bench]] 104 | name = "multiplexer" 105 | harness = false 106 | 107 | [[example]] 108 | name = "simple" 109 | 110 | [[example]] 111 | name = "cbor" 112 | 113 | [[example]] 114 | name = "axum_crud" 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dahomey Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An asynchronous Redis client for Rust. 2 | 3 | [![Crate](https://img.shields.io/crates/v/rustis.svg)](https://crates.io/crates/rustis) 4 | [![docs.rs](https://docs.rs/rustis/badge.svg)](https://docs.rs/rustis) 5 | [![Build](https://github.com/dahomey-technologies/rustis/actions/workflows/compile_and_test.yml/badge.svg)](https://github.com/dahomey-technologies/rustis/actions/workflows/compile_and_test.yml) 6 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 7 | [![libs.tech recommends](https://libs.tech/project/530004740/badge.svg)](https://libs.tech/project/530004740/rustis) 8 | 9 | # Documentation 10 | 11 | [Official Documentation](https://docs.rs/rustis/latest/rustis/) 12 | 13 | # Philosophy 14 | 15 | * Low allocations 16 | * Full async library 17 | * Lock free implementation 18 | * Rust idiomatic API 19 | * Multiplexing as a core feature 20 | 21 | # Features 22 | 23 | * Full documentation with multiple examples 24 | * Support all [Redis Commands](https://redis.io/commands/) until Redis 7.0 25 | * Async support ([tokio](https://tokio.rs/) or [async-std](https://async.rs/)) 26 | * Different client modes: 27 | * Single client 28 | * [Multiplexed](https://redis.com/blog/multiplexing-explained/) client 29 | * Pooled client manager (based on [bb8](https://docs.rs/bb8/latest/bb8/)) 30 | * Automatic command batching 31 | * Advanced reconnection & retry strategy 32 | * [Pipelining](https://redis.io/docs/manual/pipelining/) support 33 | * Configuration with Redis URL or dedicated builder 34 | * [TLS](https://redis.io/docs/manual/security/encryption/) support 35 | * [Transaction](https://redis.io/docs/manual/transactions/) support 36 | * [Pub/sub](https://redis.io/docs/manual/pubsub/) support 37 | * [Sentinel](https://redis.io/docs/manual/sentinel/) support 38 | * [LUA Scripts/Functions](https://redis.io/docs/manual/programmability/) support 39 | * [Cluster](https://redis.io/docs/manual/scaling/) support (minimus supported Redis version is 6) 40 | * [Redis Stack](https://redis.io/docs/stack/) support: 41 | * [RedisJSON v2.4](https://redis.io/docs/stack/json/) support 42 | * [RedisSearch v2.6](https://redis.io/docs/stack/search/) support 43 | * [RedisGraph v2.10](https://redis.io/docs/stack/graph/) support 44 | * [RedisBloom v2.4](https://redis.io/docs/stack/bloom/) support 45 | * [RedisTimeSeries v1.8](https://redis.io/docs/stack/timeseries/) support 46 | 47 | # Basic Usage 48 | 49 | ```rust 50 | use rustis::{ 51 | client::Client, 52 | commands::{FlushingMode, ServerCommands, StringCommands}, 53 | Result, 54 | }; 55 | 56 | #[tokio::main] 57 | async fn main() -> Result<()> { 58 | // Connect the client to a Redis server from its IP and port 59 | let client = Client::connect("127.0.0.1:6379").await?; 60 | 61 | // Flush all existing data in Redis 62 | client.flushdb(FlushingMode::Sync).await?; 63 | 64 | // sends the command SET to Redis. This command is defined in the StringCommands trait 65 | client.set("key", "value").await?; 66 | 67 | // sends the command GET to Redis. This command is defined in the StringCommands trait 68 | let value: String = client.get("key").await?; 69 | println!("value: {value:?}"); 70 | 71 | Ok(()) 72 | } 73 | ``` 74 | 75 | # Tests 76 | 77 | 1. From the `redis` directory, run `docker_up.sh` or `docker_up.cmd` 78 | 2. run `cargo test --features pool,redis-stack,tokio-tls` (Tokio runtime) 79 | 3. run `cargo test --no-default-features --features redis-stack,async-std-runtime,async-std-tls` (async-std runtime) 80 | 4. run `cargo fmt --all -- --check` 81 | 82 | # Benchmarks 83 | 84 | 1. From the `redis` directory, run `docker_up.sh` or `docker_up.cmd` 85 | 2. run `cargo bench` 86 | -------------------------------------------------------------------------------- /benches/generic_api.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Bencher, Criterion}; 2 | use futures_util::Future; 3 | use std::time::Duration; 4 | 5 | pub fn current_thread_runtime() -> tokio::runtime::Runtime { 6 | let mut builder = tokio::runtime::Builder::new_current_thread(); 7 | builder.enable_io(); 8 | builder.enable_time(); 9 | builder.build().unwrap() 10 | } 11 | 12 | pub fn block_on_all(f: F) -> F::Output 13 | where 14 | F: Future, 15 | { 16 | current_thread_runtime().block_on(f) 17 | } 18 | 19 | fn get_redis_client() -> redis::Client { 20 | redis::Client::open("redis://127.0.0.1:6379").unwrap() 21 | } 22 | 23 | async fn get_fred_client() -> fred::clients::Client { 24 | use fred::prelude::*; 25 | 26 | let config = Config::default(); 27 | let client = Client::new(config, None, None, None); 28 | client.connect(); 29 | client.wait_for_connect().await.unwrap(); 30 | 31 | client 32 | } 33 | 34 | async fn get_rustis_client() -> rustis::client::Client { 35 | rustis::client::Client::connect("127.0.0.1:6379") 36 | .await 37 | .unwrap() 38 | } 39 | 40 | fn bench_redis_simple_getsetdel_async(b: &mut Bencher) { 41 | use redis::{cmd, RedisError}; 42 | 43 | let client = get_redis_client(); 44 | let runtime = current_thread_runtime(); 45 | let con = client.get_multiplexed_async_connection(); 46 | let mut con = runtime.block_on(con).unwrap(); 47 | 48 | b.iter(|| { 49 | runtime 50 | .block_on(async { 51 | let key = "test_key"; 52 | cmd("SET") 53 | .arg(key) 54 | .arg(42.423456) 55 | .query_async::<()>(&mut con) 56 | .await?; 57 | let _: f64 = cmd("GET").arg(key).query_async(&mut con).await?; 58 | cmd("DEL").arg(key).query_async::(&mut con).await?; 59 | Ok::<_, RedisError>(()) 60 | }) 61 | .unwrap() 62 | }); 63 | } 64 | 65 | fn bench_fred_simple_getsetdel_async(b: &mut Bencher) { 66 | use fred::prelude::*; 67 | use fred::types::CustomCommand; 68 | 69 | let runtime = current_thread_runtime(); 70 | let client = runtime.block_on(get_fred_client()); 71 | 72 | b.iter(|| { 73 | runtime 74 | .block_on(async { 75 | let key = "test_key"; 76 | 77 | let args: Vec = vec![key.into(), 42.423456.into()]; 78 | client 79 | .custom::<(), _>(CustomCommand::new_static("SET", None, false), args) 80 | .await?; 81 | 82 | let args: Vec = vec![key.into()]; 83 | client 84 | .custom::(CustomCommand::new_static("GET", None, false), args) 85 | .await?; 86 | 87 | let args: Vec = vec![key.into()]; 88 | client 89 | .custom::(CustomCommand::new_static("DEL", None, false), args) 90 | .await?; 91 | 92 | Ok::<_, Error>(()) 93 | }) 94 | .unwrap() 95 | }); 96 | } 97 | 98 | fn bench_rustis_simple_getsetdel_async(b: &mut Bencher) { 99 | use rustis::{resp::cmd, Error}; 100 | 101 | let runtime = current_thread_runtime(); 102 | 103 | let client = runtime.block_on(get_rustis_client()); 104 | 105 | b.iter(|| { 106 | runtime 107 | .block_on(async { 108 | let key = "test_key"; 109 | 110 | client 111 | .send(cmd("SET").arg(key).arg(42.423456), None) 112 | .await?; 113 | let _: f64 = client.send(cmd("GET").arg(key), None).await?.to()?; 114 | client.send(cmd("DEL").arg(key), None).await?; 115 | 116 | Ok::<_, Error>(()) 117 | }) 118 | .unwrap() 119 | }); 120 | } 121 | 122 | fn bench_generic_api(c: &mut Criterion) { 123 | let mut group = c.benchmark_group("generic_api"); 124 | group 125 | .measurement_time(Duration::from_secs(10)) 126 | .bench_function( 127 | "redis_simple_getsetdel_async", 128 | bench_redis_simple_getsetdel_async, 129 | ) 130 | .bench_function( 131 | "fred_simple_getsetdel_async", 132 | bench_fred_simple_getsetdel_async, 133 | ) 134 | .bench_function( 135 | "rustis_simple_getsetdel_async", 136 | bench_rustis_simple_getsetdel_async, 137 | ); 138 | group.finish(); 139 | } 140 | 141 | criterion_group!(bench, bench_generic_api); 142 | criterion_main!(bench); 143 | -------------------------------------------------------------------------------- /benches/multiplexer.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Bencher, Criterion}; 2 | use futures_util::Future; 3 | use std::time::Duration; 4 | 5 | pub fn current_thread_runtime() -> tokio::runtime::Runtime { 6 | let mut builder = tokio::runtime::Builder::new_current_thread(); 7 | builder.enable_io(); 8 | builder.enable_time(); 9 | builder.build().unwrap() 10 | } 11 | 12 | pub fn block_on_all(f: F) -> F::Output 13 | where 14 | F: Future, 15 | { 16 | current_thread_runtime().block_on(f) 17 | } 18 | 19 | fn get_redis_client() -> redis::Client { 20 | redis::Client::open("redis://127.0.0.1:6379").unwrap() 21 | } 22 | 23 | async fn get_rustis_client() -> rustis::client::Client { 24 | rustis::client::Client::connect("127.0.0.1:6379") 25 | .await 26 | .unwrap() 27 | } 28 | 29 | async fn get_fred_client() -> fred::clients::Client { 30 | use fred::prelude::*; 31 | 32 | let config = Config::default(); 33 | let client = Client::new(config, None, None, None); 34 | client.connect(); 35 | client.wait_for_connect().await.unwrap(); 36 | 37 | client 38 | } 39 | 40 | const PARALLEL_QUERIES: usize = 8; 41 | const ITERATIONS: usize = 100; 42 | 43 | fn bench_redis_parallel(b: &mut Bencher) { 44 | use redis::{AsyncCommands, RedisError}; 45 | 46 | let client = get_redis_client(); 47 | let runtime = current_thread_runtime(); 48 | let con = runtime 49 | .block_on(client.get_multiplexed_tokio_connection()) 50 | .unwrap(); 51 | 52 | b.iter(|| { 53 | runtime.block_on(async { 54 | let tasks: Vec<_> = (0..PARALLEL_QUERIES) 55 | .map(|i| { 56 | let mut con = con.clone(); 57 | tokio::spawn(async move { 58 | for _ in 0..ITERATIONS { 59 | let key = format!("key{i}"); 60 | let value = format!("value{i}"); 61 | let _: Result<(), RedisError> = con.set(key, value).await; 62 | } 63 | }) 64 | }) 65 | .collect(); 66 | 67 | futures_util::future::join_all(tasks).await; 68 | }) 69 | }); 70 | } 71 | 72 | fn bench_fred_parallel(b: &mut Bencher) { 73 | use fred::prelude::*; 74 | 75 | let runtime = current_thread_runtime(); 76 | let client = runtime.block_on(get_fred_client()); 77 | 78 | b.iter(|| { 79 | runtime.block_on(async { 80 | let tasks: Vec<_> = (0..PARALLEL_QUERIES) 81 | .map(|i| { 82 | let client = client.clone(); 83 | tokio::spawn(async move { 84 | for _ in 0..ITERATIONS { 85 | let key = format!("key{i}"); 86 | let value = format!("value{i}"); 87 | let _: Result<(), Error> = 88 | client.set(key, value, None, None, false).await; 89 | } 90 | }) 91 | }) 92 | .collect(); 93 | 94 | futures_util::future::join_all(tasks).await; 95 | }) 96 | }); 97 | } 98 | 99 | fn bench_rustis_parallel(b: &mut Bencher) { 100 | use rustis::commands::StringCommands; 101 | 102 | let runtime = current_thread_runtime(); 103 | 104 | let client = runtime.block_on(get_rustis_client()); 105 | 106 | b.iter(|| { 107 | runtime.block_on(async { 108 | let tasks: Vec<_> = (0..PARALLEL_QUERIES) 109 | .map(|i| { 110 | let client = client.clone(); 111 | tokio::spawn(async move { 112 | for _ in 0..ITERATIONS { 113 | let key = format!("key{i}"); 114 | let value = format!("value{i}"); 115 | let _ = client.set(key, value).await; 116 | } 117 | }) 118 | }) 119 | .collect(); 120 | 121 | futures_util::future::join_all(tasks).await; 122 | }) 123 | }); 124 | } 125 | 126 | fn bench_parallel(c: &mut Criterion) { 127 | let mut group = c.benchmark_group("parallel"); 128 | group 129 | .measurement_time(Duration::from_secs(15)) 130 | .bench_function("redis_parallel", bench_redis_parallel) 131 | .bench_function("fred_parallel", bench_fred_parallel) 132 | .bench_function("rustis_parallel", bench_rustis_parallel); 133 | group.finish(); 134 | } 135 | 136 | criterion_group!(bench, bench_parallel); 137 | criterion_main!(bench); 138 | -------------------------------------------------------------------------------- /benches/native_api.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Bencher, Criterion}; 2 | use futures_util::Future; 3 | use std::time::Duration; 4 | 5 | pub fn current_thread_runtime() -> tokio::runtime::Runtime { 6 | let mut builder = tokio::runtime::Builder::new_current_thread(); 7 | builder.enable_io(); 8 | builder.enable_time(); 9 | builder.build().unwrap() 10 | } 11 | 12 | pub fn block_on_all(f: F) -> F::Output 13 | where 14 | F: Future, 15 | { 16 | current_thread_runtime().block_on(f) 17 | } 18 | 19 | fn get_redis_client() -> redis::Client { 20 | redis::Client::open("redis://127.0.0.1:6379").unwrap() 21 | } 22 | 23 | async fn get_rustis_client() -> rustis::client::Client { 24 | rustis::client::Client::connect("127.0.0.1:6379") 25 | .await 26 | .unwrap() 27 | } 28 | 29 | async fn get_fred_client() -> fred::clients::Client { 30 | use fred::prelude::*; 31 | 32 | let config = Config::default(); 33 | let client = Client::new(config, None, None, None); 34 | client.connect(); 35 | client.wait_for_connect().await.unwrap(); 36 | 37 | client 38 | } 39 | 40 | fn bench_redis_simple_getsetdel_async(b: &mut Bencher) { 41 | use redis::{AsyncCommands, RedisError}; 42 | 43 | let client = get_redis_client(); 44 | let runtime = current_thread_runtime(); 45 | let con = client.get_multiplexed_async_connection(); 46 | let mut con = runtime.block_on(con).unwrap(); 47 | 48 | b.iter(|| { 49 | runtime 50 | .block_on(async { 51 | let key = "test_key"; 52 | con.set::<_, _, ()>(key, 42.423456).await?; 53 | let _: f64 = con.get(key).await?; 54 | con.del::<_, usize>(key).await?; 55 | Ok::<_, RedisError>(()) 56 | }) 57 | .unwrap() 58 | }); 59 | } 60 | 61 | fn bench_fred_simple_getsetdel_async(b: &mut Bencher) { 62 | use fred::prelude::*; 63 | 64 | let runtime = current_thread_runtime(); 65 | let client = runtime.block_on(get_fred_client()); 66 | 67 | b.iter(|| { 68 | runtime 69 | .block_on(async { 70 | let key = "test_key"; 71 | client 72 | .set::<(), _, _>(key, 42.423456, None, None, false) 73 | .await?; 74 | let _: f64 = client.get(key).await?; 75 | client.del::(key).await?; 76 | 77 | Ok::<_, Error>(()) 78 | }) 79 | .unwrap() 80 | }); 81 | } 82 | 83 | fn bench_rustis_simple_getsetdel_async(b: &mut Bencher) { 84 | use rustis::{ 85 | commands::{GenericCommands, StringCommands}, 86 | Error, 87 | }; 88 | 89 | let runtime = current_thread_runtime(); 90 | 91 | let client = runtime.block_on(get_rustis_client()); 92 | 93 | b.iter(|| { 94 | runtime 95 | .block_on(async { 96 | let key = "test_key"; 97 | client.set(key, 42.423456).await?; 98 | let _: f64 = client.get(key).await?; 99 | client.del(key).await?; 100 | 101 | Ok::<_, Error>(()) 102 | }) 103 | .unwrap() 104 | }); 105 | } 106 | 107 | fn bench_generic_api(c: &mut Criterion) { 108 | let mut group = c.benchmark_group("native_api"); 109 | group 110 | .measurement_time(Duration::from_secs(10)) 111 | .bench_function( 112 | "redis_simple_getsetdel_async", 113 | bench_redis_simple_getsetdel_async, 114 | ) 115 | .bench_function( 116 | "fred_simple_getsetdel_async", 117 | bench_fred_simple_getsetdel_async, 118 | ) 119 | .bench_function( 120 | "rustis_simple_getsetdel_async", 121 | bench_rustis_simple_getsetdel_async, 122 | ); 123 | group.finish(); 124 | } 125 | 126 | criterion_group!(bench, bench_generic_api); 127 | criterion_main!(bench); 128 | -------------------------------------------------------------------------------- /examples/actix_crud.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{ 2 | delete, get, http::StatusCode, post, web, App, HttpResponse, HttpServer, Responder, 3 | }; 4 | use rustis::{ 5 | client::Client, 6 | commands::{GenericCommands, StringCommands}, 7 | }; 8 | use std::{ 9 | fmt::{self, Display}, 10 | net::SocketAddr, 11 | }; 12 | 13 | #[tokio::main] 14 | async fn main() -> std::io::Result<()> { 15 | // build rustis client in multiplexer mode (a unique rustis instance for all actix workers) 16 | let redis = web::Data::new(Client::connect("redis://127.0.0.1:6379").await.unwrap()); 17 | 18 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 19 | println!("listening on {}", addr); 20 | HttpServer::new(move || { 21 | App::new() 22 | .app_data(redis.clone()) 23 | .service(read) 24 | .service(update) 25 | .service(delete) 26 | }) 27 | .bind(addr)? 28 | .run() 29 | .await 30 | } 31 | 32 | #[get("/{key}")] 33 | async fn read(redis: web::Data, key: web::Path) -> Result { 34 | let key = key.into_inner(); 35 | let value: Option = redis.get(&key).await?; 36 | value.ok_or_else(|| { 37 | ServiceError::new( 38 | StatusCode::NOT_FOUND, 39 | format!("Key `{key}` does not exist."), 40 | ) 41 | }) 42 | } 43 | 44 | #[post("/{key}")] 45 | async fn update( 46 | redis: web::Data, 47 | key: web::Path, 48 | value: Option, 49 | ) -> Result { 50 | let Some(value) = value else { 51 | return Err(ServiceError::new( 52 | StatusCode::BAD_REQUEST, 53 | "Value not provided", 54 | )); 55 | }; 56 | redis.set(key.into_inner(), value).await?; 57 | Ok(HttpResponse::Ok()) 58 | } 59 | 60 | #[delete("/{key}")] 61 | async fn delete( 62 | redis: web::Data, 63 | key: web::Path, 64 | ) -> Result { 65 | let key = key.into_inner(); 66 | let deleted = redis.del(&key).await?; 67 | if deleted > 0 { 68 | Ok(HttpResponse::Ok()) 69 | } else { 70 | Err(ServiceError::new( 71 | StatusCode::NOT_FOUND, 72 | format!("Key `{key}` does not exist."), 73 | )) 74 | } 75 | } 76 | 77 | #[derive(Debug)] 78 | struct ServiceError(StatusCode, String); 79 | 80 | impl ServiceError { 81 | fn new(status_code: StatusCode, description: impl ToString) -> Self { 82 | Self(status_code, description.to_string()) 83 | } 84 | } 85 | 86 | impl Display for ServiceError { 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | f.write_str(&self.1) 89 | } 90 | } 91 | 92 | impl actix_web::error::ResponseError for ServiceError { 93 | fn status_code(&self) -> actix_web::http::StatusCode { 94 | self.0 95 | } 96 | } 97 | 98 | impl From for ServiceError { 99 | fn from(e: rustis::Error) -> Self { 100 | eprintln!("rustis error: {e}"); 101 | ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/actix_long_polling_pubsub.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, http::StatusCode, post, web, App, HttpResponse, HttpServer, Responder}; 2 | use futures_util::StreamExt; 3 | use rustis::{ 4 | client::Client, 5 | commands::{ListCommands, PubSubCommands}, 6 | }; 7 | use std::{fmt, net::SocketAddr, time::Duration}; 8 | 9 | const POLL_TIMEOUT: Duration = Duration::from_secs(10); 10 | 11 | pub struct RedisClients { 12 | /// Redis client for regular operations 13 | pub regular: Client, 14 | /// Redis client for subscriptions 15 | pub sub: Client, 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> std::io::Result<()> { 20 | // build rustis client in multiplexer mode (a unique rustis instance for all actix workers) 21 | // build a separated rustis client for subscriptions only in multiplexer mode (a unique rustis instance for all actix workers) 22 | let redis_uri = "redis://127.0.0.1:6379"; 23 | let redis_clients = web::Data::new(RedisClients { 24 | regular: Client::connect(redis_uri).await.unwrap(), 25 | sub: Client::connect(redis_uri).await.unwrap(), 26 | }); 27 | 28 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 29 | println!("listening on {}", addr); 30 | HttpServer::new(move || { 31 | App::new() 32 | .app_data(redis_clients.clone()) 33 | .service(poll_messages) 34 | .service(publish) 35 | }) 36 | .bind(addr)? 37 | .run() 38 | .await 39 | } 40 | 41 | #[get("/{channel}")] 42 | async fn poll_messages( 43 | redis: web::Data, 44 | channel: web::Path, 45 | ) -> Result { 46 | let channel = channel.into_inner(); 47 | 48 | let messages = get_messages_from_queue(&redis.regular, &channel).await?; 49 | if POLL_TIMEOUT.is_zero() || !messages.is_empty() { 50 | return Ok(web::Json(messages)); 51 | } 52 | 53 | let mut sub_stream = redis.sub.subscribe(&channel).await?; 54 | let msg = tokio::time::timeout(POLL_TIMEOUT, sub_stream.next()).await; 55 | 56 | let messages: Vec = match msg { 57 | Ok(Some(Ok(_msg))) => get_messages_from_queue(&redis.regular, &channel).await?, 58 | Ok(Some(Err(e))) => { 59 | return Err(ServiceError::new( 60 | StatusCode::INTERNAL_SERVER_ERROR, 61 | format!("error received from PubSubStream: {e}"), 62 | )) 63 | } 64 | // stream closed 65 | Ok(None) => Vec::new(), 66 | // timeout 67 | Err(_e) => Vec::new(), 68 | }; 69 | 70 | Ok(web::Json(messages)) 71 | } 72 | 73 | async fn get_messages_from_queue( 74 | redis: &Client, 75 | channel: &str, 76 | ) -> Result, ServiceError> { 77 | Ok(redis.lpop(channel, i32::MAX as usize).await?) 78 | } 79 | 80 | #[post("/{channel}")] 81 | async fn publish( 82 | redis: web::Data, 83 | channel: web::Path, 84 | message: Option, 85 | ) -> Result { 86 | let Some(message) = message else { 87 | return Err(ServiceError::new( 88 | StatusCode::BAD_REQUEST, 89 | "Message not provided", 90 | )); 91 | }; 92 | 93 | let channel = channel.into_inner(); 94 | 95 | // data is not sent via pub/sub; the pub/sub API is used only to notify subscriber to check for new notifications 96 | // the actual data is pushed into a list used as a queue 97 | redis.regular.lpush(&channel, &message).await?; 98 | redis.regular.publish(&channel, "new").await?; 99 | Ok(HttpResponse::Ok()) 100 | } 101 | 102 | #[derive(Debug)] 103 | struct ServiceError(StatusCode, String); 104 | 105 | impl ServiceError { 106 | fn new(status_code: StatusCode, description: impl ToString) -> Self { 107 | Self(status_code, description.to_string()) 108 | } 109 | } 110 | 111 | impl fmt::Display for ServiceError { 112 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 113 | f.write_str(&self.1) 114 | } 115 | } 116 | 117 | impl actix_web::error::ResponseError for ServiceError { 118 | fn status_code(&self) -> actix_web::http::StatusCode { 119 | self.0 120 | } 121 | } 122 | 123 | impl From for ServiceError { 124 | fn from(e: rustis::Error) -> Self { 125 | eprintln!("rustis error: {e}"); 126 | ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/axum_crud.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | extract::{Path, State}, 3 | http::StatusCode, 4 | response::{IntoResponse, Response}, 5 | routing::get, 6 | Router, 7 | }; 8 | use rustis::{ 9 | client::Client, 10 | commands::{GenericCommands, StringCommands}, 11 | }; 12 | use std::{net::SocketAddr, sync::Arc}; 13 | use tokio::net::TcpListener; 14 | 15 | #[tokio::main] 16 | async fn main() { 17 | // build rustis client in multiplexer mode (a unique rustis instance for all axum workers) 18 | let redis = Arc::new(Client::connect("redis://127.0.0.1:6379").await.unwrap()); 19 | 20 | // build our application with a route 21 | let app = Router::new() 22 | .route("/:key", get(read).post(update).delete(del)) 23 | .with_state(redis); 24 | 25 | // run it 26 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 27 | println!("listening on {}", addr); 28 | let listener = TcpListener::bind(&addr).await.unwrap(); 29 | axum::serve(listener, app).await.unwrap(); 30 | } 31 | 32 | async fn read( 33 | State(redis): State>, 34 | Path(key): Path, 35 | ) -> Result { 36 | let value: Option = redis.get(&key).await?; 37 | value.ok_or_else(|| { 38 | ServiceError::new( 39 | StatusCode::NOT_FOUND, 40 | format!("Key `{key}` does not exist."), 41 | ) 42 | }) 43 | } 44 | 45 | async fn update( 46 | State(redis): State>, 47 | Path(key): Path, 48 | value: String, 49 | ) -> Result<(), ServiceError> { 50 | if value.is_empty() { 51 | return Err(ServiceError::new( 52 | StatusCode::BAD_REQUEST, 53 | "Value not provided", 54 | )); 55 | } 56 | redis.set(key, value).await?; 57 | Ok(()) 58 | } 59 | 60 | async fn del( 61 | State(redis): State>, 62 | Path(key): Path, 63 | ) -> Result<(), ServiceError> { 64 | let deleted = redis.del(&key).await?; 65 | if deleted > 0 { 66 | Ok(()) 67 | } else { 68 | Err(ServiceError::new( 69 | StatusCode::NOT_FOUND, 70 | format!("Key `{key}` does not exist."), 71 | )) 72 | } 73 | } 74 | 75 | struct ServiceError(StatusCode, String); 76 | 77 | impl ServiceError { 78 | fn new(status_code: StatusCode, description: impl ToString) -> Self { 79 | Self(status_code, description.to_string()) 80 | } 81 | } 82 | 83 | impl IntoResponse for ServiceError { 84 | fn into_response(self) -> Response { 85 | (self.0, self.1).into_response() 86 | } 87 | } 88 | 89 | impl From for ServiceError { 90 | fn from(e: rustis::Error) -> Self { 91 | eprintln!("rustis error: {e}"); 92 | ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/axum_long_polling_pubsub.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | extract::{Path, State}, 3 | http::StatusCode, 4 | response::{IntoResponse, Response}, 5 | routing::get, 6 | Json, Router, 7 | }; 8 | use futures_util::StreamExt; 9 | use rustis::{ 10 | client::Client, 11 | commands::{ListCommands, PubSubCommands}, 12 | }; 13 | use std::{net::SocketAddr, sync::Arc, time::Duration}; 14 | use tokio::net::TcpListener; 15 | 16 | const POLL_TIMEOUT: Duration = Duration::from_secs(10); 17 | 18 | pub struct RedisClients { 19 | /// Redis client for regular operations 20 | pub regular: Client, 21 | /// Redis client for subscriptions 22 | pub sub: Client, 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() { 27 | // build rustis client in multiplexer mode (a unique rustis instance for all actix workers) 28 | // build a separated rustis client for subscriptions only in multiplexer mode (a unique rustis instance for all actix workers) 29 | let redis_uri = "redis://127.0.0.1:6379"; 30 | let redis_clients = Arc::new(RedisClients { 31 | regular: Client::connect(redis_uri).await.unwrap(), 32 | sub: Client::connect(redis_uri).await.unwrap(), 33 | }); 34 | 35 | // build our application with a route 36 | let app = Router::new() 37 | .route("/:key", get(poll_messages).post(publish)) 38 | .with_state(redis_clients); 39 | 40 | // run it 41 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 42 | println!("listening on {}", addr); 43 | let listener = TcpListener::bind(&addr).await.unwrap(); 44 | axum::serve(listener, app).await.unwrap(); 45 | } 46 | 47 | async fn poll_messages( 48 | State(redis): State>, 49 | Path(channel): Path, 50 | ) -> Result>, ServiceError> { 51 | let messages = get_messages_from_queue(&redis.regular, &channel).await?; 52 | if POLL_TIMEOUT.is_zero() || !messages.is_empty() { 53 | return Ok(Json(messages)); 54 | } 55 | 56 | let mut sub_stream = redis.sub.subscribe(&channel).await?; 57 | let msg = tokio::time::timeout(POLL_TIMEOUT, sub_stream.next()).await; 58 | 59 | let messages: Vec = match msg { 60 | Ok(Some(Ok(_msg))) => get_messages_from_queue(&redis.regular, &channel).await?, 61 | Ok(Some(Err(e))) => { 62 | return Err(ServiceError::new( 63 | StatusCode::INTERNAL_SERVER_ERROR, 64 | format!("error received from PubSubStream: {e}"), 65 | )) 66 | } 67 | // stream closed 68 | Ok(None) => Vec::new(), 69 | // timeout 70 | Err(_e) => Vec::new(), 71 | }; 72 | 73 | Ok(Json(messages)) 74 | } 75 | 76 | async fn get_messages_from_queue( 77 | redis: &Client, 78 | channel: &str, 79 | ) -> Result, ServiceError> { 80 | Ok(redis.lpop(channel, i32::MAX as usize).await?) 81 | } 82 | 83 | async fn publish( 84 | State(redis): State>, 85 | Path(channel): Path, 86 | message: String, 87 | ) -> Result<(), ServiceError> { 88 | if message.is_empty() { 89 | return Err(ServiceError::new( 90 | StatusCode::BAD_REQUEST, 91 | "Message not provided", 92 | )); 93 | }; 94 | 95 | // data is not sent via pub/sub; the pub/sub API is used only to notify subscriber to check for new notifications 96 | // the actual data is pushed into a list used as a queue 97 | redis.regular.lpush(&channel, &message).await?; 98 | redis.regular.publish(&channel, "new").await?; 99 | Ok(()) 100 | } 101 | 102 | struct ServiceError(StatusCode, String); 103 | 104 | impl ServiceError { 105 | fn new(status_code: StatusCode, description: impl ToString) -> Self { 106 | Self(status_code, description.to_string()) 107 | } 108 | } 109 | 110 | impl IntoResponse for ServiceError { 111 | fn into_response(self) -> Response { 112 | (self.0, self.1).into_response() 113 | } 114 | } 115 | 116 | impl From for ServiceError { 117 | fn from(e: rustis::Error) -> Self { 118 | eprintln!("rustis error: {e}"); 119 | ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error") 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /examples/cbor.rs: -------------------------------------------------------------------------------- 1 | use rustis::{ 2 | client::Client, 3 | commands::{GenericCommands, StringCommands}, 4 | resp::BulkString, 5 | Result, 6 | }; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<()> { 10 | let client = Client::connect("127.0.0.1:6379").await?; 11 | 12 | let key = "test_key"; 13 | // {"foo": "bar"} in CBOR 14 | let cbor_value = b"\xa1\x63\x66\x6F\x6F\x63\x62\x61\x72"; 15 | 16 | client.set(key, cbor_value).await?; 17 | let value: BulkString = client.get(key).await?; 18 | assert_eq!(cbor_value, value.as_bytes()); 19 | client.del(key).await?; 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use rustis::{ 2 | client::Client, 3 | commands::{GenericCommands, StringCommands}, 4 | Result, 5 | }; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | let client = Client::connect("127.0.0.1:6379").await?; 10 | 11 | for _i in 0..1000 { 12 | let key = "test_key"; 13 | client.set(key, 42.423456).await?; 14 | let _: f64 = client.get(key).await?; 15 | client.del(key).await?; 16 | } 17 | 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /redis/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFSzCCAzOgAwIBAgIULTp8cWRl326SijHSTdHpP0y/SkAwDQYJKoZIhvcNAQEL 3 | BQAwNTETMBEGA1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUg 4 | QXV0aG9yaXR5MB4XDTIyMTAwMTA4MjMyM1oXDTMyMDkyODA4MjMyM1owNTETMBEG 5 | A1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5 6 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApWm0kFW03v2s58VX3FI/ 7 | gdIo0l+aEdYXoSaic9xPWl5MV5+T7YQ7V9tck6whi4ceUtDAKa8QZBoiJ9gn/Lbr 8 | e6ebZiJ7blBscEqKzXZk5URlHbxXlbfldHScnKNxluI5ApJ0sYov58R60klJNWeK 9 | Wlz+Hn2ubN1IkXuClMJ59i0UZ+MlALpXzpSiW1gS7pT4gIkuCQfdWWwUNDNuFN57 10 | /l9fU0VYQd/7AI9eJnV9ltTOaVyiL/uO3mueWBmM+AeeGaX0WRtctRcR2sNqDMIx 11 | JW/bUziTRncI+HEGQd7Wf1+yi4fjajUzLj8omVvO7RBrCyq5RV9dpMEAgY4LIM8w 12 | g+VlLDbOT52CY90ADbTfMDgiH5mg2Zt3l4xNiwno/Itkng93Hh/AbPomOGtJSYPr 13 | CIrzhkfD6PXMXYzdXAJLzjCc5sOIBrFhDSIzkGOAxX0DZaxngimCytigw8c1KdIw 14 | Z/j71rDjv6blleGJ6ZXBwtdQEG2clDSuVjBRuIIxe64/wMEe702MMC28Y97SZ3WV 15 | JU4KaQoW5oVaoom9+hngCT6btpmT6adu0oC424bmSxUdB6/Kk+kgsD6SyaXt6VCf 16 | PUpfipNwbS/GFoqevLSGjOsyrEl5nzF0VBdcg9TOodlqruVtNFSnShQh93hKmY1J 17 | Mz62dg/LnnZ4+yO1ARZk+tcCAwEAAaNTMFEwHQYDVR0OBBYEFHr9VpODSEUfgO/W 18 | sVebyT+YTcy9MB8GA1UdIwQYMBaAFHr9VpODSEUfgO/WsVebyT+YTcy9MA8GA1Ud 19 | EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBABImtJJhwE2b0cNeSI+ng9oa 20 | 6PBXX5usNZQBuw3wvaLFephpbUH/HrWFJCbscubZ0wmt0UD6Ly7v32DJl505NFQQ 21 | XMDAdApLRMHcbFcyqIIVkcSlOoRNlf5Dx9A2oqUzwI392OrDeYEF+a+9CcLGo6Fv 22 | m8vii6Z1JS7TgGa9KqFErOgyvVv5xoSH31EVgezIoMgYWya2oiKZayYLNlr/eo3c 23 | 8DxEvJ2sdv8MI09lrwAZ61bx4aDYcpnDckVzvttdjGrunr1AyblCo6yhKUax1w5K 24 | qLvLoVwuTFo4VFzMJMeIuRLm79hxhQIjAgIba8Cms0EPxcivVaWG5KvY8/oXLlP6 25 | YRgmWvuA9UGvnrAvUw+eAZj1aFzLRGLXG3VxHUSJyhEV54dZCMWMfK0KOdyptbR3 26 | 7phJCeCGYS8/kJnCMAXU4NfiGWmRcTxkJTqgHC3txgzJQ4Izt8oeekJwlOJEN6R8 27 | OCT4DeNGKy0bcAwaUT2n+b+OmQpaT/F7u2Hx/n0356QjVSoNTgmg6Bjsp5hNlX6i 28 | I22ZhrayIRlXmMUmivMWBriz44yu6bo74EV6zHNvF1LYR7u2ajtdzlk1fHSE4OfM 29 | +J8SNDwRYRFTxZPTK2Yf/PQtyl+xaWAHcT7NumXQcqOVxq9jfaurIOCWz4i4BIeK 30 | bsPEVuonk6XLwUlSNI2W 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /redis/certs/redis.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID6DCCAdACFEgIFxNIvsbdVchHniC4XDKivF9nMA0GCSqGSIb3DQEBCwUAMDUx 3 | EzARBgNVBAoMClJlZGlzIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhv 4 | cml0eTAeFw0yMjEwMDEwODIzMjNaFw0yMzEwMDEwODIzMjNaMCwxEzARBgNVBAoM 5 | ClJlZGlzIFRlc3QxFTATBgNVBAMMDEdlbmVyaWMtY2VydDCCASIwDQYJKoZIhvcN 6 | AQEBBQADggEPADCCAQoCggEBAKD5m63bIrErikY/yeEaSscASuX4wotoqh7xZ/SG 7 | InupjVOAJ54pUZCasyQ+uKmJf8/qOfCDbGqzAjb+ZF4oi2w5EgkvDN1O2T1byvsg 8 | dWrmaG0hvP3VzVrGrnv2N1jMvtvy5M3O6LC4wLVzBUKRINtNIQ4XeEw+f+36KAx4 9 | n5Ys9umtDY5v08vKynUR4mM8YGklFOwqQe0GCak/W4PJL6suReMR0gHK8cP0kJnO 10 | TAN1yh9nt4Yc9wMWsQSQkb654pcX+XIOxX93PF4S/GGcqMjNVmlXZUQqIYFY1Xmj 11 | jkbRPWd2JFz/zbAiWFPW82oHWH7YnOiAW65224gpB8DUSBkCAwEAATANBgkqhkiG 12 | 9w0BAQsFAAOCAgEAKfHaF3NGr7vJZE8+IxFQQqqouYbrKzh9bE43bGm4rCfj3As/ 13 | oe46fDptNFHRuJ3KnVKJa6vaM7/4KmNOgcoK50kEXM//ZbWJHXslxdLNdDUHHfGT 14 | plBw9kaiCQSXFXMNZg8Ww3rq9IYpXoj0YcFe0YaL33ZEAmYSEJs8l3/qaHkHQYrm 15 | PZud8H0ZAiUuO/ZQVYj1eaEieKRmKxtwtKzNSxd2mFE88HfVbY52cwu57P4QqWiG 16 | lEFm403WblFa8ozf2W6XUIp7irPMyZmtd+tZsTljIs0d6mYp1r9i1jvD6YFEYJyg 17 | gicKM8ZbffSa0Y/N8vBTzXNHkC2r2QIIBBiNFTkG0ridjrbqvGy/KJHFwhwvBY80 18 | UoX+41bWEhzGXMfErAybbyQl0PmT7ygTGwBkWSWb2JmZVua9kUJD9/j3Ys7VRL2n 19 | LDZlXrVXZ5yeg113IHUfuMW7SuOWGo/oiSWZ8HjtoXGZ88XceAEy+oWLQgcfzsiN 20 | MnM43igMDeTAcDCUvdChnp67rzE2A0C82uergpy9EgETkOFEIdU/dSMjfMWBqYLv 21 | Wlvryaj/g6KD/NJjs85x0uTo6OpZOZ/SGSC6g/tPsaT2GPfAF5hpj9RPAnV0jx6g 22 | PPxESYK/qwK4OOi2EmS/2UFS1NYx7RcfuXqznUWybRP0OVw0YSXUyIMklE8= 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /redis/certs/redis.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAoPmbrdsisSuKRj/J4RpKxwBK5fjCi2iqHvFn9IYie6mNU4An 3 | nilRkJqzJD64qYl/z+o58INsarMCNv5kXiiLbDkSCS8M3U7ZPVvK+yB1auZobSG8 4 | /dXNWsaue/Y3WMy+2/Lkzc7osLjAtXMFQpEg200hDhd4TD5/7fooDHifliz26a0N 5 | jm/Ty8rKdRHiYzxgaSUU7CpB7QYJqT9bg8kvqy5F4xHSAcrxw/SQmc5MA3XKH2e3 6 | hhz3AxaxBJCRvrnilxf5cg7Ff3c8XhL8YZyoyM1WaVdlRCohgVjVeaOORtE9Z3Yk 7 | XP/NsCJYU9bzagdYftic6IBbrnbbiCkHwNRIGQIDAQABAoIBAH/tvqYNN+XbN1ma 8 | yiJl1bWO6vcWme1WsZRDv5zZmL/0QEdy2olcRuEHwIR2gCTkDDo85+zXVSr6tjDk 9 | PPmDeUH5nPbFeJDMX+Led1LaOdZJ5AWOwXZtGnUg3nvPca7VkHyV7PYOkGgZe7T4 10 | At8jHRyLkFB1oHnfVaXXn0pB9nnOYTLy57m8K96yIPKY4R9vtJNMwwu8rOv7EdU0 11 | uGeuTYwAGronBosbIXOKSTDSfJfks8hMUNwKREkqQ3tSSZNLRv9jZIERIDn3d1TX 12 | ctL2u9LiUzc5mu11GlDVITd/fPpiV2i1rRL42kyoys9KsP1xSkvgsuoAChmZQDzw 13 | rsR1mwECgYEA1Fv8918RtkwKTIf7SNjE9noJruuzti/opve20oq18D+outxVLN87 14 | dV5Y/W8JVwEjI+dH98k45UxAZXP5UPSYgf3uam1D07HwbhKu09+sEb3gHnN40O+i 15 | WTGcnXgimau5LfVqV+J66Mr5IQHS0D8503qfxETQvgZoZaWvf7f7C+ECgYEAwg5X 16 | 8EGGZwRNDLNAaPyW0nOTZa9N2/eB/cwT6cvOrdvdq2qbFJeWvjQh3X/kluZydlcH 17 | z59EJAV1W9TTQPp475L95vCr7tLhewYoCCdLl3Zc77f0AlKsG22oLx21cqSQZqV4 18 | BLmuIa8pbNZF0rlPCYfCMtDSoumiP1+yEBorAzkCgYBTe6ZDCVjJNbOGyp560Nz9 19 | mJRd88M+iC8KFLAGrQlBXYVTkHLbpRaW2XRajWA6l/PclXuxaaW4XvWh7KnCraWW 20 | 1OD5beMQQg+m9ilMmc3nW6HT1slUOiC0t9A+B5ByoRO3gZdA3YQ8KC8wTqs1uuVc 21 | wgQ1AGifi51W+H+50fYbgQKBgF3tJWFTxeMM8OJJzM5EHBhG+rwICu6CMTgP1D+g 22 | dywttHBozCE+cickApQ8d/8WEab33v7+frPUtIY7T/kuguSqyBxEt2OETMEILM4t 23 | fugT0U3yQrfxHf60p+gOQCn6Py5/vsTCvubs6mcUm30wVnItdLC15I4X+YElJ4Gl 24 | uNX5AoGBAMRrCo7jL5+3At/g8MYwjHbUToY7atNzCa7dueM3BpmOktUjBEaUtnAE 25 | EnsoJVFuiaMAvoqxQ9pskpdMgdA76G+yyDe+vwJJtb1rq9bNZlUk8aE+u8Wngeqq 26 | ZafEJOFrI08CXODJadCvHym7iqOrUzCVeTMb/oNoz25WszYm8K3O 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /redis/cluster-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sed -i "s/\$PORT/$PORT/g" /redis/cluster.conf 3 | sed -i "s/\$BUS_PORT/$BUS_PORT/g" /redis/cluster.conf 4 | sed -i "s/\$HOST_IP/$HOST_IP/g" /redis/cluster.conf 5 | redis-server /redis/cluster.conf 6 | -------------------------------------------------------------------------------- /redis/cluster.conf: -------------------------------------------------------------------------------- 1 | port $PORT 2 | cluster-enabled yes 3 | cluster-config-file nodes.conf 4 | cluster-node-timeout 5000 5 | cluster-announce-ip $HOST_IP 6 | cluster-announce-port $PORT 7 | cluster-announce-bus-port $BUS_PORT 8 | enable-debug-command yes 9 | busy-reply-threshold 500 10 | appendonly yes 11 | -------------------------------------------------------------------------------- /redis/cluster.dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis:alpine 2 | 3 | WORKDIR /redis 4 | COPY cluster.conf . 5 | RUN chown redis:redis /redis/cluster.conf 6 | EXPOSE 6379 7 | COPY cluster-entrypoint.sh . 8 | ENTRYPOINT ["/redis/cluster-entrypoint.sh"] 9 | -------------------------------------------------------------------------------- /redis/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # Standalone 3 | redis-standalone: 4 | image: redis:alpine 5 | container_name: redis-standalone 6 | restart: on-failure:20 7 | ports: 8 | - "6379:6379" 9 | volumes: 10 | - .:/config 11 | command: redis-server /config/standalone.conf 12 | 13 | # TLS 14 | redis-tls: 15 | image: docker.io/bitnami/redis 16 | container_name: redis-tls 17 | restart: on-failure:20 18 | ports: 19 | - "6380:6379" 20 | volumes: 21 | - ./certs:/certs 22 | environment: 23 | - ALLOW_EMPTY_PASSWORD=false 24 | - REDIS_PASSWORD=pwd 25 | - REDIS_TLS_CERT_FILE=/certs/redis.crt 26 | - REDIS_TLS_KEY_FILE=/certs/redis.key 27 | - REDIS_TLS_CA_FILE=/certs/ca.crt 28 | - REDIS_TLS_ENABLED=yes 29 | - REDIS_TLS_PORT=6379 30 | - REDIS_TLS_AUTH_CLIENTS=no 31 | 32 | # Sentinel 33 | redis-master: 34 | image: redis:alpine 35 | container_name: redis-master 36 | restart: on-failure:20 37 | ports: 38 | - "6381:6381" 39 | command: redis-server --port 6381 40 | 41 | redis-replica: 42 | image: redis:alpine 43 | container_name: redis-replica 44 | restart: on-failure:20 45 | ports: 46 | - "6382:6382" 47 | command: redis-server --port 6382 --replicaof "${HOST_IP}" 6381 --slave-announce-ip "${HOST_IP}" 48 | depends_on: 49 | - redis-master 50 | 51 | redis-sentinel1: 52 | build: 53 | context: . 54 | dockerfile: ./sentinel.dockerfile 55 | image: redis-sentinel1 56 | container_name: redis-sentinel1 57 | restart: on-failure:20 58 | ports: 59 | - "26379:26379" 60 | depends_on: 61 | - redis-master 62 | environment: 63 | - PORT=26379 64 | - HOST_IP="${HOST_IP}" 65 | 66 | redis-sentinel2: 67 | build: 68 | context: . 69 | dockerfile: ./sentinel.dockerfile 70 | image: redis-sentinel2 71 | container_name: redis-sentinel2 72 | restart: on-failure:20 73 | ports: 74 | - "26380:26379" 75 | depends_on: 76 | - redis-master 77 | environment: 78 | - PORT=26380 79 | - HOST_IP="${HOST_IP}" 80 | 81 | redis-sentinel3: 82 | build: 83 | context: . 84 | dockerfile: ./sentinel.dockerfile 85 | image: redis-sentinel3 86 | container_name: redis-sentinel3 87 | restart: on-failure:20 88 | ports: 89 | - "26381:26379" 90 | depends_on: 91 | - redis-master 92 | environment: 93 | - PORT=26381 94 | - HOST_IP="${HOST_IP}" 95 | 96 | # Cluster 97 | redis-cluster: 98 | image: 'redis:alpine' 99 | command: redis-cli --cluster create "${HOST_IP}":7000 "${HOST_IP}":7001 "${HOST_IP}":7002 "${HOST_IP}":7003 "${HOST_IP}":7004 "${HOST_IP}":7005 --cluster-replicas 1 --cluster-yes 100 | container_name: redis-cluster 101 | depends_on: 102 | - redis-node1 103 | - redis-node2 104 | - redis-node3 105 | - redis-node4 106 | - redis-node5 107 | - redis-node6 108 | 109 | redis-node1: 110 | build: 111 | context: . 112 | dockerfile: ./cluster.dockerfile 113 | image: redis-node1 114 | container_name: redis-node1 115 | restart: on-failure:20 116 | ports: 117 | - "7000:7000" 118 | - "17000:17000" 119 | environment: 120 | - PORT=7000 121 | - BUS_PORT=17000 122 | - HOST_IP="${HOST_IP}" 123 | 124 | redis-node2: 125 | build: 126 | context: . 127 | dockerfile: ./cluster.dockerfile 128 | image: redis-node2 129 | container_name: redis-node2 130 | restart: on-failure:20 131 | ports: 132 | - "7001:7001" 133 | - "17001:17001" 134 | environment: 135 | - PORT=7001 136 | - BUS_PORT=17001 137 | - HOST_IP="${HOST_IP}" 138 | 139 | redis-node3: 140 | build: 141 | context: . 142 | dockerfile: ./cluster.dockerfile 143 | image: redis-node3 144 | container_name: redis-node3 145 | restart: on-failure:20 146 | ports: 147 | - "7002:7002" 148 | - "17002:17002" 149 | environment: 150 | - PORT=7002 151 | - BUS_PORT=17002 152 | - HOST_IP="${HOST_IP}" 153 | 154 | redis-node4: 155 | build: 156 | context: . 157 | dockerfile: ./cluster.dockerfile 158 | image: redis-node4 159 | container_name: redis-node4 160 | restart: on-failure:20 161 | ports: 162 | - "7003:7003" 163 | - "17003:17003" 164 | environment: 165 | - PORT=7003 166 | - BUS_PORT=17003 167 | - HOST_IP="${HOST_IP}" 168 | 169 | redis-node5: 170 | build: 171 | context: . 172 | dockerfile: ./cluster.dockerfile 173 | image: redis-node5 174 | container_name: redis-node5 175 | restart: on-failure:20 176 | ports: 177 | - "7004:7004" 178 | - "17004:17004" 179 | environment: 180 | - PORT=7004 181 | - BUS_PORT=17004 182 | - HOST_IP="${HOST_IP}" 183 | 184 | redis-node6: 185 | build: 186 | context: . 187 | dockerfile: ./cluster.dockerfile 188 | image: redis-node6 189 | container_name: redis-node6 190 | restart: on-failure:20 191 | ports: 192 | - "7005:7005" 193 | - "17005:17005" 194 | environment: 195 | - PORT=7005 196 | - BUS_PORT=17005 197 | - HOST_IP="${HOST_IP}" 198 | 199 | # RedisStack 200 | redis-stack: 201 | image: redis/redis-stack-server:7.0.6-RC5 202 | container_name: redis-stack 203 | restart: on-failure:20 204 | ports: 205 | - "8000:6379" 206 | -------------------------------------------------------------------------------- /redis/docker_down.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | call set_host_ip.cmd 3 | docker compose down 4 | -------------------------------------------------------------------------------- /redis/docker_down.sh: -------------------------------------------------------------------------------- 1 | ./set_host_ip.sh 2 | docker compose down 3 | -------------------------------------------------------------------------------- /redis/docker_up.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | call set_host_ip.cmd 3 | docker compose up -d 4 | -------------------------------------------------------------------------------- /redis/docker_up.sh: -------------------------------------------------------------------------------- 1 | sh ./set_host_ip.sh 2 | docker compose up -d --build 3 | -------------------------------------------------------------------------------- /redis/sentinel-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sed -i "s/\$PORT/$PORT/g" /redis/sentinel.conf 3 | sed -i "s/\$HOST_IP/$HOST_IP/g" /redis/sentinel.conf 4 | redis-server /redis/sentinel.conf --sentinel 5 | -------------------------------------------------------------------------------- /redis/sentinel.conf: -------------------------------------------------------------------------------- 1 | port 26379 2 | 3 | dir "/tmp" 4 | 5 | sentinel resolve-hostnames yes 6 | sentinel announce-ip $HOST_IP 7 | sentinel announce-port $PORT 8 | sentinel monitor myservice $HOST_IP 6381 2 9 | sentinel down-after-milliseconds myservice 1000 10 | sentinel failover-timeout myservice 1000 11 | -------------------------------------------------------------------------------- /redis/sentinel.dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis:alpine 2 | 3 | RUN mkdir -p /redis 4 | WORKDIR /redis 5 | COPY sentinel.conf . 6 | RUN chown redis:redis /redis/sentinel.conf 7 | EXPOSE 26379 8 | COPY sentinel-entrypoint.sh . 9 | ENTRYPOINT ["/redis/sentinel-entrypoint.sh"] 10 | -------------------------------------------------------------------------------- /redis/set_host_ip.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr IPv4') do (SET HOST_IP=%%a) 3 | set HOST_IP=%HOST_IP: =% -------------------------------------------------------------------------------- /redis/set_host_ip.sh: -------------------------------------------------------------------------------- 1 | IP=`ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*'` 2 | echo IP=$IP 3 | echo "HOST_IP=$IP" > .env 4 | -------------------------------------------------------------------------------- /run_tests.cmd: -------------------------------------------------------------------------------- 1 | cd redis && .\docker_up.cmd && cd .. && cargo test --features tokio-tls,pool,redis-stack 2 | -------------------------------------------------------------------------------- /src/client/client_state.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use std::{ 3 | any::Any, 4 | collections::{hash_map::Entry, HashMap}, 5 | }; 6 | 7 | /// A struct which goal is to give a generic access to attach any state to a client instance 8 | /// 9 | /// It is internally used to cache [RedisGraph](crate::commands::GraphCommands) metadata. 10 | #[derive(Default)] 11 | pub struct ClientState { 12 | cache: HashMap>, 13 | } 14 | 15 | impl ClientState { 16 | pub(crate) fn new() -> ClientState { 17 | ClientState { 18 | cache: HashMap::new(), 19 | } 20 | } 21 | 22 | /// Get state with a specific type `S` for a specific `key` 23 | /// 24 | /// # Return 25 | /// Casted state to the required type or Ok(None) if `key` has not been found. 26 | /// 27 | /// If the state does not already exists, it is created on the fly 28 | /// by calling `S::default()` 29 | /// 30 | /// # Errors 31 | /// An error if an entry has been found for the `key` but this entry cannot be 32 | /// downcasted to the required type. 33 | pub fn get_state(&self, key: &str) -> Result> { 34 | match self.cache.get(key) { 35 | Some(cache_entry) => match cache_entry.downcast_ref::() { 36 | Some(cache_entry) => Ok(Some(cache_entry)), 37 | None => Err(Error::Client(format!( 38 | "Cannot downcast cache entry '{key}'" 39 | ))), 40 | }, 41 | None => Ok(None), 42 | } 43 | } 44 | 45 | /// Get state with a specific type `S` for a specific `key` 46 | /// 47 | /// # Return 48 | /// Casted state to the required type. 49 | /// 50 | /// If the state does not already exists, it is created on the fly 51 | /// by calling `S::default()` 52 | /// 53 | /// # Errors 54 | /// An error if an entry has been found for the `key` but this entry cannot be 55 | /// downcasted to the required type. 56 | pub fn get_state_mut( 57 | &mut self, 58 | key: &str, 59 | ) -> Result<&mut S> { 60 | let cache_entry = match self.cache.entry(key.to_string()) { 61 | Entry::Occupied(o) => o.into_mut(), 62 | Entry::Vacant(v) => v.insert(Box::::default()), 63 | }; 64 | 65 | let cache_entry = cache_entry 66 | .downcast_mut::() 67 | .ok_or_else(|| Error::Client(format!("Cannot downcast cache entry '{key}'"))); 68 | 69 | cache_entry 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/client/client_tracking_invalidation_stream.rs: -------------------------------------------------------------------------------- 1 | use crate::network::PushReceiver; 2 | use futures_util::{Stream, StreamExt}; 3 | use std::{ 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | pub(crate) struct ClientTrackingInvalidationStream { 9 | receiver: PushReceiver, 10 | } 11 | 12 | impl ClientTrackingInvalidationStream { 13 | pub(crate) fn new(receiver: PushReceiver) -> Self { 14 | Self { receiver } 15 | } 16 | } 17 | 18 | impl Stream for ClientTrackingInvalidationStream { 19 | type Item = Vec; 20 | 21 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { 22 | match self.get_mut().receiver.poll_next_unpin(cx) { 23 | Poll::Ready(resp_buffer) => match resp_buffer { 24 | Some(resp_buffer) => match resp_buffer { 25 | Ok(resp_buffer) => match resp_buffer.to::<(&str, Vec)>() { 26 | Ok((_invalidate, keys)) => Poll::Ready(Some(keys)), 27 | Err(_) => Poll::Ready(None), 28 | }, 29 | Err(_) => Poll::Ready(None), 30 | }, 31 | None => Poll::Ready(None), 32 | }, 33 | Poll::Pending => Poll::Pending, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/client/monitor_stream.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{Client, ClientPreparedCommand}, 3 | commands::ConnectionCommands, 4 | network::PushReceiver, 5 | Result, 6 | }; 7 | use futures_util::{Stream, StreamExt}; 8 | use log::error; 9 | use serde::{de, Deserialize, Deserializer}; 10 | use std::{ 11 | net::SocketAddr, 12 | pin::Pin, 13 | task::{Context, Poll}, 14 | }; 15 | 16 | /// Stream to get [`MONITOR`](https://redis.io/commands/monitor/) command events 17 | /// when the stream is dropped or closed, a reset command is sent to the Redis server 18 | pub struct MonitorStream { 19 | closed: bool, 20 | receiver: PushReceiver, 21 | client: Client, 22 | } 23 | 24 | impl MonitorStream { 25 | pub(crate) fn new(receiver: PushReceiver, client: Client) -> Self { 26 | Self { 27 | closed: false, 28 | receiver, 29 | client, 30 | } 31 | } 32 | 33 | pub async fn close(&mut self) -> Result<()> { 34 | self.client.reset().await?; 35 | self.closed = true; 36 | Ok(()) 37 | } 38 | } 39 | 40 | impl Stream for MonitorStream { 41 | type Item = MonitoredCommandInfo; 42 | 43 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { 44 | if self.closed { 45 | Poll::Ready(None) 46 | } else { 47 | match self.get_mut().receiver.poll_next_unpin(cx) { 48 | Poll::Ready(bytes) => match bytes { 49 | Some(bytes) => match bytes { 50 | Ok(resp_buf) => match resp_buf.to() { 51 | Ok(info) => Poll::Ready(Some(info)), 52 | Err(e) => { 53 | error!("Error will receiving data in monitor stream: {e}"); 54 | Poll::Ready(None) 55 | } 56 | }, 57 | Err(_) => Poll::Ready(None), 58 | }, 59 | None => Poll::Ready(None), 60 | }, 61 | Poll::Pending => Poll::Pending, 62 | } 63 | } 64 | } 65 | } 66 | 67 | impl Drop for MonitorStream { 68 | fn drop(&mut self) { 69 | if self.closed { 70 | return; 71 | } 72 | 73 | let _result = self.client.reset().forget(); 74 | } 75 | } 76 | 77 | /// Result for the [`monitor`](crate::commands::BlockingCommands::monitor) command. 78 | #[derive(Debug)] 79 | pub struct MonitoredCommandInfo { 80 | pub unix_timestamp_millis: f64, 81 | pub database: usize, 82 | pub server_addr: SocketAddr, 83 | pub command: String, 84 | pub command_args: Vec, 85 | } 86 | 87 | impl<'de> Deserialize<'de> for MonitoredCommandInfo { 88 | fn deserialize(deserializer: D) -> std::result::Result 89 | where 90 | D: Deserializer<'de>, 91 | { 92 | let line = <&str>::deserialize(deserializer)?; 93 | let mut parts = line.split(' '); 94 | 95 | let info = match (parts.next(), parts.next(), parts.next(), parts.next()) { 96 | (Some(unix_timestamp_millis), Some(database), Some(server_addr), Some(command)) => { 97 | let database = &database[1..]; 98 | let server_addr = &server_addr[..server_addr.len() - 1]; 99 | match ( 100 | unix_timestamp_millis.parse::(), 101 | server_addr.parse::(), 102 | database.parse::(), 103 | ) { 104 | (Ok(unix_timestamp_millis), Ok(server_addr), Ok(database)) => Some(Self { 105 | unix_timestamp_millis, 106 | database, 107 | server_addr, 108 | command: command[1..command.len() - 1].to_owned(), 109 | command_args: parts.map(|a| a[1..a.len() - 1].to_owned()).collect(), 110 | }), 111 | _ => None, 112 | } 113 | } 114 | _ => None, 115 | }; 116 | 117 | info.ok_or_else(|| { 118 | de::Error::custom(format!("Cannot parse result from MONITOR event: {line}")) 119 | }) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/client/pooled_client_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{Client, Config, IntoConfig}, 3 | commands::ConnectionCommands, 4 | Error, Result, 5 | }; 6 | use bb8::ManageConnection; 7 | 8 | /// An object which manages a pool of clients, based on [bb8](https://docs.rs/bb8/latest/bb8/) 9 | pub struct PooledClientManager { 10 | config: Config, 11 | } 12 | 13 | impl PooledClientManager { 14 | pub fn new(config: impl IntoConfig) -> Result { 15 | Ok(Self { 16 | config: config.into_config()?, 17 | }) 18 | } 19 | } 20 | 21 | impl ManageConnection for PooledClientManager { 22 | type Connection = Client; 23 | type Error = Error; 24 | 25 | async fn connect(&self) -> Result { 26 | let config = self.config.clone(); 27 | Client::connect(config).await 28 | } 29 | 30 | async fn is_valid(&self, client: &mut Client) -> Result<()> { 31 | client.ping::(Default::default()).await?; 32 | Ok(()) 33 | } 34 | 35 | fn has_broken(&self, _client: &mut Client) -> bool { 36 | false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/client/prepared_command.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::Client, 3 | resp::{Command, RespBuf, Response}, 4 | Future, 5 | }; 6 | use std::marker::PhantomData; 7 | 8 | type CustomConverter<'a, R> = dyn Fn(RespBuf, Command, &'a Client) -> Future<'a, R> + Send + Sync; 9 | 10 | /// Wrapper around a command about to be send with a marker for the response type 11 | /// and a few options to decide how the response send back by Redis should be processed. 12 | pub struct PreparedCommand<'a, E, R = ()> 13 | where 14 | R: Response, 15 | { 16 | /// Marker of the type in which the command response will be transformed 17 | phantom: PhantomData, 18 | /// Client, Transaction or Pipeline that will actually 19 | /// send the command to the Redis server. 20 | pub executor: E, 21 | /// Command to send 22 | pub command: Command, 23 | /// Custom converter to transform a RESP Buffer in to `R` type 24 | pub custom_converter: Option>>, 25 | /// Flag to retry sending the command on network error. 26 | pub retry_on_error: Option, 27 | } 28 | 29 | impl<'a, E, R> PreparedCommand<'a, E, R> 30 | where 31 | R: Response, 32 | { 33 | /// Create a new prepared command. 34 | #[must_use] 35 | pub fn new(executor: E, command: Command) -> Self { 36 | PreparedCommand { 37 | phantom: PhantomData, 38 | executor, 39 | command, 40 | custom_converter: None, 41 | retry_on_error: None, 42 | } 43 | } 44 | 45 | /// Set the functor [`self.custom_converter`] 46 | pub fn custom_converter(mut self, custom_converter: Box>) -> Self { 47 | self.custom_converter = Some(custom_converter); 48 | self 49 | } 50 | 51 | /// Set a flag to override default `retry_on_error` behavior. 52 | /// 53 | /// See [Config::retry_on_error](crate::client::Config::retry_on_error) 54 | pub fn retry_on_error(mut self, retry_on_error: bool) -> Self { 55 | self.retry_on_error = Some(retry_on_error); 56 | self 57 | } 58 | 59 | /// Get a reference to the command to send 60 | pub fn command(&self) -> &Command { 61 | &self.command 62 | } 63 | } 64 | 65 | /// Shortcut function to creating a [`PreparedCommand`](PreparedCommand). 66 | pub(crate) fn prepare_command<'a, E, R: Response>( 67 | executor: E, 68 | command: Command, 69 | ) -> PreparedCommand<'a, E, R> { 70 | PreparedCommand::new(executor, command) 71 | } 72 | -------------------------------------------------------------------------------- /src/commands/count_min_sktech_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{prepare_command, PreparedCommand}, 3 | resp::{cmd, CollectionResponse, KeyValueArgsCollection, SingleArg, SingleArgCollection}, 4 | }; 5 | use serde::Deserialize; 6 | 7 | /// A group of Redis commands related to [`Count-min Sketch`](https://redis.io/docs/stack/bloom/) 8 | /// 9 | /// # See Also 10 | /// [Count-min Sketch Commands](https://redis.io/commands/?group=cms) 11 | pub trait CountMinSketchCommands<'a> { 12 | /// Increases the count of item by increment. 13 | /// 14 | /// Multiple items can be increased with one call. 15 | /// 16 | /// # Arguments 17 | /// * `key` - The name of the sketch. 18 | /// * `items` - A collection of tuples of 19 | /// * `item` - The item which counter is to be increased. 20 | /// * `increment`- Amount by which the item counter is to be increased. 21 | /// 22 | /// # Return 23 | /// A collection of count of each item after increment. 24 | /// 25 | /// # See Also 26 | /// * [](https://redis.io/commands/cms.incrby/) 27 | #[must_use] 28 | fn cms_incrby>( 29 | self, 30 | key: impl SingleArg, 31 | items: impl KeyValueArgsCollection, 32 | ) -> PreparedCommand<'a, Self, R> 33 | where 34 | Self: Sized, 35 | { 36 | prepare_command(self, cmd("CMS.INCRBY").arg(key).arg(items)) 37 | } 38 | 39 | /// Returns width, depth and total count of the sketch. 40 | /// 41 | /// # Arguments 42 | /// * `key` - The name of the sketch. 43 | /// 44 | /// # See Also 45 | /// * [](https://redis.io/commands/cms.info/) 46 | #[must_use] 47 | fn cms_info(self, key: impl SingleArg) -> PreparedCommand<'a, Self, CmsInfoResult> 48 | where 49 | Self: Sized, 50 | { 51 | prepare_command(self, cmd("CMS.INFO").arg(key)) 52 | } 53 | 54 | /// Initializes a Count-Min Sketch to dimensions specified by user. 55 | /// 56 | /// Multiple items can be increased with one call. 57 | /// 58 | /// # Arguments 59 | /// * `key` - The name of the sketch. 60 | /// * `width` - Number of counters in each array. Reduces the error size. 61 | /// * `depth` - Number of counter-arrays. Reduces the probability for an error of a certain size (percentage of total count). 62 | /// 63 | /// # See Also 64 | /// * [](https://redis.io/commands/cms.initbydim/) 65 | #[must_use] 66 | fn cms_initbydim( 67 | self, 68 | key: impl SingleArg, 69 | width: usize, 70 | depth: usize, 71 | ) -> PreparedCommand<'a, Self, ()> 72 | where 73 | Self: Sized, 74 | { 75 | prepare_command(self, cmd("CMS.INITBYDIM").arg(key).arg(width).arg(depth)) 76 | } 77 | 78 | /// Initializes a Count-Min Sketch to accommodate requested tolerances. 79 | /// 80 | /// # Arguments 81 | /// * `key` - The name of the sketch. 82 | /// * `error` - Estimate size of error.\ 83 | /// The error is a percent of total counted items. This effects the width of the sketch. 84 | /// * `probability` - The desired probability for inflated count. \ 85 | /// This should be a decimal value between 0 and 1. 86 | /// This effects the depth of the sketch. 87 | /// For example, for a desired false positive rate of 0.1% (1 in 1000), 88 | /// error_rate should be set to 0.001. The closer this number is to zero, 89 | /// the greater the memory consumption per item and the more CPU usage per operation. 90 | /// 91 | /// # See Also 92 | /// * [](https://redis.io/commands/cms.initbyprob/) 93 | #[must_use] 94 | fn cms_initbyprob( 95 | self, 96 | key: impl SingleArg, 97 | error: f64, 98 | probability: f64, 99 | ) -> PreparedCommand<'a, Self, ()> 100 | where 101 | Self: Sized, 102 | { 103 | prepare_command( 104 | self, 105 | cmd("CMS.INITBYPROB").arg(key).arg(error).arg(probability), 106 | ) 107 | } 108 | 109 | /// Returns the count for one or more items in a sketch. 110 | /// 111 | /// All sketches must have identical width and depth. 112 | /// Weights can be used to multiply certain sketches. 113 | /// Default weight is 1. 114 | /// 115 | /// # Arguments 116 | /// * `destination` - The name of destination sketch. Must be initialized. 117 | /// * `sources` - Names of source sketches to be merged. 118 | /// * `weights` - Multiple of each sketch. Default =1. 119 | /// 120 | /// # See Also 121 | /// * [](https://redis.io/commands/cms.merge/) 122 | #[must_use] 123 | fn cms_merge>( 124 | self, 125 | destination: impl SingleArg, 126 | sources: impl SingleArgCollection, 127 | weights: Option, 128 | ) -> PreparedCommand<'a, Self, ()> 129 | where 130 | Self: Sized, 131 | { 132 | prepare_command( 133 | self, 134 | cmd("CMS.MERGE") 135 | .arg(destination) 136 | .arg(sources.num_args()) 137 | .arg(sources) 138 | .arg(weights.map(|w| ("WEIGHTS", w))), 139 | ) 140 | } 141 | 142 | /// Merges several sketches into one sketch. 143 | /// 144 | /// All sketches must have identical width and depth. 145 | /// Weights can be used to multiply certain sketches. 146 | /// Default weight is 1. 147 | /// 148 | /// # Arguments 149 | /// * `key` - The name of the sketch. 150 | /// * `item` - One or more items for which to return the count. 151 | /// 152 | /// # Return 153 | /// Count of one or more items 154 | /// 155 | /// # See Also 156 | /// * [](https://redis.io/commands/cms.query/) 157 | #[must_use] 158 | fn cms_query>( 159 | self, 160 | key: impl SingleArg, 161 | items: impl SingleArgCollection, 162 | ) -> PreparedCommand<'a, Self, C> 163 | where 164 | Self: Sized, 165 | { 166 | prepare_command(self, cmd("CMS.QUERY").arg(key).arg(items)) 167 | } 168 | } 169 | 170 | /// Result for the [`cms_info`](CountMinSketchCommands::cms_info) command. 171 | #[derive(Debug, Deserialize)] 172 | pub struct CmsInfoResult { 173 | /// Width of the sketch 174 | pub width: usize, 175 | /// Depth of the sketch 176 | pub depth: usize, 177 | /// Total count of the sketch 178 | #[serde(rename = "count")] 179 | pub total_count: usize, 180 | } 181 | -------------------------------------------------------------------------------- /src/commands/debug_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{prepare_command, PreparedCommand}, 3 | resp::cmd, 4 | }; 5 | use std::time::Duration; 6 | 7 | /// A group of Redis commands related to Debug functionality of redis 8 | /// # See Also 9 | /// [Redis Debug Commands](https://redis.io/commands/debug/) 10 | /// The DEBUG command is an internal command. It is meant to be used 11 | /// for developing and testing Redis and libraries. 12 | pub trait DebugCommands<'a> { 13 | /// Stop the server for . Decimals allowed. 14 | #[must_use] 15 | fn debug_sleep(self, duration: Duration) -> PreparedCommand<'a, Self, ()> 16 | where 17 | Self: Sized, 18 | { 19 | prepare_command(self, cmd("DEBUG").arg("SLEEP").arg(duration.as_secs_f32())) 20 | } 21 | 22 | /// Graceful restart: save config, db, restart after a delay (default 0). 23 | #[must_use] 24 | fn debug_restart(self, delay: Option) -> PreparedCommand<'a, Self, ()> 25 | where 26 | Self: Sized, 27 | { 28 | prepare_command( 29 | self, 30 | cmd("DEBUG") 31 | .arg("RESTART") 32 | .arg(delay.map(|d| u64::try_from(d.as_millis()).unwrap())), 33 | ) 34 | } 35 | 36 | /// Hard crash and restart after a delay (default 0). 37 | #[must_use] 38 | fn debug_crash_and_recover(self, delay: Option) -> PreparedCommand<'a, Self, ()> 39 | where 40 | Self: Sized, 41 | { 42 | prepare_command( 43 | self, 44 | cmd("DEBUG") 45 | .arg("CRASH-AND-RECOVER") 46 | .arg(delay.map(|d| u64::try_from(d.as_millis()).unwrap())), 47 | ) 48 | } 49 | 50 | /// Crash the server by assertion failed. 51 | #[must_use] 52 | fn debug_assert(self) -> PreparedCommand<'a, Self, ()> 53 | where 54 | Self: Sized, 55 | { 56 | prepare_command(self, cmd("DEBUG").arg("ASSERT")) 57 | } 58 | 59 | /// Crash the server simulating an out-of-memory error. 60 | #[must_use] 61 | fn debug_oom(self) -> PreparedCommand<'a, Self, ()> 62 | where 63 | Self: Sized, 64 | { 65 | prepare_command(self, cmd("DEBUG").arg("OOM")) 66 | } 67 | 68 | /// Crash the server simulating a panic. 69 | #[must_use] 70 | fn debug_panic(self) -> PreparedCommand<'a, Self, ()> 71 | where 72 | Self: Sized, 73 | { 74 | prepare_command(self, cmd("DEBUG").arg("PANIC")) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/commands/hyper_log_log_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{prepare_command, PreparedCommand}, 3 | resp::{cmd, SingleArg, SingleArgCollection}, 4 | }; 5 | 6 | /// A group of Redis commands related to [`HyperLogLog`](https://redis.io/docs/data-types/hyperloglogs/) 7 | /// 8 | /// # See Also 9 | /// [Redis Hash Commands](https://redis.io/commands/?group=hyperloglog) 10 | pub trait HyperLogLogCommands<'a> { 11 | /// Adds the specified elements to the specified HyperLogLog. 12 | /// 13 | /// # Return 14 | /// * `true` if at least 1 HyperLogLog inFternal register was altered. 15 | /// * `false` otherwise. 16 | /// 17 | /// # See Also 18 | /// [](https://redis.io/commands/pfadd/) 19 | fn pfadd(self, key: K, elements: EE) -> PreparedCommand<'a, Self, bool> 20 | where 21 | Self: Sized, 22 | K: SingleArg, 23 | E: SingleArg, 24 | EE: SingleArgCollection, 25 | { 26 | prepare_command(self, cmd("PFADD").arg(key).arg(elements)) 27 | } 28 | 29 | /// Return the approximated cardinality of the set(s) 30 | /// observed by the HyperLogLog at key(s). 31 | /// 32 | /// # Return 33 | /// The approximated number of unique elements observed via PFADD. 34 | /// 35 | /// # See Also 36 | /// [](https://redis.io/commands/pfcount/) 37 | fn pfcount(self, keys: KK) -> PreparedCommand<'a, Self, usize> 38 | where 39 | Self: Sized, 40 | K: SingleArg, 41 | KK: SingleArgCollection, 42 | { 43 | prepare_command(self, cmd("PFCOUNT").arg(keys)) 44 | } 45 | 46 | /// Merge N different HyperLogLogs into a single one. 47 | /// 48 | /// # See Also 49 | /// [](https://redis.io/commands/pfmerge/) 50 | fn pfmerge(self, dest_key: D, source_keys: SS) -> PreparedCommand<'a, Self, ()> 51 | where 52 | Self: Sized, 53 | D: SingleArg, 54 | S: SingleArg, 55 | SS: SingleArgCollection, 56 | { 57 | prepare_command(self, cmd("PFMERGE").arg(dest_key).arg(source_keys)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/commands/internal_pub_sub_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{prepare_command, PreparedCommand}, 3 | resp::{cmd, SingleArg, SingleArgCollection, Value}, 4 | }; 5 | 6 | /// A group of Redis commands related to [`Pub/Sub`](https://redis.io/docs/manual/pubsub/) 7 | /// # See Also 8 | /// [Redis Pub/Sub Commands](https://redis.io/commands/?group=pubsub) 9 | pub(crate) trait InternalPubSubCommands<'a> { 10 | fn psubscribe(self, patterns: PP) -> PreparedCommand<'a, Self, Value> 11 | where 12 | Self: Sized, 13 | P: SingleArg, 14 | PP: SingleArgCollection

, 15 | { 16 | prepare_command(self, cmd("PSUBSCRIBE").arg(patterns)) 17 | } 18 | 19 | /// Unsubscribes the client from the given patterns, or from all of them if none is given. 20 | /// 21 | /// # See Also 22 | /// [](https://redis.io/commands/punsubscribe/) 23 | fn punsubscribe(self, patterns: PP) -> PreparedCommand<'a, Self, ()> 24 | where 25 | Self: Sized, 26 | P: SingleArg + Send, 27 | PP: SingleArgCollection

, 28 | { 29 | prepare_command(self, cmd("PUNSUBSCRIBE").arg(patterns)) 30 | } 31 | 32 | fn ssubscribe(self, shardchannels: CC) -> PreparedCommand<'a, Self, Value> 33 | where 34 | Self: Sized, 35 | C: SingleArg, 36 | CC: SingleArgCollection, 37 | { 38 | prepare_command(self, cmd("SSUBSCRIBE").arg(shardchannels)) 39 | } 40 | 41 | fn subscribe(self, channels: CC) -> PreparedCommand<'a, Self, Value> 42 | where 43 | Self: Sized, 44 | C: SingleArg, 45 | CC: SingleArgCollection, 46 | { 47 | prepare_command(self, cmd("SUBSCRIBE").arg(channels)) 48 | } 49 | 50 | /// Unsubscribes the client from the given shard channels, or from all of them if none is given. 51 | /// 52 | /// # See Also 53 | /// [](https://redis.io/commands/sunsubscribe//) 54 | fn sunsubscribe(self, shardchannels: CC) -> PreparedCommand<'a, Self, ()> 55 | where 56 | Self: Sized, 57 | C: SingleArg, 58 | CC: SingleArgCollection, 59 | { 60 | prepare_command(self, cmd("SUNSUBSCRIBE").arg(shardchannels)) 61 | } 62 | 63 | /// Unsubscribes the client from the given channels, or from all of them if none is given. 64 | /// 65 | /// # See Also 66 | /// [](https://redis.io/commands/unsubscribe/) 67 | fn unsubscribe(self, channels: CC) -> PreparedCommand<'a, Self, ()> 68 | where 69 | Self: Sized, 70 | C: SingleArg, 71 | CC: SingleArgCollection, 72 | { 73 | prepare_command(self, cmd("UNSUBSCRIBE").arg(channels)) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/commands/transaction_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{prepare_command, PreparedCommand}, 3 | resp::{cmd, SingleArg, SingleArgCollection}, 4 | }; 5 | 6 | /// A group of Redis commands related to Transactions 7 | /// # See Also 8 | /// [Redis Generic Commands](https://redis.io/commands/?group=transactions) 9 | pub trait TransactionCommands<'a> { 10 | /// Marks the given keys to be watched for conditional execution of a transaction. 11 | /// 12 | /// # See Also 13 | /// [](https://redis.io/commands/watch/) 14 | #[must_use] 15 | fn watch(self, keys: KK) -> PreparedCommand<'a, Self, ()> 16 | where 17 | Self: Sized, 18 | K: SingleArg, 19 | KK: SingleArgCollection, 20 | { 21 | prepare_command(self, cmd("WATCH").arg(keys)) 22 | } 23 | 24 | /// Flushes all the previously watched keys for a transaction. 25 | /// 26 | /// If you call [`execute`](crate::client::Transaction::execute), 27 | /// there's no need to manually call UNWATCH. 28 | /// 29 | /// # See Also 30 | /// [](https://redis.io/commands/unwatch/) 31 | #[must_use] 32 | fn unwatch(self) -> PreparedCommand<'a, Self, ()> 33 | where 34 | Self: Sized, 35 | { 36 | prepare_command(self, cmd("UNWATCH")) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/network/connection.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{Config, PreparedCommand, ServerConfig}, 3 | commands::InternalPubSubCommands, 4 | resp::{Command, RespBuf}, 5 | ClusterConnection, Error, Future, Result, RetryReason, SentinelConnection, 6 | StandaloneConnection, 7 | }; 8 | use serde::de::DeserializeOwned; 9 | use smallvec::SmallVec; 10 | use std::future::IntoFuture; 11 | 12 | #[allow(clippy::large_enum_variant)] 13 | pub enum Connection { 14 | Standalone(StandaloneConnection), 15 | Sentinel(SentinelConnection), 16 | Cluster(ClusterConnection), 17 | } 18 | 19 | impl Connection { 20 | #[inline] 21 | pub async fn connect(config: Config) -> Result { 22 | match &config.server { 23 | ServerConfig::Standalone { host, port } => Ok(Connection::Standalone( 24 | StandaloneConnection::connect(host, *port, &config).await?, 25 | )), 26 | ServerConfig::Sentinel(sentinel_config) => Ok(Connection::Sentinel( 27 | SentinelConnection::connect(sentinel_config, &config).await?, 28 | )), 29 | ServerConfig::Cluster(cluster_config) => Ok(Connection::Cluster( 30 | ClusterConnection::connect(cluster_config, &config).await?, 31 | )), 32 | } 33 | } 34 | 35 | #[inline] 36 | pub async fn write(&mut self, command: &Command) -> Result<()> { 37 | match self { 38 | Connection::Standalone(connection) => connection.write(command).await, 39 | Connection::Sentinel(connection) => connection.write(command).await, 40 | Connection::Cluster(connection) => connection.write(command).await, 41 | } 42 | } 43 | 44 | #[inline] 45 | pub async fn write_batch( 46 | &mut self, 47 | commands: SmallVec<[&mut Command; 10]>, 48 | retry_reasons: &[RetryReason], 49 | ) -> Result<()> { 50 | match self { 51 | Connection::Standalone(connection) => { 52 | connection.write_batch(commands, retry_reasons).await 53 | } 54 | Connection::Sentinel(connection) => { 55 | connection.write_batch(commands, retry_reasons).await 56 | } 57 | Connection::Cluster(connection) => { 58 | connection.write_batch(commands, retry_reasons).await 59 | } 60 | } 61 | } 62 | 63 | #[inline] 64 | pub async fn read(&mut self) -> Option> { 65 | match self { 66 | Connection::Standalone(connection) => connection.read().await, 67 | Connection::Sentinel(connection) => connection.read().await, 68 | Connection::Cluster(connection) => connection.read().await, 69 | } 70 | } 71 | 72 | #[inline] 73 | pub async fn reconnect(&mut self) -> Result<()> { 74 | match self { 75 | Connection::Standalone(connection) => connection.reconnect().await, 76 | Connection::Sentinel(connection) => connection.reconnect().await, 77 | Connection::Cluster(connection) => connection.reconnect().await, 78 | } 79 | } 80 | 81 | #[inline] 82 | pub async fn send(&mut self, command: &Command) -> Result { 83 | self.write(command).await?; 84 | self.read() 85 | .await 86 | .ok_or_else(|| Error::Client("Disconnected by peer".to_owned()))? 87 | } 88 | 89 | pub(crate) fn tag(&self) -> &str { 90 | match self { 91 | Connection::Standalone(connection) => connection.tag(), 92 | Connection::Sentinel(connection) => connection.tag(), 93 | Connection::Cluster(connection) => connection.tag(), 94 | } 95 | } 96 | } 97 | 98 | impl<'a, R> IntoFuture for PreparedCommand<'a, &'a mut Connection, R> 99 | where 100 | R: DeserializeOwned + Send + 'a, 101 | { 102 | type Output = Result; 103 | type IntoFuture = Future<'a, R>; 104 | 105 | #[inline] 106 | fn into_future(self) -> Self::IntoFuture { 107 | Box::pin(async move { 108 | let result = self.executor.send(&self.command).await?; 109 | result.to() 110 | }) 111 | } 112 | } 113 | 114 | impl<'a> InternalPubSubCommands<'a> for &'a mut Connection {} 115 | -------------------------------------------------------------------------------- /src/network/mod.rs: -------------------------------------------------------------------------------- 1 | mod async_executor_strategy; 2 | mod cluster_connection; 3 | mod command_info_manager; 4 | mod connection; 5 | mod network_handler; 6 | mod reconnection_state; 7 | mod sentinel_connection; 8 | mod standalone_connection; 9 | mod util; 10 | mod version; 11 | 12 | pub(crate) use async_executor_strategy::*; 13 | pub(crate) use cluster_connection::*; 14 | pub(crate) use command_info_manager::*; 15 | pub(crate) use connection::*; 16 | pub(crate) use network_handler::*; 17 | pub(crate) use reconnection_state::*; 18 | pub(crate) use sentinel_connection::*; 19 | pub(crate) use standalone_connection::*; 20 | pub(crate) use version::*; 21 | -------------------------------------------------------------------------------- /src/network/reconnection_state.rs: -------------------------------------------------------------------------------- 1 | use crate::client::ReconnectionConfig; 2 | use rand::{rng, Rng}; 3 | use std::cmp; 4 | 5 | pub(crate) struct ReconnectionState { 6 | config: ReconnectionConfig, 7 | attempts: u32, 8 | } 9 | 10 | impl ReconnectionState { 11 | pub fn new(config: ReconnectionConfig) -> Self { 12 | Self { 13 | config, 14 | attempts: 0, 15 | } 16 | } 17 | 18 | /// Reset the number of reconnection attempts. 19 | pub fn reset_attempts(&mut self) { 20 | self.attempts = 0; 21 | } 22 | 23 | /// Calculate the next delay, incrementing `attempts` in the process. 24 | pub fn next_delay(&mut self) -> Option { 25 | match &self.config { 26 | ReconnectionConfig::Constant { 27 | delay, 28 | max_attempts, 29 | jitter, 30 | } => { 31 | self.attempts = incr_with_max(self.attempts, *max_attempts)?; 32 | Some(add_jitter(*delay as u64, *jitter)) 33 | } 34 | ReconnectionConfig::Linear { 35 | max_delay, 36 | max_attempts, 37 | delay, 38 | jitter, 39 | } => { 40 | self.attempts = incr_with_max(self.attempts, *max_attempts)?; 41 | let delay = (*delay as u64).saturating_mul(self.attempts as u64); 42 | 43 | Some(cmp::min(*max_delay as u64, add_jitter(delay, *jitter))) 44 | } 45 | ReconnectionConfig::Exponential { 46 | min_delay, 47 | max_delay, 48 | max_attempts, 49 | multiplicative_factor, 50 | jitter, 51 | } => { 52 | self.attempts = incr_with_max(self.attempts, *max_attempts)?; 53 | let delay = (*multiplicative_factor as u64) 54 | .saturating_pow(self.attempts - 1) 55 | .saturating_mul(*min_delay as u64); 56 | 57 | Some(cmp::min(*max_delay as u64, add_jitter(delay, *jitter))) 58 | } 59 | } 60 | } 61 | } 62 | 63 | fn incr_with_max(curr: u32, max: u32) -> Option { 64 | if max != 0 && curr >= max { 65 | None 66 | } else { 67 | Some(curr.saturating_add(1)) 68 | } 69 | } 70 | 71 | fn add_jitter(delay: u64, jitter: u32) -> u64 { 72 | if jitter == 0 { 73 | delay 74 | } else { 75 | delay.saturating_add(rng().random_range(0..jitter as u64)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/network/sentinel_connection.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{Config, SentinelConfig}, 3 | commands::{RoleResult, SentinelCommands, ServerCommands}, 4 | resp::{Command, RespBuf}, 5 | sleep, Error, Result, RetryReason, StandaloneConnection, 6 | }; 7 | use log::debug; 8 | use smallvec::SmallVec; 9 | 10 | pub struct SentinelConnection { 11 | sentinel_config: SentinelConfig, 12 | config: Config, 13 | pub inner_connection: StandaloneConnection, 14 | } 15 | 16 | impl SentinelConnection { 17 | #[inline] 18 | pub async fn write(&mut self, command: &Command) -> Result<()> { 19 | self.inner_connection.write(command).await 20 | } 21 | 22 | #[inline] 23 | pub async fn write_batch( 24 | &mut self, 25 | commands: SmallVec<[&mut Command; 10]>, 26 | retry_reasons: &[RetryReason], 27 | ) -> Result<()> { 28 | self.inner_connection 29 | .write_batch(commands, retry_reasons) 30 | .await 31 | } 32 | 33 | #[inline] 34 | pub async fn read(&mut self) -> Option> { 35 | self.inner_connection.read().await 36 | } 37 | 38 | #[inline] 39 | pub async fn reconnect(&mut self) -> Result<()> { 40 | self.inner_connection = 41 | Self::connect_to_sentinel(&self.sentinel_config, &self.config).await?; 42 | 43 | Ok(()) 44 | } 45 | 46 | /// Follow `Redis service discovery via Sentinel` documentation 47 | /// #See 48 | /// 49 | /// # Remark 50 | /// this function must be desugared because of async recursion: 51 | /// 52 | pub async fn connect( 53 | sentinel_config: &SentinelConfig, 54 | config: &Config, 55 | ) -> Result { 56 | let inner_connection = Self::connect_to_sentinel(sentinel_config, config).await?; 57 | 58 | Ok(SentinelConnection { 59 | sentinel_config: sentinel_config.clone(), 60 | config: config.clone(), 61 | inner_connection, 62 | }) 63 | } 64 | 65 | async fn connect_to_sentinel( 66 | sentinel_config: &SentinelConfig, 67 | config: &Config, 68 | ) -> Result { 69 | let mut restart = false; 70 | let mut unreachable_sentinel = true; 71 | 72 | let mut sentinel_node_config = config.clone(); 73 | sentinel_node_config 74 | .username 75 | .clone_from(&sentinel_config.username); 76 | sentinel_node_config 77 | .password 78 | .clone_from(&sentinel_config.password); 79 | 80 | loop { 81 | for sentinel_instance in &sentinel_config.instances { 82 | // Step 1: connecting to Sentinel 83 | let (host, port) = sentinel_instance; 84 | 85 | let mut sentinel_connection = 86 | match StandaloneConnection::connect(host, *port, &sentinel_node_config).await { 87 | Ok(sentinel_connection) => sentinel_connection, 88 | Err(e) => { 89 | debug!("Cannot connect to Sentinel {}:{} : {}", *host, *port, e); 90 | continue; 91 | } 92 | }; 93 | 94 | // Step 2: ask for master address 95 | let (master_host, master_port) = match sentinel_connection 96 | .sentinel_get_master_addr_by_name(sentinel_config.service_name.clone()) 97 | .await 98 | { 99 | Ok(Some((master_host, master_port))) => (master_host, master_port), 100 | Ok(None) => { 101 | debug!( 102 | "Sentinel {}:{} does not know master `{}`", 103 | *host, *port, sentinel_config.service_name 104 | ); 105 | unreachable_sentinel = false; 106 | continue; 107 | } 108 | Err(e) => { 109 | debug!("Cannot execute command `SENTINEL get-master-addr-by-name` with Sentinel {}:{}: {}", *host, *port, e); 110 | continue; 111 | } 112 | }; 113 | 114 | // Step 3: call the ROLE command in the target instance 115 | let mut master_connection = 116 | StandaloneConnection::connect(&master_host, master_port, config).await?; 117 | 118 | let role: RoleResult = master_connection.role().await?; 119 | 120 | if let RoleResult::Master { 121 | master_replication_offset: _, 122 | replica_infos: _, 123 | } = role 124 | { 125 | return Ok(master_connection); 126 | } else { 127 | sleep(sentinel_config.wait_between_failures).await; 128 | // restart from the beginning 129 | restart = true; 130 | break; 131 | } 132 | } 133 | 134 | if !restart { 135 | break; 136 | } else { 137 | restart = false; 138 | } 139 | } 140 | 141 | if unreachable_sentinel { 142 | Err(Error::Sentinel( 143 | "All Sentinel instances are unreachable".to_owned(), 144 | )) 145 | } else { 146 | Err(Error::Sentinel(format!( 147 | "master {} is unknown by all Sentinel instances", 148 | sentinel_config.service_name 149 | ))) 150 | } 151 | } 152 | 153 | pub(crate) fn tag(&self) -> &str { 154 | self.inner_connection.tag() 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/network/util.rs: -------------------------------------------------------------------------------- 1 | use crate::resp::{BytesSeed, RespBuf, RespDeserializer}; 2 | use serde::{de::Visitor, Deserializer}; 3 | use std::fmt; 4 | 5 | pub enum RefPubSubMessage<'a> { 6 | Subscribe(&'a [u8]), 7 | PSubscribe(&'a [u8]), 8 | SSubscribe(&'a [u8]), 9 | Unsubscribe(&'a [u8]), 10 | PUnsubscribe(&'a [u8]), 11 | SUnsubscribe(&'a [u8]), 12 | Message(&'a [u8], &'a [u8]), 13 | PMessage(&'a [u8], &'a [u8], &'a [u8]), 14 | SMessage(&'a [u8], &'a [u8]), 15 | } 16 | 17 | impl std::fmt::Debug for RefPubSubMessage<'_> { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | match self { 20 | Self::Subscribe(arg0) => f 21 | .debug_tuple("Subscribe") 22 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 23 | .finish(), 24 | Self::PSubscribe(arg0) => f 25 | .debug_tuple("PSubscribe") 26 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 27 | .finish(), 28 | Self::SSubscribe(arg0) => f 29 | .debug_tuple("SSubscribe") 30 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 31 | .finish(), 32 | Self::Unsubscribe(arg0) => f 33 | .debug_tuple("Unsubscribe") 34 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 35 | .finish(), 36 | Self::PUnsubscribe(arg0) => f 37 | .debug_tuple("PUnsubscribe") 38 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 39 | .finish(), 40 | Self::SUnsubscribe(arg0) => f 41 | .debug_tuple("SUnsubscribe") 42 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 43 | .finish(), 44 | Self::Message(arg0, arg1) => f 45 | .debug_tuple("Message") 46 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 47 | .field(&std::str::from_utf8(arg1).map_err(|_| fmt::Error)?) 48 | .finish(), 49 | Self::PMessage(arg0, arg1, arg2) => f 50 | .debug_tuple("PMessage") 51 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 52 | .field(&std::str::from_utf8(arg1).map_err(|_| fmt::Error)?) 53 | .field(&std::str::from_utf8(arg2).map_err(|_| fmt::Error)?) 54 | .finish(), 55 | Self::SMessage(arg0, arg1) => f 56 | .debug_tuple("SMessage") 57 | .field(&std::str::from_utf8(arg0).map_err(|_| fmt::Error)?) 58 | .field(&std::str::from_utf8(arg1).map_err(|_| fmt::Error)?) 59 | .finish(), 60 | } 61 | } 62 | } 63 | 64 | impl<'a> RefPubSubMessage<'a> { 65 | pub fn from_resp(resp_buffer: &'a RespBuf) -> Option> { 66 | struct RefPubSubMessageVisitor; 67 | 68 | impl<'de> Visitor<'de> for RefPubSubMessageVisitor { 69 | type Value = Option>; 70 | 71 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 72 | formatter.write_str("bool") 73 | } 74 | 75 | fn visit_seq(self, mut seq: A) -> std::result::Result 76 | where 77 | A: serde::de::SeqAccess<'de>, 78 | { 79 | let Ok(Some(kind)) = seq.next_element::<&str>() else { 80 | return Ok(None); 81 | }; 82 | 83 | let Ok(Some(channel_or_pattern)) = seq.next_element_seed(BytesSeed) else { 84 | return Ok(None); 85 | }; 86 | 87 | match kind { 88 | "subscribe" => Ok(Some(RefPubSubMessage::Subscribe(channel_or_pattern))), 89 | "psubscribe" => Ok(Some(RefPubSubMessage::PSubscribe(channel_or_pattern))), 90 | "ssubscribe" => Ok(Some(RefPubSubMessage::SSubscribe(channel_or_pattern))), 91 | "unsubscribe" => Ok(Some(RefPubSubMessage::Unsubscribe(channel_or_pattern))), 92 | "punsubscribe" => Ok(Some(RefPubSubMessage::PUnsubscribe(channel_or_pattern))), 93 | "sunsubscribe" => Ok(Some(RefPubSubMessage::SUnsubscribe(channel_or_pattern))), 94 | "message" => { 95 | let Ok(Some(payload)) = seq.next_element_seed(BytesSeed) else { 96 | return Ok(None); 97 | }; 98 | 99 | Ok(Some(RefPubSubMessage::Message(channel_or_pattern, payload))) 100 | } 101 | "pmessage" => { 102 | let Ok(Some(channel)) = seq.next_element_seed(BytesSeed) else { 103 | return Ok(None); 104 | }; 105 | 106 | let Ok(Some(payload)) = seq.next_element_seed(BytesSeed) else { 107 | return Ok(None); 108 | }; 109 | 110 | Ok(Some(RefPubSubMessage::PMessage( 111 | channel_or_pattern, 112 | channel, 113 | payload, 114 | ))) 115 | } 116 | "smessage" => { 117 | let Ok(Some(payload)) = seq.next_element_seed(BytesSeed) else { 118 | return Ok(None); 119 | }; 120 | 121 | Ok(Some(RefPubSubMessage::SMessage( 122 | channel_or_pattern, 123 | payload, 124 | ))) 125 | } 126 | _ => Ok(None), 127 | } 128 | } 129 | } 130 | 131 | if resp_buffer.is_push_message() { 132 | let mut deserializer = RespDeserializer::new(resp_buffer); 133 | deserializer 134 | .deserialize_seq(RefPubSubMessageVisitor) 135 | .unwrap_or_default() 136 | } else { 137 | None 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/network/version.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | 3 | pub struct Version { 4 | pub major: u8, 5 | #[allow(dead_code)] 6 | pub minor: u8, 7 | #[allow(dead_code)] 8 | pub revision: u8, 9 | } 10 | 11 | impl TryFrom<&str> for Version { 12 | type Error = Error; 13 | 14 | fn try_from(value: &str) -> std::result::Result { 15 | let mut split = value.split('.'); 16 | 17 | let (Some(major), Some(minor), Some(revision), None) = 18 | (split.next(), split.next(), split.next(), split.next()) 19 | else { 20 | return Err(Error::Client( 21 | "Cannot parse Redis server version".to_owned(), 22 | )); 23 | }; 24 | 25 | let (Some(major), Some(minor), Some(revision)) = ( 26 | atoi::atoi(major.as_bytes()), 27 | atoi::atoi(minor.as_bytes()), 28 | atoi::atoi(revision.as_bytes()), 29 | ) else { 30 | return Err(Error::Client( 31 | "Cannot parse Redis server version".to_owned(), 32 | )); 33 | }; 34 | 35 | Ok(Version { 36 | major, 37 | minor, 38 | revision, 39 | }) 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::Version; 46 | 47 | #[test] 48 | fn version() { 49 | let version: Version = "7.0.0".try_into().unwrap(); 50 | assert_eq!((7, 0, 0), (version.major, version.minor, version.revision)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/resp/buffer_decoder.rs: -------------------------------------------------------------------------------- 1 | use super::RespDeserializer; 2 | use crate::{resp::RespBuf, Error, Result}; 3 | use bytes::BytesMut; 4 | use serde::{de::IgnoredAny, Deserialize}; 5 | use tokio_util::codec::Decoder; 6 | 7 | pub(crate) struct BufferDecoder; 8 | 9 | impl Decoder for BufferDecoder { 10 | type Item = RespBuf; 11 | type Error = Error; 12 | 13 | fn decode(&mut self, src: &mut BytesMut) -> Result> { 14 | if src.is_empty() { 15 | return Ok(None); 16 | } 17 | 18 | let bytes = src.as_ref(); 19 | let mut deserializer = RespDeserializer::new(bytes); 20 | let result = IgnoredAny::deserialize(&mut deserializer); 21 | match result { 22 | Ok(_) => Ok(Some(RespBuf::new( 23 | src.split_to(deserializer.get_pos()).freeze(), 24 | ))), 25 | Err(Error::EOF) => Ok(None), 26 | Err(e) => Err(e), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/resp/bulk_string.rs: -------------------------------------------------------------------------------- 1 | use crate::resp::{deserialize_byte_buf, serialize_byte_buf}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::{fmt, ops::Deref}; 4 | 5 | /// Represents the [Bulk String](https://redis.io/docs/reference/protocol-spec/#resp-bulk-strings) RESP type 6 | #[derive(Deserialize, Serialize)] 7 | pub struct BulkString( 8 | #[serde( 9 | deserialize_with = "deserialize_byte_buf", 10 | serialize_with = "serialize_byte_buf" 11 | )] 12 | Vec, 13 | ); 14 | 15 | impl BulkString { 16 | /// Constructs a new `BulkString` from a bytes buffer 17 | #[inline] 18 | pub fn new(bytes: Vec) -> Self { 19 | Self(bytes) 20 | } 21 | 22 | /// Returns the internal buffer as a byte slice 23 | #[inline] 24 | pub fn as_bytes(&self) -> &[u8] { 25 | &self.0 26 | } 27 | } 28 | 29 | impl Deref for BulkString { 30 | type Target = [u8]; 31 | 32 | #[inline] 33 | fn deref(&self) -> &Self::Target { 34 | &self.0 35 | } 36 | } 37 | 38 | impl From for Vec { 39 | #[inline] 40 | fn from(bs: BulkString) -> Self { 41 | bs.0 42 | } 43 | } 44 | 45 | impl From> for BulkString { 46 | #[inline] 47 | fn from(bytes: Vec) -> Self { 48 | BulkString(bytes) 49 | } 50 | } 51 | 52 | impl From<&[u8; N]> for BulkString { 53 | #[inline] 54 | fn from(bytes: &[u8; N]) -> Self { 55 | BulkString(bytes.to_vec()) 56 | } 57 | } 58 | 59 | impl fmt::Debug for BulkString { 60 | #[inline] 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | f.debug_tuple("BulkString").field(&self.0).finish() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/resp/command.rs: -------------------------------------------------------------------------------- 1 | use crate::resp::{CommandArgs, ToArgs}; 2 | 3 | #[cfg(debug_assertions)] 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | 6 | #[cfg(debug_assertions)] 7 | static COMMAND_SEQUENCE_COUNTER: AtomicUsize = AtomicUsize::new(0); 8 | 9 | /// Shortcut function for creating a command. 10 | #[must_use] 11 | #[inline(always)] 12 | pub fn cmd(name: &'static str) -> Command { 13 | Command::new(name) 14 | } 15 | 16 | /// Generic command meant to be sent to the Redis Server 17 | #[derive(Debug, Clone)] 18 | pub struct Command { 19 | /// Name of the command. 20 | /// 21 | /// Note: Sub commands are expressed as the first argument of the command. 22 | /// 23 | /// e.g. `cmd("CONFIG").arg("SET").arg("hash-max-listpack-entries").arg("1024")` 24 | pub name: &'static str, 25 | /// Collection of arguments of the command. 26 | pub args: CommandArgs, 27 | #[doc(hidden)] 28 | #[cfg(debug_assertions)] 29 | pub kill_connection_on_write: usize, 30 | #[cfg(debug_assertions)] 31 | #[allow(unused)] 32 | pub(crate) command_seq: usize, 33 | } 34 | 35 | impl Command { 36 | /// Creates an new command. 37 | /// 38 | /// [`cmd`](crate::resp::cmd) function can be used as a shortcut. 39 | #[must_use] 40 | #[inline(always)] 41 | pub fn new(name: &'static str) -> Self { 42 | Self { 43 | name, 44 | args: CommandArgs::default(), 45 | #[cfg(debug_assertions)] 46 | kill_connection_on_write: 0, 47 | #[cfg(debug_assertions)] 48 | command_seq: COMMAND_SEQUENCE_COUNTER.fetch_add(1, Ordering::SeqCst), 49 | } 50 | } 51 | 52 | /// Builder function to add an argument to an existing command. 53 | #[must_use] 54 | #[inline(always)] 55 | pub fn arg(mut self, arg: A) -> Self 56 | where 57 | A: ToArgs, 58 | { 59 | arg.write_args(&mut self.args); 60 | self 61 | } 62 | 63 | /// Builder function to add an argument to an existing command, only if a condition is `true`. 64 | #[must_use] 65 | #[inline(always)] 66 | pub fn arg_if(mut self, condition: bool, arg: A) -> Self 67 | where 68 | A: ToArgs, 69 | { 70 | if condition { 71 | arg.write_args(&mut self.args); 72 | } 73 | self 74 | } 75 | 76 | #[cfg(debug_assertions)] 77 | #[inline] 78 | pub fn kill_connection_on_write(mut self, num_kills: usize) -> Self { 79 | self.kill_connection_on_write = num_kills; 80 | self 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/resp/command_args.rs: -------------------------------------------------------------------------------- 1 | use smallvec::SmallVec; 2 | 3 | use crate::resp::ToArgs; 4 | use std::fmt; 5 | 6 | /// Collection of arguments of [`Command`](crate::resp::Command). 7 | #[derive(Clone, Default)] 8 | pub struct CommandArgs { 9 | args: SmallVec<[Vec; 10]>, 10 | } 11 | 12 | impl CommandArgs { 13 | /// Builder function to add an argument to an existing command collection. 14 | #[inline] 15 | pub fn arg(&mut self, args: A) -> &mut Self 16 | where 17 | A: ToArgs, 18 | { 19 | args.write_args(self); 20 | self 21 | } 22 | 23 | /// Builder function to add an argument by ref to an existing command collection. 24 | #[inline] 25 | pub fn arg_ref(&mut self, args: &A) -> &mut Self 26 | where 27 | A: ToArgs, 28 | { 29 | args.write_args(self); 30 | self 31 | } 32 | 33 | /// Builder function to add an argument to an existing command collection, 34 | /// only if a condition is `true`. 35 | #[inline] 36 | pub fn arg_if(&mut self, condition: bool, args: A) -> &mut Self 37 | where 38 | A: ToArgs, 39 | { 40 | if condition { 41 | self.arg(args) 42 | } else { 43 | self 44 | } 45 | } 46 | 47 | /// helper to build a CommandArgs in one line. 48 | #[inline] 49 | pub fn build(&mut self) -> Self { 50 | let mut args = CommandArgs::default(); 51 | std::mem::swap(&mut args.args, &mut self.args); 52 | args 53 | } 54 | 55 | /// Number of arguments of the collection 56 | #[must_use] 57 | #[inline] 58 | pub fn len(&self) -> usize { 59 | self.args.len() 60 | } 61 | 62 | /// Check if the collection is empty 63 | #[must_use] 64 | #[inline] 65 | pub fn is_empty(&self) -> bool { 66 | self.len() == 0 67 | } 68 | 69 | #[inline] 70 | pub(crate) fn write_arg(&mut self, buf: &[u8]) { 71 | self.args.push(buf.to_vec()); 72 | } 73 | 74 | pub(crate) fn retain(&mut self, mut f: F) 75 | where 76 | F: FnMut(&[u8]) -> bool, 77 | { 78 | self.args.retain(|arg| f(arg)) 79 | } 80 | } 81 | 82 | impl<'a> IntoIterator for &'a CommandArgs { 83 | type Item = &'a [u8]; 84 | type IntoIter = CommandArgsIterator<'a>; 85 | 86 | #[inline] 87 | fn into_iter(self) -> Self::IntoIter { 88 | CommandArgsIterator { 89 | iter: self.args.iter(), 90 | } 91 | } 92 | } 93 | 94 | /// [`CommandArgs`] iterator 95 | pub struct CommandArgsIterator<'a> { 96 | iter: std::slice::Iter<'a, Vec>, 97 | } 98 | 99 | impl<'a> Iterator for CommandArgsIterator<'a> { 100 | type Item = &'a [u8]; 101 | 102 | #[inline] 103 | fn next(&mut self) -> Option { 104 | self.iter.next().map(|v| v.as_slice()) 105 | } 106 | } 107 | 108 | impl std::ops::Deref for CommandArgs { 109 | type Target = [Vec]; 110 | 111 | #[inline] 112 | fn deref(&self) -> &Self::Target { 113 | &self.args 114 | } 115 | } 116 | 117 | impl fmt::Debug for CommandArgs { 118 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 119 | f.debug_struct("CommandArgs") 120 | .field( 121 | "args", 122 | &self 123 | .args 124 | .iter() 125 | .map(|a| String::from_utf8_lossy(a.as_slice())) 126 | .collect::>(), 127 | ) 128 | .finish() 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/resp/command_encoder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | resp::{Command, CommandArgs}, 3 | Error, Result, 4 | }; 5 | use bytes::{BufMut, BytesMut}; 6 | use tokio_util::codec::Encoder; 7 | 8 | pub(crate) struct CommandEncoder; 9 | 10 | impl Encoder<&Command> for CommandEncoder { 11 | type Error = Error; 12 | 13 | #[inline] 14 | fn encode(&mut self, command: &Command, buf: &mut BytesMut) -> Result<()> { 15 | buf.reserve(calculate_buf_size(command)); 16 | 17 | buf.put_u8(b'*'); 18 | encode_integer(command.args.len() as i64 + 1, buf); 19 | encode_crlf(buf); 20 | encode_bulkstring(command.name.as_bytes(), buf); 21 | encode_command_args(&command.args, buf); 22 | Ok(()) 23 | } 24 | } 25 | 26 | #[inline] 27 | fn calculate_buf_size(command: &Command) -> usize { 28 | let mut buf_size = 0; 29 | 30 | // *\r\n 31 | let num_args = command.args.len() + 1; 32 | buf_size += if num_args <= 9 { 4 } else { 5 }; 33 | 34 | // $\r\n\r\n 35 | let name = command.name.as_bytes(); 36 | buf_size += if name.len() <= 9 { 37 | 6 + name.len() 38 | } else { 39 | 7 + name.len() 40 | }; 41 | 42 | for arg in &command.args { 43 | // $\r\n\r\n 44 | buf_size += if arg.len() <= 9 { 45 | 6 + arg.len() 46 | } else { 47 | 7 + arg.len() 48 | }; 49 | } 50 | 51 | buf_size 52 | } 53 | 54 | #[inline] 55 | fn encode_bulkstring(arg: &[u8], buf: &mut BytesMut) { 56 | buf.put_u8(b'$'); 57 | encode_integer(arg.len() as i64, buf); 58 | encode_crlf(buf); 59 | buf.put(arg); 60 | encode_crlf(buf); 61 | } 62 | 63 | #[inline] 64 | fn encode_command_args(args: &CommandArgs, buf: &mut BytesMut) { 65 | for arg in args { 66 | encode_bulkstring(arg, buf); 67 | } 68 | } 69 | 70 | #[inline] 71 | fn encode_integer(i: i64, buf: &mut BytesMut) { 72 | let mut buffer = itoa::Buffer::new(); 73 | let str = buffer.format(i); 74 | buf.put(str.as_bytes()); 75 | } 76 | 77 | #[inline] 78 | fn encode_crlf(buf: &mut BytesMut) { 79 | buf.put(&b"\r\n"[..]); 80 | } 81 | -------------------------------------------------------------------------------- /src/resp/resp_batch_deserializer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | resp::{RespBuf, RespDeserializer}, 3 | Error, Result, 4 | }; 5 | use serde::{de::DeserializeSeed, forward_to_deserialize_any, Deserializer}; 6 | use std::slice; 7 | 8 | pub(crate) struct RespBatchDeserializer<'de> { 9 | bufs: &'de Vec, 10 | } 11 | 12 | impl<'de> RespBatchDeserializer<'de> { 13 | pub fn new(bufs: &'de Vec) -> RespBatchDeserializer<'de> { 14 | RespBatchDeserializer { bufs } 15 | } 16 | } 17 | 18 | impl<'de> Deserializer<'de> for &'de RespBatchDeserializer<'de> { 19 | type Error = Error; 20 | 21 | fn deserialize_any(self, visitor: V) -> Result 22 | where 23 | V: serde::de::Visitor<'de>, 24 | { 25 | self.deserialize_seq(visitor) 26 | } 27 | 28 | forward_to_deserialize_any! { 29 | bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string 30 | bytes byte_buf option unit_struct newtype_struct tuple 31 | tuple_struct map struct enum identifier ignored_any 32 | } 33 | 34 | fn deserialize_seq(self, visitor: V) -> Result 35 | where 36 | V: serde::de::Visitor<'de>, 37 | { 38 | visitor.visit_seq(SeqAccess::new(self.bufs)) 39 | } 40 | 41 | fn deserialize_unit(self, visitor: V) -> Result 42 | where 43 | V: serde::de::Visitor<'de>, 44 | { 45 | if self.bufs.is_empty() { 46 | visitor.visit_unit() 47 | } else { 48 | self.deserialize_seq(visitor) 49 | } 50 | } 51 | } 52 | 53 | struct SeqAccess<'de> { 54 | iter: slice::Iter<'de, RespBuf>, 55 | len: usize, 56 | } 57 | 58 | impl<'de> SeqAccess<'de> { 59 | pub fn new(bufs: &'de [RespBuf]) -> Self { 60 | Self { 61 | len: bufs.len(), 62 | iter: bufs.iter(), 63 | } 64 | } 65 | } 66 | 67 | impl<'de> serde::de::SeqAccess<'de> for SeqAccess<'de> { 68 | type Error = Error; 69 | 70 | fn next_element_seed(&mut self, seed: T) -> Result> 71 | where 72 | T: DeserializeSeed<'de>, 73 | { 74 | match self.iter.next() { 75 | Some(buf) => seed.deserialize(&mut RespDeserializer::new(buf)).map(Some), 76 | None => Ok(None), 77 | } 78 | } 79 | 80 | fn size_hint(&self) -> Option { 81 | Some(self.len) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/resp/resp_buf.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | resp::{ 3 | RespDeserializer, Value, ARRAY_TAG, BLOB_ERROR_TAG, ERROR_TAG, PUSH_TAG, SIMPLE_STRING_TAG, 4 | }, 5 | Result, 6 | }; 7 | use bytes::{BufMut, Bytes, BytesMut}; 8 | use serde::Deserialize; 9 | use std::{fmt, ops::Deref}; 10 | 11 | /// Represents a [RESP](https://redis.io/docs/reference/protocol-spec/) Buffer incoming from the network 12 | #[derive(Clone)] 13 | pub struct RespBuf(Bytes); 14 | 15 | impl RespBuf { 16 | /// Constructs a new `RespBuf` from a `Bytes` buffer 17 | #[inline] 18 | pub fn new(bytes: Bytes) -> Self { 19 | Self(bytes) 20 | } 21 | 22 | /// Constructs a new `RespBuf` as a RESP Array from a collection of chunks (byte slices) 23 | pub fn from_chunks(chunks: &Vec<&[u8]>) -> Self { 24 | let mut bytes = BytesMut::new(); 25 | 26 | bytes.put_u8(ARRAY_TAG); 27 | 28 | let mut temp = itoa::Buffer::new(); 29 | let str = temp.format(chunks.len()); 30 | bytes.put_slice(str.as_bytes()); 31 | bytes.put_slice(b"\r\n"); 32 | 33 | for chunk in chunks { 34 | bytes.put_slice(chunk) 35 | } 36 | 37 | Self(bytes.freeze()) 38 | } 39 | 40 | /// Constructs a new `RespBuf` from a byte slice 41 | #[inline] 42 | pub fn from_slice(data: &[u8]) -> RespBuf { 43 | RespBuf(Bytes::copy_from_slice(data)) 44 | } 45 | 46 | /// Returns `true` if the RESP Buffer is a push message 47 | #[inline] 48 | pub fn is_push_message(&self) -> bool { 49 | (!self.0.is_empty() && self.0[0] == PUSH_TAG) || self.is_monitor_message() 50 | } 51 | 52 | /// Returns `true` if the RESP Buffer is a monitor message 53 | #[inline] 54 | pub fn is_monitor_message(&self) -> bool { 55 | self.0.len() > 1 && self.0[0] == SIMPLE_STRING_TAG && (self.0[1] as char).is_numeric() 56 | } 57 | 58 | /// Returns `true` if the RESP Buffer is a Redis error 59 | #[inline] 60 | pub fn is_error(&self) -> bool { 61 | self.0.len() > 1 && (self.0[0] == ERROR_TAG || self.0[0] == BLOB_ERROR_TAG) 62 | } 63 | 64 | /// Convert the RESP Buffer to a Rust type `T` by using serde deserialization 65 | #[inline] 66 | pub fn to<'de, T: Deserialize<'de>>(&'de self) -> Result { 67 | let mut deserializer = RespDeserializer::new(&self.0); 68 | T::deserialize(&mut deserializer) 69 | } 70 | 71 | /// Returns the internal buffer as a byte slice 72 | #[inline] 73 | pub fn as_bytes(&self) -> &[u8] { 74 | &self.0 75 | } 76 | 77 | /// Constructs a new `RespBuf` as a RESP Ok message (+OK\r\n) 78 | #[inline] 79 | pub fn ok() -> RespBuf { 80 | RespBuf(Bytes::from_static(b"+OK\r\n")) 81 | } 82 | 83 | /// Constructs a new `RespBuf` as a RESP Nil message (_\r\n) 84 | #[inline] 85 | pub fn nil() -> RespBuf { 86 | RespBuf(Bytes::from_static(b"_\r\n")) 87 | } 88 | } 89 | 90 | impl Deref for RespBuf { 91 | type Target = [u8]; 92 | 93 | #[inline] 94 | fn deref(&self) -> &Self::Target { 95 | &self.0 96 | } 97 | } 98 | 99 | impl fmt::Display for RespBuf { 100 | #[inline] 101 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 102 | match self.to::() { 103 | Ok(value) => { 104 | let str = format!("{value:?}"); 105 | if str.len() > 1000 { 106 | f.write_str(&str[..1000]) 107 | } else { 108 | f.write_str(&str) 109 | } 110 | } 111 | Err(e) => f.write_fmt(format_args!("RESP buffer error: {e:?}")), 112 | } 113 | } 114 | } 115 | 116 | impl fmt::Debug for RespBuf { 117 | #[inline] 118 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 119 | fmt::Display::fmt(&self, f) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/resp/response.rs: -------------------------------------------------------------------------------- 1 | use crate::resp::{BulkString, Value}; 2 | use serde::de::DeserializeOwned; 3 | use smallvec::SmallVec; 4 | use std::{ 5 | collections::{BTreeMap, BTreeSet, HashMap, HashSet}, 6 | hash::{BuildHasher, Hash}, 7 | }; 8 | 9 | /// Marker for a RESP Response 10 | pub trait Response {} 11 | 12 | impl Response for T where T: DeserializeOwned {} 13 | 14 | /// Marker for a primitive response 15 | pub trait PrimitiveResponse: Response {} 16 | 17 | impl PrimitiveResponse for Value {} 18 | impl PrimitiveResponse for () {} 19 | impl PrimitiveResponse for i8 {} 20 | impl PrimitiveResponse for u16 {} 21 | impl PrimitiveResponse for i16 {} 22 | impl PrimitiveResponse for u32 {} 23 | impl PrimitiveResponse for i32 {} 24 | impl PrimitiveResponse for u64 {} 25 | impl PrimitiveResponse for i64 {} 26 | impl PrimitiveResponse for usize {} 27 | impl PrimitiveResponse for isize {} 28 | impl PrimitiveResponse for f32 {} 29 | impl PrimitiveResponse for f64 {} 30 | impl PrimitiveResponse for bool {} 31 | impl PrimitiveResponse for String {} 32 | impl PrimitiveResponse for BulkString {} 33 | impl PrimitiveResponse for Option {} 34 | 35 | /// Marker for a collection response 36 | pub trait CollectionResponse: Response 37 | where 38 | T: Response + DeserializeOwned, 39 | { 40 | } 41 | 42 | impl CollectionResponse for () where T: Response + DeserializeOwned {} 43 | impl CollectionResponse for [T; 2] where T: Response + DeserializeOwned {} 44 | impl CollectionResponse for [T; 3] where T: Response + DeserializeOwned {} 45 | impl CollectionResponse for [T; 4] where T: Response + DeserializeOwned {} 46 | impl CollectionResponse for [T; 5] where T: Response + DeserializeOwned {} 47 | impl CollectionResponse for [T; 6] where T: Response + DeserializeOwned {} 48 | impl CollectionResponse for [T; 7] where T: Response + DeserializeOwned {} 49 | impl CollectionResponse for [T; 8] where T: Response + DeserializeOwned {} 50 | impl CollectionResponse for [T; 9] where T: Response + DeserializeOwned {} 51 | impl CollectionResponse for [T; 10] where T: Response + DeserializeOwned {} 52 | impl CollectionResponse for [T; 11] where T: Response + DeserializeOwned {} 53 | impl CollectionResponse for [T; 12] where T: Response + DeserializeOwned {} 54 | impl CollectionResponse for [T; 13] where T: Response + DeserializeOwned {} 55 | impl CollectionResponse for [T; 14] where T: Response + DeserializeOwned {} 56 | impl CollectionResponse for [T; 15] where T: Response + DeserializeOwned {} 57 | impl CollectionResponse for Vec where T: Response + DeserializeOwned {} 58 | impl CollectionResponse for SmallVec 59 | where 60 | A: smallvec::Array, 61 | T: Response + DeserializeOwned, 62 | { 63 | } 64 | impl CollectionResponse for HashSet where 65 | T: Response + Eq + Hash + DeserializeOwned 66 | { 67 | } 68 | impl CollectionResponse for BTreeSet where T: Response + Ord + DeserializeOwned {} 69 | 70 | /// Marker for key/value collection response 71 | pub trait KeyValueCollectionResponse: Response 72 | where 73 | K: PrimitiveResponse, 74 | V: Response, 75 | { 76 | } 77 | 78 | impl KeyValueCollectionResponse for () 79 | where 80 | K: PrimitiveResponse, 81 | V: Response, 82 | { 83 | } 84 | 85 | impl KeyValueCollectionResponse for Vec<(K, V)> 86 | where 87 | K: PrimitiveResponse + DeserializeOwned, 88 | V: Response + DeserializeOwned, 89 | { 90 | } 91 | 92 | impl KeyValueCollectionResponse for SmallVec 93 | where 94 | A: smallvec::Array, 95 | K: PrimitiveResponse + DeserializeOwned, 96 | V: Response + DeserializeOwned, 97 | { 98 | } 99 | 100 | impl KeyValueCollectionResponse for HashMap 101 | where 102 | K: PrimitiveResponse + Eq + Hash + DeserializeOwned, 103 | V: Response + DeserializeOwned, 104 | { 105 | } 106 | 107 | impl KeyValueCollectionResponse for BTreeMap 108 | where 109 | K: PrimitiveResponse + Ord + DeserializeOwned, 110 | V: Response + DeserializeOwned, 111 | { 112 | } 113 | -------------------------------------------------------------------------------- /src/resp/util.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | de::{self, DeserializeOwned, DeserializeSeed, Visitor}, 3 | Deserializer, Serializer, 4 | }; 5 | use std::{fmt, marker::PhantomData}; 6 | 7 | /// Deserialize a Vec of pairs from a sequence 8 | pub fn deserialize_vec_of_pairs<'de, D, T1, T2>( 9 | deserializer: D, 10 | ) -> std::result::Result, D::Error> 11 | where 12 | D: Deserializer<'de>, 13 | T1: DeserializeOwned, 14 | T2: DeserializeOwned, 15 | { 16 | struct VecOfPairsVisitor 17 | where 18 | T1: DeserializeOwned, 19 | T2: DeserializeOwned, 20 | { 21 | phantom: PhantomData<(T1, T2)>, 22 | } 23 | 24 | impl<'de, T1, T2> Visitor<'de> for VecOfPairsVisitor 25 | where 26 | T1: DeserializeOwned, 27 | T2: DeserializeOwned, 28 | { 29 | type Value = Vec<(T1, T2)>; 30 | 31 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 32 | formatter.write_str("Vec<(T1, T2)>") 33 | } 34 | 35 | fn visit_seq(self, mut seq: A) -> Result 36 | where 37 | A: serde::de::SeqAccess<'de>, 38 | { 39 | let mut v = if let Some(size) = seq.size_hint() { 40 | Vec::with_capacity(size / 2) 41 | } else { 42 | Vec::new() 43 | }; 44 | 45 | while let Some(first) = seq.next_element()? { 46 | let Some(second) = seq.next_element()? else { 47 | return Err(de::Error::custom("invalid length")); 48 | }; 49 | 50 | v.push((first, second)); 51 | } 52 | 53 | Ok(v) 54 | } 55 | } 56 | 57 | deserializer.deserialize_seq(VecOfPairsVisitor { 58 | phantom: PhantomData, 59 | }) 60 | } 61 | 62 | /// Deserialize a Vec of triplets from a sequence 63 | pub fn deserialize_vec_of_triplets<'de, D, T1, T2, T3>( 64 | deserializer: D, 65 | ) -> std::result::Result, D::Error> 66 | where 67 | D: Deserializer<'de>, 68 | T1: DeserializeOwned, 69 | T2: DeserializeOwned, 70 | T3: DeserializeOwned, 71 | { 72 | struct VecOfTripletVisitor 73 | where 74 | T1: DeserializeOwned, 75 | T2: DeserializeOwned, 76 | T3: DeserializeOwned, 77 | { 78 | phantom: PhantomData<(T1, T2, T3)>, 79 | } 80 | 81 | impl<'de, T1, T2, T3> Visitor<'de> for VecOfTripletVisitor 82 | where 83 | T1: DeserializeOwned, 84 | T2: DeserializeOwned, 85 | T3: DeserializeOwned, 86 | { 87 | type Value = Vec<(T1, T2, T3)>; 88 | 89 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 90 | formatter.write_str("Vec<(T1, T2, T3)>") 91 | } 92 | 93 | fn visit_seq(self, mut seq: A) -> Result 94 | where 95 | A: serde::de::SeqAccess<'de>, 96 | { 97 | let mut v = if let Some(size) = seq.size_hint() { 98 | Vec::with_capacity(size / 3) 99 | } else { 100 | Vec::new() 101 | }; 102 | 103 | while let Some(first) = seq.next_element()? { 104 | let Some(second) = seq.next_element()? else { 105 | return Err(de::Error::custom("invalid length")); 106 | }; 107 | 108 | let Some(third) = seq.next_element()? else { 109 | return Err(de::Error::custom("invalid length")); 110 | }; 111 | 112 | v.push((first, second, third)); 113 | } 114 | 115 | Ok(v) 116 | } 117 | } 118 | 119 | deserializer.deserialize_seq(VecOfTripletVisitor { 120 | phantom: PhantomData, 121 | }) 122 | } 123 | 124 | /// Deserialize a byte buffer (Vec\) 125 | pub fn deserialize_byte_buf<'de, D>(deserializer: D) -> std::result::Result, D::Error> 126 | where 127 | D: Deserializer<'de>, 128 | { 129 | struct ByteBufVisitor; 130 | 131 | impl Visitor<'_> for ByteBufVisitor { 132 | type Value = Vec; 133 | 134 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 135 | formatter.write_str("Vec") 136 | } 137 | 138 | fn visit_byte_buf(self, v: Vec) -> Result 139 | where 140 | E: serde::de::Error, 141 | { 142 | Ok(v) 143 | } 144 | } 145 | 146 | deserializer.deserialize_byte_buf(ByteBufVisitor) 147 | } 148 | 149 | /// Serialize a byte buffer (&\[u8\]) 150 | pub fn serialize_byte_buf(bytes: &[u8], serializer: S) -> Result 151 | where 152 | S: Serializer, 153 | { 154 | serializer.serialize_bytes(bytes) 155 | } 156 | 157 | pub(crate) struct ByteBufSeed; 158 | 159 | impl<'de> DeserializeSeed<'de> for ByteBufSeed { 160 | type Value = Vec; 161 | 162 | fn deserialize(self, deserializer: D) -> std::result::Result 163 | where 164 | D: Deserializer<'de>, 165 | { 166 | deserialize_byte_buf(deserializer) 167 | } 168 | } 169 | 170 | /// Deserialize a byte slice (&\[u8\]) 171 | pub fn deserialize_bytes<'de, D>(deserializer: D) -> std::result::Result<&'de [u8], D::Error> 172 | where 173 | D: Deserializer<'de>, 174 | { 175 | struct ByteBufVisitor; 176 | 177 | impl<'de> Visitor<'de> for ByteBufVisitor { 178 | type Value = &'de [u8]; 179 | 180 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 181 | formatter.write_str("&'de [u8]") 182 | } 183 | 184 | fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result 185 | where 186 | E: de::Error, 187 | { 188 | Ok(v) 189 | } 190 | } 191 | 192 | deserializer.deserialize_bytes(ByteBufVisitor) 193 | } 194 | 195 | pub(crate) struct BytesSeed; 196 | 197 | impl<'de> DeserializeSeed<'de> for BytesSeed { 198 | type Value = &'de [u8]; 199 | 200 | fn deserialize(self, deserializer: D) -> std::result::Result 201 | where 202 | D: Deserializer<'de>, 203 | { 204 | deserialize_bytes(deserializer) 205 | } 206 | } 207 | 208 | #[derive(Default)] 209 | pub(crate) struct VecOfPairsSeed 210 | where 211 | T1: DeserializeOwned, 212 | T2: DeserializeOwned, 213 | { 214 | phatom: PhantomData<(T1, T2)>, 215 | } 216 | 217 | impl VecOfPairsSeed 218 | where 219 | T1: DeserializeOwned, 220 | T2: DeserializeOwned, 221 | { 222 | #[allow(dead_code)] 223 | pub fn new() -> Self { 224 | Self { 225 | phatom: PhantomData, 226 | } 227 | } 228 | } 229 | 230 | impl<'de, T1, T2> DeserializeSeed<'de> for VecOfPairsSeed 231 | where 232 | T1: DeserializeOwned, 233 | T2: DeserializeOwned, 234 | { 235 | type Value = Vec<(T1, T2)>; 236 | 237 | #[inline] 238 | fn deserialize(self, deserializer: D) -> std::result::Result 239 | where 240 | D: Deserializer<'de>, 241 | { 242 | deserialize_vec_of_pairs(deserializer) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/resp/value.rs: -------------------------------------------------------------------------------- 1 | use crate::{RedisError, Result}; 2 | use serde::de::DeserializeOwned; 3 | use std::{ 4 | collections::HashMap, 5 | fmt::{self, Display, Formatter, Write}, 6 | hash::{Hash, Hasher}, 7 | }; 8 | 9 | /// Generic Redis Object Model 10 | /// 11 | /// This enum is a direct mapping to [`Redis serialization protocol`](https://redis.io/docs/reference/protocol-spec/) (RESP) 12 | #[derive(Default)] 13 | pub enum Value { 14 | /// [RESP Simple String](https://redis.io/docs/reference/protocol-spec/#resp-simple-strings) 15 | SimpleString(String), 16 | /// [RESP Integer](https://redis.io/docs/reference/protocol-spec/#resp-integers) 17 | Integer(i64), 18 | /// [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) Double 19 | Double(f64), 20 | /// [RESP Bulk String](https://redis.io/docs/reference/protocol-spec/#resp-bulk-strings) 21 | BulkString(Vec), 22 | /// [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) Boolean 23 | Boolean(bool), 24 | /// [RESP Array](https://redis.io/docs/reference/protocol-spec/#resp-arrays) 25 | Array(Vec), 26 | /// [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) Map type 27 | Map(HashMap), 28 | /// [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) Push 29 | Set(Vec), 30 | /// [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) Set reply 31 | Push(Vec), 32 | /// [RESP Error](https://redis.io/docs/reference/protocol-spec/#resp-errors) 33 | Error(RedisError), 34 | /// [RESP Null](https://redis.io/docs/reference/protocol-spec/#resp-bulk-strings) 35 | #[default] 36 | Nil, 37 | } 38 | 39 | impl Value { 40 | /// A [`Value`](crate::resp::Value) to user type conversion that consumes the input value. 41 | /// 42 | /// # Errors 43 | /// Any parsing error ([`Error::Client`](crate::Error::Client)) due to incompatibility between Value variant and taget type 44 | #[inline] 45 | pub fn into(self) -> Result 46 | where 47 | T: DeserializeOwned, 48 | { 49 | T::deserialize(&self) 50 | } 51 | } 52 | 53 | impl Hash for Value { 54 | fn hash(&self, state: &mut H) { 55 | match self { 56 | Value::SimpleString(s) => s.hash(state), 57 | Value::Integer(i) => i.hash(state), 58 | Value::Double(d) => d.to_string().hash(state), 59 | Value::BulkString(bs) => bs.hash(state), 60 | Value::Error(e) => e.hash(state), 61 | Value::Nil => "_\r\n".hash(state), 62 | _ => unimplemented!("Hash not implemented for {self}"), 63 | } 64 | } 65 | } 66 | 67 | impl PartialEq for Value { 68 | fn eq(&self, other: &Self) -> bool { 69 | match (self, other) { 70 | (Self::SimpleString(l0), Self::SimpleString(r0)) => l0 == r0, 71 | (Self::Integer(l0), Self::Integer(r0)) => l0 == r0, 72 | (Self::Double(l0), Self::Double(r0)) => l0 == r0, 73 | (Self::BulkString(l0), Self::BulkString(r0)) => l0 == r0, 74 | (Self::Array(l0), Self::Array(r0)) => l0 == r0, 75 | (Self::Map(l0), Self::Map(r0)) => l0 == r0, 76 | (Self::Set(l0), Self::Set(r0)) => l0 == r0, 77 | (Self::Push(l0), Self::Push(r0)) => l0 == r0, 78 | (Self::Error(l0), Self::Error(r0)) => l0 == r0, 79 | _ => core::mem::discriminant(self) == core::mem::discriminant(other), 80 | } 81 | } 82 | } 83 | 84 | impl Eq for Value {} 85 | 86 | impl Display for Value { 87 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 88 | match &self { 89 | Value::SimpleString(s) => s.fmt(f), 90 | Value::Integer(i) => i.fmt(f), 91 | Value::Double(d) => d.fmt(f), 92 | Value::BulkString(s) => String::from_utf8_lossy(s).fmt(f), 93 | Value::Boolean(b) => b.fmt(f), 94 | Value::Array(v) => { 95 | f.write_char('[')?; 96 | let mut first = true; 97 | for value in v { 98 | if !first { 99 | f.write_str(", ")?; 100 | } 101 | first = false; 102 | value.fmt(f)?; 103 | } 104 | f.write_char(']') 105 | } 106 | Value::Map(m) => { 107 | f.write_char('{')?; 108 | let mut first = true; 109 | for (key, value) in m { 110 | if !first { 111 | f.write_str(", ")?; 112 | } 113 | first = false; 114 | key.fmt(f)?; 115 | f.write_str(": ")?; 116 | value.fmt(f)?; 117 | } 118 | f.write_char('}') 119 | } 120 | Value::Set(v) => { 121 | f.write_char('[')?; 122 | let mut first = true; 123 | for value in v { 124 | if !first { 125 | f.write_str(", ")?; 126 | } 127 | first = false; 128 | value.fmt(f)?; 129 | } 130 | f.write_char(']') 131 | } 132 | Value::Push(v) => { 133 | f.write_char('[')?; 134 | let mut first = true; 135 | for value in v { 136 | if !first { 137 | f.write_str(", ")?; 138 | } 139 | first = false; 140 | value.fmt(f)?; 141 | } 142 | f.write_char(']') 143 | } 144 | Value::Error(e) => e.fmt(f), 145 | Value::Nil => f.write_str("Nil"), 146 | } 147 | } 148 | } 149 | 150 | impl fmt::Debug for Value { 151 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 152 | match self { 153 | Self::SimpleString(arg0) => f.debug_tuple("SimpleString").field(arg0).finish(), 154 | Self::Integer(arg0) => f.debug_tuple("Integer").field(arg0).finish(), 155 | Self::Double(arg0) => f.debug_tuple("Double").field(arg0).finish(), 156 | Self::BulkString(arg0) => f 157 | .debug_tuple("BulkString") 158 | .field(&String::from_utf8_lossy(arg0).into_owned()) 159 | .finish(), 160 | Self::Boolean(arg0) => f.debug_tuple("Boolean").field(arg0).finish(), 161 | Self::Array(arg0) => f.debug_tuple("Array").field(arg0).finish(), 162 | Self::Map(arg0) => f.debug_tuple("Map").field(arg0).finish(), 163 | Self::Set(arg0) => f.debug_tuple("Set").field(arg0).finish(), 164 | Self::Push(arg0) => f.debug_tuple("Push").field(arg0).finish(), 165 | Self::Error(arg0) => f.debug_tuple("Error").field(arg0).finish(), 166 | Self::Nil => write!(f, "Nil"), 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/resp/value_serialize.rs: -------------------------------------------------------------------------------- 1 | use crate::resp::{Value, ERROR_FAKE_FIELD, PUSH_FAKE_FIELD, SET_FAKE_FIELD}; 2 | use serde::{ 3 | ser::{SerializeMap, SerializeSeq, SerializeTupleStruct}, 4 | Serialize, 5 | }; 6 | 7 | impl Serialize for Value { 8 | fn serialize(&self, serializer: S) -> Result 9 | where 10 | S: serde::Serializer, 11 | { 12 | match self { 13 | Value::SimpleString(s) => serializer.serialize_str(s), 14 | Value::Integer(i) => serializer.serialize_i64(*i), 15 | Value::Double(d) => serializer.serialize_f64(*d), 16 | Value::BulkString(bs) => serializer.serialize_bytes(bs), 17 | Value::Boolean(b) => serializer.serialize_bool(*b), 18 | Value::Array(a) => { 19 | let mut seq = serializer.serialize_seq(Some(a.len()))?; 20 | for e in a { 21 | seq.serialize_element(e)?; 22 | } 23 | seq.end() 24 | } 25 | Value::Map(m) => { 26 | let mut map = serializer.serialize_map(Some(m.len()))?; 27 | for (k, v) in m { 28 | map.serialize_entry(k, v)?; 29 | } 30 | map.end() 31 | } 32 | Value::Set(s) => { 33 | let mut ts = serializer.serialize_tuple_struct(SET_FAKE_FIELD, s.len())?; 34 | for e in s { 35 | ts.serialize_field(e)?; 36 | } 37 | ts.end() 38 | } 39 | Value::Push(p) => { 40 | let mut ts = serializer.serialize_tuple_struct(PUSH_FAKE_FIELD, p.len())?; 41 | for e in p { 42 | ts.serialize_field(e)?; 43 | } 44 | ts.end() 45 | } 46 | Value::Error(e) => { 47 | serializer.serialize_newtype_struct(ERROR_FAKE_FIELD, e.to_string().as_str()) 48 | } 49 | Value::Nil => serializer.serialize_unit(), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/tests/bitmap_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{ 3 | BitFieldGetSubCommand, BitFieldOverflow, BitFieldSubCommand, BitOperation, BitRange, 4 | BitUnit, BitmapCommands, StringCommands, 5 | }, 6 | tests::get_test_client, 7 | Result, 8 | }; 9 | use serial_test::serial; 10 | 11 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 12 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 13 | #[serial] 14 | async fn bitcount() -> Result<()> { 15 | let client = get_test_client().await?; 16 | 17 | client.set("mykey", "foobar").await?; 18 | 19 | let count = client.bitcount("mykey", BitRange::default()).await?; 20 | assert_eq!(26, count); 21 | 22 | let count = client.bitcount("mykey", BitRange::range(0, 0)).await?; 23 | assert_eq!(4, count); 24 | 25 | let count = client.bitcount("mykey", BitRange::range(1, 1)).await?; 26 | assert_eq!(6, count); 27 | 28 | let count = client 29 | .bitcount("mykey", BitRange::range(1, 1).unit(BitUnit::Byte)) 30 | .await?; 31 | assert_eq!(6, count); 32 | 33 | let count = client 34 | .bitcount("mykey", BitRange::range(5, 30).unit(BitUnit::Bit)) 35 | .await?; 36 | assert_eq!(17, count); 37 | 38 | client.close().await?; 39 | 40 | Ok(()) 41 | } 42 | 43 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 44 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 45 | #[serial] 46 | async fn bitfield() -> Result<()> { 47 | let client = get_test_client().await?; 48 | 49 | client.set("mykey", "foobar").await?; 50 | 51 | let results = client 52 | .bitfield( 53 | "mykey", 54 | [ 55 | BitFieldSubCommand::incr_by("i5", 100, 1), 56 | BitFieldSubCommand::get("u4", 0), 57 | ], 58 | ) 59 | .await?; 60 | assert!(matches!(results[..], [1, 6])); 61 | 62 | client.set("mykey", "foobar").await?; 63 | 64 | let results = client 65 | .bitfield( 66 | "mykey", 67 | [ 68 | BitFieldSubCommand::set("i8", "#0", 65), 69 | BitFieldSubCommand::set("i8", "#1", 66), 70 | ], 71 | ) 72 | .await?; 73 | assert!(matches!(results[..], [102, 111])); 74 | 75 | client.set("mykey", "foobar").await?; 76 | 77 | let results = client 78 | .bitfield( 79 | "mykey", 80 | [ 81 | BitFieldSubCommand::incr_by("u2", "100", 1), 82 | BitFieldSubCommand::overflow(BitFieldOverflow::Sat), 83 | BitFieldSubCommand::incr_by("u2", "102", 1), 84 | ], 85 | ) 86 | .await?; 87 | assert!(matches!(results[..], [1, 1])); 88 | 89 | let results = client 90 | .bitfield( 91 | "mykey", 92 | [BitFieldSubCommand::::overflow( 93 | BitFieldOverflow::Fail, 94 | )], 95 | ) 96 | .await?; 97 | assert_eq!(0, results.len()); 98 | 99 | client.close().await?; 100 | 101 | Ok(()) 102 | } 103 | 104 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 105 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 106 | #[serial] 107 | async fn bitfield_readonly() -> Result<()> { 108 | let client = get_test_client().await?; 109 | 110 | client.set("mykey", "foobar").await?; 111 | 112 | let results = client 113 | .bitfield_readonly("mykey", [BitFieldGetSubCommand::new("i8", 0)]) 114 | .await?; 115 | assert_eq!(1, results.len()); 116 | assert_eq!(b'f' as u64, results[0]); 117 | 118 | Ok(()) 119 | } 120 | 121 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 122 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 123 | #[serial] 124 | async fn bitop() -> Result<()> { 125 | let client = get_test_client().await?; 126 | 127 | client.set("key1", "foobar").await?; 128 | client.set("key2", "abcdef").await?; 129 | 130 | let len = client 131 | .bitop(BitOperation::And, "dest", ["key1", "key2"]) 132 | .await?; 133 | assert_eq!(6, len); 134 | 135 | let value: String = client.get("dest").await?; 136 | assert_eq!("`bc`ab", value); 137 | 138 | client.close().await?; 139 | 140 | Ok(()) 141 | } 142 | 143 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 144 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 145 | #[serial] 146 | async fn bitpos() -> Result<()> { 147 | let client = get_test_client().await?; 148 | 149 | client.set("mykey", vec![0xFFu8, 0xF0u8, 0x00u8]).await?; 150 | 151 | let pos = client.bitpos("mykey", 1, BitRange::default()).await?; 152 | assert_eq!(0, pos); 153 | 154 | client.set("mykey", vec![0x00u8, 0xFFu8, 0xF0u8]).await?; 155 | let pos = client.bitpos("mykey", 0, BitRange::range(0, -1)).await?; 156 | assert_eq!(0, pos); 157 | 158 | let pos = client.bitpos("mykey", 1, BitRange::range(2, -1)).await?; 159 | assert_eq!(16, pos); 160 | 161 | let pos = client 162 | .bitpos("mykey", 1, BitRange::range(2, -1).unit(BitUnit::Byte)) 163 | .await?; 164 | assert_eq!(16, pos); 165 | 166 | let pos = client 167 | .bitpos("mykey", 1, BitRange::range(7, 15).unit(BitUnit::Bit)) 168 | .await?; 169 | assert_eq!(8, pos); 170 | 171 | let pos = client 172 | .bitpos("mykey", 1, BitRange::range(7, -3).unit(BitUnit::Bit)) 173 | .await?; 174 | assert_eq!(8, pos); 175 | 176 | Ok(()) 177 | } 178 | 179 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 180 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 181 | #[serial] 182 | async fn getbit() -> Result<()> { 183 | let client = get_test_client().await?; 184 | 185 | client.set("mykey", "foobar").await?; 186 | 187 | let value = client.getbit("mykey", 6).await?; 188 | assert_eq!(1, value); 189 | 190 | client.close().await?; 191 | 192 | Ok(()) 193 | } 194 | 195 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 196 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 197 | #[serial] 198 | async fn setbit() -> Result<()> { 199 | let client = get_test_client().await?; 200 | 201 | client.set("mykey", "foobar").await?; 202 | 203 | let value = client.setbit("mykey", 7, 1).await?; 204 | assert_eq!(0, value); 205 | 206 | let value = client.setbit("mykey", 7, 0).await?; 207 | assert_eq!(1, value); 208 | 209 | let value = client.getbit("mykey", 7).await?; 210 | assert_eq!(0, value); 211 | 212 | client.close().await?; 213 | 214 | Ok(()) 215 | } 216 | -------------------------------------------------------------------------------- /src/tests/bloom_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{ 3 | BfInfoParameter, BfInsertOptions, BfReserveOptions, BfScanDumpResult, BloomCommands, 4 | FlushingMode, ServerCommands, 5 | }, 6 | tests::get_redis_stack_test_client, 7 | Result, 8 | }; 9 | use serial_test::serial; 10 | use std::collections::VecDeque; 11 | 12 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 13 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 14 | #[serial] 15 | async fn bf_add() -> Result<()> { 16 | let client = get_redis_stack_test_client().await?; 17 | client.flushall(FlushingMode::Sync).await?; 18 | 19 | let result = client.bf_add("key", "item").await?; 20 | assert!(result); 21 | 22 | let result = client.bf_add("key", "item").await?; 23 | assert!(!result); 24 | 25 | Ok(()) 26 | } 27 | 28 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 29 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 30 | #[serial] 31 | async fn bf_exists() -> Result<()> { 32 | let client = get_redis_stack_test_client().await?; 33 | client.flushall(FlushingMode::Sync).await?; 34 | 35 | let result = client.bf_exists("key", "item").await?; 36 | assert!(!result); 37 | 38 | let result = client.bf_add("key", "item").await?; 39 | assert!(result); 40 | 41 | let result = client.bf_exists("key", "item").await?; 42 | assert!(result); 43 | 44 | Ok(()) 45 | } 46 | 47 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 48 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 49 | #[serial] 50 | async fn bf_info() -> Result<()> { 51 | let client = get_redis_stack_test_client().await?; 52 | client.flushall(FlushingMode::Sync).await?; 53 | 54 | client.bf_add("key", "item1").await?; 55 | client.bf_add("key", "item2").await?; 56 | client.bf_add("key", "item3").await?; 57 | 58 | let result = client 59 | .bf_info("key", BfInfoParameter::NumItemsInserted) 60 | .await?; 61 | assert_eq!(3, result); 62 | 63 | let result = client.bf_info_all("key").await?; 64 | assert_eq!(3, result.num_items_inserted); 65 | assert_eq!(1, result.num_filters); 66 | 67 | Ok(()) 68 | } 69 | 70 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 71 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 72 | #[serial] 73 | async fn bf_insert() -> Result<()> { 74 | let client = get_redis_stack_test_client().await?; 75 | client.flushall(FlushingMode::Sync).await?; 76 | 77 | let results: Vec = client 78 | .bf_insert("filter", ["boo", "bar", "barz"], BfInsertOptions::default()) 79 | .await?; 80 | assert_eq!(vec![true, true, true], results); 81 | 82 | let results: Vec = client 83 | .bf_insert("filter", "hello", BfInsertOptions::default().capacity(1000)) 84 | .await?; 85 | assert_eq!(vec![true], results); 86 | 87 | let results: Vec = client 88 | .bf_insert( 89 | "filter", 90 | ["boo", "bar"], 91 | BfInsertOptions::default().nocreate(), 92 | ) 93 | .await?; 94 | assert_eq!(vec![false, false], results); 95 | 96 | Ok(()) 97 | } 98 | 99 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 100 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 101 | #[serial] 102 | async fn bf_madd() -> Result<()> { 103 | let client = get_redis_stack_test_client().await?; 104 | client.flushall(FlushingMode::Sync).await?; 105 | 106 | let results: Vec = client.bf_madd("filter", ["item1", "item2"]).await?; 107 | assert_eq!(vec![true, true], results); 108 | 109 | let results: Vec = client.bf_madd("filter", ["item2", "item3"]).await?; 110 | assert_eq!(vec![false, true], results); 111 | 112 | Ok(()) 113 | } 114 | 115 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 116 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 117 | #[serial] 118 | async fn bf_mexists() -> Result<()> { 119 | let client = get_redis_stack_test_client().await?; 120 | client.flushall(FlushingMode::Sync).await?; 121 | 122 | let results: [bool; 2] = client.bf_madd("filter", ["item1", "item2"]).await?; 123 | assert_eq!([true, true], results); 124 | 125 | let results: [bool; 3] = client 126 | .bf_mexists("filter", ["item1", "item2", "item3"]) 127 | .await?; 128 | assert_eq!([true, true, false], results); 129 | 130 | Ok(()) 131 | } 132 | 133 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 134 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 135 | #[serial] 136 | async fn bf_reserve_loadchunk_scandump() -> Result<()> { 137 | let client = get_redis_stack_test_client().await?; 138 | client.flushall(FlushingMode::Sync).await?; 139 | 140 | client 141 | .bf_reserve("bf", 0.1, 10, BfReserveOptions::default()) 142 | .await?; 143 | 144 | let result = client.bf_add("bf", "item1").await?; 145 | assert!(result); 146 | 147 | let mut iterator: i64 = 0; 148 | let mut chunks: VecDeque = VecDeque::new(); 149 | 150 | loop { 151 | let result = client.bf_scandump("bf", iterator).await?; 152 | 153 | if result.iterator == 0 { 154 | break; 155 | } else { 156 | iterator = result.iterator; 157 | chunks.push_back(result); 158 | } 159 | } 160 | 161 | client.flushall(FlushingMode::Sync).await?; 162 | 163 | while let Some(dump_result) = chunks.pop_front() { 164 | client 165 | .bf_loadchunk("bf", dump_result.iterator, dump_result.data) 166 | .await?; 167 | } 168 | 169 | let result = client.bf_exists("bf", "item1").await?; 170 | assert!(result); 171 | 172 | Ok(()) 173 | } 174 | -------------------------------------------------------------------------------- /src/tests/buffer_decoder.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use tokio_util::codec::Decoder; 3 | 4 | use crate::{resp::BufferDecoder, Result}; 5 | 6 | fn decode(str: &str) -> Result>> { 7 | let mut buffer_decoder = BufferDecoder; 8 | let mut buf: BytesMut = str.into(); 9 | buffer_decoder 10 | .decode(&mut buf) 11 | .map(|b| b.map(|b| b.to_vec())) 12 | } 13 | 14 | #[test] 15 | fn integer() -> Result<()> { 16 | let result = decode(":12\r\n")?; 17 | assert_eq!(Some(":12\r\n".as_bytes().to_vec()), result); 18 | 19 | let result = decode(":12\r")?; 20 | assert_eq!(None, result); 21 | 22 | let result = decode(":12")?; 23 | assert_eq!(None, result); 24 | 25 | // malformed numbers are not checked 26 | let result = decode(":a\r\n"); 27 | assert!(result.is_ok()); 28 | 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | fn string() -> Result<()> { 34 | let result = decode("+OK\r\n")?; 35 | assert_eq!(Some("+OK\r\n".as_bytes().to_vec()), result); 36 | 37 | let result = decode("+OK\r")?; 38 | assert_eq!(None, result); 39 | 40 | let result = decode("+OK")?; 41 | assert_eq!(None, result); 42 | 43 | Ok(()) 44 | } 45 | 46 | #[test] 47 | fn error() -> Result<()> { 48 | let result = decode("-ERR error\r\n")?; 49 | assert_eq!(Some("-ERR error\r\n".as_bytes().to_vec()), result); 50 | 51 | let result = decode("-ERR error\r")?; 52 | assert_eq!(None, result); 53 | 54 | let result = decode("-ERR error")?; 55 | assert_eq!(None, result); 56 | 57 | Ok(()) 58 | } 59 | 60 | #[test] 61 | fn double() -> Result<()> { 62 | let result = decode(",12.12\r\n")?; 63 | assert_eq!(Some(",12.12\r\n".as_bytes().to_vec()), result); 64 | 65 | let result = decode(",12.12\r")?; 66 | assert_eq!(None, result); 67 | 68 | let result = decode(",12.12")?; 69 | assert_eq!(None, result); 70 | 71 | // malformed numbers are not checked 72 | let result = decode(",a\r\n"); 73 | assert!(result.is_ok()); 74 | 75 | Ok(()) 76 | } 77 | 78 | #[test] 79 | fn bool() -> Result<()> { 80 | let result = decode("#f\r\n")?; 81 | assert_eq!(Some("#f\r\n".as_bytes().to_vec()), result); 82 | 83 | let result = decode("#f\r")?; 84 | assert_eq!(None, result); 85 | 86 | let result = decode("#f")?; 87 | assert_eq!(None, result); 88 | 89 | // malformed booleans are not checked 90 | let result = decode("#a\r\n"); 91 | assert!(result.is_ok()); 92 | 93 | Ok(()) 94 | } 95 | 96 | #[test] 97 | fn nil() -> Result<()> { 98 | let result = decode("_\r\n")?; 99 | assert_eq!(Some("_\r\n".as_bytes().to_vec()), result); 100 | 101 | let result = decode("_\r")?; 102 | assert_eq!(None, result); 103 | 104 | let result = decode("_")?; 105 | assert_eq!(None, result); 106 | 107 | Ok(()) 108 | } 109 | 110 | #[test] 111 | fn bulk_string() -> Result<()> { 112 | let result = decode("$5\r\nhello\r\n")?; 113 | assert_eq!(Some("$5\r\nhello\r\n".as_bytes().to_vec()), result); 114 | 115 | let result = decode("$7\r\nhel\r\nlo\r\n")?; // b"hel\r\nlo" 116 | assert_eq!(Some("$7\r\nhel\r\nlo\r\n".as_bytes().to_vec()), result); 117 | 118 | let result = decode("$0\r\n\r\n")?; // b"" 119 | assert_eq!(Some("$0\r\n\r\n".as_bytes().to_vec()), result); 120 | 121 | let result = decode("$5")?; 122 | assert_eq!(None, result); 123 | 124 | let result = decode("$5\r")?; 125 | assert_eq!(None, result); 126 | 127 | let result = decode("$5\r\n")?; 128 | assert_eq!(None, result); 129 | 130 | let result = decode("$5\r\nhello")?; 131 | assert_eq!(None, result); 132 | 133 | let result = decode("$5\r\nhello\r")?; 134 | assert_eq!(None, result); 135 | 136 | Ok(()) 137 | } 138 | 139 | #[test] 140 | fn array() -> Result<()> { 141 | let result = decode("*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n")?; 142 | assert_eq!( 143 | Some("*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n".as_bytes().to_vec()), 144 | result 145 | ); 146 | 147 | let result = decode("*2")?; 148 | assert_eq!(None, result); 149 | 150 | let result = decode("*2\r")?; 151 | assert_eq!(None, result); 152 | 153 | let result = decode("*2\r\n")?; 154 | assert_eq!(None, result); 155 | 156 | let result = decode("*2\r\n$5")?; 157 | assert_eq!(None, result); 158 | 159 | let result = decode("*2\r\n$5\r")?; 160 | assert_eq!(None, result); 161 | 162 | let result = decode("*2\r\n$5\r\n")?; 163 | assert_eq!(None, result); 164 | 165 | let result = decode("*2\r\n$5\r\nhello")?; 166 | assert_eq!(None, result); 167 | 168 | let result = decode("*2\r\n$5\r\nhello\r")?; 169 | assert_eq!(None, result); 170 | 171 | let result = decode("*2\r\n$5\r\nhello\r\n")?; 172 | assert_eq!(None, result); 173 | 174 | Ok(()) 175 | } 176 | 177 | #[test] 178 | fn map() -> Result<()> { 179 | let result = decode("%1\r\n$5\r\nhello\r\n$5\r\nworld\r\n")?; 180 | assert_eq!( 181 | Some("%1\r\n$5\r\nhello\r\n$5\r\nworld\r\n".as_bytes().to_vec()), 182 | result 183 | ); 184 | 185 | let result = decode("%1")?; 186 | assert_eq!(None, result); 187 | 188 | let result = decode("%1\r")?; 189 | assert_eq!(None, result); 190 | 191 | let result = decode("%1\r\n")?; 192 | assert_eq!(None, result); 193 | 194 | let result = decode("%1\r\n$5")?; 195 | assert_eq!(None, result); 196 | 197 | let result = decode("%1\r\n$5\r")?; 198 | assert_eq!(None, result); 199 | 200 | let result = decode("%1\r\n$5\r\n")?; 201 | assert_eq!(None, result); 202 | 203 | let result = decode("%1\r\n$5\r\nhello")?; 204 | assert_eq!(None, result); 205 | 206 | let result = decode("%1\r\n$5\r\nhello\r")?; 207 | assert_eq!(None, result); 208 | 209 | let result = decode("%1\r\n$5\r\nhello\r\n")?; 210 | assert_eq!(None, result); 211 | 212 | Ok(()) 213 | } 214 | -------------------------------------------------------------------------------- /src/tests/client.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::{ 4 | client::{Client, IntoConfig}, 5 | commands::{ 6 | BlockingCommands, ClientKillOptions, ConnectionCommands, FlushingMode, LMoveWhere, 7 | ListCommands, ServerCommands, StringCommands, 8 | }, 9 | resp::cmd, 10 | tests::{get_default_addr, get_test_client, log_try_init}, 11 | Error, Result, 12 | }; 13 | use serial_test::serial; 14 | 15 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 16 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 17 | #[serial] 18 | async fn send() -> Result<()> { 19 | let client = get_test_client().await?; 20 | 21 | client.send(cmd("PING"), None).await?; 22 | 23 | client.close().await?; 24 | 25 | Ok(()) 26 | } 27 | 28 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 29 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 30 | #[serial] 31 | async fn forget() -> Result<()> { 32 | let client = get_test_client().await?; 33 | 34 | client.send_and_forget(cmd("PING"), None)?; 35 | client.send(cmd("PING"), None).await?; 36 | 37 | client.close().await?; 38 | 39 | Ok(()) 40 | } 41 | 42 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 43 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 44 | #[serial] 45 | async fn on_reconnect() -> Result<()> { 46 | let client1 = get_test_client().await?; 47 | let client2 = get_test_client().await?; 48 | 49 | let mut receiver = client1.on_reconnect(); 50 | 51 | let result = receiver.try_recv(); 52 | assert!(result.is_err()); 53 | 54 | let client1_id = client1.client_id().await?; 55 | client2 56 | .client_kill(ClientKillOptions::default().id(client1_id)) 57 | .await?; 58 | 59 | // send command to be sure that the reconnection has been done 60 | client1.set("key", "value").retry_on_error(true).await?; 61 | 62 | let result = receiver.try_recv(); 63 | assert!(result.is_ok()); 64 | 65 | client1.close().await?; 66 | client2.close().await?; 67 | 68 | Ok(()) 69 | } 70 | 71 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 72 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 73 | #[serial] 74 | async fn command_timeout() -> Result<()> { 75 | log_try_init(); 76 | 77 | let client = get_test_client().await?; 78 | 79 | client.flushall(FlushingMode::Sync).await?; 80 | 81 | // create an empty list 82 | client.lpush("key", "value").await?; 83 | let _result: Vec = client.lpop("key", 1).await?; 84 | 85 | client.close().await?; 86 | 87 | let mut config = get_default_addr().into_config()?; 88 | config.command_timeout = Duration::from_millis(10); 89 | 90 | let client = Client::connect(config).await?; 91 | 92 | // block for 5 seconds 93 | // since the timeout is configured to 10ms, we should have a timeout error 94 | let result: Result)>> = 95 | client.blmpop(5., "key", LMoveWhere::Left, 1).await; 96 | assert!(matches!(result, Err(Error::Timeout(_)))); 97 | 98 | client.close().await?; 99 | 100 | Ok(()) 101 | } 102 | 103 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 104 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 105 | #[serial] 106 | async fn connection_name() -> Result<()> { 107 | log_try_init(); 108 | 109 | let mut config = get_default_addr().into_config()?; 110 | "myconnection".clone_into(&mut config.connection_name); 111 | 112 | let client = Client::connect(config).await?; 113 | 114 | client.flushall(FlushingMode::Sync).await?; 115 | 116 | let connection_name: Option = client.client_getname().await?; 117 | assert_eq!(Some("myconnection".to_owned()), connection_name); 118 | 119 | client.close().await?; 120 | 121 | Ok(()) 122 | } 123 | 124 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 125 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 126 | #[serial] 127 | async fn mget_mset() -> Result<()> { 128 | let client = Client::connect("127.0.0.1:6379").await?; 129 | 130 | client 131 | .send( 132 | cmd("MSET") 133 | .arg("key1") 134 | .arg("value1") 135 | .arg("key2") 136 | .arg("value2") 137 | .arg("key3") 138 | .arg("value3") 139 | .arg("key4") 140 | .arg("value4"), 141 | None, 142 | ) 143 | .await? 144 | .to::<()>()?; 145 | 146 | let values: Vec = client 147 | .send( 148 | cmd("MGET").arg("key1").arg("key2").arg("key3").arg("key4"), 149 | None, 150 | ) 151 | .await? 152 | .to()?; 153 | 154 | assert_eq!( 155 | vec![ 156 | "value1".to_owned(), 157 | "value2".to_owned(), 158 | "value3".to_owned(), 159 | "value4".to_owned() 160 | ], 161 | values 162 | ); 163 | 164 | Ok(()) 165 | } 166 | -------------------------------------------------------------------------------- /src/tests/cluster_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::Client, 3 | commands::{ClusterCommands, ClusterShardResult, LegacyClusterShardResult}, 4 | tests::log_try_init, 5 | Result, 6 | }; 7 | use log::debug; 8 | use serial_test::serial; 9 | 10 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 11 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 12 | #[serial] 13 | async fn asking() -> Result<()> { 14 | log_try_init(); 15 | let client = Client::connect("127.0.0.1:7000").await?; 16 | 17 | client.asking().await?; 18 | 19 | Ok(()) 20 | } 21 | 22 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 23 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 24 | #[serial] 25 | async fn cluster_shards() -> Result<()> { 26 | log_try_init(); 27 | let client = Client::connect("127.0.0.1:7000").await?; 28 | 29 | let shards: Vec = client.cluster_shards().await?; 30 | debug!("shards: {shards:?}"); 31 | assert_eq!(3, shards.len()); 32 | 33 | Ok(()) 34 | } 35 | 36 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 37 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 38 | #[serial] 39 | async fn cluster_slots() -> Result<()> { 40 | log_try_init(); 41 | let client = Client::connect("127.0.0.1:7000").await?; 42 | 43 | let shards: Vec = client.cluster_slots().await?; 44 | debug!("shards: {shards:?}"); 45 | assert_eq!(3, shards.len()); 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /src/tests/command_args.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{GenericCommands, HashCommands, SetCommands}, 3 | tests::get_test_client, 4 | Result, 5 | }; 6 | use serial_test::serial; 7 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; 8 | 9 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 10 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 11 | #[serial] 12 | async fn key_value_collection() -> Result<()> { 13 | let client = get_test_client().await?; 14 | 15 | client.del("key").await?; 16 | let items = ("field1", "value1"); 17 | let len = client.hset("key", items).await?; 18 | assert_eq!(1, len); 19 | 20 | client.del("key").await?; 21 | let items = HashMap::from([("field1", "value1"), ("field2", "value2")]); 22 | let len = client.hset("key", items).await?; 23 | assert_eq!(2, len); 24 | 25 | client.del("key").await?; 26 | let items = BTreeMap::from([("field1", "value1"), ("field2", "value2")]); 27 | let len = client.hset("key", items).await?; 28 | assert_eq!(2, len); 29 | 30 | client.del("key").await?; 31 | let items = vec![("field1", "value1"), ("field2", "value2")]; 32 | let len = client.hset("key", items).await?; 33 | assert_eq!(2, len); 34 | 35 | client.del("key").await?; 36 | let items = [("field1", "value1"), ("field2", "value2")]; 37 | let len = client.hset("key", items).await?; 38 | assert_eq!(2, len); 39 | 40 | client.del("key").await?; 41 | let items = [("field1", "value1"), ("field2", "value2")]; 42 | let len = client.hset("key", items.as_slice()).await?; 43 | assert_eq!(2, len); 44 | 45 | client.close().await?; 46 | 47 | Ok(()) 48 | } 49 | 50 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 51 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 52 | #[serial] 53 | async fn set_collection() -> Result<()> { 54 | let client = get_test_client().await?; 55 | 56 | client.del("key").await?; 57 | let items = "member1"; 58 | let len = client.sadd("key", items).await?; 59 | assert_eq!(1, len); 60 | 61 | client.del("key").await?; 62 | let items = ["member1", "member2"]; 63 | let len = client.sadd("key", items).await?; 64 | assert_eq!(2, len); 65 | 66 | client.del("key").await?; 67 | let items = ["member1", "member2"]; 68 | let len = client.sadd("key", items.as_slice()).await?; 69 | assert_eq!(2, len); 70 | 71 | client.del("key").await?; 72 | let items = vec!["member1", "member2"]; 73 | let len = client.sadd("key", items).await?; 74 | assert_eq!(2, len); 75 | 76 | client.del("key").await?; 77 | let items = HashSet::from(["member1", "member2"]); 78 | let len = client.sadd("key", items).await?; 79 | assert_eq!(2, len); 80 | 81 | client.del("key").await?; 82 | let items = BTreeSet::from(["member1", "member2"]); 83 | let len = client.sadd("key", items).await?; 84 | assert_eq!(2, len); 85 | 86 | client.close().await?; 87 | 88 | Ok(()) 89 | } 90 | -------------------------------------------------------------------------------- /src/tests/command_info_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::IntoConfig, 3 | commands::{ 4 | GenericCommands, MigrateOptions, SortOptions, SortOrder, SortedSetCommands, StreamCommands, 5 | StringCommands, XReadGroupOptions, XReadOptions, ZAggregate, 6 | }, 7 | network::StandaloneConnection, 8 | tests::{get_default_addr, get_default_host, get_default_port, get_test_client}, 9 | CommandInfoManager, Result, 10 | }; 11 | use serial_test::serial; 12 | 13 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 14 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 15 | #[serial] 16 | async fn extract_keys() -> Result<()> { 17 | let client = get_test_client().await?; 18 | let mut connection = StandaloneConnection::connect( 19 | &get_default_host(), 20 | get_default_port(), 21 | &get_default_addr().into_config()?, 22 | ) 23 | .await?; 24 | let command_info_manager = CommandInfoManager::initialize(&mut connection).await?; 25 | 26 | // SET 27 | let keys = command_info_manager 28 | .extract_keys(client.set("key", "value").command(), &mut connection) 29 | .await?; 30 | assert_eq!(1, keys.len()); 31 | assert_eq!("key", keys[0]); 32 | 33 | // MSET 34 | let keys = command_info_manager 35 | .extract_keys( 36 | client 37 | .mset([("key1", "value1"), ("key2", "value2")]) 38 | .command(), 39 | &mut connection, 40 | ) 41 | .await?; 42 | assert_eq!(2, keys.len()); 43 | assert_eq!("key1", keys[0]); 44 | assert_eq!("key2", keys[1]); 45 | 46 | // XREAD 47 | let keys = command_info_manager 48 | .extract_keys( 49 | client 50 | .xread::<_, _, _, _, String, Vec<(_, _)>>( 51 | XReadOptions::default().count(2), 52 | ["mystream", "writers"], 53 | ["1526999352406-0", "1526985685298-0"], 54 | ) 55 | .command(), 56 | &mut connection, 57 | ) 58 | .await?; 59 | assert_eq!(2, keys.len()); 60 | assert_eq!("mystream", keys[0]); 61 | assert_eq!("writers", keys[1]); 62 | 63 | // XREADGROUP 64 | let keys = command_info_manager 65 | .extract_keys( 66 | client 67 | .xreadgroup::<_, _, _, _, _, _, String, Vec<(_, _)>>( 68 | "mygroup", 69 | "myconsumer", 70 | XReadGroupOptions::default().count(2), 71 | ["mystream", "writers"], 72 | ["1526999352406-0", "1526985685298-0"], 73 | ) 74 | .command(), 75 | &mut connection, 76 | ) 77 | .await?; 78 | assert_eq!(2, keys.len(), "unexpected keys: {:?}", keys); 79 | assert_eq!("mystream", keys[0]); 80 | assert_eq!("writers", keys[1]); 81 | 82 | // MIGRATE 83 | let keys = command_info_manager 84 | .extract_keys( 85 | client 86 | .migrate( 87 | "192.168.1.34", 88 | 6379, 89 | "", 90 | 0, 91 | 5000, 92 | MigrateOptions::default().keys(["key1", "key2", "key3"]), 93 | ) 94 | .command(), 95 | &mut connection, 96 | ) 97 | .await?; 98 | assert_eq!(3, keys.len()); 99 | assert_eq!("key1", keys[0]); 100 | assert_eq!("key2", keys[1]); 101 | assert_eq!("key3", keys[2]); 102 | 103 | // ZUNION 104 | let keys = command_info_manager 105 | .extract_keys( 106 | client 107 | .zunion::<_, _, _, String>(["zset1", "zset2"], Some([1.5, 2.5]), ZAggregate::Max) 108 | .command(), 109 | &mut connection, 110 | ) 111 | .await?; 112 | assert_eq!(2, keys.len()); 113 | assert_eq!("zset1", keys[0]); 114 | assert_eq!("zset2", keys[1]); 115 | 116 | // SORT 117 | let keys = command_info_manager 118 | .extract_keys( 119 | client 120 | .sort_and_store( 121 | "src", 122 | "dst", 123 | SortOptions::default() 124 | .limit(0, 5) 125 | .alpha() 126 | .order(SortOrder::Desc), 127 | ) 128 | .command(), 129 | &mut connection, 130 | ) 131 | .await?; 132 | assert_eq!(2, keys.len()); 133 | assert_eq!("src", keys[0]); 134 | assert_eq!("dst", keys[1]); 135 | 136 | Ok(()) 137 | } 138 | -------------------------------------------------------------------------------- /src/tests/count_min_sktech_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{CountMinSketchCommands, FlushingMode, ServerCommands}, 3 | tests::get_redis_stack_test_client, 4 | Result, 5 | }; 6 | use serial_test::serial; 7 | 8 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 9 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 10 | #[serial] 11 | async fn cms_incrby() -> Result<()> { 12 | let client = get_redis_stack_test_client().await?; 13 | client.flushall(FlushingMode::Sync).await?; 14 | 15 | let result: Result> = client.cms_incrby("key", [("item1", 1), ("item2", 2)]).await; 16 | assert!(result.is_err()); // key does not exist 17 | 18 | client.cms_initbydim("key", 2000, 5).await?; 19 | 20 | let result: Vec = client 21 | .cms_incrby("key", [("item1", 1), ("item2", 2)]) 22 | .await?; 23 | assert_eq!(vec![1, 2], result); 24 | 25 | Ok(()) 26 | } 27 | 28 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 29 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 30 | #[serial] 31 | async fn cms_info() -> Result<()> { 32 | let client = get_redis_stack_test_client().await?; 33 | client.flushall(FlushingMode::Sync).await?; 34 | 35 | client.cms_initbydim("key", 2000, 5).await?; 36 | 37 | let result: Vec = client 38 | .cms_incrby("key", [("item1", 1), ("item2", 2)]) 39 | .await?; 40 | assert_eq!(vec![1, 2], result); 41 | 42 | let info = client.cms_info("key").await?; 43 | assert_eq!(2000, info.width); 44 | assert_eq!(5, info.depth); 45 | assert_eq!(3, info.total_count); 46 | 47 | Ok(()) 48 | } 49 | 50 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 51 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 52 | #[serial] 53 | async fn cms_initbydim() -> Result<()> { 54 | let client = get_redis_stack_test_client().await?; 55 | client.flushall(FlushingMode::Sync).await?; 56 | 57 | client.cms_initbydim("key", 2000, 5).await?; 58 | 59 | Ok(()) 60 | } 61 | 62 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 63 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 64 | #[serial] 65 | async fn cms_initbyprob() -> Result<()> { 66 | let client = get_redis_stack_test_client().await?; 67 | client.flushall(FlushingMode::Sync).await?; 68 | 69 | client.cms_initbyprob("key", 0.001, 0.01).await?; 70 | 71 | Ok(()) 72 | } 73 | 74 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 75 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 76 | #[serial] 77 | async fn cms_merge() -> Result<()> { 78 | let client = get_redis_stack_test_client().await?; 79 | client.flushall(FlushingMode::Sync).await?; 80 | 81 | client.cms_initbydim("key1", 2000, 5).await?; 82 | 83 | let result: Vec = client 84 | .cms_incrby("key1", [("item1", 1), ("item2", 2)]) 85 | .await?; 86 | assert_eq!(vec![1, 2], result); 87 | 88 | client.cms_initbydim("key2", 2000, 5).await?; 89 | 90 | let result: Vec = client 91 | .cms_incrby("key2", [("item1", 1), ("item2", 2)]) 92 | .await?; 93 | assert_eq!(vec![1, 2], result); 94 | 95 | client.cms_initbydim("key3", 2000, 5).await?; 96 | 97 | client 98 | .cms_merge("key3", ["key1", "key2"], Some([1, 2])) 99 | .await?; 100 | let info = client.cms_info("key3").await?; 101 | assert_eq!(9, info.total_count); 102 | 103 | client.cms_initbydim("key4", 2000, 5).await?; 104 | 105 | client 106 | .cms_merge("key4", ["key1", "key2"], Option::::None) 107 | .await?; 108 | let info = client.cms_info("key4").await?; 109 | assert_eq!(6, info.total_count); 110 | 111 | Ok(()) 112 | } 113 | 114 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 115 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 116 | #[serial] 117 | async fn cms_query() -> Result<()> { 118 | let client = get_redis_stack_test_client().await?; 119 | client.flushall(FlushingMode::Sync).await?; 120 | 121 | client.cms_initbydim("key1", 2000, 5).await?; 122 | 123 | let _result: Vec = client 124 | .cms_incrby("key1", [("item1", 1), ("item2", 2)]) 125 | .await?; 126 | let _result: Vec = client 127 | .cms_incrby("key1", [("item1", 2), ("item2", 1)]) 128 | .await?; 129 | 130 | let result: Vec = client.cms_query("key1", ["item1", "item2"]).await?; 131 | assert_eq!(vec![3, 3], result); 132 | 133 | Ok(()) 134 | } 135 | -------------------------------------------------------------------------------- /src/tests/debug_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{ConnectionCommands, DebugCommands, PingOptions}, 3 | tests::{get_cluster_test_client_with_command_timeout, get_test_client}, 4 | Error, Result, 5 | }; 6 | use serial_test::serial; 7 | 8 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 9 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 10 | #[serial] 11 | #[ignore] 12 | async fn standalone_server_panic() -> Result<()> { 13 | let client = get_test_client().await?; 14 | 15 | let panic_result = client.debug_panic().await; 16 | 17 | assert!(panic_result.is_err()); 18 | 19 | let ping_result = client.ping::<()>(PingOptions::default()).await; 20 | 21 | assert!(ping_result.is_err()); 22 | 23 | Ok(()) 24 | } 25 | 26 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 27 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 28 | #[serial] 29 | #[ignore] 30 | async fn cluster_server_panic() -> Result<()> { 31 | let client = get_cluster_test_client_with_command_timeout().await?; 32 | 33 | let panic_result = client.debug_panic().await; 34 | 35 | assert!(panic_result.is_err()); 36 | 37 | let ping_result = client.ping::<()>(PingOptions::default()).await; 38 | 39 | assert!(matches!(ping_result, Err(err) if !matches!(err, Error::Timeout(_)))); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /src/tests/from_value.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{resp::Value, Result}; 4 | use serde::Deserialize; 5 | use smallvec::SmallVec; 6 | 7 | #[test] 8 | fn map_to_tuple_vec() -> Result<()> { 9 | let value = Value::Map(HashMap::from([ 10 | ( 11 | Value::BulkString(b"field1".to_vec()), 12 | Value::BulkString(b"hello".to_vec()), 13 | ), 14 | ( 15 | Value::BulkString(b"field2".to_vec()), 16 | Value::BulkString(b"world".to_vec()), 17 | ), 18 | ])); 19 | 20 | let values: Vec<(String, String)> = value.into()?; 21 | assert_eq!(2, values.len()); 22 | assert!(values 23 | .iter() 24 | .any(|i| *i == ("field1".to_owned(), "hello".to_owned()))); 25 | assert!(values 26 | .iter() 27 | .any(|i| *i == ("field2".to_owned(), "world".to_owned()))); 28 | 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | fn map_to_tuple_smallvec() -> Result<()> { 34 | let value = Value::Map(HashMap::from([ 35 | ( 36 | Value::BulkString(b"field1".to_vec()), 37 | Value::BulkString(b"hello".to_vec()), 38 | ), 39 | ( 40 | Value::BulkString(b"field2".to_vec()), 41 | Value::BulkString(b"world".to_vec()), 42 | ), 43 | ])); 44 | 45 | let values: SmallVec<[(String, String); 2]> = value.into()?; 46 | assert_eq!(2, values.len()); 47 | assert!(values 48 | .iter() 49 | .any(|i| *i == ("field1".to_owned(), "hello".to_owned()))); 50 | assert!(values 51 | .iter() 52 | .any(|i| *i == ("field2".to_owned(), "world".to_owned()))); 53 | 54 | Ok(()) 55 | } 56 | 57 | #[test] 58 | fn pub_sub_message() -> Result<()> { 59 | let value = Value::Push(vec![ 60 | Value::BulkString(b"message".to_vec()), 61 | Value::BulkString(b"mychannel".to_vec()), 62 | Value::BulkString(b"mymessage".to_vec()), 63 | ]); 64 | 65 | let values: Vec = value.into()?; 66 | 67 | assert_eq!(Value::BulkString(b"message".to_vec()), values[0]); 68 | assert_eq!(Value::BulkString(b"mychannel".to_vec()), values[1]); 69 | assert_eq!(Value::BulkString(b"mymessage".to_vec()), values[2]); 70 | 71 | Ok(()) 72 | } 73 | 74 | #[test] 75 | fn value_to_value() -> Result<()> { 76 | let value = Value::BulkString(b"foo".to_vec()); 77 | let value2 = Value::deserialize(&value)?; 78 | 79 | assert_eq!(Value::BulkString(b"foo".to_vec()), value2); 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /src/tests/hyper_log_log_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{FlushingMode, HyperLogLogCommands, ServerCommands}, 3 | tests::get_test_client, 4 | Result, 5 | }; 6 | use serial_test::serial; 7 | 8 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 9 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 10 | #[serial] 11 | async fn pfadd() -> Result<()> { 12 | let client = get_test_client().await?; 13 | client.flushdb(FlushingMode::Sync).await?; 14 | 15 | let result = client.pfadd("key", "a").await?; 16 | assert!(result); 17 | 18 | let result = client.pfadd("key", ["b", "c", "d", "e", "f", "g"]).await?; 19 | assert!(result); 20 | 21 | let result = client.pfadd("key", "a").await?; 22 | assert!(!result); 23 | 24 | let result = client.pfadd("key", ["c", "d", "e"]).await?; 25 | assert!(!result); 26 | 27 | Ok(()) 28 | } 29 | 30 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 31 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 32 | #[serial] 33 | async fn pfcount() -> Result<()> { 34 | let client = get_test_client().await?; 35 | client.flushdb(FlushingMode::Sync).await?; 36 | 37 | client 38 | .pfadd("key1", ["a", "b", "c", "d", "e", "f", "g"]) 39 | .await?; 40 | 41 | let count = client.pfcount("key1").await?; 42 | assert_eq!(7, count); 43 | 44 | client.pfadd("key2", ["f", "g", "h", "i"]).await?; 45 | 46 | let count = client.pfcount(["key1", "key2"]).await?; 47 | assert_eq!(9, count); 48 | 49 | Ok(()) 50 | } 51 | 52 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 53 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 54 | #[serial] 55 | async fn pfmerge() -> Result<()> { 56 | let client = get_test_client().await?; 57 | client.flushdb(FlushingMode::Sync).await?; 58 | 59 | client.pfadd("key1", ["foo", "bar", "zap", "a"]).await?; 60 | 61 | client.pfadd("key2", ["a", "b", "c", "foo"]).await?; 62 | 63 | client.pfmerge("out", ["key1", "key2"]).await?; 64 | 65 | let count = client.pfcount("out").await?; 66 | assert_eq!(6, count); 67 | 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod bitmap_commands; 2 | #[cfg(feature = "redis-bloom")] 3 | mod bloom_commands; 4 | mod buffer_decoder; 5 | mod client; 6 | mod cluster; 7 | mod cluster_commands; 8 | mod command_args; 9 | mod command_info_manager; 10 | mod config; 11 | mod connection_commands; 12 | #[cfg(feature = "redis-bloom")] 13 | mod count_min_sktech_commands; 14 | #[cfg(feature = "redis-bloom")] 15 | mod cuckoo_commands; 16 | mod debug_commands; 17 | mod error; 18 | mod from_value; 19 | mod generic_commands; 20 | mod geo_commands; 21 | #[cfg(feature = "redis-graph")] 22 | mod graph_commands; 23 | mod hash_commands; 24 | mod hyper_log_log_commands; 25 | #[cfg(feature = "redis-json")] 26 | mod json_commands; 27 | mod list_commands; 28 | mod multiplexed_client; 29 | mod pipeline; 30 | #[cfg(feature = "pool")] 31 | mod pooled_client_manager; 32 | mod pub_sub_commands; 33 | mod resp3; 34 | mod resp_deserializer; 35 | mod resp_serializer; 36 | mod scripting_commands; 37 | #[cfg(feature = "redis-search")] 38 | mod search_commands; 39 | mod sentinel; 40 | mod server_commands; 41 | mod set_commands; 42 | mod sorted_set_commands; 43 | mod stream_commands; 44 | mod string_commands; 45 | #[cfg(feature = "redis-bloom")] 46 | mod t_disgest_commands; 47 | #[cfg(feature = "redis-time-series")] 48 | mod time_series_commands; 49 | mod tls; 50 | #[cfg(feature = "redis-bloom")] 51 | mod top_k_commands; 52 | mod transaction; 53 | mod util; 54 | mod value; 55 | mod value_deserialize; 56 | mod value_deserializer; 57 | mod value_serialize; 58 | 59 | pub(crate) use util::*; 60 | -------------------------------------------------------------------------------- /src/tests/multiplexed_client.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::Client, 3 | commands::{FlushingMode, ServerCommands, StringCommands}, 4 | spawn, 5 | tests::log_try_init, 6 | Result, 7 | }; 8 | use futures_util::future; 9 | use rand::Rng; 10 | use serial_test::serial; 11 | 12 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 13 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 14 | #[serial] 15 | async fn multiplexed_client() -> Result<()> { 16 | log_try_init(); 17 | let client = Client::connect("redis://127.0.0.1:6379").await?; 18 | client.flushdb(FlushingMode::Sync).await?; 19 | 20 | client 21 | .mset( 22 | (0..100) 23 | .map(|i| (format!("key{i}"), format!("value{i}"))) 24 | .collect::>(), 25 | ) 26 | .await?; 27 | 28 | let tasks: Vec<_> = (1..100) 29 | .map(|_| { 30 | let client = client.clone(); 31 | spawn(async move { 32 | for _ in 0..100 { 33 | let i = rand::rng().random_range(0..100); 34 | let key = format!("key{}", i); 35 | let valyue: String = client.get(key.clone()).await.unwrap(); 36 | assert_eq!(format!("value{}", i), valyue) 37 | } 38 | }) 39 | }) 40 | .collect(); 41 | 42 | future::join_all(tasks).await; 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/tests/pipeline.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::BatchPreparedCommand, 3 | commands::{FlushingMode, ServerCommands, StringCommands}, 4 | resp::{cmd, Value}, 5 | tests::{get_cluster_test_client, get_test_client}, 6 | Result, 7 | }; 8 | use serial_test::serial; 9 | 10 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 11 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 12 | #[serial] 13 | async fn pipeline() -> Result<()> { 14 | let client = get_test_client().await?; 15 | client.flushdb(FlushingMode::Sync).await?; 16 | 17 | let mut pipeline = client.create_pipeline(); 18 | pipeline.set("key1", "value1").forget(); 19 | pipeline.set("key2", "value2").forget(); 20 | pipeline.get::<_, ()>("key1").queue(); 21 | pipeline.get::<_, ()>("key2").queue(); 22 | 23 | let (value1, value2): (String, String) = pipeline.execute().await?; 24 | assert_eq!("value1", value1); 25 | assert_eq!("value2", value2); 26 | 27 | Ok(()) 28 | } 29 | 30 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 31 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 32 | #[serial] 33 | async fn error() -> Result<()> { 34 | let client = get_test_client().await?; 35 | client.flushdb(FlushingMode::Sync).await?; 36 | 37 | let mut pipeline = client.create_pipeline(); 38 | pipeline.set("key1", "value1").forget(); 39 | pipeline.set("key2", "value2").forget(); 40 | pipeline.queue(cmd("UNKNOWN")); 41 | pipeline.get::<_, ()>("key1").queue(); 42 | pipeline.get::<_, ()>("key2").queue(); 43 | 44 | let result: Result<(Value, String, String)> = pipeline.execute().await; 45 | assert!(result.is_err()); 46 | 47 | Ok(()) 48 | } 49 | 50 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 51 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 52 | #[serial] 53 | async fn pipeline_on_cluster() -> Result<()> { 54 | let client = get_cluster_test_client().await?; 55 | client.flushall(FlushingMode::Sync).await?; 56 | 57 | let mut pipeline = client.create_pipeline(); 58 | pipeline.set("key1", "value1").forget(); 59 | pipeline.set("key2", "value2").forget(); 60 | pipeline.get::<_, ()>("key1").queue(); 61 | pipeline.get::<_, ()>("key2").queue(); 62 | 63 | let (value1, value2): (String, String) = pipeline.execute().await?; 64 | assert_eq!("value1", value1); 65 | assert_eq!("value2", value2); 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/tests/pooled_client_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::PooledClientManager, commands::StringCommands, tests::get_default_addr, Result, 3 | }; 4 | use serial_test::serial; 5 | 6 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 7 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 8 | #[serial] 9 | async fn pooled_client_manager() -> Result<()> { 10 | let manager = PooledClientManager::new(get_default_addr())?; 11 | let pool = crate::bb8::Pool::builder().build(manager).await?; 12 | let client = pool.get().await.unwrap(); 13 | 14 | client.set("key", "value").await?; 15 | let value: String = client.get("key").await?; 16 | assert_eq!("value", value); 17 | 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /src/tests/resp3.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{ 3 | ConnectionCommands, FlushingMode, HelloOptions, ServerCommands, SortedSetCommands, 4 | StringCommands, 5 | }, 6 | tests::get_test_client, 7 | Result, 8 | }; 9 | use serial_test::serial; 10 | 11 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 12 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 13 | #[serial] 14 | async fn double() -> Result<()> { 15 | let client = get_test_client().await?; 16 | client.flushdb(FlushingMode::Sync).await?; 17 | 18 | client.hello(HelloOptions::new(3)).await?; 19 | 20 | client 21 | .zadd( 22 | "key", 23 | [(1.1, "one"), (2.2, "two"), (3.3, "three")], 24 | Default::default(), 25 | ) 26 | .await?; 27 | 28 | let values: Vec<(String, f64)> = client 29 | .zrange_with_scores("key", 0, -1, Default::default()) 30 | .await?; 31 | assert_eq!(3, values.len()); 32 | assert_eq!(("one".to_owned(), 1.1), values[0]); 33 | assert_eq!(("two".to_owned(), 2.2), values[1]); 34 | assert_eq!(("three".to_owned(), 3.3), values[2]); 35 | 36 | Ok(()) 37 | } 38 | 39 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 40 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 41 | #[serial] 42 | async fn null() -> Result<()> { 43 | let client = get_test_client().await?; 44 | client.flushdb(FlushingMode::Sync).await?; 45 | 46 | client.hello(HelloOptions::new(3)).await?; 47 | 48 | let value: Option = client.get("key").await?; 49 | assert_eq!(None, value); 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /src/tests/resp_serializer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | resp::{BulkString, RespSerializer}, 3 | tests::log_try_init, 4 | Result, 5 | }; 6 | use serde::Serialize; 7 | use smallvec::{smallvec, SmallVec}; 8 | use std::collections::HashMap; 9 | 10 | fn serialize(value: T) -> Result 11 | where 12 | T: serde::Serialize, 13 | { 14 | let mut serializer = RespSerializer::new(); 15 | value.serialize(&mut serializer)?; 16 | let buf = serializer.get_output(); 17 | Ok(String::from_utf8(buf.to_vec())?) 18 | } 19 | 20 | #[test] 21 | fn bool() -> Result<()> { 22 | log_try_init(); 23 | 24 | let result = serialize(true)?; 25 | assert_eq!("#t\r\n", result); 26 | 27 | let result = serialize(false)?; 28 | assert_eq!("#f\r\n", result); 29 | 30 | Ok(()) 31 | } 32 | 33 | #[test] 34 | fn integer() -> Result<()> { 35 | log_try_init(); 36 | 37 | let result = serialize(12)?; 38 | assert_eq!(":12\r\n", result); 39 | 40 | let result = serialize(0)?; 41 | assert_eq!(":0\r\n", result); 42 | 43 | let result = serialize(-12)?; 44 | assert_eq!(":-12\r\n", result); 45 | 46 | Ok(()) 47 | } 48 | 49 | #[test] 50 | fn float() -> Result<()> { 51 | log_try_init(); 52 | 53 | let result = serialize(12.12)?; 54 | assert_eq!(",12.12\r\n", result); 55 | 56 | let result = serialize(0.0)?; 57 | assert_eq!(",0.0\r\n", result); 58 | 59 | let result = serialize(-12.12)?; 60 | assert_eq!(",-12.12\r\n", result); 61 | 62 | Ok(()) 63 | } 64 | 65 | #[test] 66 | fn char() -> Result<()> { 67 | log_try_init(); 68 | 69 | let result = serialize('c')?; 70 | assert_eq!("+c\r\n", result); 71 | 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | fn str() -> Result<()> { 77 | log_try_init(); 78 | 79 | let result = serialize("OK")?; 80 | assert_eq!("+OK\r\n", result); 81 | 82 | let result = serialize("OK".to_owned())?; 83 | assert_eq!("+OK\r\n", result); 84 | 85 | Ok(()) 86 | } 87 | 88 | #[test] 89 | fn bytes() -> Result<()> { 90 | log_try_init(); 91 | 92 | let bytes: BulkString = b"abc".into(); 93 | let result = serialize(bytes)?; 94 | assert_eq!("$3\r\nabc\r\n", result); 95 | 96 | Ok(()) 97 | } 98 | 99 | #[test] 100 | fn option() -> Result<()> { 101 | log_try_init(); 102 | 103 | let result = serialize(Some(12))?; 104 | assert_eq!(":12\r\n", result); 105 | 106 | let result = serialize(Option::::None)?; 107 | assert_eq!("_\r\n", result); 108 | 109 | let result = serialize(Some(12.12))?; 110 | assert_eq!(",12.12\r\n", result); 111 | 112 | let result = serialize(Option::::None)?; 113 | assert_eq!("_\r\n", result); 114 | 115 | let result = serialize(Some("OK"))?; 116 | assert_eq!("+OK\r\n", result); 117 | 118 | let result = serialize(Option::<&str>::None)?; 119 | assert_eq!("_\r\n", result); 120 | 121 | Ok(()) 122 | } 123 | 124 | #[test] 125 | fn unit() -> Result<()> { 126 | log_try_init(); 127 | 128 | let result = serialize(())?; 129 | assert_eq!("_\r\n", result); 130 | 131 | Ok(()) 132 | } 133 | 134 | #[test] 135 | fn unit_struct() -> Result<()> { 136 | log_try_init(); 137 | 138 | #[derive(Serialize)] 139 | struct Unit; 140 | 141 | let result = serialize(Unit)?; 142 | assert_eq!("_\r\n", result); 143 | 144 | Ok(()) 145 | } 146 | 147 | #[test] 148 | fn newtype_struct() -> Result<()> { 149 | log_try_init(); 150 | 151 | #[derive(Serialize)] 152 | struct Millimeters(u8); 153 | 154 | let result = serialize(Millimeters(12))?; 155 | assert_eq!(":12\r\n", result); 156 | 157 | Ok(()) 158 | } 159 | 160 | #[test] 161 | fn seq() -> Result<()> { 162 | log_try_init(); 163 | 164 | let result = serialize([12, 13])?; 165 | assert_eq!("*2\r\n:12\r\n:13\r\n", result); 166 | 167 | let result = serialize(vec![12, 13])?; 168 | assert_eq!("*2\r\n:12\r\n:13\r\n", result); 169 | 170 | let result = serialize(smallvec![12, 13] as SmallVec<[i32; 2]>)?; 171 | assert_eq!("*2\r\n:12\r\n:13\r\n", result); 172 | 173 | Ok(()) 174 | } 175 | 176 | #[test] 177 | fn tuple() -> Result<()> { 178 | log_try_init(); 179 | 180 | let result = serialize((12, 13))?; 181 | assert_eq!("*2\r\n:12\r\n:13\r\n", result); 182 | 183 | Ok(()) 184 | } 185 | 186 | #[test] 187 | fn tuple_struct() -> Result<()> { 188 | log_try_init(); 189 | 190 | #[derive(Serialize)] 191 | struct Rgb(u8, u8, u8); 192 | 193 | let result = serialize(Rgb(12, 13, 14))?; 194 | assert_eq!("*3\r\n:12\r\n:13\r\n:14\r\n", result); 195 | 196 | Ok(()) 197 | } 198 | 199 | #[test] 200 | fn map() -> Result<()> { 201 | log_try_init(); 202 | 203 | let result = serialize(HashMap::from([(12, 13), (14, 15)]))?; 204 | assert!( 205 | result == "%2\r\n:12\r\n:13\r\n:14\r\n:15\r\n" 206 | || result == "%2\r\n:14\r\n:15\r\n:12\r\n:13\r\n" 207 | ); 208 | 209 | Ok(()) 210 | } 211 | 212 | #[test] 213 | fn _struct() -> Result<()> { 214 | log_try_init(); 215 | 216 | #[derive(Serialize)] 217 | struct Person { 218 | pub id: u64, 219 | pub name: String, 220 | } 221 | 222 | let result = serialize(Person { 223 | id: 12, 224 | name: "Mike".to_owned(), 225 | })?; 226 | assert_eq!("%2\r\n+id\r\n:12\r\n+name\r\n+Mike\r\n", result); 227 | 228 | Ok(()) 229 | } 230 | 231 | #[test] 232 | fn _enum() -> Result<()> { 233 | log_try_init(); 234 | 235 | #[derive(Serialize)] 236 | enum E { 237 | A, // unit_variant 238 | B(u8), // newtype_variant 239 | C(u8, u8), // tuple_variant 240 | D { r: u8, g: u8, b: u8 }, // struct_variant 241 | } 242 | 243 | let result = serialize(E::A)?; 244 | assert_eq!("+A\r\n", result); 245 | 246 | let result = serialize(E::B(12))?; 247 | assert_eq!("%1\r\n+B\r\n:12\r\n", result); 248 | 249 | let result = serialize(E::C(12, 13))?; 250 | assert_eq!("%1\r\n+C\r\n*2\r\n:12\r\n:13\r\n", result); 251 | 252 | let result = serialize(E::D { 253 | r: 12, 254 | g: 13, 255 | b: 14, 256 | })?; 257 | assert_eq!( 258 | "%1\r\n+D\r\n%3\r\n+r\r\n:12\r\n+g\r\n:13\r\n+b\r\n:14\r\n", 259 | result 260 | ); 261 | 262 | Ok(()) 263 | } 264 | -------------------------------------------------------------------------------- /src/tests/tls.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "tls")] 2 | use crate::{commands::StringCommands, tests::get_tls_test_client, Result}; 3 | #[cfg(feature = "tls")] 4 | use serial_test::serial; 5 | 6 | #[cfg(feature = "tls")] 7 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 8 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 9 | #[serial] 10 | async fn tls() -> Result<()> { 11 | let client = get_tls_test_client().await?; 12 | 13 | client.set("key", "value").await?; 14 | let value: String = client.get("key").await?; 15 | assert_eq!("value", value); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /src/tests/top_k_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{FlushingMode, ServerCommands, TopKCommands, TopKListWithCountResult}, 3 | tests::get_redis_stack_test_client, 4 | Result, 5 | }; 6 | use serial_test::serial; 7 | 8 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 9 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 10 | #[serial] 11 | async fn tdigest_add() -> Result<()> { 12 | let client = get_redis_stack_test_client().await?; 13 | client.flushall(FlushingMode::Sync).await?; 14 | 15 | client.topk_reserve("key", 3, None).await?; 16 | 17 | let item: Vec> = client.topk_add("key", "baz").await?; 18 | assert_eq!(vec![None], item); 19 | 20 | let items: Vec> = client.topk_add("key", ["foo", "bar", "42"]).await?; 21 | assert_eq!(vec![None, None, Some("baz".to_owned())], items); 22 | 23 | Ok(()) 24 | } 25 | 26 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 27 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 28 | #[serial] 29 | async fn tdigest_incrby() -> Result<()> { 30 | let client = get_redis_stack_test_client().await?; 31 | client.flushall(FlushingMode::Sync).await?; 32 | 33 | client.topk_reserve("key", 3, None).await?; 34 | 35 | let items: Vec> = client 36 | .topk_incrby("key", [("foo", 3), ("bar", 2), ("42", 30)]) 37 | .await?; 38 | assert_eq!(vec![None, None, None], items); 39 | 40 | Ok(()) 41 | } 42 | 43 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 44 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 45 | #[serial] 46 | async fn tdigest_info() -> Result<()> { 47 | let client = get_redis_stack_test_client().await?; 48 | client.flushall(FlushingMode::Sync).await?; 49 | 50 | client 51 | .topk_reserve("topk", 50, Some((200, 7, 0.925))) 52 | .await?; 53 | 54 | let info = client.topk_info("topk").await?; 55 | assert_eq!(50, info.k); 56 | assert_eq!(200, info.width); 57 | assert_eq!(7, info.depth); 58 | assert_eq!(0.925, info.decay); 59 | 60 | Ok(()) 61 | } 62 | 63 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 64 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 65 | #[serial] 66 | async fn tdigest_list() -> Result<()> { 67 | let client = get_redis_stack_test_client().await?; 68 | client.flushall(FlushingMode::Sync).await?; 69 | 70 | client.topk_reserve("key", 50, None).await?; 71 | 72 | let _items: Vec> = client.topk_add("key", ["foo", "bar", "42"]).await?; 73 | let _items: Vec> = client 74 | .topk_incrby("key", [("foo", 3), ("bar", 2), ("42", 30)]) 75 | .await?; 76 | 77 | let items: Vec = client.topk_list("key").await?; 78 | assert_eq!( 79 | vec!["42".to_owned(), "foo".to_owned(), "bar".to_owned()], 80 | items 81 | ); 82 | 83 | let result: TopKListWithCountResult = client.topk_list_with_count("key").await?; 84 | assert_eq!( 85 | vec![ 86 | ("42".to_owned(), 31), 87 | ("foo".to_owned(), 4), 88 | ("bar".to_owned(), 3) 89 | ], 90 | result.items 91 | ); 92 | 93 | Ok(()) 94 | } 95 | 96 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 97 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 98 | #[serial] 99 | async fn tdigest_query() -> Result<()> { 100 | let client = get_redis_stack_test_client().await?; 101 | client.flushall(FlushingMode::Sync).await?; 102 | 103 | client.topk_reserve("key", 50, None).await?; 104 | 105 | let _items: Vec> = client.topk_add("key", "42").await?; 106 | 107 | let items: Vec = client.topk_query("key", ["42", "unknown"]).await?; 108 | assert_eq!(vec![true, false], items); 109 | 110 | Ok(()) 111 | } 112 | 113 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 114 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 115 | #[serial] 116 | async fn tdigest_reserve() -> Result<()> { 117 | let client = get_redis_stack_test_client().await?; 118 | client.flushall(FlushingMode::Sync).await?; 119 | 120 | client 121 | .topk_reserve("topk", 50, Some((200, 7, 0.925))) 122 | .await?; 123 | 124 | Ok(()) 125 | } 126 | -------------------------------------------------------------------------------- /src/tests/transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::BatchPreparedCommand, 3 | commands::{FlushingMode, ListCommands, ServerCommands, StringCommands, TransactionCommands}, 4 | resp::cmd, 5 | tests::{get_cluster_test_client, get_test_client}, 6 | Error, RedisError, RedisErrorKind, Result, 7 | }; 8 | use serial_test::serial; 9 | 10 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 11 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 12 | #[serial] 13 | async fn transaction_exec() -> Result<()> { 14 | let client = get_test_client().await?; 15 | 16 | let mut transaction = client.create_transaction(); 17 | 18 | transaction.set("key1", "value1").forget(); 19 | transaction.set("key2", "value2").forget(); 20 | transaction.get::<_, ()>("key1").queue(); 21 | transaction.get::<_, ()>("key2").queue(); 22 | let (value1, value2): (String, String) = transaction.execute().await?; 23 | 24 | assert_eq!("value1", value1); 25 | assert_eq!("value2", value2); 26 | 27 | let mut transaction = client.create_transaction(); 28 | 29 | transaction.set("key", "value").forget(); 30 | transaction.get::<_, ()>("key").queue(); 31 | let value: String = transaction.execute().await?; 32 | 33 | assert_eq!("value", value); 34 | 35 | Ok(()) 36 | } 37 | 38 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 39 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 40 | #[serial] 41 | async fn transaction_error() -> Result<()> { 42 | let client = get_test_client().await?; 43 | 44 | let mut transaction = client.create_transaction(); 45 | 46 | transaction.set("key1", "abc").forget(); 47 | transaction.queue(cmd("UNKNOWN")); 48 | let result: Result = transaction.execute().await; 49 | 50 | assert!(matches!( 51 | result, 52 | Err(Error::Redis(RedisError { 53 | kind: RedisErrorKind::Err, 54 | description: _ 55 | })) 56 | )); 57 | 58 | let mut transaction = client.create_transaction(); 59 | 60 | transaction.set("key1", "abc").forget(); 61 | transaction.lpop::<_, (), ()>("key1", 1).queue(); 62 | let result: Result = transaction.execute().await; 63 | 64 | assert!(matches!( 65 | result, 66 | Err(Error::Redis(RedisError { 67 | kind: RedisErrorKind::WrongType, 68 | description: _ 69 | })) 70 | )); 71 | 72 | Ok(()) 73 | } 74 | 75 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 76 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 77 | #[serial] 78 | async fn watch() -> Result<()> { 79 | let client = get_test_client().await?; 80 | client.flushdb(FlushingMode::Sync).await?; 81 | 82 | client.set("key", 1).await?; 83 | client.watch("key").await?; 84 | 85 | let mut value: i32 = client.get("key").await?; 86 | value += 1; 87 | 88 | let mut transaction = client.create_transaction(); 89 | 90 | transaction.set("key", value).queue(); 91 | transaction.execute::<()>().await?; 92 | 93 | let value: i32 = client.get("key").await?; 94 | assert_eq!(2, value); 95 | 96 | let value = 3; 97 | client.watch("key").await?; 98 | 99 | let mut transaction = client.create_transaction(); 100 | 101 | // set key on another client during the transaction 102 | let client2 = get_test_client().await?; 103 | client2.set("key", value).await?; 104 | 105 | transaction.set("key", value).queue(); 106 | let result: Result<()> = transaction.execute().await; 107 | assert!(matches!(result, Err(Error::Aborted))); 108 | 109 | Ok(()) 110 | } 111 | 112 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 113 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 114 | #[serial] 115 | async fn unwatch() -> Result<()> { 116 | let client = get_test_client().await?; 117 | client.flushdb(FlushingMode::Sync).await?; 118 | 119 | client.set("key", 1).await?; 120 | client.watch("key").await?; 121 | 122 | let mut value: i32 = client.get("key").await?; 123 | value += 1; 124 | 125 | client.watch("key").await?; 126 | client.unwatch().await?; 127 | 128 | let mut transaction = client.create_transaction(); 129 | 130 | // set key on another client during the transaction 131 | let client2 = get_test_client().await?; 132 | client2.set("key", 3).await?; 133 | 134 | transaction.set("key", value).queue(); 135 | transaction.execute::<()>().await?; 136 | 137 | let value: i32 = client.get("key").await?; 138 | assert_eq!(2, value); 139 | 140 | Ok(()) 141 | } 142 | 143 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 144 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 145 | #[serial] 146 | async fn transaction_discard() -> Result<()> { 147 | let client = get_test_client().await?; 148 | 149 | let mut transaction = client.create_transaction(); 150 | 151 | transaction.set("key1", "value1").forget(); 152 | transaction.set("key2", "value2").forget(); 153 | transaction.get::<_, ()>("key1").queue(); 154 | 155 | std::mem::drop(transaction); 156 | 157 | client.set("key", "value").await?; 158 | let value: String = client.get("key").await?; 159 | assert_eq!("value", value); 160 | 161 | Ok(()) 162 | } 163 | 164 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 165 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 166 | #[serial] 167 | async fn transaction_on_cluster_connection_with_keys_with_same_slot() -> Result<()> { 168 | let client = get_cluster_test_client().await?; 169 | client.flushall(FlushingMode::Sync).await?; 170 | 171 | let mut transaction = client.create_transaction(); 172 | 173 | transaction 174 | .mset([("{hash}key1", "value1"), ("{hash}key2", "value2")]) 175 | .queue(); 176 | transaction.get::<_, String>("{hash}key1").queue(); 177 | transaction.get::<_, String>("{hash}key2").queue(); 178 | let ((), val1, val2): ((), String, String) = transaction.execute().await.unwrap(); 179 | assert_eq!("value1", val1); 180 | assert_eq!("value2", val2); 181 | 182 | Ok(()) 183 | } 184 | 185 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 186 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 187 | #[serial] 188 | async fn transaction_on_cluster_connection_with_keys_with_different_slots() -> Result<()> { 189 | let client = get_cluster_test_client().await?; 190 | client.flushall(FlushingMode::Sync).await?; 191 | 192 | let mut transaction = client.create_transaction(); 193 | 194 | transaction 195 | .mset([("key1", "value1"), ("key2", "value2")]) 196 | .queue(); 197 | transaction.get::<_, String>("key1").queue(); 198 | transaction.get::<_, String>("key2").queue(); 199 | let result: Result<((), String, String)> = transaction.execute().await; 200 | assert!(result.is_err()); 201 | 202 | Ok(()) 203 | } 204 | -------------------------------------------------------------------------------- /src/tests/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{Client, Config, IntoConfig}, 3 | Result, 4 | }; 5 | #[cfg(feature = "tls")] 6 | use native_tls::Certificate; 7 | 8 | /// copy-paste of the root certificate located at crt/certs/ca.crt 9 | #[cfg(feature = "tls")] 10 | const ROOT_CERTIFICATE: &str = r#"-----BEGIN CERTIFICATE----- 11 | MIIFSzCCAzOgAwIBAgIULTp8cWRl326SijHSTdHpP0y/SkAwDQYJKoZIhvcNAQEL 12 | BQAwNTETMBEGA1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUg 13 | QXV0aG9yaXR5MB4XDTIyMTAwMTA4MjMyM1oXDTMyMDkyODA4MjMyM1owNTETMBEG 14 | A1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5 15 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApWm0kFW03v2s58VX3FI/ 16 | gdIo0l+aEdYXoSaic9xPWl5MV5+T7YQ7V9tck6whi4ceUtDAKa8QZBoiJ9gn/Lbr 17 | e6ebZiJ7blBscEqKzXZk5URlHbxXlbfldHScnKNxluI5ApJ0sYov58R60klJNWeK 18 | Wlz+Hn2ubN1IkXuClMJ59i0UZ+MlALpXzpSiW1gS7pT4gIkuCQfdWWwUNDNuFN57 19 | /l9fU0VYQd/7AI9eJnV9ltTOaVyiL/uO3mueWBmM+AeeGaX0WRtctRcR2sNqDMIx 20 | JW/bUziTRncI+HEGQd7Wf1+yi4fjajUzLj8omVvO7RBrCyq5RV9dpMEAgY4LIM8w 21 | g+VlLDbOT52CY90ADbTfMDgiH5mg2Zt3l4xNiwno/Itkng93Hh/AbPomOGtJSYPr 22 | CIrzhkfD6PXMXYzdXAJLzjCc5sOIBrFhDSIzkGOAxX0DZaxngimCytigw8c1KdIw 23 | Z/j71rDjv6blleGJ6ZXBwtdQEG2clDSuVjBRuIIxe64/wMEe702MMC28Y97SZ3WV 24 | JU4KaQoW5oVaoom9+hngCT6btpmT6adu0oC424bmSxUdB6/Kk+kgsD6SyaXt6VCf 25 | PUpfipNwbS/GFoqevLSGjOsyrEl5nzF0VBdcg9TOodlqruVtNFSnShQh93hKmY1J 26 | Mz62dg/LnnZ4+yO1ARZk+tcCAwEAAaNTMFEwHQYDVR0OBBYEFHr9VpODSEUfgO/W 27 | sVebyT+YTcy9MB8GA1UdIwQYMBaAFHr9VpODSEUfgO/WsVebyT+YTcy9MA8GA1Ud 28 | EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBABImtJJhwE2b0cNeSI+ng9oa 29 | 6PBXX5usNZQBuw3wvaLFephpbUH/HrWFJCbscubZ0wmt0UD6Ly7v32DJl505NFQQ 30 | XMDAdApLRMHcbFcyqIIVkcSlOoRNlf5Dx9A2oqUzwI392OrDeYEF+a+9CcLGo6Fv 31 | m8vii6Z1JS7TgGa9KqFErOgyvVv5xoSH31EVgezIoMgYWya2oiKZayYLNlr/eo3c 32 | 8DxEvJ2sdv8MI09lrwAZ61bx4aDYcpnDckVzvttdjGrunr1AyblCo6yhKUax1w5K 33 | qLvLoVwuTFo4VFzMJMeIuRLm79hxhQIjAgIba8Cms0EPxcivVaWG5KvY8/oXLlP6 34 | YRgmWvuA9UGvnrAvUw+eAZj1aFzLRGLXG3VxHUSJyhEV54dZCMWMfK0KOdyptbR3 35 | 7phJCeCGYS8/kJnCMAXU4NfiGWmRcTxkJTqgHC3txgzJQ4Izt8oeekJwlOJEN6R8 36 | OCT4DeNGKy0bcAwaUT2n+b+OmQpaT/F7u2Hx/n0356QjVSoNTgmg6Bjsp5hNlX6i 37 | I22ZhrayIRlXmMUmivMWBriz44yu6bo74EV6zHNvF1LYR7u2ajtdzlk1fHSE4OfM 38 | +J8SNDwRYRFTxZPTK2Yf/PQtyl+xaWAHcT7NumXQcqOVxq9jfaurIOCWz4i4BIeK 39 | bsPEVuonk6XLwUlSNI2W 40 | -----END CERTIFICATE----- 41 | "#; 42 | 43 | pub(crate) fn get_default_host() -> String { 44 | match std::env::var("REDIS_HOST") { 45 | Ok(host) => host, 46 | Err(_) => "127.0.0.1".to_string(), 47 | } 48 | } 49 | 50 | pub(crate) fn get_default_port() -> u16 { 51 | match std::env::var("REDIS_PORT") { 52 | Ok(port) => port.parse::().unwrap(), 53 | Err(_) => 6379, 54 | } 55 | } 56 | 57 | #[cfg(feature = "tls")] 58 | pub(crate) fn get_default_tls_port() -> u16 { 59 | match std::env::var("REDIS_TLS_PORT") { 60 | Ok(port) => port.parse::().unwrap(), 61 | Err(_) => 6380, 62 | } 63 | } 64 | 65 | pub(crate) fn get_default_addr() -> String { 66 | format!("{}:{}", get_default_host(), get_default_port()) 67 | } 68 | 69 | pub(crate) fn get_default_config() -> Result { 70 | get_default_addr().into_config() 71 | } 72 | 73 | pub(crate) async fn get_test_client_with_config(config: impl IntoConfig) -> Result { 74 | log_try_init(); 75 | Client::connect(config).await 76 | } 77 | 78 | pub(crate) async fn get_test_client() -> Result { 79 | get_test_client_with_config(get_default_config()?).await 80 | } 81 | 82 | #[cfg(feature = "tls")] 83 | pub(crate) async fn get_tls_test_client() -> Result { 84 | log_try_init(); 85 | 86 | let uri = format!( 87 | "rediss://:pwd@{}:{}", 88 | get_default_host(), 89 | get_default_tls_port() 90 | ); 91 | 92 | let mut config = uri.into_config()?; 93 | 94 | if let Some(tls_config) = &mut config.tls_config { 95 | let root_cert = Certificate::from_pem(ROOT_CERTIFICATE.as_bytes())?; 96 | tls_config.root_certificates(vec![root_cert]); 97 | // non trusted cert for tests 98 | tls_config.danger_accept_invalid_certs(true); 99 | } 100 | 101 | Client::connect(config).await 102 | } 103 | 104 | pub(crate) async fn get_sentinel_test_client() -> Result { 105 | log_try_init(); 106 | let host = get_default_host(); 107 | Client::connect(format!("redis://{host}:26379")).await 108 | } 109 | 110 | pub fn get_sentinel_master_test_uri() -> String { 111 | let host = get_default_host(); 112 | format!("redis+sentinel://{host}:26379,{host}:26380,{host}:26381/myservice") 113 | } 114 | 115 | pub(crate) async fn get_sentinel_master_test_client() -> Result { 116 | log_try_init(); 117 | Client::connect(get_sentinel_master_test_uri()).await 118 | } 119 | 120 | pub(crate) async fn get_cluster_test_client() -> Result { 121 | log_try_init(); 122 | let host = get_default_host(); 123 | Client::connect(format!( 124 | "redis+cluster://{host}:7000,{host}:7001,{host}:7002" 125 | )) 126 | .await 127 | } 128 | 129 | pub(crate) async fn get_cluster_test_client_with_command_timeout() -> Result { 130 | log_try_init(); 131 | let host = get_default_host(); 132 | Client::connect(format!( 133 | "redis+cluster://{host}:7000,{host}:7001,{host}:7002?command_timeout=2000" 134 | )) 135 | .await 136 | } 137 | 138 | #[cfg(feature = "redis-json")] 139 | pub(crate) async fn get_redis_stack_test_client() -> Result { 140 | log_try_init(); 141 | Client::connect(format!("redis://{}:{}", get_default_host(), 8000)).await 142 | } 143 | 144 | pub fn log_try_init() { 145 | let _ = env_logger::builder() 146 | .format_target(false) 147 | .format_timestamp(None) 148 | .filter_level(log::LevelFilter::Debug) 149 | .target(env_logger::Target::Stdout) 150 | .is_test(true) 151 | .parse_default_env() 152 | .try_init(); 153 | } 154 | -------------------------------------------------------------------------------- /src/tests/value.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{FlushingMode, ServerCommands, SetCommands}, 3 | resp::Value, 4 | tests::{get_test_client, log_try_init}, 5 | RedisError, RedisErrorKind, Result, 6 | }; 7 | use serial_test::serial; 8 | use std::collections::{BTreeSet, HashMap, HashSet}; 9 | 10 | #[cfg_attr(feature = "tokio-runtime", tokio::test)] 11 | #[cfg_attr(feature = "async-std-runtime", async_std::test)] 12 | #[serial] 13 | async fn from_single_value_array() -> Result<()> { 14 | let client = get_test_client().await?; 15 | 16 | client.flushall(FlushingMode::Sync).await?; 17 | 18 | client 19 | .sadd("key", ["member1", "member2", "member3"]) 20 | .await?; 21 | 22 | let members: Vec = client.smembers("key").await?; 23 | assert_eq!(3, members.len()); 24 | assert!(members.contains(&"member1".to_owned())); 25 | assert!(members.contains(&"member2".to_owned())); 26 | assert!(members.contains(&"member3".to_owned())); 27 | 28 | let members: HashSet = client.smembers("key").await?; 29 | assert_eq!(3, members.len()); 30 | assert!(members.contains("member1")); 31 | assert!(members.contains("member2")); 32 | assert!(members.contains("member3")); 33 | 34 | let members: BTreeSet = client.smembers("key").await?; 35 | assert_eq!(3, members.len()); 36 | assert!(members.contains("member1")); 37 | assert!(members.contains("member2")); 38 | assert!(members.contains("member3")); 39 | 40 | Ok(()) 41 | } 42 | 43 | #[test] 44 | fn tuple() -> Result<()> { 45 | log_try_init(); 46 | 47 | let value = Value::Array(vec![ 48 | Value::BulkString("first".as_bytes().to_vec()), 49 | Value::BulkString("second".as_bytes().to_vec()), 50 | ]); 51 | let result: Vec = value.into()?; 52 | assert_eq!(2, result.len()); 53 | assert_eq!("first".to_owned(), result[0]); 54 | assert_eq!("second".to_owned(), result[1]); 55 | 56 | let values = Value::Array(vec![ 57 | Value::BulkString("first".as_bytes().to_vec()), 58 | Value::BulkString("second".as_bytes().to_vec()), 59 | ]); 60 | let result: (String, String) = values.into()?; 61 | assert_eq!(("first".to_owned(), "second".to_owned()), result); 62 | 63 | let value = Value::Array(vec![ 64 | Value::Array(vec![ 65 | Value::BulkString("first".as_bytes().to_vec()), 66 | Value::BulkString("second".as_bytes().to_vec()), 67 | ]), 68 | Value::Array(vec![ 69 | Value::BulkString("third".as_bytes().to_vec()), 70 | Value::BulkString("fourth".as_bytes().to_vec()), 71 | ]), 72 | ]); 73 | let result: Vec<(String, String)> = value.into()?; 74 | assert_eq!(2, result.len()); 75 | assert_eq!(("first".to_owned(), "second".to_owned()), result[0]); 76 | assert_eq!(("third".to_owned(), "fourth".to_owned()), result[1]); 77 | 78 | Ok(()) 79 | } 80 | 81 | #[test] 82 | fn display() { 83 | log_try_init(); 84 | 85 | log::debug!( 86 | "{}", 87 | Value::Array(vec![ 88 | Value::Integer(12), 89 | Value::Double(12.12), 90 | Value::SimpleString("OK".to_owned()), 91 | Value::BulkString(b"mystring".to_vec()), 92 | Value::Boolean(true), 93 | Value::Error(RedisError { 94 | kind: RedisErrorKind::Err, 95 | description: "MyError".to_owned() 96 | }), 97 | Value::Nil, 98 | Value::Map(HashMap::from([ 99 | (Value::BulkString(b"field1".to_vec()), Value::Integer(12)), 100 | (Value::BulkString(b"field2".to_vec()), Value::Double(12.12)) 101 | ])) 102 | ]) 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /src/tests/value_deserialize.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | resp::{RespDeserializer, Value}, 3 | tests::log_try_init, 4 | Error, RedisError, RedisErrorKind, Result, 5 | }; 6 | use serde::Deserialize; 7 | 8 | fn deserialize_value(str: &str) -> Result { 9 | let buf = str.as_bytes(); 10 | let mut deserializer = RespDeserializer::new(buf); 11 | Value::deserialize(&mut deserializer) 12 | } 13 | 14 | #[test] 15 | fn simple_string() -> Result<()> { 16 | log_try_init(); 17 | 18 | let result = deserialize_value("+OK\r\n")?; // "OK" 19 | assert_eq!(Value::SimpleString("OK".to_owned()), result); 20 | 21 | Ok(()) 22 | } 23 | 24 | #[test] 25 | fn integer() -> Result<()> { 26 | log_try_init(); 27 | 28 | let result = deserialize_value(":12\r\n")?; // 12 29 | assert_eq!(Value::Integer(12), result); 30 | 31 | Ok(()) 32 | } 33 | 34 | #[test] 35 | fn double() -> Result<()> { 36 | log_try_init(); 37 | 38 | let result = deserialize_value(",12.12\r\n")?; // 12.12 39 | assert_eq!(Value::Double(12.12), result); 40 | 41 | Ok(()) 42 | } 43 | 44 | #[test] 45 | fn bulk_string() -> Result<()> { 46 | log_try_init(); 47 | 48 | let result = deserialize_value("$5\r\nhello\r\n")?; // b"hello" 49 | assert_eq!(Value::BulkString(b"hello".to_vec()), result); 50 | 51 | let result = deserialize_value("$7\r\nhel\r\nlo\r\n")?; // b"hel\r\nlo" 52 | assert_eq!(Value::BulkString(b"hel\r\nlo".to_vec()), result); 53 | 54 | let result = deserialize_value("$5\r\nhello\r"); 55 | assert!(matches!(result, Err(Error::EOF))); 56 | 57 | let result = deserialize_value("$5\r\nhello"); 58 | assert!(matches!(result, Err(Error::EOF))); 59 | 60 | let result = deserialize_value("$5\r"); 61 | assert!(matches!(result, Err(Error::EOF))); 62 | 63 | let result = deserialize_value("$5"); 64 | assert!(matches!(result, Err(Error::EOF))); 65 | 66 | let result = deserialize_value("$"); 67 | assert!(matches!(result, Err(Error::EOF))); 68 | 69 | let result = deserialize_value("$6\r\nhello\r\n"); 70 | assert!(matches!(result, Err(Error::EOF))); 71 | 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | fn array() -> Result<()> { 77 | log_try_init(); 78 | 79 | let result = deserialize_value("*2\r\n:12\r\n:13\r\n")?; // [12, 13] 80 | assert_eq!( 81 | Value::Array(vec![Value::Integer(12), Value::Integer(13)]), 82 | result 83 | ); 84 | 85 | let result = deserialize_value("*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n")?; // [b"hello, b"world"] 86 | assert_eq!( 87 | Value::Array(vec![ 88 | Value::BulkString(b"hello".to_vec()), 89 | Value::BulkString(b"world".to_vec()) 90 | ]), 91 | result 92 | ); 93 | 94 | let result = deserialize_value("*0\r\n")?; // [] 95 | assert_eq!(Value::Nil, result); 96 | 97 | Ok(()) 98 | } 99 | 100 | #[test] 101 | fn nil() -> Result<()> { 102 | log_try_init(); 103 | 104 | let result = deserialize_value("_\r\n")?; 105 | assert_eq!(Value::Nil, result); 106 | 107 | Ok(()) 108 | } 109 | 110 | #[test] 111 | fn map() -> Result<()> { 112 | log_try_init(); 113 | 114 | let result = deserialize_value("%2\r\n$2\r\nid\r\n:12\r\n$4\r\nname\r\n$4\r\nMike\r\n")?; // {b"id": 12, b"name": b"Mike"} 115 | assert_eq!( 116 | Value::Array(vec![ 117 | Value::BulkString(b"id".to_vec()), 118 | Value::Integer(12), 119 | Value::BulkString(b"name".to_vec()), 120 | Value::BulkString(b"Mike".to_vec()) 121 | ]), 122 | result 123 | ); 124 | 125 | let result = deserialize_value("%0\r\n")?; // {} 126 | assert_eq!(Value::Nil, result); 127 | 128 | Ok(()) 129 | } 130 | 131 | #[test] 132 | fn set() -> Result<()> { 133 | log_try_init(); 134 | 135 | let result = deserialize_value("~2\r\n:12\r\n:13\r\n")?; // [12, 13] 136 | assert_eq!( 137 | Value::Array(vec![Value::Integer(12), Value::Integer(13)]), 138 | result 139 | ); 140 | 141 | let result = deserialize_value("~2\r\n$5\r\nhello\r\n$5\r\nworld\r\n")?; // [b"hello, b"world"] 142 | assert_eq!( 143 | Value::Array(vec![ 144 | Value::BulkString(b"hello".to_vec()), 145 | Value::BulkString(b"world".to_vec()) 146 | ]), 147 | result 148 | ); 149 | 150 | let result = deserialize_value("~0\r\n")?; // [] 151 | assert_eq!(Value::Nil, result); 152 | 153 | Ok(()) 154 | } 155 | 156 | #[test] 157 | fn push() -> Result<()> { 158 | log_try_init(); 159 | 160 | let result = deserialize_value(">3\r\n$7\r\nmessage\r\n$7\r\nchannel\r\n$7\r\npayload\r\n")?; // [b"message, b"channel", b"payload"] 161 | assert_eq!( 162 | Value::Push(vec![ 163 | Value::BulkString(b"message".to_vec()), 164 | Value::BulkString(b"channel".to_vec()), 165 | Value::BulkString(b"payload".to_vec()) 166 | ]), 167 | result 168 | ); 169 | 170 | let result = deserialize_value(">0\r\n")?; // [] 171 | assert_eq!(Value::Nil, result); 172 | 173 | Ok(()) 174 | } 175 | 176 | #[test] 177 | fn error() -> Result<()> { 178 | log_try_init(); 179 | 180 | let result = deserialize_value("-ERR error\r\n"); 181 | println!("result: {result:?}"); 182 | assert!(matches!( 183 | result, 184 | Err(Error::Redis(RedisError { 185 | kind: RedisErrorKind::Err, 186 | description 187 | })) if description == "error" 188 | )); 189 | 190 | Ok(()) 191 | } 192 | 193 | #[test] 194 | fn blob_error() -> Result<()> { 195 | log_try_init(); 196 | 197 | let result = deserialize_value("!9\r\nERR error\r\n"); 198 | println!("result: {result:?}"); 199 | assert!(matches!( 200 | result, 201 | Err(Error::Redis(RedisError { 202 | kind: RedisErrorKind::Err, 203 | description 204 | })) if description == "error" 205 | )); 206 | 207 | let result = deserialize_value("!11\r\nERR er\r\nror\r\n"); 208 | println!("result: {result:?}"); 209 | assert!(matches!( 210 | result, 211 | Err(Error::Redis(RedisError { 212 | kind: RedisErrorKind::Err, 213 | description 214 | })) if description == "er\r\nror" 215 | )); 216 | 217 | Ok(()) 218 | } 219 | -------------------------------------------------------------------------------- /src/tests/value_serialize.rs: -------------------------------------------------------------------------------- 1 | use super::log_try_init; 2 | use crate::{ 3 | resp::{RespBuf, RespSerializer, Value}, 4 | RedisError, RedisErrorKind, Result, 5 | }; 6 | use serde::Serialize; 7 | 8 | fn serialize(value: Value) -> Result { 9 | let mut serializer = RespSerializer::new(); 10 | value.serialize(&mut serializer)?; 11 | let output = serializer.get_output(); 12 | Ok(RespBuf::new(output.freeze())) 13 | } 14 | 15 | #[test] 16 | fn integer() -> Result<()> { 17 | log_try_init(); 18 | 19 | let resp_buf = serialize(Value::Integer(12))?; 20 | log::debug!("resp_buf: {resp_buf}"); 21 | assert_eq!(b":12\r\n", resp_buf.as_bytes()); 22 | 23 | Ok(()) 24 | } 25 | 26 | #[test] 27 | fn error() -> Result<()> { 28 | log_try_init(); 29 | 30 | let resp_buf = serialize(Value::Error(RedisError { 31 | kind: RedisErrorKind::Err, 32 | description: "error".to_owned(), 33 | }))?; 34 | log::debug!("resp_buf: {resp_buf}"); 35 | assert_eq!(b"-ERR error\r\n", resp_buf.as_bytes()); 36 | 37 | Ok(()) 38 | } 39 | 40 | #[test] 41 | fn push() -> Result<()> { 42 | log_try_init(); 43 | 44 | let resp_buf = serialize(Value::Push(vec![ 45 | Value::Integer(12), 46 | Value::BulkString(b"foo".to_vec()), 47 | ]))?; 48 | log::debug!("resp_buf: {resp_buf}"); 49 | assert_eq!(b">2\r\n:12\r\n$3\r\nfoo\r\n", resp_buf.as_bytes()); 50 | 51 | Ok(()) 52 | } 53 | 54 | #[test] 55 | fn set() -> Result<()> { 56 | log_try_init(); 57 | 58 | let resp_buf = serialize(Value::Set(vec![ 59 | Value::Integer(12), 60 | Value::BulkString(b"foo".to_vec()), 61 | ]))?; 62 | log::debug!("resp_buf: {resp_buf}"); 63 | assert_eq!(b"~2\r\n:12\r\n$3\r\nfoo\r\n", resp_buf.as_bytes()); 64 | 65 | Ok(()) 66 | } 67 | 68 | #[test] 69 | fn array() -> Result<()> { 70 | log_try_init(); 71 | 72 | let resp_buf = serialize(Value::Array(vec![ 73 | Value::Integer(12), 74 | Value::BulkString(b"foo".to_vec()), 75 | ]))?; 76 | log::debug!("resp_buf: {resp_buf}"); 77 | assert_eq!(b"*2\r\n:12\r\n$3\r\nfoo\r\n", resp_buf.as_bytes()); 78 | 79 | Ok(()) 80 | } 81 | --------------------------------------------------------------------------------