├── .clog.toml
├── .editorconfig
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE
├── README.md
├── appveyor.yml
├── benches
└── regression.rs
├── examples
└── serialization.rs
├── src
├── client.rs
├── constants.rs
├── errors.rs
├── lib.rs
└── protocol.rs
└── tests
└── client.rs
/.clog.toml:
--------------------------------------------------------------------------------
1 | [clog]
2 | repository = "https://github.com/jaysonsantos/bmemcached-rs"
3 | link-style = "github"
4 | changelog = "CHANGELOG.md"
5 | output-format = "markdown"
6 | from-latest-tag = false
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | trim_trailing_whitespace = true
3 | end_of_line = lf
4 | insert_final_newline = true
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled files
2 | *.o
3 | *.so
4 | *.rlib
5 | *.dll
6 |
7 | # Executables
8 | *.exe
9 |
10 | # Generated by Cargo
11 | /target/
12 |
13 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
14 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
15 | Cargo.lock
16 |
17 | # Emacs files
18 | *#
19 | .#*
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: rust
3 | rust:
4 | - stable
5 | - beta
6 | - nightly
7 | matrix:
8 | allow_failures:
9 | - rust: nightly
10 | services:
11 | - memcached
12 | env:
13 | - RUST_TEST_THREADS=1
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## 0.5.0 (2018-07-30)
3 |
4 |
5 | #### Features
6 |
7 | * Make protocol public to allow client to choose how to use it ([eca24b94](https://github.com/jaysonsantos/bmemcached-rs/commit/eca24b9433e1a5706054109987636c0a3c9b1d08))
8 | * Enable custom types to be used by user (#9) ([db76fd63](https://github.com/jaysonsantos/bmemcached-rs/commit/db76fd631b75e59a207e95f891f0e3150fc13c9b))
9 | * Enable custom types to be used by user (#7) ([97d2816f](https://github.com/jaysonsantos/bmemcached-rs/commit/97d2816f044415c89e457265bbfa11a81dc55716))
10 | * **error:** Change error system to error-chain (#11) ([ea627ee3](https://github.com/jaysonsantos/bmemcached-rs/commit/ea627ee3456f73d39c7097b1f1647039bc74f27f))
11 | * **protocol:**
12 | * Add support to insert slices ([d9956b79](https://github.com/jaysonsantos/bmemcached-rs/commit/d9956b79ea87be8b781b1b73f08bebc03b77201c))
13 | * Add support to InvalidArguments ([72c6f5c9](https://github.com/jaysonsantos/bmemcached-rs/commit/72c6f5c96911cba0e7feacd3b9e621253fd7f50b))
14 | * Add error for big payloads ([3da968d7](https://github.com/jaysonsantos/bmemcached-rs/commit/3da968d73f1c42f30b116873fcec04e1f779f747))
15 | * Make write_request write to a buffer before sending the whole payload ([0534d479](https://github.com/jaysonsantos/bmemcached-rs/commit/0534d479ebb5770c114c0e6da764860e9bb11a45))
16 | * Make protocol's connection buffered ([3c7e9e70](https://github.com/jaysonsantos/bmemcached-rs/commit/3c7e9e70b51dbe935fff6d5594076d42230862f2))
17 |
18 | #### Bug Fixes
19 |
20 | * **protocol:** Make sure the library wont break with big key names ([a327f208](https://github.com/jaysonsantos/bmemcached-rs/commit/a327f2081234560f32d26a95606ef300cae25cc6))
21 |
22 |
23 |
24 |
25 | ## 0.4.0 (2017-11-01)
26 |
27 |
28 | #### Features
29 |
30 | * **error:** Change error system to error-chain (#11) ([ea627ee3](https://github.com/jaysonsantos/bmemcached-rs/commit/ea627ee3456f73d39c7097b1f1647039bc74f27f))
31 |
32 |
33 |
34 |
35 | ### 0.3.0 (2017-09-13)
36 |
37 |
38 | #### Features
39 |
40 | * Enable custom types to be used by user (#9) ([db76fd63](https://github.com/jaysonsantos/bmemcached-rs/commit/db76fd631b75e59a207e95f891f0e3150fc13c9b))
41 | * Enable custom types to be used by user (#7) ([97d2816f](https://github.com/jaysonsantos/bmemcached-rs/commit/97d2816f044415c89e457265bbfa11a81dc55716))
42 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bmemcached"
3 | version = "0.5.0"
4 | authors = ["Jayson Reis "]
5 | description = "Memcached binary protocol in pure rust with support for 'pools' and consistent hashing. (For now minor versions will break API until v1 is released)"
6 | homepage = "https://github.com/jaysonsantos/bmemcached-rs"
7 | repository = "https://github.com/jaysonsantos/bmemcached-rs"
8 | documentation = "https://jaysonsantos.github.io/bmemcached-rs/bmemcached/index.html"
9 | license = "MIT"
10 | readme = "README.md"
11 |
12 | [dependencies]
13 | bitflags = "1.0.3"
14 | byteorder = "1.1.0"
15 | conhash = "0.4.0"
16 | error-chain = "0.12.0"
17 | enum_primitive = "0.1.0"
18 | log = "0.4.3"
19 | num = "0.2.0"
20 |
21 | [dev-dependencies]
22 | criterion = "0.2.4"
23 | env_logger = "0.5.11"
24 | serde = "1.0.70"
25 | serde_derive= "1.0.70"
26 | serde_json = "1.0.24"
27 |
28 | [[bench]]
29 | name = "regression"
30 | harness = false
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jayson Reis
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 | # bmemcached-rs
2 |
3 | [](https://travis-ci.org/jaysonsantos/bmemcached-rs)
4 | [](https://ci.appveyor.com/project/jaysonsantos/bmemcached-rs/branch/master)
5 |
6 | Rust binary memcached implementation (ON GOING)
7 |
8 | # Usage
9 | ```rust
10 | extern crate bmemcached;
11 |
12 | use std::sync::Arc;
13 | use std::thread;
14 |
15 | use bmemcached::MemcachedClient;
16 |
17 | fn main() {
18 | // Use arc for threading support
19 | let client = Arc::new(MemcachedClient::new(vec!["127.0.0.1:11211"], 5).unwrap());
20 |
21 | // Traits examples
22 | let value = "value";
23 | client.set("string", value, 1000);
24 | let rv: String = client.get("string").unwrap();
25 | assert_eq!(rv, "value");
26 |
27 | client.set("integer", 10 as u8, 1000);
28 | let rv: u8 = client.get("integer").unwrap();
29 | assert_eq!(rv, 10 as u8);
30 |
31 | // Threads example
32 | let mut threads = vec![];
33 | for i in 0..4 {
34 | let client = client.clone();
35 | threads.push(thread::spawn(move || {
36 | let data = format!("data_n{}", i);
37 | client.set(&data, &data, 100).unwrap();
38 | let val: String = client.get(&data).unwrap();
39 | client.delete(&data).unwrap();
40 | val
41 | }));
42 | }
43 | for (i, thread) in threads.into_iter().enumerate() {
44 | let result = thread.join();
45 | assert_eq!(result.unwrap(), format!("data_n{}", i));
46 | }
47 | }
48 | ```
49 |
50 | # Why
51 | I am trying to learn rust by reimplementing a python project that I wrote.
52 |
53 | # What works
54 | * Add
55 | * Set
56 | * Replace
57 | * Get
58 | * Delete
59 | * Increment
60 | * Decrement
61 | * Consistent Hashing
62 | * Threading Support
63 |
64 | ## Trait usage
65 | On all supported functions we use traits to be able to send any type of values to memcached.
66 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # Configuration file based on https://raw.githubusercontent.com/BurntSushi/rust-csv/master/appveyor.yml
2 | environment:
3 | matrix:
4 | - TARGET: x86_64-pc-windows-msvc
5 | - TARGET: i686-pc-windows-msvc
6 | - TARGET: i686-pc-windows-gnu
7 | install:
8 | - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe"
9 | - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
10 | - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
11 | - SET PATH=%PATH%;C:\MinGW\bin
12 | - rustc -V
13 | - cargo -V
14 |
15 | # Got from https://github.com/frerich/clcache/blob/master/appveyor.yml
16 | - appveyor DownloadFile "http://s3.amazonaws.com/downloads.northscale.com/memcached-win64-1.4.4-14.zip" -FileName memcached.zip
17 | - 7z x memcached.zip -y
18 | - ps: $Memcached = Start-Process memcached\memcached.exe -PassThru
19 |
20 | build: false
21 |
22 | test_script:
23 | - cargo build --verbose
24 | - cargo test --verbose
25 |
--------------------------------------------------------------------------------
/benches/regression.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate criterion;
3 |
4 | #[macro_use]
5 | extern crate serde_derive;
6 | extern crate serde;
7 | extern crate serde_json;
8 |
9 | extern crate bmemcached;
10 |
11 | use criterion::Criterion;
12 |
13 | use bmemcached::errors::Result;
14 | use bmemcached::protocol::Protocol;
15 | use bmemcached::{FromMemcached, StoredType, ToMemcached};
16 |
17 | #[derive(Debug, Default, Serialize, Deserialize)]
18 | struct Data {
19 | value: usize,
20 | }
21 |
22 | impl ToMemcached for Data {
23 | fn get_value(&self) -> Result<(Vec, StoredType)> {
24 | Ok((
25 | serde_json::to_vec(self).unwrap(),
26 | StoredType::MTYPE_USER_DEFINED_1,
27 | ))
28 | }
29 | }
30 |
31 | impl FromMemcached for Data {
32 | fn get_value(flags: StoredType, buf: Vec) -> Result {
33 | assert!(flags == StoredType::MTYPE_USER_DEFINED_1);
34 | Ok(serde_json::from_slice(&*buf).unwrap())
35 | }
36 | }
37 |
38 | fn criterion_benchmark(c: &mut Criterion) {
39 | c.bench_function("set/get/delete", |b| {
40 | let mut cli = Protocol::connect("127.0.0.1:11211").unwrap();
41 | b.iter(|| {
42 | let key = "benchmark test";
43 | cli.set(key, "abc", 10_000).unwrap();
44 | let returned_value: String = cli.get(key).unwrap();
45 | assert_eq!(returned_value, "abc");
46 | cli.delete(key).unwrap();
47 | })
48 | });
49 |
50 | c.bench_function("set/get/delete struct", |b| {
51 | let mut cli = Protocol::connect("127.0.0.1:11211").unwrap();
52 | b.iter(|| {
53 | let key = "benchmark test";
54 | let data = Data::default();
55 | cli.set(key, data, 10_000).unwrap();
56 | let returned_value: Data = cli.get(key).unwrap();
57 | assert_eq!(returned_value.value, 0);
58 | cli.delete(key).unwrap();
59 | })
60 | });
61 | }
62 |
63 | criterion_group!(benches, criterion_benchmark);
64 | criterion_main!(benches);
65 |
--------------------------------------------------------------------------------
/examples/serialization.rs:
--------------------------------------------------------------------------------
1 | /*!
2 | This is an example of how to make bmemcached save your own types and it uses serde for serializing
3 | and deserializing.
4 | */
5 | extern crate bmemcached;
6 | #[macro_use] extern crate serde_derive;
7 | extern crate serde;
8 | extern crate serde_json;
9 | use bmemcached::{ToMemcached, FromMemcached, StoredType};
10 | use bmemcached::errors::Result;
11 |
12 | #[derive(Debug, Serialize, Deserialize)]
13 | struct Data {
14 | name: String,
15 | age: u8,
16 | registered: bool
17 | }
18 |
19 | impl<'a> ToMemcached for &'a Data {
20 | fn get_value(&self) -> Result<(Vec, StoredType)> {
21 | Ok((serde_json::to_vec(self).unwrap(), StoredType::MTYPE_USER_DEFINED_1))
22 | }
23 | }
24 |
25 | impl FromMemcached for Data {
26 | fn get_value(flags: StoredType, buf: Vec) -> Result {
27 | assert!(flags == StoredType::MTYPE_USER_DEFINED_1);
28 | Ok(serde_json::from_slice(&*buf).unwrap())
29 | }
30 | }
31 |
32 | fn main() {
33 | let data = Data { name: "Testing".to_owned(), age: 8, registered: false };
34 | let memcached = bmemcached::MemcachedClient::new(
35 | vec!["127.0.0.1:11211"], 5).unwrap();
36 | println!("Storing {:?}", data);
37 | memcached.set("testing", &data, 10000).unwrap();
38 | let rv: Vec = memcached.get("testing").unwrap();
39 | let string = String::from_utf8(rv).unwrap();
40 | println!("Raw data {:?}", string);
41 | let rv: Data = memcached.get("testing").unwrap();
42 | println!("Parsed data {:?}", rv);
43 | }
44 |
--------------------------------------------------------------------------------
/src/client.rs:
--------------------------------------------------------------------------------
1 | use std::net::ToSocketAddrs;
2 | use std::sync::{Arc, Mutex};
3 |
4 | use conhash::{ConsistentHash, Node};
5 |
6 | use errors::Result;
7 | use protocol;
8 |
9 | #[derive(Debug, Clone)]
10 | struct ClonableProtocol {
11 | connection: Arc>,
12 | }
13 |
14 | impl Node for ClonableProtocol {
15 | fn name(&self) -> String {
16 | let protocol = self.clone();
17 | let connection = protocol.connection.lock().unwrap();
18 | connection.connection_info()
19 | }
20 | }
21 |
22 | /// Struct that holds all connections and proxy commands to the right server based on the key
23 | pub struct MemcachedClient {
24 | connections: ConsistentHash,
25 | }
26 |
27 | impl MemcachedClient {
28 | pub fn new(
29 | addrs: Vec,
30 | connections_per_addr: u8,
31 | ) -> Result {
32 | let mut ch = ConsistentHash::new();
33 | for addr in &addrs {
34 | for _ in 0..connections_per_addr {
35 | let protocol = protocol::Protocol::connect(addr)?;
36 | ch.add(
37 | &ClonableProtocol { connection: Arc::new(Mutex::new(protocol)) },
38 | 1,
39 | );
40 | }
41 | }
42 | Ok(MemcachedClient { connections: ch })
43 | }
44 |
45 | pub fn set(&self, key: K, value: V, time: u32) -> Result<()>
46 | where
47 | K: AsRef<[u8]>,
48 | V: protocol::ToMemcached,
49 | {
50 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap();
51 | let mut protocol = clonable_protocol.connection.lock().unwrap();
52 | protocol.set(key, value, time)
53 | }
54 |
55 | pub fn add(&self, key: K, value: V, time: u32) -> Result<()>
56 | where
57 | K: AsRef<[u8]>,
58 | V: protocol::ToMemcached,
59 | {
60 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap();
61 | let mut protocol = clonable_protocol.connection.lock().unwrap();
62 | protocol.add(key, value, time)
63 | }
64 |
65 | pub fn replace(&self, key: K, value: V, time: u32) -> Result<()>
66 | where
67 | K: AsRef<[u8]>,
68 | V: protocol::ToMemcached,
69 | {
70 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap();
71 | let mut protocol = clonable_protocol.connection.lock().unwrap();
72 | protocol.replace(key, value, time)
73 | }
74 |
75 | pub fn get(&self, key: K) -> Result
76 | where
77 | K: AsRef<[u8]>,
78 | V: protocol::FromMemcached,
79 | {
80 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap();
81 | let mut protocol = clonable_protocol.connection.lock().unwrap();
82 | protocol.get(key)
83 | }
84 |
85 | pub fn delete(&self, key: K) -> Result<()>
86 | where
87 | K: AsRef<[u8]>,
88 | {
89 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap();
90 | let mut protocol = clonable_protocol.connection.lock().unwrap();
91 | protocol.delete(key)
92 | }
93 |
94 | pub fn increment(
95 | &self,
96 | key: K,
97 | amount: u64,
98 | initial: u64,
99 | time: u32,
100 | ) -> Result
101 | where
102 | K: AsRef<[u8]>,
103 | {
104 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap();
105 | let mut protocol = clonable_protocol.connection.lock().unwrap();
106 | protocol.increment(key, amount, initial, time)
107 | }
108 |
109 | pub fn decrement(
110 | &self,
111 | key: K,
112 | amount: u64,
113 | initial: u64,
114 | time: u32,
115 | ) -> Result
116 | where
117 | K: AsRef<[u8]>,
118 | {
119 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap();
120 | let mut protocol = clonable_protocol.connection.lock().unwrap();
121 | protocol.decrement(key, amount, initial, time)
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/constants.rs:
--------------------------------------------------------------------------------
1 | bitflags! {
2 | pub struct StoredType: u32 {
3 | const MTYPE_STRING = 1 << 0;
4 | const MTYPE_U8 = 1 << 1;
5 | const MTYPE_U16 = 1 << 2;
6 | const MTYPE_U32 = 1 << 3;
7 | const MTYPE_U64 = 1 << 4;
8 | const MTYPE_VECTOR = 1 << 5;
9 | #[allow(dead_code)]
10 | const MTYPE_COMPRESSED = 1 << 6;
11 | #[allow(dead_code)]
12 | const MTYPE_USER_DEFINED_1 = 1 << 10;
13 | #[allow(dead_code)]
14 | const MTYPE_USER_DEFINED_2 = 1 << 11;
15 | #[allow(dead_code)]
16 | const MTYPE_USER_DEFINED_3 = 1 << 13;
17 | #[allow(dead_code)]
18 | const MTYPE_USER_DEFINED_4 = 1 << 14;
19 | #[allow(dead_code)]
20 | const MTYPE_USER_DEFINED_5 = 1 << 15;
21 | #[allow(dead_code)]
22 | const MTYPE_USER_DEFINED_6 = 1 << 16;
23 | #[allow(dead_code)]
24 | const MTYPE_USER_DEFINED_7 = 1 << 17;
25 | #[allow(dead_code)]
26 | const MTYPE_USER_DEFINED_8 = 1 << 18;
27 | #[allow(dead_code)]
28 | const MTYPE_USER_DEFINED_9 = 1 << 19;
29 | #[allow(dead_code)]
30 | const MTYPE_USER_DEFINED_10 = 1 << 20;
31 | #[allow(dead_code)]
32 | const MTYPE_USER_DEFINED_11 = 1 << 21;
33 | #[allow(dead_code)]
34 | const MTYPE_USER_DEFINED_12 = 1 << 22;
35 | #[allow(dead_code)]
36 | const MTYPE_USER_DEFINED_13 = 1 << 23;
37 | #[allow(dead_code)]
38 | const MTYPE_USER_DEFINED_14 = 1 << 24;
39 | #[allow(dead_code)]
40 | const MTYPE_USER_DEFINED_15 = 1 << 25;
41 | #[allow(dead_code)]
42 | const MTYPE_USER_DEFINED_16 = 1 << 26;
43 | #[allow(dead_code)]
44 | const MTYPE_USER_DEFINED_17 = 1 << 27;
45 | #[allow(dead_code)]
46 | const MTYPE_USER_DEFINED_18 = 1 << 28;
47 | #[allow(dead_code)]
48 | const MTYPE_USER_DEFINED_19 = 1 << 29;
49 | #[allow(dead_code)]
50 | const MTYPE_USER_DEFINED_20 = 1 << 30;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/errors.rs:
--------------------------------------------------------------------------------
1 | use constants::StoredType;
2 | use protocol::{Status, KEY_MAXIMUM_SIZE};
3 |
4 | error_chain! {
5 | foreign_links {
6 | IoError(::std::io::Error);
7 | Utf8Error(::std::string::FromUtf8Error);
8 | }
9 |
10 | errors {
11 | Status(s: Status) {
12 | description("Invalid status received")
13 | display("Invalid status received {:?}", s)
14 | }
15 | /// In case you tried to coerce to a value that does not match with the stored.
16 | /// The returned flags are inside the error.
17 | TypeMismatch(s: StoredType) {
18 | description("Requested type is different from the one stored in memcached")
19 | display("Requested type is different from the one stored in memcached: {:?}", s)
20 | }
21 |
22 | KeyLengthTooLong(length: usize) {
23 | description("Key length is too long")
24 | display("Key length {} is too long, the maximum is {}", length, KEY_MAXIMUM_SIZE)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | /*!
2 | This is a binary memcached protocol implemented only in rust with support of traits to send
3 | and receive `T` and consistent hashing to select connections from a pool to distribute data.
4 | # Example
5 | ```rust
6 | extern crate bmemcached;
7 |
8 | use std::sync::Arc;
9 | use std::thread;
10 |
11 | use bmemcached::MemcachedClient;
12 |
13 | fn main() {
14 | // Use arc for threading support
15 | let client = Arc::new(MemcachedClient::new(vec!["127.0.0.1:11211"], 5).unwrap());
16 |
17 | // Traits examples
18 | let value = "value";
19 | client.set("string", value, 1000);
20 | let rv: String = client.get("string").unwrap();
21 | assert_eq!(rv, "value");
22 |
23 | client.set("integer", 10 as u8, 1000);
24 | let rv: u8 = client.get("integer").unwrap();
25 | assert_eq!(rv, 10 as u8);
26 |
27 | // Threads example
28 | let mut threads = vec![];
29 | for i in 0..4 {
30 | let client = client.clone();
31 | threads.push(thread::spawn(move || {
32 | let data = format!("data_n{}", i);
33 | client.set(&data, &data, 100).unwrap();
34 | let val: String = client.get(&data).unwrap();
35 | client.delete(&data).unwrap();
36 | val
37 | }));
38 | }
39 | for (i, thread) in threads.into_iter().enumerate() {
40 | let result = thread.join();
41 | assert_eq!(result.unwrap(), format!("data_n{}", i));
42 | }
43 | }
44 | ```
45 | */
46 | #![forbid(unsafe_code)]
47 | #[macro_use]
48 | extern crate bitflags;
49 | extern crate byteorder;
50 | extern crate conhash;
51 | #[macro_use]
52 | extern crate enum_primitive;
53 | #[macro_use]
54 | extern crate log;
55 | extern crate num;
56 |
57 | mod client;
58 | pub mod constants;
59 | pub mod errors;
60 | pub mod protocol;
61 |
62 | pub use protocol::{FromMemcached, Status, ToMemcached};
63 | #[macro_use]
64 | extern crate error_chain;
65 |
66 | pub use client::MemcachedClient;
67 | pub use constants::StoredType;
68 |
--------------------------------------------------------------------------------
/src/protocol.rs:
--------------------------------------------------------------------------------
1 | use std::io::{BufReader, BufWriter, Cursor, Read, Write};
2 | use std::net::{TcpStream, ToSocketAddrs};
3 |
4 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
5 | use enum_primitive::FromPrimitive;
6 |
7 | use constants::*;
8 | use errors::{ErrorKind, Result};
9 |
10 | pub const KEY_MAXIMUM_SIZE: usize = 250;
11 |
12 | enum Type {
13 | Request = 0x80,
14 | Response = 0x81,
15 | }
16 |
17 | #[derive(Debug)]
18 | enum Command {
19 | Get = 0x00,
20 | Set = 0x01,
21 | Add = 0x02,
22 | Replace = 0x03,
23 | Delete = 0x04,
24 | Increment = 0x05,
25 | Decrement = 0x06,
26 | // Quit = 0x07,
27 | // Flush = 0x08,
28 | // GetQ = 0x09,
29 | // NoOp = 0x0A,
30 | // Version = 0x0B,
31 | // GetK = 0x0C,
32 | // GetKQ = 0x0D,
33 | // Append = 0x0E,
34 | // Prepend = 0x0F,
35 | // Stat = 0x10,
36 | // SetQ = 0x11,
37 | // AddQ = 0x12,
38 | // ReplaceQ = 0x13,
39 | // DeleteQ = 0x14,
40 | // IncrementQ = 0x15,
41 | // DecrementQ = 0x16,
42 | // QuitQ = 0x17,
43 | // FlushQ = 0x18,
44 | // AppendQ = 0x19,
45 | // PrependQ = 0x1A
46 | }
47 |
48 | enum_from_primitive! {
49 | #[derive(Debug, PartialEq)]
50 | pub enum Status {
51 | Success = 0x00,
52 | KeyNotFound = 0x01,
53 | KeyExists = 0x02,
54 | ValueTooBig = 0x03,
55 | InvalidArguments = 0x04,
56 | AuthError = 0x08,
57 | UnknownCommand = 0x81
58 | }
59 | }
60 |
61 | #[derive(Debug)]
62 | pub struct Request {
63 | magic: u8,
64 | opcode: u8,
65 | key_length: u16,
66 | extras_length: u8,
67 | data_type: u8,
68 | reserved: u16,
69 | body_length: u32,
70 | opaque: u32,
71 | cas: u64,
72 | }
73 |
74 | #[derive(Debug)]
75 | pub struct Response {
76 | magic: u8,
77 | opcode: u8,
78 | key_length: u16,
79 | extras_length: u8,
80 | data_type: u8,
81 | status: u16,
82 | body_length: u32,
83 | opaque: u32,
84 | cas: u64,
85 | }
86 |
87 | #[derive(Debug)]
88 | pub struct Protocol {
89 | connection: BufReader,
90 | }
91 |
92 | pub trait ToMemcached {
93 | fn get_value(&self) -> Result<(Vec, StoredType)>;
94 | }
95 |
96 | pub trait FromMemcached: Sized {
97 | fn get_value(flags: StoredType, buf: Vec) -> Result;
98 | }
99 |
100 | impl Protocol {
101 | pub fn connect(addr: A) -> Result {
102 | Ok(Protocol {
103 | connection: BufReader::new(TcpStream::connect(addr)?),
104 | })
105 | }
106 |
107 | pub fn connection_info(&self) -> String {
108 | let connection = self.connection.get_ref();
109 | connection.peer_addr().unwrap().to_string()
110 | }
111 |
112 | fn build_request(
113 | command: Command,
114 | key_length: usize,
115 | value_length: usize,
116 | data_type: u8,
117 | extras_length: usize,
118 | cas: u64,
119 | ) -> Result {
120 | if key_length > KEY_MAXIMUM_SIZE {
121 | bail!(ErrorKind::KeyLengthTooLong(key_length));
122 | }
123 | Ok(Request {
124 | magic: Type::Request as u8,
125 | opcode: command as u8,
126 | key_length: key_length as u16,
127 | extras_length: extras_length as u8,
128 | data_type: data_type,
129 | reserved: 0,
130 | body_length: (key_length + value_length + extras_length) as u32,
131 | opaque: 0,
132 | cas: cas,
133 | })
134 | }
135 |
136 | fn write_request(&mut self, request: Request, final_payload: &[u8]) -> Result<()> {
137 | let connection = self.connection.get_mut();
138 | let mut buf = BufWriter::new(connection);
139 | buf.write_u8(request.magic)?;
140 | buf.write_u8(request.opcode)?;
141 | buf.write_u16::(request.key_length)?;
142 | buf.write_u8(request.extras_length)?;
143 | buf.write_u8(request.data_type)?;
144 | buf.write_u16::(request.reserved)?;
145 | buf.write_u32::(request.body_length)?;
146 | buf.write_u32::(request.opaque)?;
147 | buf.write_u64::(request.cas)?;
148 | buf.write(final_payload)?;
149 | buf.flush()?;
150 | Ok(())
151 | }
152 |
153 | fn read_response(&mut self) -> Result {
154 | let buf = &mut self.connection;
155 | let magic: u8 = buf.read_u8()?;
156 | if magic != Type::Response as u8 {
157 | // TODO Consume the stream, disconnect or something?
158 | debug!("Server sent an unknown magic code {:?}", magic);
159 | bail!("Server sent an unknown magic code");
160 | }
161 | Ok(Response {
162 | magic,
163 | opcode: buf.read_u8()?,
164 | key_length: buf.read_u16::()?,
165 | extras_length: buf.read_u8()?,
166 | data_type: buf.read_u8()?,
167 | status: buf.read_u16::()?,
168 | body_length: buf.read_u32::()?,
169 | opaque: buf.read_u32::()?,
170 | cas: buf.read_u64::()?,
171 | })
172 | }
173 |
174 | fn consume_body(&mut self, size: u32) -> Result<()> {
175 | debug!("Consuming body");
176 | let mut buf: Vec = vec![0; size as usize];
177 | self.connection.read(&mut *buf)?;
178 | let str_buf = String::from_utf8(buf)?;
179 | debug!("Consumed body {:?}", str_buf);
180 | Ok(())
181 | }
182 |
183 | fn set_add_replace(&mut self, command: Command, key: K, value: V, time: u32) -> Result<()>
184 | where
185 | K: AsRef<[u8]>,
186 | V: ToMemcached,
187 | {
188 | let key = key.as_ref();
189 | let (value, flags) = value.get_value()?;
190 |
191 | let extras_length = 8; // Flags: u32 and Expiration time: u32
192 | let request =
193 | Protocol::build_request(command, key.len(), value.len(), 0x00, extras_length, 0x00)?;
194 | let mut final_payload = vec![];
195 | // Flags
196 | final_payload.write_u32::(flags.bits())?;
197 | final_payload.write_u32::(time)?;
198 | // After flags key and value
199 | final_payload.write(key)?;
200 | final_payload.write(&value)?;
201 | self.write_request(request, final_payload.as_slice())?;
202 | let response = self.read_response()?;
203 | match Status::from_u16(response.status) {
204 | Some(Status::Success) => Ok(()),
205 | Some(rest) => {
206 | self.consume_body(response.body_length)?;
207 | bail!(ErrorKind::Status(rest))
208 | }
209 | None => bail!(
210 | "Server returned an unknown status code 0x{:02x}",
211 | response.status
212 | ),
213 | }
214 | }
215 |
216 | pub fn set(&mut self, key: K, value: V, time: u32) -> Result<()>
217 | where
218 | K: AsRef<[u8]>,
219 | V: ToMemcached,
220 | {
221 | self.set_add_replace(Command::Set, key, value, time)
222 | }
223 |
224 | pub fn add(&mut self, key: K, value: V, time: u32) -> Result<()>
225 | where
226 | K: AsRef<[u8]>,
227 | V: ToMemcached,
228 | {
229 | self.set_add_replace(Command::Add, key, value, time)
230 | }
231 |
232 | pub fn replace(&mut self, key: K, value: V, time: u32) -> Result<()>
233 | where
234 | K: AsRef<[u8]>,
235 | V: ToMemcached,
236 | {
237 | self.set_add_replace(Command::Replace, key, value, time)
238 | }
239 |
240 | pub fn get(&mut self, key: K) -> Result
241 | where
242 | K: AsRef<[u8]>,
243 | V: FromMemcached,
244 | {
245 | let key = key.as_ref();
246 | let request = Protocol::build_request(Command::Get, key.len(), 0 as usize, 0, 0, 0x00)?;
247 | self.write_request(request, key)?;
248 | let response = self.read_response()?;
249 | match Status::from_u16(response.status) {
250 | Some(Status::Success) => {}
251 | Some(status) => {
252 | self.consume_body(response.body_length)?;
253 | bail!(ErrorKind::Status(status));
254 | }
255 | None => {
256 | bail!(
257 | "Server sent an unknown status code 0x{:02x}",
258 | response.status
259 | );
260 | }
261 | };
262 | let flags = StoredType::from_bits(self.connection.read_u32::()?).unwrap();
263 | let mut outbuf = vec![0; (response.body_length - response.extras_length as u32) as usize];
264 | self.connection.read_exact(&mut outbuf)?;
265 | FromMemcached::get_value(flags, outbuf)
266 | }
267 |
268 | pub fn delete(&mut self, key: K) -> Result<()>
269 | where
270 | K: AsRef<[u8]>,
271 | {
272 | let key = key.as_ref();
273 | let request = Protocol::build_request(Command::Delete, key.len(), 0 as usize, 0, 0, 0x00)?;
274 | self.write_request(request, key)?;
275 | let response = self.read_response()?;
276 |
277 | match Status::from_u16(response.status) {
278 | Some(Status::Success) => Ok(()),
279 | Some(Status::KeyNotFound) => {
280 | self.consume_body(response.body_length)?;
281 | Ok(())
282 | }
283 | Some(status) => {
284 | self.consume_body(response.body_length)?;
285 | bail!(ErrorKind::Status(status))
286 | }
287 | None => bail!(
288 | "Server sent an unknown status code 0x{:02x}",
289 | response.status
290 | ),
291 | }
292 | }
293 |
294 | fn increment_decrement(
295 | &mut self,
296 | key: K,
297 | amount: u64,
298 | initial: u64,
299 | time: u32,
300 | command: Command,
301 | ) -> Result
302 | where
303 | K: AsRef<[u8]>,
304 | {
305 | let key = key.as_ref();
306 | let extras_length = 20; // Amount: u64, Initial: u64, Time: u32
307 | let request = Protocol::build_request(command, key.len(), 0, 0, extras_length, 0x00)?;
308 | let mut final_payload: Vec = vec![];
309 | final_payload.write_u64::(amount)?;
310 | final_payload.write_u64::(initial)?;
311 | final_payload.write_u32::(time)?;
312 | final_payload.write(key)?;
313 | self.write_request(request, &final_payload)?;
314 | let response = self.read_response()?;
315 | match Status::from_u16(response.status) {
316 | Some(Status::Success) => Ok(self.connection.read_u64::()?),
317 | Some(status) => {
318 | self.consume_body(response.body_length)?;
319 | bail!(ErrorKind::Status(status))
320 | }
321 | None => bail!("Server sent an unknown status code"),
322 | }
323 | }
324 |
325 | pub fn increment(&mut self, key: K, amount: u64, initial: u64, time: u32) -> Result
326 | where
327 | K: AsRef<[u8]>,
328 | {
329 | self.increment_decrement(key, amount, initial, time, Command::Increment)
330 | }
331 |
332 | pub fn decrement(&mut self, key: K, amount: u64, initial: u64, time: u32) -> Result
333 | where
334 | K: AsRef<[u8]>,
335 | {
336 | self.increment_decrement(key, amount, initial, time, Command::Decrement)
337 | }
338 | }
339 |
340 | impl ToMemcached for u8 {
341 | fn get_value(&self) -> Result<(Vec, StoredType)> {
342 | Ok((vec![*self], StoredType::MTYPE_U8))
343 | }
344 | }
345 |
346 | impl ToMemcached for u16 {
347 | fn get_value(&self) -> Result<(Vec, StoredType)> {
348 | let mut buf = vec![];
349 | buf.write_u16::(*self)?;
350 | Ok((buf, StoredType::MTYPE_U16))
351 | }
352 | }
353 |
354 | impl ToMemcached for u32 {
355 | fn get_value(&self) -> Result<(Vec, StoredType)> {
356 | let mut buf = vec![];
357 | buf.write_u32::(*self)?;
358 | Ok((buf, StoredType::MTYPE_U32))
359 | }
360 | }
361 |
362 | impl ToMemcached for u64 {
363 | fn get_value(&self) -> Result<(Vec, StoredType)> {
364 | let mut buf = vec![];
365 | buf.write_u64::(*self)?;
366 | Ok((buf, StoredType::MTYPE_U64))
367 | }
368 | }
369 |
370 | impl<'a> ToMemcached for &'a String {
371 | fn get_value(&self) -> Result<(Vec, StoredType)> {
372 | let v = *self;
373 | Ok((v.clone().into_bytes(), StoredType::MTYPE_STRING))
374 | }
375 | }
376 |
377 | impl<'a> ToMemcached for &'a str {
378 | fn get_value(&self) -> Result<(Vec, StoredType)> {
379 | Ok((self.as_bytes().to_vec(), StoredType::MTYPE_STRING))
380 | }
381 | }
382 |
383 | impl<'a> ToMemcached for &'a [u8] {
384 | fn get_value(&self) -> Result<(Vec, StoredType)> {
385 | Ok((self.to_vec(), StoredType::MTYPE_VECTOR))
386 | }
387 | }
388 |
389 | impl FromMemcached for String {
390 | fn get_value(flags: StoredType, buf: Vec) -> Result {
391 | if flags & StoredType::MTYPE_STRING != StoredType::empty() {
392 | Ok(String::from_utf8(buf)?)
393 | } else {
394 | bail!(ErrorKind::TypeMismatch(flags))
395 | }
396 | }
397 | }
398 |
399 | impl FromMemcached for u8 {
400 | fn get_value(flags: StoredType, buf: Vec) -> Result {
401 | if flags & StoredType::MTYPE_U8 != StoredType::empty() {
402 | let mut buf = Cursor::new(buf);
403 | Ok(buf.read_u8()?)
404 | } else {
405 | bail!(ErrorKind::TypeMismatch(flags))
406 | }
407 | }
408 | }
409 |
410 | impl FromMemcached for u16 {
411 | fn get_value(flags: StoredType, buf: Vec) -> Result {
412 | if flags & StoredType::MTYPE_U16 != StoredType::empty() {
413 | let mut buf = Cursor::new(buf);
414 | Ok(buf.read_u16::()?)
415 | } else {
416 | bail!(ErrorKind::TypeMismatch(flags))
417 | }
418 | }
419 | }
420 |
421 | impl FromMemcached for u32 {
422 | fn get_value(flags: StoredType, buf: Vec) -> Result {
423 | if flags & StoredType::MTYPE_U32 != StoredType::empty() {
424 | let mut buf = Cursor::new(buf);
425 | Ok(buf.read_u32::()?)
426 | } else {
427 | bail!(ErrorKind::TypeMismatch(flags))
428 | }
429 | }
430 | }
431 |
432 | impl FromMemcached for u64 {
433 | #[allow(unused_variables)]
434 | fn get_value(flags: StoredType, buf: Vec) -> Result {
435 | // As increment and decrement don't allow us to send flags, we don't
436 | // enforce type checking.
437 | let mut buf = Cursor::new(buf);
438 | Ok(buf.read_u64::()?)
439 | }
440 | }
441 |
442 | impl FromMemcached for Vec {
443 | #[allow(unused_variables)]
444 | fn get_value(flags: StoredType, buf: Vec) -> Result {
445 | Ok(buf)
446 | }
447 | }
448 |
449 | #[cfg(test)]
450 | mod tests {
451 | extern crate env_logger;
452 |
453 | use std::iter;
454 |
455 | use super::*;
456 | use errors::{Error, Result};
457 |
458 | #[test]
459 | fn set() {
460 | let _ = env_logger::try_init();
461 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
462 | let key = "Hello Set";
463 | let value = "World";
464 | p.set(key, value, 1000).unwrap();
465 | p.delete(key).unwrap();
466 | let data: String = iter::repeat("0").take(1024 * 1024).collect();
467 | let err = p.set("big-data", &data, 100_000).unwrap_err();
468 | match err.kind() {
469 | &ErrorKind::Status(Status::ValueTooBig) => {}
470 | e => panic!("Value should not be {:?}", e),
471 | }
472 | }
473 |
474 | #[test]
475 | fn set_u8() {
476 | let _ = env_logger::try_init();
477 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
478 | let key = "Hello";
479 | let value = 1 as u8;
480 | p.set(key, value, 1000).unwrap();
481 | p.delete(key).unwrap();
482 | }
483 |
484 | #[test]
485 | fn set_u16() {
486 | let _ = env_logger::try_init();
487 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
488 | let key = "Hello";
489 | let value = 1 as u16;
490 | p.set(key, value, 1000).unwrap();
491 | p.delete(key).unwrap();
492 | }
493 |
494 | #[test]
495 | fn set_u32() {
496 | let _ = env_logger::try_init();
497 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
498 | let key = "Hello";
499 | let value = 1 as u32;
500 | p.set(key, value, 100).unwrap();
501 | p.delete(key).unwrap();
502 | }
503 |
504 | #[test]
505 | fn set_u64() {
506 | let _ = env_logger::try_init();
507 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
508 | let key = "Hello";
509 | let value = 1 as u64;
510 | p.set(key, value, 1000).unwrap();
511 | p.delete(key).unwrap();
512 | }
513 |
514 | #[test]
515 | fn set_slice() {
516 | let _ = env_logger::try_init();
517 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
518 | let key = "Hello";
519 | let value = vec![1, 2, 3];
520 | p.set(key, &value[..], 1000).unwrap();
521 | p.delete(key).unwrap();
522 | }
523 |
524 | #[test]
525 | fn add_key() {
526 | let _ = env_logger::try_init();
527 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
528 | let key = "Hello Add";
529 | let value = "World";
530 | p.add(key, value, 10).unwrap();
531 | let result = p.add(key, value, 10);
532 | match result {
533 | Ok(()) => panic!("Add key should return error"),
534 | Err(Error(ErrorKind::Status(Status::KeyExists), _)) => {}
535 | Err(_) => panic!("Some strange error that should not happen"),
536 | };
537 | p.delete(key).unwrap();
538 | }
539 |
540 | #[test]
541 | fn get_key() {
542 | let _ = env_logger::try_init();
543 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
544 | let key = "Hello Get";
545 | let value = "World";
546 | p.set(key, value, 10000).unwrap();
547 | let rv: String = p.get(key).unwrap();
548 | assert_eq!(rv, value);
549 |
550 | let not_found: Result = p.get("not found".to_string());
551 | match not_found {
552 | Ok(_) => panic!("This key should not exist"),
553 | Err(Error(ErrorKind::Status(Status::KeyNotFound), _)) => {}
554 | Err(_) => panic!("This should return KeyNotFound"),
555 | };
556 | p.delete(key).unwrap();
557 | let big_key: String = iter::repeat("0").take(260).collect();
558 | match p.get::<_, Vec>(big_key) {
559 | Ok(_) => panic!("Should be an error"),
560 | Err(Error(ErrorKind::KeyLengthTooLong(260), _)) => {}
561 | Err(e) => panic!("This should be KeyLengthTooLong and not {:?}", e),
562 | };
563 | }
564 |
565 | #[test]
566 | fn delete_key() {
567 | let _ = env_logger::try_init();
568 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
569 | let key = "Hello Delete";
570 | let value = "World";
571 | p.set(key, value, 1000).unwrap();
572 | p.delete(key).unwrap();
573 | p.delete(key).unwrap();
574 | }
575 |
576 | #[test]
577 | fn increment() {
578 | let _ = env_logger::try_init();
579 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
580 | let key = "Hello Increment";
581 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 0);
582 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 1);
583 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 2);
584 | p.delete(key).unwrap();
585 | }
586 |
587 | #[test]
588 | fn decrement() {
589 | let _ = env_logger::try_init();
590 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap();
591 | let key = "Hello Decrement";
592 | assert_eq!(p.decrement(key, 1, 0, 1000).unwrap(), 0);
593 | assert_eq!(p.decrement(key, 1, 0, 1000).unwrap(), 0);
594 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 1);
595 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 2);
596 | assert_eq!(p.decrement(key, 1, 0, 1000).unwrap(), 1);
597 | p.delete(key).unwrap();
598 | }
599 | }
600 |
--------------------------------------------------------------------------------
/tests/client.rs:
--------------------------------------------------------------------------------
1 | extern crate env_logger;
2 | #[macro_use]
3 | extern crate log;
4 | extern crate bmemcached;
5 |
6 | use std::sync::Arc;
7 | use std::thread;
8 |
9 | use bmemcached::errors::{Error, ErrorKind};
10 | use bmemcached::{MemcachedClient, Status};
11 |
12 | #[test]
13 | fn multiple_threads() {
14 | let _ = env_logger::try_init();
15 | let mut threads = vec![];
16 | let client = Arc::new(MemcachedClient::new(vec!["127.0.0.1:11211"], 5).unwrap());
17 | for i in 0..4 {
18 | let client = client.clone();
19 | debug!("Starting thread {}", i);
20 | threads.push(thread::spawn(move || {
21 | debug!("Started {}", i);
22 | let data = format!("data_n{}", i);
23 | client.set(&data, &data, 100).unwrap();
24 | let val: String = client.get(&data).unwrap();
25 | client.delete(&data).unwrap();
26 | debug!("Finished {}", i);
27 | val
28 | }));
29 | }
30 | for (i, thread) in threads.into_iter().enumerate() {
31 | let result = thread.join();
32 | assert_eq!(result.unwrap(), format!("data_n{}", i));
33 | }
34 | }
35 |
36 | #[test]
37 | fn get_set_delete() {
38 | let _ = env_logger::try_init();
39 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
40 | let key = "Hello Get, Set, Delete Client";
41 | let value = "World";
42 | client.set(key, value, 1000).unwrap();
43 | let rv: String = client.get(key).unwrap();
44 | assert_eq!(rv, value);
45 | client.delete(key).unwrap();
46 | }
47 |
48 | #[test]
49 | fn get_set_u8() {
50 | let _ = env_logger::try_init();
51 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
52 | let key = "Hello u8";
53 | let value = 1 as u8;
54 | client.set(key, value, 1000).unwrap();
55 |
56 | let rv: u8 = client.get(key).unwrap();
57 | assert_eq!(rv, value);
58 | client.delete(key).unwrap();
59 | }
60 |
61 | #[test]
62 | fn get_set_u16() {
63 | let _ = env_logger::try_init();
64 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
65 | let key = "Hello u16";
66 | let value = 1 as u16;
67 | client.set(key, value, 1000).unwrap();
68 |
69 | let rv: u16 = client.get(key).unwrap();
70 | assert_eq!(rv, value);
71 | client.delete(key).unwrap();
72 | }
73 |
74 | #[test]
75 | fn get_set_u32() {
76 | let _ = env_logger::try_init();
77 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
78 | let key = "Hello u32";
79 | let value = 1 as u32;
80 | client.set(key, value, 1000).unwrap();
81 |
82 | let rv: u32 = client.get(key).unwrap();
83 | assert_eq!(rv, value);
84 | client.delete(key).unwrap();
85 | }
86 |
87 | #[test]
88 | fn get_set_u64() {
89 | let _ = env_logger::try_init();
90 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
91 | let key = "Hello u64";
92 | let value = 1 as u64;
93 | client.set(key, value, 1000).unwrap();
94 |
95 | let rv: u64 = client.get(key).unwrap();
96 | assert_eq!(rv, value);
97 | client.delete(key).unwrap();
98 | }
99 |
100 | #[test]
101 | fn add() {
102 | let _ = env_logger::try_init();
103 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
104 | let key = "Hello Add Client";
105 | let value = "World";
106 | client.add(key, value, 1000).unwrap();
107 | let rv: String = client.get(key).unwrap();
108 | assert_eq!(rv, value);
109 | match client.add(key, value, 1000) {
110 | Err(Error(ErrorKind::Status(Status::KeyExists), _)) => (),
111 | e => panic!("Wrong status returned {:?}", e),
112 | }
113 | client.delete(key).unwrap();
114 | }
115 |
116 | #[test]
117 | fn replace() {
118 | let _ = env_logger::try_init();
119 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
120 | let key = "Hello Replace Client";
121 | let value = "World";
122 | client.add(key, value, 1000).unwrap();
123 |
124 | let rv: String = client.get(key).unwrap();
125 | assert_eq!(rv, value);
126 |
127 | client.replace(key, "New value", 100).unwrap();
128 | let rv: String = client.get(key).unwrap();
129 | assert_eq!(rv, "New value");
130 | client.delete(key).unwrap();
131 | }
132 |
133 | #[test]
134 | fn increment() {
135 | let _ = env_logger::try_init();
136 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
137 | let key = "Hello Increment Client";
138 | assert_eq!(client.increment(key, 1, 0, 1000).unwrap(), 0);
139 | assert_eq!(client.increment(key, 1, 1, 1000).unwrap(), 1);
140 | client.delete(key).unwrap();
141 | }
142 |
143 | #[test]
144 | fn decrement() {
145 | let _ = env_logger::try_init();
146 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap();
147 | let key = "Hello Decrement Client";
148 | assert_eq!(client.decrement(key, 1, 10, 1000).unwrap(), 10);
149 | assert_eq!(client.decrement(key, 1, 1, 1000).unwrap(), 9);
150 | client.delete(key).unwrap();
151 | }
152 |
--------------------------------------------------------------------------------