From 7693a28d8d6d37fa6e515f29d065721b9d7e129c Mon Sep 17 00:00:00 2001 From: Shahab Dogar Date: Mon, 1 Apr 2024 07:18:02 -0500 Subject: [PATCH 1/3] :sparkles: add automatic typed json ser/de operations --- Cargo.toml | 5 +++- src/client.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 7 ++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9556522..7d2fe79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,9 @@ keywords = ["memcache", "memcached", "driver", "cache", "database"] edition = "2018" [features] -default = ["tls"] +default = ["tls", "json"] tls = ["openssl"] +json = ["dep:serde", "dep:serde_json"] [dependencies] byteorder = "1" @@ -20,3 +21,5 @@ rand = "0.8" enum_dispatch = "0.3" openssl = { version = "^0.10", optional = true } r2d2 = "^0.8" +serde = { version = "1.0.197", optional = true , features = ["derive"] } +serde_json = { version = "1.0.115", optional = true } diff --git a/src/client.rs b/src/client.rs index 38274d0..2061b5b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,6 +12,9 @@ use crate::stream::Stream; use crate::value::{FromMemcacheValueExt, ToMemcacheValue}; use r2d2::Pool; +#[cfg(feature = "json")] +use serde::{Deserialize, Serialize}; + pub type Stats = HashMap; pub trait Connectable { @@ -266,6 +269,47 @@ impl Client { return Ok(result); } + /// Get a key from memcached server and deserialize it as JSON. This function is only available when the `json` feature is enabled. + /// The value type should implement `FromMemcacheValueExt` and `serde::Deserialize`. + /// + /// Example: + /// ```rust + /// use serde::{Serialize, Deserialize}; + /// + /// #[derive(Debug, Serialize, Deserialize)] + /// struct MyStruct { + /// value: String, + /// number: i32, + /// flag: bool, + /// array: Vec, + /// } + /// + /// let client = memcache::Client::connect("memcache://localhost:12345").unwrap(); + /// let value = MyStruct { + /// value: "hello".to_string(), + /// number: 42, + /// flag: true, + /// array: vec![1, 2, 3], + /// }; + /// client.set_json("foo", value, 10).unwrap(); + /// let result: MyStruct = client.get_json("foo").unwrap().unwrap(); + /// + /// assert_eq!(result.value, "hello"); + /// assert_eq!(result.number, 42); + /// assert_eq!(result.flag, true); + /// assert_eq!(result.array, vec![1, 2, 3]); + /// ``` + #[cfg(feature = "json")] + pub fn get_json Deserialize<'a>>(&self, key: &str) -> Result, MemcacheError> { + let value: Option = self.get(key)?; + let value = match value { + Some(value) => value, + None => return Ok(None), + }; + + return serde_json::from_str(&value).map_err(|e| MemcacheError::JsonError(e)); + } + /// Set a key with associate value into memcached server with expiration seconds. /// /// Example: @@ -280,6 +324,32 @@ impl Client { return self.get_connection(key).get()?.set(key, value, expiration); } + /// Set a key with associate value into memcached server with expiration seconds. The value will be serialized as JSON. This function is only available when the `json` feature is enabled. + /// + /// Example: + /// ```rust + /// use serde::Serialize; + /// + /// #[derive(Serialize)] + /// struct MyStruct { + /// value: String, + /// number: i32, + /// } + /// + /// let client = memcache::Client::connect("memcache://localhost:12345").unwrap(); + /// let value = MyStruct { + /// value: "hello".to_string(), + /// number: 42, + /// }; + /// client.set_json("foo", value, 10).unwrap(); + /// client.flush().unwrap(); + /// ``` + #[cfg(feature = "json")] + pub fn set_json(&self, key: &str, value: V, expiration: u32) -> Result<(), MemcacheError> { + let value = serde_json::to_string(&value).map_err(|e| MemcacheError::JsonError(e))?; + return self.set(key, value, expiration); + } + /// Compare and swap a key with the associate value into memcached server with expiration seconds. /// `cas_id` should be obtained from a previous `gets` call. /// diff --git a/src/error.rs b/src/error.rs index 041b9a3..ae8ca4e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -239,6 +239,9 @@ pub enum MemcacheError { ParseError(ParseError), /// ConnectionPool errors PoolError(r2d2::Error), + /// Json errors (only available with `json` feature) for invalid json strings + #[cfg(feature = "json")] + JsonError(serde_json::Error), } impl fmt::Display for MemcacheError { @@ -253,6 +256,8 @@ impl fmt::Display for MemcacheError { MemcacheError::ServerError(ref err) => err.fmt(f), MemcacheError::CommandError(ref err) => err.fmt(f), MemcacheError::PoolError(ref err) => err.fmt(f), + #[cfg(feature = "json")] + MemcacheError::JsonError(ref err) => err.fmt(f), } } } @@ -269,6 +274,8 @@ impl error::Error for MemcacheError { MemcacheError::ServerError(_) => None, MemcacheError::CommandError(_) => None, MemcacheError::PoolError(ref p) => p.source(), + #[cfg(feature = "json")] + MemcacheError::JsonError(ref e) => e.source(), } } } From 98efc6049824e6fe02dc0a9defb2a25888e43097 Mon Sep 17 00:00:00 2001 From: Shahab Dogar Date: Mon, 1 Apr 2024 07:23:59 -0500 Subject: [PATCH 2/3] json off by default --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7d2fe79..bd5c953 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["memcache", "memcached", "driver", "cache", "database"] edition = "2018" [features] -default = ["tls", "json"] +default = ["tls"] tls = ["openssl"] json = ["dep:serde", "dep:serde_json"] From b3a35c758bbdec03a03e6eaadb1dc503b3b697f7 Mon Sep 17 00:00:00 2001 From: Shahab Dogar Date: Mon, 1 Apr 2024 07:26:54 -0500 Subject: [PATCH 3/3] update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d51d98..ea337d2 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ memcache = "*" - [ ] Encodings - [x] Typed interface - [ ] Automatically compress - - [ ] Automatically serialize to JSON / msgpack etc + - [x] Automatically serialize to JSON + - [ ] Automatically serialize to msgpack - [x] Memcached cluster support with custom key hash algorithm - [x] Authority - [x] Binary protocol (plain SASL authority plain)