Skip to main content

paper_client/
client.rs

1/*
2 * Copyright (c) Kia Shakiba
3 *
4 * This source code is licensed under the GNU AGPLv3 license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8use std::net::TcpStream;
9
10pub use paper_utils::stream::{StreamError, StreamReader};
11
12use crate::{
13	addr::FromPaperAddr,
14	arg::{AsPaperAuthToken, AsPaperKey},
15	command::Command,
16	error::{PaperClientError, PaperClientResult},
17	policy::PaperPolicy,
18	status::Status,
19	value::PaperValue,
20};
21
22const RECONNECT_MAX_ATTEMPTS: u8 = 3;
23
24#[derive(Debug)]
25pub struct PaperClient {
26	addr: String,
27
28	auth_token:         Option<String>,
29	reconnect_attempts: u8,
30
31	stream: TcpStream,
32}
33
34impl PaperClient {
35	/// Creates a new instance of the client and connects to the server.
36	/// If a connection could not be established, a `PaperClientError`
37	/// is returned.
38	///
39	/// # Examples
40	/// ```
41	/// use paper_client::PaperClient;
42	///
43	/// let client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
44	/// ```
45	pub fn new(paper_addr: impl FromPaperAddr) -> PaperClientResult<Self> {
46		let addr = paper_addr.to_addr()?;
47		let stream = init_stream(&addr)?;
48
49		let mut client = PaperClient {
50			addr,
51
52			auth_token: None,
53			reconnect_attempts: 0,
54
55			stream,
56		};
57
58		client.handshake()?;
59
60		Ok(client)
61	}
62
63	/// Pings the server.
64	///
65	/// # Examples
66	/// ```
67	/// use paper_client::PaperClient;
68	///
69	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
70	///
71	/// match client.ping() {
72	///     Ok(value) => println!("{value:?}"),
73	///     Err(err) => println!("{err:?}"),
74	/// }
75	/// ```
76	pub fn ping(&mut self) -> PaperClientResult<PaperValue> {
77		self.process_value(&Command::Ping)
78	}
79
80	/// Gets the cache version.
81	///
82	/// # Examples
83	/// ```
84	/// use paper_client::PaperClient;
85	///
86	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
87	///
88	/// match client.version() {
89	///     Ok(value) => println!("{value:?}"),
90	///     Err(err) => println!("{err:?}"),
91	/// }
92	/// ```
93	pub fn version(&mut self) -> PaperClientResult<PaperValue> {
94		self.process_value(&Command::Version)
95	}
96
97	/// Attempts to authorize the connection with the supplied auth token. This
98	/// must match the auth token specified in the server's configuration to be
99	/// successful.
100	///
101	/// # Examples
102	/// ```
103	/// use paper_client::PaperClient;
104	///
105	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
106	///
107	/// match client.auth("my_token") {
108	///     Ok(_) => println!("done"),
109	///     Err(err) => println!("{err:?}"),
110	/// }
111	/// ```
112	pub fn auth(&mut self, token: impl AsPaperAuthToken) -> PaperClientResult<()> {
113		let auth_token = token.as_paper_auth_token();
114
115		let command = Command::Auth(auth_token);
116		let result = self.process(&command);
117
118		self.auth_token = Some(auth_token.to_owned());
119
120		result
121	}
122
123	/// Gets the value of the supplied key from the cache.
124	///
125	/// # Examples
126	/// ```
127	/// use paper_client::PaperClient;
128	///
129	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
130	///
131	/// match client.get("key") {
132	///     Ok(value) => println!("{value:?}"),
133	///     Err(err) => println!("{err:?}"),
134	/// }
135	/// ```
136	pub fn get(&mut self, key: impl AsPaperKey) -> PaperClientResult<PaperValue> {
137		let command = Command::Get(key.as_paper_key());
138		self.process_value(&command)
139	}
140
141	/// Sets the supplied key, value, and ttl to the cache.
142	///
143	/// # Examples
144	/// ```
145	/// use paper_client::PaperClient;
146	///
147	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
148	///
149	/// match client.set("key", "value", None) {
150	///     Ok(_) => println!("done"),
151	///     Err(err) => println!("{err:?}"),
152	/// }
153	/// ```
154	pub fn set(
155		&mut self,
156		key: impl AsPaperKey,
157		value: impl TryInto<PaperValue>,
158		ttl: Option<u32>,
159	) -> PaperClientResult<()> {
160		let value: PaperValue = value
161			.try_into()
162			.map_err(|_| PaperClientError::InvalidValue)?;
163
164		let command = Command::Set(key.as_paper_key(), value, ttl.unwrap_or(0));
165
166		self.process(&command)
167	}
168
169	/// Deletes the value of the supplied key from the cache.
170	///
171	/// # Examples
172	/// ```
173	/// use paper_client::PaperClient;
174	///
175	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
176	///
177	/// match client.del("key") {
178	///     Ok(_) => println!("done"),
179	///     Err(err) => println!("{err:?}"),
180	/// }
181	/// ```
182	pub fn del(&mut self, key: impl AsPaperKey) -> PaperClientResult<()> {
183		let command = Command::Del(key.as_paper_key());
184		self.process(&command)
185	}
186
187	/// Checks if the cache contains an object with the supplied key
188	/// without altering the eviction order of the objects.
189	///
190	/// # Examples
191	/// ```
192	/// use paper_client::PaperClient;
193	///
194	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
195	///
196	/// match client.has("key") {
197	///     Ok(has) => println!("{has}"),
198	///     Err(err) => println!("{err:?}"),
199	/// }
200	/// ```
201	pub fn has(&mut self, key: impl AsPaperKey) -> PaperClientResult<bool> {
202		let command = Command::Has(key.as_paper_key());
203		self.process_has(&command)
204	}
205
206	/// Gets (peeks) the value of the supplied key from the cache without
207	/// altering the eviction order of the objects.
208	///
209	/// # Examples
210	/// ```
211	/// use paper_client::PaperClient;
212	///
213	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
214	///
215	/// match client.peek("key") {
216	///     Ok(value) => println!("{value:?}"),
217	///     Err(err) => println!("{err:?}"),
218	/// }
219	/// ```
220	pub fn peek(&mut self, key: impl AsPaperKey) -> PaperClientResult<PaperValue> {
221		let command = Command::Peek(key.as_paper_key());
222		self.process_value(&command)
223	}
224
225	/// Sets the TTL associated with the supplied key.
226	///
227	/// # Examples
228	/// ```
229	/// use paper_client::PaperClient;
230	///
231	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
232	///
233	/// match client.ttl("key", Some(5)) {
234	///     Ok(_) => println!("done"),
235	///     Err(err) => println!("{err:?}"),
236	/// }
237	/// ```
238	pub fn ttl(&mut self, key: impl AsPaperKey, ttl: Option<u32>) -> PaperClientResult<()> {
239		let command = Command::Ttl(key.as_paper_key(), ttl.unwrap_or(0));
240		self.process(&command)
241	}
242
243	/// Gets the size of the value of the supplied key from the cache in bytes.
244	///
245	/// # Examples
246	/// ```
247	/// use paper_client::PaperClient;
248	///
249	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
250	///
251	/// match client.size("key") {
252	///     Ok(size) => println!("{size}"),
253	///     Err(err) => println!("{err:?}"),
254	/// }
255	/// ```
256	pub fn size(&mut self, key: impl AsPaperKey) -> PaperClientResult<u32> {
257		let command = Command::Size(key.as_paper_key());
258		self.process_size(&command)
259	}
260
261	/// Wipes the contents of the cache.
262	///
263	/// # Examples
264	/// ```
265	/// use paper_client::PaperClient;
266	///
267	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
268	///
269	/// match client.wipe() {
270	///     Ok(_) => println!("done"),
271	///     Err(err) => println!("{err:?}"),
272	/// }
273	/// ```
274	pub fn wipe(&mut self) -> PaperClientResult<()> {
275		self.process(&Command::Wipe)
276	}
277
278	/// Resizes the cache to the supplied size.
279	///
280	/// # Examples
281	/// ```
282	/// use paper_client::PaperClient;
283	///
284	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
285	///
286	/// match client.resize(10) {
287	///     Ok(_) => println!("done"),
288	///     Err(err) => println!("{err:?}"),
289	/// }
290	/// ```
291	pub fn resize(&mut self, size: u64) -> PaperClientResult<()> {
292		let command = Command::Resize(size);
293		self.process(&command)
294	}
295
296	/// Sets the cache's eviction policy.
297	///
298	/// # Examples
299	/// ```
300	/// use paper_client::{PaperClient, PaperPolicy};
301	///
302	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
303	///
304	/// match client.policy(PaperPolicy::Lru) {
305	///     Ok(_) => println!("done"),
306	///     Err(err) => println!("{err:?}"),
307	/// }
308	/// ```
309	pub fn policy(&mut self, policy: PaperPolicy) -> PaperClientResult<()> {
310		let command = Command::Policy(policy);
311		self.process(&command)
312	}
313
314	/// Gets the cache's status.
315	///
316	/// # Examples
317	/// ```
318	/// use paper_client::PaperClient;
319	///
320	/// let mut client = PaperClient::new("paper://127.0.0.1:3145").unwrap();
321	///
322	/// match client.status() {
323	///     Ok(status) => println!("{status:?}"),
324	///     Err(err) => println!("{err:?}"),
325	/// }
326	/// ```
327	pub fn status(&mut self) -> PaperClientResult<Status> {
328		self.process_status(&Command::Status)
329	}
330
331	fn process(&mut self, command: &Command<'_>) -> PaperClientResult<()> {
332		match self
333			.send(command)
334			.and_then(|_| self.receive(command))
335		{
336			Ok(response) => {
337				self.reconnect_attempts = 0;
338				Ok(response)
339			},
340
341			Err(PaperClientError::InvalidResponse) => {
342				self.reconnect_attempts += 1;
343				self.reconnect()?;
344				self.process(command)
345			},
346
347			err => err,
348		}
349	}
350
351	fn process_value(&mut self, command: &Command<'_>) -> PaperClientResult<PaperValue> {
352		match self
353			.send(command)
354			.and_then(|_| self.receive_value(command))
355		{
356			Ok(response) => {
357				self.reconnect_attempts = 0;
358				Ok(response)
359			},
360
361			Err(PaperClientError::InvalidResponse) => {
362				self.reconnect_attempts += 1;
363				self.reconnect()?;
364				self.process_value(command)
365			},
366
367			err => err,
368		}
369	}
370
371	fn process_has(&mut self, command: &Command<'_>) -> PaperClientResult<bool> {
372		match self
373			.send(command)
374			.and_then(|_| self.receive_has(command))
375		{
376			Ok(response) => {
377				self.reconnect_attempts = 0;
378				Ok(response)
379			},
380
381			Err(PaperClientError::InvalidResponse) => {
382				self.reconnect_attempts += 1;
383				self.reconnect()?;
384				self.process_has(command)
385			},
386
387			err => err,
388		}
389	}
390
391	fn process_size(&mut self, command: &Command<'_>) -> PaperClientResult<u32> {
392		match self
393			.send(command)
394			.and_then(|_| self.receive_size(command))
395		{
396			Ok(response) => {
397				self.reconnect_attempts = 0;
398				Ok(response)
399			},
400
401			Err(PaperClientError::InvalidResponse) => {
402				self.reconnect_attempts += 1;
403				self.reconnect()?;
404				self.process_size(command)
405			},
406
407			err => err,
408		}
409	}
410
411	fn process_status(&mut self, command: &Command<'_>) -> PaperClientResult<Status> {
412		match self
413			.send(command)
414			.and_then(|_| self.receive_status(command))
415		{
416			Ok(response) => {
417				self.reconnect_attempts = 0;
418				Ok(response)
419			},
420
421			Err(PaperClientError::InvalidResponse) => {
422				self.reconnect_attempts += 1;
423				self.reconnect()?;
424				self.process_status(command)
425			},
426
427			err => err,
428		}
429	}
430
431	fn send(&mut self, command: &Command<'_>) -> PaperClientResult<()> {
432		command
433			.write(&mut self.stream)
434			.map_err(|err| match err {
435				StreamError::InvalidStream => PaperClientError::Disconnected,
436				_ => PaperClientError::InvalidCommand,
437			})
438	}
439
440	fn receive(&mut self, command: &Command<'_>) -> PaperClientResult<()> {
441		command.parse_reader(&mut self.stream)
442	}
443
444	fn receive_value(&mut self, command: &Command<'_>) -> PaperClientResult<PaperValue> {
445		command.parse_buf_reader(&mut self.stream)
446	}
447
448	fn receive_has(&mut self, command: &Command<'_>) -> PaperClientResult<bool> {
449		command.parse_has_reader(&mut self.stream)
450	}
451
452	fn receive_size(&mut self, command: &Command<'_>) -> PaperClientResult<u32> {
453		command.parse_size_reader(&mut self.stream)
454	}
455
456	fn receive_status(&mut self, command: &Command<'_>) -> PaperClientResult<Status> {
457		command.parse_status_reader(&mut self.stream)
458	}
459
460	fn handshake(&mut self) -> PaperClientResult<()> {
461		let mut reader = StreamReader::new(&mut self.stream);
462
463		let is_ok = reader
464			.read_bool()
465			.map_err(|_| PaperClientError::UnreachableServer)?;
466
467		match is_ok {
468			true => Ok(()),
469			false => Err(PaperClientError::from_reader(reader)),
470		}
471	}
472
473	fn reconnect(&mut self) -> PaperClientResult<()> {
474		if self.reconnect_attempts > RECONNECT_MAX_ATTEMPTS {
475			return Err(PaperClientError::Disconnected);
476		}
477
478		self.stream = init_stream(&self.addr)?;
479		self.handshake()?;
480
481		if let Some(token) = self.auth_token.clone() {
482			self.auth(token)?;
483		}
484
485		Ok(())
486	}
487}
488
489fn init_stream(addr: &str) -> PaperClientResult<TcpStream> {
490	let stream = TcpStream::connect(addr).map_err(|_| PaperClientError::UnreachableServer)?;
491
492	if stream.set_nodelay(true).is_err() {
493		return Err(PaperClientError::Internal);
494	}
495
496	Ok(stream)
497}