diff --git a/CHANGELOG.md b/CHANGELOG.md index e464c159..10a7ffa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Rework the MOVED/ASK implementation to more quickly and reliably follow cluster redirects. * Rework the sentinel interface to more reliably handle failover scenarios. * Fix several bugs related to detecting closed connections. +* Support the `functions` interface. * Add `Script`, `Library`, and `Function` structs. * Add `Message` and `MessageKind` pubsub structs. * Add a DNS configuration interface. diff --git a/Cargo.toml b/Cargo.toml index 615f3f2d..6b79093b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,10 +72,6 @@ doc = true name = "fred" test = true -[[example]] -name = "basic" -required-features = ["partial-tracing"] - [[example]] name = "monitor" required-features = ["monitor"] diff --git a/README.md b/README.md index d9a2c5ec..7489f507 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Fred [![Crates.io](https://img.shields.io/crates/v/fred.svg)](https://crates.io/crates/fred) [![API docs](https://docs.rs/fred/badge.svg)](https://docs.rs/fred) -An async Redis client for Rust built on Tokio and Futures. +An async Redis client for Rust. ## Example @@ -42,7 +42,7 @@ async fn main() -> Result<(), RedisError> { } ``` -See the [examples](examples/README.md) for more. +See the [examples](https://github.com/aembke/fred.rs/tree/main/examples) for more. ## Features @@ -67,38 +67,27 @@ See the [examples](examples/README.md) for more. ## Build Time Features -| Name | Default | Description | -|-------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| enable-native-tls | | Enable TLS support via [native-tls](https://crates.io/crates/native-tls). | -| enable-rustls | | Enable TLS support via [rustls](https://crates.io/crates/rustls). | -| vendored-openssl | | Enable the `native-tls/vendored` feature, if possible. | -| ignore-auth-error | x | Ignore auth errors that occur when a password is supplied but not required. | -| metrics | | Enable the metrics interface to track overall latency, network latency, and request/response sizes. | +| Name | Default | Description | +|-------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| enable-native-tls | | Enable TLS support via [native-tls](https://crates.io/crates/native-tls). | +| enable-rustls | | Enable TLS support via [rustls](https://crates.io/crates/rustls). | +| vendored-openssl | | Enable the `native-tls/vendored` feature, if possible. | +| ignore-auth-error | x | Ignore auth errors that occur when a password is supplied but not required. | +| metrics | | Enable the metrics interface to track overall latency, network latency, and request/response sizes. | | reconnect-on-auth-error | | A NOAUTH error is treated the same as a general connection failure and the client will reconnect based on the reconnection policy. This is [recommended](https://github.com/StackExchange/StackExchange.Redis/issues/1273#issuecomment-651823824) if callers are using ElastiCache. | -| pool-prefer-active | x | Prefer connected clients over clients in a disconnected state when using the `RedisPool` interface. | -| full-tracing | | Enable full [tracing](./src/trace/README.md) support. This can emit a lot of data so a partial tracing feature is also provided. | -| partial-tracing | | Enable partial [tracing](./src/trace/README.md) support, only emitting traces for top level commands and network latency. Note: this has a non-trivial impact on performance. | -| blocking-encoding | | Use a blocking task for encoding or decoding frames. This can be useful for clients that send or receive large payloads, but will only work when used with a multi-thread Tokio runtime. | -| network-logs | | Enable TRACE level logging statements that will print out all data sent to or received from the server. These are the only logging statements that can ever contain potentially sensitive user data. | -| custom-reconnect-errors | | Enable an interface for callers to customize the types of errors that should automatically trigger reconnection logic. | -| monitor | | Enable an interface for running the `MONITOR` command. | -| sentinel-client | | Enable an interface for communicating directly with Sentinel nodes. This is not necessary to use normal Redis clients behind a sentinel layer. | -| sentinel-auth | | Enable an interface for using different authentication credentials to sentinel nodes. | -| subscriber-client | | Enable an optional subscriber client that manages channel subscription state for callers. | -| serde-json | | Enable an interface to automatically convert Redis types to JSON. | -| no-client-setname | | Disable the automatic `CLIENT SETNAME` command used to associate server logs with client logs. | -| mocks | | Enable a mocking layer interface that can be used to intercept and process commands in tests. | -| dns | | Enable an interface that allows callers to override the DNS lookup logic. | -| check-unresponsive | | Enable additional monitoring to detect unresponsive connections. | -| replicas | | [Beta] Enable an interface that routes commands to replica nodes. | - - -## Tests - -See the [testing documentation](./tests/README.md) for more information. - -**Beware: the tests will periodically run `flushall`.** - -## Contributing - -See the [contributing](CONTRIBUTING.md) documentation for info on adding new commands. +| pool-prefer-active | x | Prefer connected clients over clients in a disconnected state when using the `RedisPool` interface. | +| full-tracing | | Enable full [tracing](./src/trace/README.md) support. This can emit a lot of data. | +| partial-tracing | | Enable partial [tracing](./src/trace/README.md) support, only emitting traces for top level commands and network latency. | +| blocking-encoding | | Use a blocking task for encoding or decoding frames. This can be useful for clients that send or receive large payloads, but will only work when used with a multi-thread Tokio runtime. | +| network-logs | | Enable TRACE level logging statements that will print out all data sent to or received from the server. These are the only logging statements that can ever contain potentially sensitive user data. | +| custom-reconnect-errors | | Enable an interface for callers to customize the types of errors that should automatically trigger reconnection logic. | +| monitor | | Enable an interface for running the `MONITOR` command. | +| sentinel-client | | Enable an interface for communicating directly with Sentinel nodes. This is not necessary to use normal Redis clients behind a sentinel layer. | +| sentinel-auth | | Enable an interface for using different authentication credentials to sentinel nodes. | +| subscriber-client | | Enable an optional subscriber client that manages channel subscription state for callers. | +| serde-json | | Enable an interface to automatically convert Redis types to JSON. | +| no-client-setname | | Disable the automatic `CLIENT SETNAME` command used to associate server logs with client logs. | +| mocks | | Enable a mocking layer interface that can be used to intercept and process commands in tests. | +| dns | | Enable an interface that allows callers to override the DNS lookup logic. | +| check-unresponsive | | Enable additional monitoring to detect unresponsive connections. | +| replicas | | (Beta) Enable an interface that routes commands to replica nodes. | diff --git a/bin/inf_loop/docker-compose.yml b/bin/inf_loop/docker-compose.yml index abc0ffa0..99dee1f0 100644 --- a/bin/inf_loop/docker-compose.yml +++ b/bin/inf_loop/docker-compose.yml @@ -13,7 +13,7 @@ services: REDIS_VERSION: "${REDIS_VERSION}" networks: - app-tier - entrypoint: "cargo run --release --features \"replicas debug-ids network-logs\" -- ${TEST_ARGV}" + entrypoint: "cargo run --release --features \"replicas\" -- ${TEST_ARGV}" environment: RUST_LOG: "${RUST_LOG}" REDIS_VERSION: "${REDIS_VERSION}" diff --git a/examples/README.md b/examples/README.md index d1e0acd0..c5497b87 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,6 +7,7 @@ Examples * [Publish-Subscribe](./pubsub.rs) - Use multiple clients together with the pubsub interface in a way that survives network interruptions. * [Blocking](./blocking.rs) - Use multiple clients with the blocking list interface. * [Transactions](./transactions.rs) - Use the MULTI/EXEC interface on a client. +* [Pipeline](./pipeline.rs) - Use the manual pipeline interface. * [Lua](./lua.rs) - Use the Lua scripting interface on a client. * [Scan](./scan.rs) - Use the SCAN interface to scan and read keys. * [Prometheus](./prometheus.rs) - Use the metrics interface with prometheus. @@ -17,4 +18,4 @@ Examples * [Custom](./custom.rs) - Send custom commands or operate on RESP frames. * [DNS](./dns.rs) - Customize the DNS resolution logic. -Or check out the [tests](../tests/integration) for more examples. \ No newline at end of file +Or see the [tests](../tests/integration) for more examples. \ No newline at end of file diff --git a/examples/basic.rs b/examples/basic.rs index 2fbc35d8..d2253828 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,42 +1,47 @@ use fred::{ prelude::*, - types::{BackpressureConfig, BackpressurePolicy, PerformanceConfig, RespVersion, TlsConfig}, + types::{BackpressureConfig, BackpressurePolicy, PerformanceConfig, RespVersion}, }; -use futures::stream::StreamExt; -use std::{default::Default, sync::Arc}; #[cfg(feature = "mocks")] use fred::mocks::Echo; #[cfg(feature = "partial-tracing")] use fred::tracing::Level; +#[cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))] +use fred::types::TlsConfig; #[cfg(feature = "partial-tracing")] use fred::types::TracingConfig; +#[cfg(feature = "mocks")] +use std::{default::Default, sync::Arc}; #[tokio::main] async fn main() -> Result<(), RedisError> { + pretty_env_logger::init(); + let _ = RedisConfig::from_url("redis://username:password@foo.com:6379/1")?; - // full configuration with default values + // full configuration with testing values let config = RedisConfig { fail_fast: true, - server: ServerConfig::new_centralized("127.0.0.1", 6379), + server: ServerConfig::new_centralized("redis-main", 6379), blocking: Blocking::Block, - username: None, - password: None, + username: Some("foo".into()), + password: Some("bar".into()), version: RespVersion::RESP2, database: None, #[cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))] tls: None, #[cfg(feature = "partial-tracing")] tracing: TracingConfig { - enabled: false, - default_tracing_level: Level::INFO, - full_tracing_level: Level::DEBUG, + enabled: false, + default_tracing_level: Level::INFO, + #[cfg(feature = "full-tracing")] + full_tracing_level: Level::DEBUG, }, #[cfg(feature = "mocks")] mocks: Arc::new(Echo), }; - // example showing a full kitchen sink configuration for performance tuning options + // full configuration for performance tuning options let perf = PerformanceConfig { // whether or not to automatically pipeline commands across tasks auto_pipeline: true, @@ -72,12 +77,12 @@ async fn main() -> Result<(), RedisError> { let mut reconnect_rx = client.on_reconnect(); tokio::spawn(async move { - while let Some(error) = error_rx.recv().await { + while let Ok(error) = error_rx.recv().await { println!("Client disconnected with error: {:?}", error); } }); tokio::spawn(async move { - while let Some(_) = reconnect_rx.recv().await { + while reconnect_rx.recv().await.is_ok() { println!("Client reconnected."); } }); diff --git a/examples/dns.rs b/examples/dns.rs index 05fef600..94a3a9ec 100644 --- a/examples/dns.rs +++ b/examples/dns.rs @@ -32,7 +32,7 @@ impl Resolve for TrustDnsResolver { async fn main() -> Result<(), RedisError> { let config = RedisConfig::default(); let client = RedisClient::new(config, None, None); - client.set_resolver(Arc::new(TrustDnsResolver::new())); + client.set_resolver(Arc::new(TrustDnsResolver::new())).await; let _ = client.connect(); let _ = client.wait_for_connect().await?; diff --git a/examples/lua.rs b/examples/lua.rs index 043f3db4..fe57c6d1 100644 --- a/examples/lua.rs +++ b/examples/lua.rs @@ -1,5 +1,8 @@ -use fred::types::{Library, Script}; -use fred::{prelude::*, util as fred_utils}; +use fred::{ + prelude::*, + types::{Library, Script}, + util as fred_utils, +}; static SCRIPTS: &'static [&'static str] = &[ "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", @@ -10,8 +13,7 @@ static SCRIPTS: &'static [&'static str] = &[ #[tokio::main] async fn main() -> Result<(), RedisError> { - let config = RedisConfig::default(); - let client = RedisClient::new(config, None, None); + let client = RedisClient::default(); let _ = client.connect(); let _ = client.wait_for_connect().await?; @@ -37,8 +39,7 @@ async fn main() -> Result<(), RedisError> { // or use the `Script` utility types async fn scripts() -> Result<(), RedisError> { - let config = RedisConfig::default(); - let client = RedisClient::new(config, None, None); + let client = RedisClient::default(); let _ = client.connect(); let _ = client.wait_for_connect().await?; @@ -50,10 +51,9 @@ async fn scripts() -> Result<(), RedisError> { Ok(()) } -// or use the `Function` and `Library` utility types +// use the `Function` and `Library` utility types async fn functions() -> Result<(), RedisError> { - let config = RedisConfig::default(); - let client = RedisClient::new(config, None, None); + let client = RedisClient::default(); let _ = client.connect(); let _ = client.wait_for_connect().await?; diff --git a/examples/monitor.rs b/examples/monitor.rs index 7c7e8427..bf42441b 100644 --- a/examples/monitor.rs +++ b/examples/monitor.rs @@ -1,5 +1,4 @@ -use fred::monitor; -use fred::prelude::*; +use fred::{monitor, prelude::*}; use futures::stream::StreamExt; use std::time::Duration; use tokio::time::sleep; @@ -11,21 +10,20 @@ async fn main() -> Result<(), RedisError> { let mut monitor_stream = monitor::run(config).await?; while let Some(command) = monitor_stream.next().await { - // the Display trait prints results in the same format as redis-cli + // the Display implementation prints results in the same format as redis-cli println!("{}", command); } Ok::<(), RedisError>(()) }); - let config = RedisConfig::default(); - let client = RedisClient::new(config, None, None); + let client = RedisClient::default(); let _ = client.connect(); if let Err(error) = client.wait_for_connect().await { println!("Client failed to connect with error: {:?}", error); } - for idx in 0..50 { + for idx in 0 .. 50 { let _ = client.set("foo", idx, Some(Expiration::EX(10)), None, false).await?; } let _ = client.quit().await?; diff --git a/examples/pipeline.rs b/examples/pipeline.rs new file mode 100644 index 00000000..ba5068f2 --- /dev/null +++ b/examples/pipeline.rs @@ -0,0 +1,31 @@ +use fred::prelude::*; + +#[tokio::main] +async fn main() -> Result<(), RedisError> { + // the `auto_pipeline` config option determines whether the client will pipeline commands across tasks. + // this example shows how to pipeline commands within one task. + let client = RedisClient::default(); + let _ = client.connect(); + let _ = client.wait_for_connect().await?; + + let pipeline = client.pipeline(); + // commands are queued in memory + let result: RedisValue = pipeline.incr("foo").await?; + assert!(result.is_queued()); + let result: RedisValue = pipeline.incr("foo").await?; + assert!(result.is_queued()); + + // send the pipeline and return all the results in order + let (first, second): (i64, i64) = pipeline.all().await?; + assert_eq!((first, second), (1, 2)); + + let _: () = client.del("foo").await?; + // or send the pipeline and only return the last result + let pipeline = client.pipeline(); + let _: () = pipeline.incr("foo").await?; + let _: () = pipeline.incr("foo").await?; + assert_eq!(pipeline.last::().await?, 2); + + let _ = client.quit().await?; + Ok(()) +} diff --git a/examples/prometheus.rs b/examples/prometheus.rs index 8febc262..8b49002f 100644 --- a/examples/prometheus.rs +++ b/examples/prometheus.rs @@ -7,7 +7,7 @@ fn sample_metrics( avg_latency: IntGaugeVec, bytes_sent: IntCounterVec, ) { - let client_id = client.id().as_str(); + let client_id = client.id(); let latency_stats = client.take_latency_metrics(); let req_size_stats = client.take_req_size_metrics(); diff --git a/examples/pubsub.rs b/examples/pubsub.rs index c299cd1e..a77ffa44 100644 --- a/examples/pubsub.rs +++ b/examples/pubsub.rs @@ -9,10 +9,7 @@ const COUNT: usize = 60; #[tokio::main] async fn main() -> Result<(), RedisError> { - let config = RedisConfig::default(); - let perf = PerformanceConfig::default(); - let policy = ReconnectPolicy::new_linear(0, 5000, 500); - let publisher_client = RedisClient::new(config, Some(perf), Some(policy)); + let publisher_client = RedisClient::default(); let subscriber_client = publisher_client.clone_new(); let _ = publisher_client.connect(); @@ -24,7 +21,11 @@ async fn main() -> Result<(), RedisError> { let mut message_stream = subscriber_client.on_message(); while let Ok(message) = message_stream.recv().await { - println!("Recv {:?} on channel {}", message.value, message.channel); + println!( + "Recv {} on channel {}", + message.value.convert::()?, + message.channel + ); } Ok::<_, RedisError>(()) }); @@ -41,9 +42,7 @@ async fn main() -> Result<(), RedisError> { #[cfg(feature = "subscriber-client")] async fn subscriber_example() -> Result<(), RedisError> { let config = RedisConfig::default(); - let perf = PerformanceConfig::default(); - let policy = ReconnectPolicy::new_linear(0, 5000, 500); - let subscriber = SubscriberClient::new(config, Some(perf), Some(policy)); + let subscriber = SubscriberClient::new(config, None, None); let _ = subscriber.connect(); let _ = subscriber.wait_for_connect().await?; @@ -62,18 +61,18 @@ async fn subscriber_example() -> Result<(), RedisError> { let _ = subscriber.subscribe("foo").await?; let _ = subscriber.psubscribe(vec!["bar*", "baz*"]).await?; let _ = subscriber.ssubscribe("abc{123}").await?; + // upon reconnecting the client will automatically re-subscribe to the above channels and patterns println!("Subscriber channels: {:?}", subscriber.tracked_channels()); // "foo" println!("Subscriber patterns: {:?}", subscriber.tracked_patterns()); // "bar*", "baz*" println!("Subscriber shard channels: {:?}", subscriber.tracked_shard_channels()); // "abc{123}" let _ = subscriber.unsubscribe("foo").await?; - // now it will only automatically re-subscribe to "bar*", "baz*", and "abc{123}" after reconnecting + // now it will only re-subscribe to "bar*", "baz*", and "abc{123}" after reconnecting // force a re-subscription call to all channels or patterns let _ = subscriber.resubscribe_all().await?; // unsubscribe from all channels and patterns let _ = subscriber.unsubscribe_all().await?; - // the subscriber client also supports all the basic redis commands let _ = subscriber.quit().await; Ok(()) } diff --git a/examples/scan.rs b/examples/scan.rs index cda25134..d833bf11 100644 --- a/examples/scan.rs +++ b/examples/scan.rs @@ -19,18 +19,18 @@ async fn delete_fake_data(client: &RedisClient) -> Result<(), RedisError> { #[tokio::main] async fn main() -> Result<(), RedisError> { - let config = RedisConfig::default(); - let client = RedisClient::new(config, None, None); - + let client = RedisClient::default(); let _ = client.connect(); let _ = client.wait_for_connect().await?; let _ = create_fake_data(&client).await?; - // build up a buffer of (key, value) pairs from pages (10 keys per page) + // build up a buffer of (key, value) pairs from pages (~10 keys per page) let mut buffer = Vec::with_capacity(COUNT as usize); let mut scan_stream = client.scan("foo*", Some(10), None); - while let Some(Ok(mut page)) = scan_stream.next().await { + while let Some(result) = scan_stream.next().await { + let mut page = result.expect("SCAN failed with error"); + if let Some(keys) = page.take_results() { // create a client from the scan result, reusing the existing connection(s) let client = page.create_client(); @@ -42,8 +42,8 @@ async fn main() -> Result<(), RedisError> { } } - // move on to the next page now that we're done reading the values - // or move this before we call `get` on each key to scan results in the background as fast as possible + // move on to the next page now that we're done reading the values. or move this before we call `get` on each key + // to scan results in the background as quickly as possible. let _ = page.next(); } diff --git a/src/commands/interfaces/client.rs b/src/commands/interfaces/client.rs index 0da70de8..b5045b53 100644 --- a/src/commands/interfaces/client.rs +++ b/src/commands/interfaces/client.rs @@ -15,7 +15,7 @@ use crate::{ use bytes_utils::Str; use std::collections::HashMap; -/// Functions that implement the [CLIENT](https://redis.io/commands#connection) interface. +/// Functions that implement the [client](https://redis.io/commands#connection) interface. #[async_trait] pub trait ClientInterface: ClientLike + Sized { /// Return the ID of the current connection. diff --git a/src/commands/interfaces/cluster.rs b/src/commands/interfaces/cluster.rs index 4eb0415f..4b1ae7e0 100644 --- a/src/commands/interfaces/cluster.rs +++ b/src/commands/interfaces/cluster.rs @@ -16,7 +16,7 @@ use crate::{ }; use bytes_utils::Str; -/// Functions that implement the [CLUSTER](https://redis.io/commands#cluster) interface. +/// Functions that implement the [cluster](https://redis.io/commands#cluster) interface. #[async_trait] pub trait ClusterInterface: ClientLike + Sized { /// Read the cached cluster state used for routing commands to the correct cluster nodes. diff --git a/src/commands/interfaces/config.rs b/src/commands/interfaces/config.rs index 8fe1849e..33c30d38 100644 --- a/src/commands/interfaces/config.rs +++ b/src/commands/interfaces/config.rs @@ -7,7 +7,7 @@ use crate::{ use bytes_utils::Str; use std::convert::TryInto; -/// Functions that implement the [CONFIG](https://redis.io/commands#server) interface. +/// Functions that implement the [config](https://redis.io/commands#server) interface. #[async_trait] pub trait ConfigInterface: ClientLike + Sized { /// Resets the statistics reported by Redis using the INFO command. diff --git a/src/commands/interfaces/geo.rs b/src/commands/interfaces/geo.rs index e53ffcbd..52831e91 100644 --- a/src/commands/interfaces/geo.rs +++ b/src/commands/interfaces/geo.rs @@ -18,7 +18,7 @@ use crate::{ }; use std::convert::TryInto; -/// Functions that implement the [GEO](https://redis.io/commands#geo) interface. +/// Functions that implement the [geo](https://redis.io/commands#geo) interface. #[async_trait] pub trait GeoInterface: ClientLike + Sized { /// Adds the specified geospatial items (longitude, latitude, name) to the specified key. diff --git a/src/commands/interfaces/hashes.rs b/src/commands/interfaces/hashes.rs index dddd4042..511070d0 100644 --- a/src/commands/interfaces/hashes.rs +++ b/src/commands/interfaces/hashes.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::convert::TryInto; -/// Functions that implement the [Hashes](https://redis.io/commands#hashes) interface. +/// Functions that implement the [hashes](https://redis.io/commands#hashes) interface. #[async_trait] pub trait HashesInterface: ClientLike + Sized { /// Returns all fields and values of the hash stored at `key`. diff --git a/src/commands/interfaces/lists.rs b/src/commands/interfaces/lists.rs index 015ac9d9..916caa21 100644 --- a/src/commands/interfaces/lists.rs +++ b/src/commands/interfaces/lists.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::convert::TryInto; -/// Functions that implement the [Lists](https://redis.io/commands#lists) interface. +/// Functions that implement the [lists](https://redis.io/commands#lists) interface. #[async_trait] pub trait ListInterface: ClientLike + Sized { /// The blocking variant of [Self::lmpop]. diff --git a/src/commands/interfaces/lua.rs b/src/commands/interfaces/lua.rs index 3b67d12f..c087ca45 100644 --- a/src/commands/interfaces/lua.rs +++ b/src/commands/interfaces/lua.rs @@ -120,7 +120,7 @@ pub trait LuaInterface: ClientLike + Sized { } } -/// Functions implementing the [function interface](https://redis.io/docs/manual/programmability/functions-intro/). +/// Functions that implement the [function](https://redis.io/docs/manual/programmability/functions-intro/) interface. #[async_trait] pub trait FunctionInterface: ClientLike + Sized { /// Invoke a function. diff --git a/src/commands/interfaces/memory.rs b/src/commands/interfaces/memory.rs index 3144e7b3..4cf1d043 100644 --- a/src/commands/interfaces/memory.rs +++ b/src/commands/interfaces/memory.rs @@ -4,7 +4,7 @@ use crate::{ types::{MemoryStats, RedisKey}, }; -/// Functions that implement the [Memory](https://redis.io/commands#server) interface. +/// Functions that implement the [memory](https://redis.io/commands#server) interface. #[async_trait] pub trait MemoryInterface: ClientLike + Sized { /// The MEMORY DOCTOR command reports about different memory-related issues that the Redis server experiences, and diff --git a/src/commands/interfaces/metrics.rs b/src/commands/interfaces/metrics.rs index 1ee85af8..6a68e3f2 100644 --- a/src/commands/interfaces/metrics.rs +++ b/src/commands/interfaces/metrics.rs @@ -3,7 +3,7 @@ use crate::interfaces::ClientLike; #[cfg(feature = "metrics")] use crate::modules::metrics::Stats; -/// Functions that implement the internal metrics interface, largely controlled by the `metrics` feature flag. +/// Functions that implement the internal metrics interface. pub trait MetricsInterface: ClientLike + Sized { /// Read the number of request redeliveries. /// diff --git a/src/commands/interfaces/pubsub.rs b/src/commands/interfaces/pubsub.rs index a676fd09..be560173 100644 --- a/src/commands/interfaces/pubsub.rs +++ b/src/commands/interfaces/pubsub.rs @@ -8,7 +8,7 @@ use bytes_utils::Str; use std::convert::TryInto; use tokio::sync::broadcast::Receiver as BroadcastReceiver; -/// Functions that implement the [publish-subscribe](https://redis.io/commands#pubsub) interface. +/// Functions that implement the [pubsub](https://redis.io/commands#pubsub) interface. #[async_trait] pub trait PubsubInterface: ClientLike + Sized { /// Listen for messages on the publish-subscribe interface. diff --git a/src/commands/interfaces/sentinel.rs b/src/commands/interfaces/sentinel.rs index 85fbd288..62a028bd 100644 --- a/src/commands/interfaces/sentinel.rs +++ b/src/commands/interfaces/sentinel.rs @@ -7,7 +7,7 @@ use crate::{ use bytes_utils::Str; use std::{convert::TryInto, net::IpAddr}; -/// Functions that implement the [Sentinel](https://redis.io/topics/sentinel#sentinel-commands) interface. +/// Functions that implement the [sentinel](https://redis.io/topics/sentinel#sentinel-commands) interface. #[async_trait] pub trait SentinelInterface: ClientLike + Sized { /// Check if the current Sentinel configuration is able to reach the quorum needed to failover a master, and the diff --git a/src/commands/interfaces/server.rs b/src/commands/interfaces/server.rs index 53b1baa3..5e8f7bc7 100644 --- a/src/commands/interfaces/server.rs +++ b/src/commands/interfaces/server.rs @@ -64,7 +64,7 @@ pub trait HeartbeatInterface: ClientLike { } } -/// Functions that implement the [Server](https://redis.io/commands#server) interface. +/// Functions that implement the [server](https://redis.io/commands#server) interface. #[async_trait] pub trait ServerInterface: ClientLike { /// Instruct Redis to start an Append Only File rewrite process. diff --git a/src/commands/interfaces/sets.rs b/src/commands/interfaces/sets.rs index e2f9568f..118415af 100644 --- a/src/commands/interfaces/sets.rs +++ b/src/commands/interfaces/sets.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::convert::TryInto; -/// Functions that implement the [Sets](https://redis.io/commands#set) interface. +/// Functions that implement the [sets](https://redis.io/commands#set) interface. #[async_trait] pub trait SetsInterface: ClientLike + Sized { /// Add the specified members to the set stored at `key`. diff --git a/src/commands/interfaces/sorted_sets.rs b/src/commands/interfaces/sorted_sets.rs index 0863b009..4594956d 100644 --- a/src/commands/interfaces/sorted_sets.rs +++ b/src/commands/interfaces/sorted_sets.rs @@ -21,7 +21,7 @@ use crate::{ }; use std::convert::TryInto; -/// Functions that implement the [Sorted Sets](https://redis.io/commands#sorted_set) interface. +/// Functions that implement the [sorted sets](https://redis.io/commands#sorted_set) interface. #[async_trait] pub trait SortedSetsInterface: ClientLike + Sized { /// The blocking variant of [Self::zmpop]. diff --git a/src/commands/interfaces/streams.rs b/src/commands/interfaces/streams.rs index 6a2512c6..79c620cd 100644 --- a/src/commands/interfaces/streams.rs +++ b/src/commands/interfaces/streams.rs @@ -21,13 +21,13 @@ use crate::{ use bytes_utils::Str; use std::{convert::TryInto, hash::Hash}; -/// A trait that implements the [streams](https://redis.io/commands#stream) interface. +/// Functions that implement the [streams](https://redis.io/commands#stream) interface. /// /// **Note:** Several of the stream commands can return types with verbose type declarations. Additionally, certain /// commands can be parsed differently in RESP2 and RESP3 modes. Functions such as [xread_map](Self::xread_map), -/// [xreadgroup_map](Self::xreadgroup_map), [xrange_values](Self::xrange_values), etc exist to make this easier on +/// [xreadgroup_map](Self::xreadgroup_map), [xrange_values](Self::xrange_values), etc exist to make this easier for /// callers. These functions apply an additional layer of parsing logic that can make declaring response types easier, -/// as well as automatically handling the differences between RESP2 and RESP3 return value types. +/// as well as automatically handling any differences between RESP2 and RESP3 return value types. #[async_trait] pub trait StreamsInterface: ClientLike + Sized { /// This command returns the list of consumers that belong to the `groupname` consumer group of the stream stored at diff --git a/src/error.rs b/src/error.rs index efc00d2a..e0360b6e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,7 +7,6 @@ use std::{ convert::Infallible, error::Error, fmt, - fmt::Display, io::Error as IoError, num::{ParseFloatError, ParseIntError}, str, @@ -38,7 +37,9 @@ pub enum RedisErrorKind { Url, /// A protocol error such as an invalid or unexpected frame from the server. Protocol, - /// A TLS error. The `enable-native-tls` feature must be enabled for this to be used. + /// A TLS error. + #[cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))))] Tls, /// An error indicating the request was canceled. Canceled, @@ -77,6 +78,7 @@ impl RedisErrorKind { RedisErrorKind::Canceled => "Canceled", RedisErrorKind::Cluster => "Cluster Error", RedisErrorKind::Timeout => "Timeout Error", + #[cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))] RedisErrorKind::Tls => "TLS Error", RedisErrorKind::Config => "Config Error", RedisErrorKind::Parse => "Parse Error", @@ -123,108 +125,126 @@ impl fmt::Display for RedisError { } } +#[doc(hidden)] impl From for RedisError { fn from(e: RedisProtocolError) -> Self { RedisError::new(RedisErrorKind::Protocol, format!("{}", e)) } } +#[doc(hidden)] impl From<()> for RedisError { fn from(_: ()) -> Self { RedisError::new(RedisErrorKind::Canceled, "Empty error.") } } +#[doc(hidden)] impl From for RedisError { fn from(e: futures::channel::mpsc::SendError) -> Self { RedisError::new(RedisErrorKind::Unknown, format!("{}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: tokio::sync::oneshot::error::RecvError) -> Self { RedisError::new(RedisErrorKind::Unknown, format!("{}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: tokio::sync::broadcast::error::RecvError) -> Self { RedisError::new(RedisErrorKind::Unknown, format!("{}", e)) } } -impl From> for RedisError { +#[doc(hidden)] +impl From> for RedisError { fn from(e: tokio::sync::broadcast::error::SendError) -> Self { RedisError::new(RedisErrorKind::Unknown, format!("{}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: IoError) -> Self { RedisError::new(RedisErrorKind::IO, format!("{:?}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: ParseError) -> Self { RedisError::new(RedisErrorKind::Url, format!("{:?}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(_: ParseFloatError) -> Self { RedisError::new(RedisErrorKind::Parse, "Invalid floating point number.") } } +#[doc(hidden)] impl From for RedisError { fn from(_: ParseIntError) -> Self { RedisError::new(RedisErrorKind::Parse, "Invalid integer string.") } } +#[doc(hidden)] impl From for RedisError { fn from(_: FromUtf8Error) -> Self { RedisError::new(RedisErrorKind::Parse, "Invalid UTF-8 string.") } } +#[doc(hidden)] impl From for RedisError { fn from(_: Utf8Error) -> Self { RedisError::new(RedisErrorKind::Parse, "Invalid UTF-8 string.") } } +#[doc(hidden)] impl From> for RedisError { fn from(e: BytesUtf8Error) -> Self { e.utf8_error().into() } } +#[doc(hidden)] impl From for RedisError { fn from(e: fmt::Error) -> Self { RedisError::new(RedisErrorKind::Unknown, format!("{:?}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: Canceled) -> Self { RedisError::new(RedisErrorKind::Canceled, format!("{}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: JoinError) -> Self { RedisError::new(RedisErrorKind::Unknown, format!("Spawn Error: {:?}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: SemverError) -> Self { RedisError::new(RedisErrorKind::Protocol, format!("Invalid Redis version: {:?}", e)) } } +#[doc(hidden)] impl From for RedisError { fn from(e: Infallible) -> Self { warn!("Infallible error: {:?}", e); @@ -232,6 +252,7 @@ impl From for RedisError { } } +#[doc(hidden)] impl From for RedisError { fn from(e: Resp2Frame) -> Self { match e { @@ -244,6 +265,7 @@ impl From for RedisError { } } +#[doc(hidden)] #[cfg(feature = "enable-native-tls")] #[cfg_attr(docsrs, doc(cfg(feature = "enable-native-tls")))] impl From for RedisError { @@ -252,6 +274,7 @@ impl From for RedisError { } } +#[doc(hidden)] #[cfg(feature = "enable-rustls")] #[cfg_attr(docsrs, doc(cfg(feature = "enable-rustls")))] impl From for RedisError { @@ -260,6 +283,7 @@ impl From for RedisError { } } +#[doc(hidden)] #[cfg(feature = "enable-rustls")] #[cfg_attr(docsrs, doc(cfg(feature = "enable-rustls")))] impl From for RedisError { @@ -268,6 +292,7 @@ impl From for RedisError { } } +#[doc(hidden)] #[cfg(feature = "enable-rustls")] #[cfg_attr(docsrs, doc(cfg(feature = "enable-rustls")))] impl From for RedisError { @@ -276,6 +301,7 @@ impl From for RedisError { } } +#[doc(hidden)] #[cfg(feature = "dns")] #[cfg_attr(docsrs, doc(cfg(feature = "dns")))] impl From for RedisError { @@ -296,22 +322,6 @@ impl RedisError { } } - /// Whether or not the error is a Cluster error. - pub fn is_cluster_error(&self) -> bool { - match self.kind { - RedisErrorKind::Cluster => true, - _ => false, - } - } - - /// Whether or not the error is from a sentinel node. - pub fn is_sentinel_error(&self) -> bool { - match self.kind { - RedisErrorKind::Sentinel => true, - _ => false, - } - } - /// Read the type of error without any associated data. pub fn kind(&self) -> &RedisErrorKind { &self.kind @@ -327,10 +337,6 @@ impl RedisError { self.details.borrow() } - pub fn to_string(&self) -> String { - format!("{}: {}", &self.kind.to_str(), &self.details) - } - /// Create a new empty Canceled error. pub(crate) fn new_canceled() -> Self { RedisError::new(RedisErrorKind::Canceled, "Canceled.") @@ -357,7 +363,15 @@ impl RedisError { } } - /// Whether or not the error is a `Canceled` error. + /// Whether the error is a `Cluster` error. + pub fn is_cluster(&self) -> bool { + match self.kind { + RedisErrorKind::Cluster => true, + _ => false, + } + } + + /// Whether the error is a `Canceled` error. pub fn is_canceled(&self) -> bool { match self.kind { RedisErrorKind::Canceled => true, @@ -365,7 +379,9 @@ impl RedisError { } } + /// Whether the error is a `Replica` error. #[cfg(feature = "replicas")] + #[cfg_attr(docsrs, doc(cfg(feature = "replicas")))] pub fn is_replica(&self) -> bool { match self.kind { RedisErrorKind::Replica => true, @@ -373,7 +389,7 @@ impl RedisError { } } - /// Whether or not the error is a `NotFound` error. + /// Whether the error is a `NotFound` error. pub fn is_not_found(&self) -> bool { match self.kind { RedisErrorKind::NotFound => true, diff --git a/src/lib.rs b/src/lib.rs index d09b5e0f..1f208649 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,46 +1,7 @@ #![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, allow(unused_attributes))] - -//! Fred -//! ==== -//! -//! An async client library for [Redis](https://redis.io/) built on Tokio and Futures. -//! -//! ## Examples -//! -//! ```rust edition2018 no_run -//! use fred::prelude::*; -//! use std::future::Future; -//! -//! #[tokio::main] -//! async fn main() -> Result<(), RedisError> { -//! let config = RedisConfig::default(); -//! let policy = ReconnectPolicy::default(); -//! let perf = PerformanceConfig::default(); -//! let client = RedisClient::new(config, Some(perf), Some(policy)); -//! -//! // connect to the server, returning a handle to a task that drives the connection -//! let _ = client.connect(); -//! let _ = client.wait_for_connect().await?; -//! -//! // convert responses to many common Rust types -//! let foo: Option = client.get("foo").await?; -//! assert!(foo.is_none()); -//! -//! let _: () = client.set("foo", "bar", None, None, false).await?; -//! // or use turbofish to declare types. the first type is always the response. -//! println!("Foo: {:?}", client.get::("foo".to_owned()).await?); -//! // or use a lower level interface for responses to defer parsing, etc -//! let foo: RedisValue = client.get("foo").await?; -//! assert_eq!(foo.as_str().unwrap(), "bar"); -//! -//! let _ = client.quit().await?; -//! Ok(()) -//! } -//! ``` -//! -//! See the [github repository](https://github.com/aembke/fred.rs) for more examples. +#![doc = include_str!("../README.md")] #[macro_use] extern crate async_trait; @@ -60,8 +21,9 @@ pub extern crate rustls; pub extern crate rustls_native_certs; #[cfg(feature = "serde-json")] pub extern crate serde_json; -#[cfg(any(feature = "full-tracing", feature = "partial-tracing"))] -extern crate tracing; +#[cfg(feature = "partial-tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "partial-tracing")))] +pub extern crate tracing; #[cfg(any(feature = "full-tracing", feature = "partial-tracing"))] extern crate tracing_futures; @@ -70,8 +32,8 @@ mod macros; mod commands; mod modules; -mod router; mod protocol; +mod router; mod trace; mod utils; diff --git a/src/modules/inner.rs b/src/modules/inner.rs index 02d4c577..71ea36f1 100644 --- a/src/modules/inner.rs +++ b/src/modules/inner.rs @@ -657,7 +657,7 @@ impl RedisClientInner { } pub fn should_cluster_sync(&self, error: &RedisError) -> bool { - self.config.server.is_clustered() && error.is_cluster_error() + self.config.server.is_clustered() && error.is_cluster() } pub async fn update_backchannel(&self, transport: RedisTransport) { diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 259c2442..8c588317 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1,5 +1,5 @@ pub mod backchannel; -/// Utility functions for manipulating global values that can affect performance. +/// Utility functions for reading or changing global config values. pub mod globals; pub mod inner; pub mod metrics; diff --git a/src/protocol/connection.rs b/src/protocol/connection.rs index d07f7a12..b075b284 100644 --- a/src/protocol/connection.rs +++ b/src/protocol/connection.rs @@ -4,7 +4,7 @@ use crate::{ modules::inner::RedisClientInner, protocol::{ codec::RedisCodec, - command::{RedisCommand, RedisCommandKind}, + command::{RedisCommand, RedisCommandKind, RouterResponse}, types::{ProtocolFrame, Server}, utils as protocol_utils, }, @@ -34,7 +34,6 @@ use std::{ use tokio::{net::TcpStream, task::JoinHandle}; use tokio_util::codec::Framed; -use crate::protocol::command::RouterResponse; #[cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))] use crate::protocol::tls::TlsConnector; #[cfg(feature = "replicas")] diff --git a/src/router/replicas.rs b/src/router/replicas.rs index 2734f952..52f6ddb3 100644 --- a/src/router/replicas.rs +++ b/src/router/replicas.rs @@ -1,4 +1,4 @@ -#[cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))] +#[cfg(all(feature = "replicas", any(feature = "enable-native-tls", feature = "enable-rustls")))] use crate::types::{HostMapping, TlsHostMapping}; #[cfg(feature = "replicas")] use crate::{ diff --git a/src/router/utils.rs b/src/router/utils.rs index ceb6d96e..63e38698 100644 --- a/src/router/utils.rs +++ b/src/router/utils.rs @@ -24,7 +24,7 @@ use tokio::{ sync::{mpsc::UnboundedReceiver, oneshot::channel as oneshot_channel}, }; -#[cfg(any(feature = "metrics", feature = "partial-tracing"))] +#[cfg(all(feature = "metrics", feature = "partial-tracing"))] use crate::trace; #[cfg(feature = "check-unresponsive")] use futures::future::Either;