• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aisk / rust-memcache / 8638261556

10 Apr 2024 09:51PM UTC coverage: 72.966%. First build
8638261556

Pull #147

github

web-flow
Merge 3de93ac8e into 2e952856d
Pull Request #147: :sparkles: add support for async clients

114 of 127 new or added lines in 2 files covered. (89.76%)

1587 of 2175 relevant lines covered (72.97%)

5322.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.43
/src/async_client.rs
1
use std::collections::HashMap;
2
use std::time::Duration;
3

4
use crate::client::{Client, Stats};
5
use crate::error::MemcacheError;
6
use crate::stream::Stream;
7
use crate::value::{FromMemcacheValueExt, ToMemcacheValue};
8
use crate::Connectable;
9

10
pub struct AsyncClient {
11
    inner: Client,
12
}
13

14
impl From<Client> for AsyncClient {
15
    fn from(client: Client) -> Self {
31✔
16
        AsyncClient { inner: client }
31✔
17
    }
31✔
18
}
19

20
impl AsyncClient {
21
    pub fn connect<C: Connectable>(target: C) -> Result<Self, MemcacheError> {
6✔
22
        Ok(Client::connect(target)?.into())
6✔
23
    }
6✔
24

25
    /// Set the socket read timeout for TCP connections.
26
    ///
27
    /// Example:
28
    ///
29
    /// ```rust
30
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
31
    /// client.set_read_timeout(Some(::std::time::Duration::from_secs(3))).unwrap();
32
    /// ```
33
    pub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<(), MemcacheError> {
1✔
34
        self.inner.set_read_timeout(timeout)
1✔
35
    }
1✔
36

37
    /// Set the socket write timeout for TCP connections.
38
    ///
39
    /// Example:
40
    ///
41
    /// ```rust
42
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345?protocol=ascii").unwrap();
43
    /// client.set_write_timeout(Some(::std::time::Duration::from_secs(3))).unwrap();
44
    /// ```
45
    pub fn set_write_timeout(&self, timeout: Option<Duration>) -> Result<(), MemcacheError> {
1✔
46
        self.inner.set_write_timeout(timeout)
1✔
47
    }
1✔
48

49
    /// Get the memcached server version.
50
    ///
51
    /// Example:
52
    ///
53
    /// ```rust
54
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
55
    /// async {
56
    ///     client.version().await.unwrap();
57
    /// };
58
    /// ```
59
    pub async fn version(&self) -> Result<Vec<(String, String)>, MemcacheError> {
10✔
60
        self.inner.version()
5✔
61
    }
10✔
62

63
    /// Flush all cache on memcached server immediately.
64
    ///
65
    /// Example:
66
    ///
67
    /// ```rust
68
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
69
    /// async {
70
    ///     client.flush().await.unwrap();
71
    /// };
72
    /// ```
NEW
73
    pub async fn flush(&self) -> Result<(), MemcacheError> {
×
74
        self.inner.flush()
NEW
75
    }
×
76

77
    /// Flush all cache on memcached server with a delay seconds.
78
    ///
79
    /// Example:
80
    ///
81
    /// ```rust
82
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
83
    /// async {
84
    ///     client.flush_with_delay(10).await.unwrap();
85
    /// };
86
    /// ```
NEW
87
    pub async fn flush_with_delay(&self, delay: u32) -> Result<(), MemcacheError> {
×
88
        self.inner.flush_with_delay(delay)
NEW
89
    }
×
90

91
    /// Get a key from memcached server.
92
    ///
93
    /// Example:
94
    ///
95
    /// ```rust
96
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
97
    /// async {
98
    ///     let _: Option<String> = client.get("foo").await.unwrap();
99
    /// };
100
    /// ```
101
    pub async fn get<V: FromMemcacheValueExt>(&self, key: &str) -> Result<Option<V>, MemcacheError> {
102
        self.inner.get(key)
103
    }
104

105
    /// Get multiple keys from memcached server. Using this function instead of calling `get` multiple times can reduce network workloads.
106
    ///
107
    /// Example:
108
    ///
109
    /// ```rust
110
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
111
    /// async {
112
    ///     client.set("foo", "42", 0).await.unwrap();
113
    ///     let result: std::collections::HashMap<String, String> = client.gets(&["foo", "bar", "baz"]).await.unwrap();
114
    ///     assert_eq!(result.len(), 1);
115
    ///     assert_eq!(result["foo"], "42");
116
    /// };
117
    /// ```
118
    pub async fn gets<V: FromMemcacheValueExt>(&self, keys: &[&str]) -> Result<HashMap<String, V>, MemcacheError> {
119
        self.inner.gets(keys)
120
    }
121

122
    /// Set a key with associate value into memcached server with expiration seconds.
123
    ///
124
    /// Example:
125
    ///
126
    /// ```rust
127
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
128
    /// async {
129
    ///     client.set("foo", "bar", 10).await.unwrap();
130
    ///     client.flush().await.unwrap();
131
    /// };
132
    /// ```
133
    pub async fn set<V: ToMemcacheValue<Stream>>(
2✔
134
        &self,
2✔
135
        key: &str,
2✔
136
        value: V,
2✔
137
        expiration: u32,
2✔
138
    ) -> Result<(), MemcacheError> {
4✔
139
        self.inner.set(key, value, expiration)
2✔
140
    }
4✔
141

142
    /// Compare and swap a key with the associate value into memcached server with expiration seconds.
143
    /// `cas_id` should be obtained from a previous `gets` call.
144
    ///
145
    /// Example:
146
    ///
147
    /// ```rust
148
    /// use std::collections::HashMap;
149
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
150
    /// async {
151
    ///     client.set("foo", "bar", 10).await.unwrap();
152
    ///     let result: HashMap<String, (Vec<u8>, u32, Option<u64>)> = client.gets(&["foo"]).await.unwrap();
153
    ///     let (_, _, cas) = result.get("foo").unwrap();
154
    ///     let cas = cas.unwrap();
155
    ///     assert_eq!(true, client.cas("foo", "bar2", 10, cas).await.unwrap());
156
    ///     client.flush().await.unwrap();
157
    /// };
158
    /// ```
159
    pub async fn cas<V: ToMemcacheValue<Stream>>(
160
        &self,
161
        key: &str,
162
        value: V,
163
        expiration: u32,
164
        cas_id: u64,
165
    ) -> Result<bool, MemcacheError> {
166
        self.inner.cas(key, value, expiration, cas_id)
167
    }
168

169
    /// Add a key with associate value into memcached server with expiration seconds.
170
    ///
171
    /// Example:
172
    ///
173
    /// ```rust
174
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
175
    /// let key = "add_test";
176
    /// async {
177
    ///     client.delete(key).await.unwrap();
178
    ///     client.add(key, "bar", 100000000).await.unwrap();
179
    ///     client.flush().await.unwrap();
180
    /// };
181
    /// ```
182
    pub async fn add<V: ToMemcacheValue<Stream>>(
183
        &self,
184
        key: &str,
185
        value: V,
186
        expiration: u32,
187
    ) -> Result<(), MemcacheError> {
188
        self.inner.add(key, value, expiration)
189
    }
190

191
    /// Replace a key with associate value into memcached server with expiration seconds.
192
    ///
193
    /// Example:
194
    ///
195
    /// ```rust
196
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
197
    /// let key = "replace_test";
198
    /// async {
199
    ///     client.set(key, "bar", 0).await.unwrap();
200
    ///     client.replace(key, "baz", 100000000).await.unwrap();
201
    ///     client.flush().await.unwrap();
202
    /// };
203
    /// ```
204
    pub async fn replace<V: ToMemcacheValue<Stream>>(
205
        &self,
206
        key: &str,
207
        value: V,
208
        expiration: u32,
209
    ) -> Result<(), MemcacheError> {
210
        self.inner.replace(key, value, expiration)
211
    }
212

213
    /// Append value to the key.
214
    ///
215
    /// Example:
216
    ///
217
    /// ```rust
218
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
219
    /// let key = "key_to_append";
220
    /// async {
221
    ///     client.set(key, "hello", 0).await.unwrap();
222
    ///     client.append(key, ", world!").await.unwrap();
223
    ///     let result: String = client.get(key).await.unwrap().unwrap();
224
    ///     assert_eq!(result, "hello, world!");
225
    ///     client.flush().await.unwrap();
226
    /// };
227
    /// ```
228
    pub async fn append<V: ToMemcacheValue<Stream>>(&self, key: &str, value: V) -> Result<(), MemcacheError> {
229
        self.inner.append(key, value)
230
    }
231

232
    /// Prepend value to the key.
233
    ///
234
    /// Example:
235
    ///
236
    /// ```rust
237
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
238
    /// let key = "key_to_append";
239
    /// async {
240
    ///     client.set(key, "world!", 0).await.unwrap();
241
    ///     client.prepend(key, "hello, ").await.unwrap();
242
    ///     let result: String = client.get(key).await.unwrap().unwrap();
243
    ///     assert_eq!(result, "hello, world!");
244
    ///     client.flush().await.unwrap();
245
    /// };
246
    /// ```
247
    pub async fn prepend<V: ToMemcacheValue<Stream>>(&self, key: &str, value: V) -> Result<(), MemcacheError> {
248
        self.inner.prepend(key, value)
249
    }
250

251
    /// Delete a key from memcached server.
252
    ///
253
    /// Example:
254
    ///
255
    /// ```rust
256
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
257
    /// async {
258
    ///     client.delete("foo").await.unwrap();
259
    ///     client.flush().await.unwrap();
260
    /// };
261
    /// ```
262
    pub async fn delete(&self, key: &str) -> Result<bool, MemcacheError> {
6✔
263
        self.inner.delete(key)
3✔
264
    }
6✔
265

266
    /// Increment the value with amount.
267
    ///
268
    /// Example:
269
    ///
270
    /// ```rust
271
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
272
    /// async {
273
    ///     client.increment("counter", 42).await.unwrap();
274
    ///     client.flush().await.unwrap();
275
    /// };
276
    /// ```
277
    pub async fn increment(&self, key: &str, amount: u64) -> Result<u64, MemcacheError> {
2✔
278
        self.inner.increment(key, amount)
1✔
279
    }
2✔
280

281
    /// Decrement the value with amount.
282
    ///
283
    /// Example:
284
    ///
285
    /// ```rust
286
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
287
    /// async {
288
    ///     client.decrement("counter", 42).await.unwrap();
289
    ///     client.flush().await.unwrap();
290
    /// };
291
    /// ```
NEW
292
    pub async fn decrement(&self, key: &str, amount: u64) -> Result<u64, MemcacheError> {
×
293
        self.inner.decrement(key, amount)
NEW
294
    }
×
295

296
    /// Set a new expiration time for a exist key.
297
    ///
298
    /// Example:
299
    ///
300
    /// ```rust
301
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
302
    /// async {
303
    ///     assert_eq!(client.touch("not_exists_key", 12345).await.unwrap(), false);
304
    ///     client.set("foo", "bar", 123).await.unwrap();
305
    ///     assert_eq!(client.touch("foo", 12345).await.unwrap(), true);
306
    ///     client.flush().await.unwrap();
307
    /// };
308
    /// ```
NEW
309
    pub async fn touch(&self, key: &str, expiration: u32) -> Result<bool, MemcacheError> {
×
310
        self.inner.touch(key, expiration)
NEW
311
    }
×
312

313
    /// Get all servers' statistics.
314
    ///
315
    /// Example:
316
    /// ```rust
317
    /// let client = memcache::AsyncClient::connect("memcache://localhost:12345").unwrap();
318
    /// async {
319
    ///     let stats = client.stats().await.unwrap();
320
    /// };
321
    /// ```
NEW
322
    pub async fn stats(&self) -> Result<Vec<(String, Stats)>, MemcacheError> {
×
323
        self.inner.stats()
NEW
324
    }
×
325
}
326

327
#[cfg(test)]
328
mod tests {
329
    use std::time::Duration;
330

331
    #[tokio::test]
3✔
332
    async fn build_client_happy_path() {
2✔
333
        let client = super::Client::builder()
1✔
334
            .add_server("memcache://localhost:12345")
335
            .unwrap()
336
            .build_async()
337
            .unwrap();
338
        assert!(client.version().await.unwrap()[0].1 != "");
2✔
339
    }
3✔
340

341
    #[test]
342
    fn build_client_bad_url() {
2✔
343
        let client = super::Client::builder()
1✔
344
            .add_server("memcache://localhost:12345:")
345
            .unwrap()
346
            .build_async();
347
        assert!(client.is_err());
1✔
348
    }
2✔
349

350
    #[test]
351
    fn build_client_no_url() {
2✔
352
        let client = super::Client::builder().build_async();
1✔
353
        assert!(client.is_err());
1✔
354

355
        let client = super::Client::builder().add_server(Vec::<String>::new());
1✔
356

357
        assert!(client.is_err());
1✔
358
    }
2✔
359

360
    #[test]
361
    fn build_client_with_large_pool_size() {
2✔
362
        let client = super::Client::builder()
1✔
363
            .add_server("memcache://localhost:12345")
364
            .unwrap()
365
            // This is a large pool size, but it should still be valid.
366
            // This does make the test run very slow however.
367
            .with_max_pool_size(100)
368
            .build_async();
NEW
369
        assert!(
×
370
            client.is_ok(),
1✔
371
            "Expected successful client creation with large pool size"
372
        );
373
    }
2✔
374

375
    #[test]
376
    fn build_client_with_custom_hash_function() {
2✔
377
        fn custom_hash_function(_key: &str) -> u64 {
1✔
378
            42 // A simple, predictable hash function for testing.
379
        }
1✔
380

381
        let client = super::Client::builder()
1✔
382
            .add_server("memcache://localhost:12345")
383
            .unwrap()
384
            .with_hash_function(custom_hash_function)
385
            .build_async()
386
            .unwrap();
387

388
        // This test assumes that the custom hash function will affect the selection of connections.
389
        // As the implementation details of connection selection are not exposed, this test might need to be adjusted.
390
        assert_eq!(
1✔
391
            (client.inner.hash_function)("any_key"),
1✔
392
            42,
393
            "Expected custom hash function to be used"
394
        );
395
    }
2✔
396

397
    #[test]
398
    fn build_client_zero_min_idle_conns() {
2✔
399
        let client = super::Client::builder()
1✔
400
            .add_server("memcache://localhost:12345")
401
            .unwrap()
402
            .with_min_idle_conns(0)
403
            .build_async();
404
        assert!(client.is_ok(), "Should handle zero min idle conns");
1✔
405
    }
2✔
406

407
    #[test]
408
    fn build_client_invalid_hash_function() {
2✔
NEW
409
        let invalid_hash_function = |_: &str| -> u64 {
×
NEW
410
            panic!("This should not be called");
×
411
        };
412
        let client = super::Client::builder()
1✔
413
            .add_server("memcache://localhost:12345")
414
            .unwrap()
415
            .with_hash_function(invalid_hash_function)
416
            .build_async();
417
        assert!(client.is_ok(), "Should handle custom hash function gracefully");
1✔
418
    }
2✔
419

420
    #[test]
421
    fn build_client_with_unsupported_protocol() {
2✔
422
        let client = super::Client::builder()
1✔
423
            .add_server("unsupported://localhost:12345")
424
            .unwrap()
425
            .build_async();
426
        assert!(client.is_err(), "Expected error when using an unsupported protocol");
1✔
427
    }
2✔
428

429
    #[test]
430
    fn build_client_with_all_optional_parameters() {
2✔
431
        let client = super::Client::builder()
4✔
432
            .add_server("memcache://localhost:12345")
433
            .unwrap()
434
            .with_max_pool_size(10)
435
            .with_min_idle_conns(2)
1✔
436
            .with_max_conn_lifetime(Duration::from_secs(30))
1✔
437
            .with_read_timeout(Duration::from_secs(5))
1✔
438
            .with_write_timeout(Duration::from_secs(5))
1✔
439
            .with_connection_timeout(Duration::from_secs(2))
1✔
440
            .build_async();
441
        assert!(client.is_ok(), "Should successfully build with all optional parameters");
1✔
442
    }
2✔
443

444
    #[cfg(unix)]
445
    #[tokio::test]
3✔
446
    async fn unix() {
2✔
447
        let client = super::AsyncClient::connect("memcache:///tmp/memcached.sock").unwrap();
1✔
448
        assert!(client.version().await.unwrap()[0].1 != "");
2✔
449
    }
3✔
450

451
    #[cfg(feature = "tls")]
452
    #[tokio::test]
3✔
453
    async fn ssl_noverify() {
2✔
454
        let client = super::AsyncClient::connect("memcache+tls://localhost:12350?verify_mode=none").unwrap();
1✔
455
        assert!(client.version().await.unwrap()[0].1 != "");
2✔
456
    }
3✔
457

458
    #[cfg(feature = "tls")]
459
    #[tokio::test]
3✔
460
    async fn ssl_verify() {
2✔
461
        let client = super::AsyncClient::connect(
1✔
462
            "memcache+tls://localhost:12350?ca_path=tests/assets/RUST_MEMCACHE_TEST_CERT.crt",
463
        )
464
        .unwrap();
465
        assert!(client.version().await.unwrap()[0].1 != "");
2✔
466
    }
3✔
467

468
    #[cfg(feature = "tls")]
469
    #[tokio::test]
3✔
470
    async fn ssl_client_certs() {
2✔
471
        let client = super::AsyncClient::connect("memcache+tls://localhost:12351?key_path=tests/assets/client.key&cert_path=tests/assets/client.crt&ca_path=tests/assets/RUST_MEMCACHE_TEST_CERT.crt").unwrap();
1✔
472
        assert!(client.version().await.unwrap()[0].1 != "");
2✔
473
    }
3✔
474

475
    #[tokio::test]
3✔
476
    async fn delete() {
2✔
477
        let client = super::AsyncClient::connect("memcache://localhost:12345").unwrap();
1✔
478
        client.set("an_exists_key", "value", 0).await.unwrap();
1✔
479
        assert_eq!(client.delete("an_exists_key").await.unwrap(), true);
1✔
480
        assert_eq!(client.delete("a_not_exists_key").await.unwrap(), false);
2✔
481
    }
3✔
482

483
    #[tokio::test]
3✔
484
    async fn increment() {
2✔
485
        let client = super::AsyncClient::connect("memcache://localhost:12345").unwrap();
1✔
486
        client.delete("counter").await.unwrap();
1✔
487
        client.set("counter", 321, 0).await.unwrap();
1✔
488
        assert_eq!(client.increment("counter", 123).await.unwrap(), 444);
2✔
489
    }
3✔
490
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc