use std::net::TcpStream;
pub use paper_utils::stream::{StreamError, StreamReader};
use crate::{
addr::FromPaperAddr,
arg::{AsPaperAuthToken, AsPaperKey},
command::Command,
error::{PaperClientError, PaperClientResult},
policy::PaperPolicy,
status::Status,
value::PaperValue,
};
const RECONNECT_MAX_ATTEMPTS: u8 = 3;
#[derive(Debug)]
pub struct PaperClient {
addr: String,
auth_token: Option<String>,
reconnect_attempts: u8,
stream: TcpStream,
}
impl PaperClient {
pub fn new(paper_addr: impl FromPaperAddr) -> PaperClientResult<Self> {
let addr = paper_addr.to_addr()?;
let stream = init_stream(&addr)?;
let mut client = PaperClient {
addr,
auth_token: None,
reconnect_attempts: 0,
stream,
};
client.handshake()?;
Ok(client)
}
pub fn ping(&mut self) -> PaperClientResult<PaperValue> {
self.process_value(&Command::Ping)
}
pub fn version(&mut self) -> PaperClientResult<PaperValue> {
self.process_value(&Command::Version)
}
pub fn auth(&mut self, token: impl AsPaperAuthToken) -> PaperClientResult<()> {
let auth_token = token.as_paper_auth_token();
let command = Command::Auth(auth_token);
let result = self.process(&command);
self.auth_token = Some(auth_token.to_owned());
result
}
pub fn get(&mut self, key: impl AsPaperKey) -> PaperClientResult<PaperValue> {
let command = Command::Get(key.as_paper_key());
self.process_value(&command)
}
pub fn set(
&mut self,
key: impl AsPaperKey,
value: impl TryInto<PaperValue>,
ttl: Option<u32>,
) -> PaperClientResult<()> {
let value: PaperValue = value
.try_into()
.map_err(|_| PaperClientError::InvalidValue)?;
let command = Command::Set(key.as_paper_key(), value, ttl.unwrap_or(0));
self.process(&command)
}
pub fn del(&mut self, key: impl AsPaperKey) -> PaperClientResult<()> {
let command = Command::Del(key.as_paper_key());
self.process(&command)
}
pub fn has(&mut self, key: impl AsPaperKey) -> PaperClientResult<bool> {
let command = Command::Has(key.as_paper_key());
self.process_has(&command)
}
pub fn peek(&mut self, key: impl AsPaperKey) -> PaperClientResult<PaperValue> {
let command = Command::Peek(key.as_paper_key());
self.process_value(&command)
}
pub fn ttl(&mut self, key: impl AsPaperKey, ttl: Option<u32>) -> PaperClientResult<()> {
let command = Command::Ttl(key.as_paper_key(), ttl.unwrap_or(0));
self.process(&command)
}
pub fn size(&mut self, key: impl AsPaperKey) -> PaperClientResult<u32> {
let command = Command::Size(key.as_paper_key());
self.process_size(&command)
}
pub fn wipe(&mut self) -> PaperClientResult<()> {
self.process(&Command::Wipe)
}
pub fn resize(&mut self, size: u64) -> PaperClientResult<()> {
let command = Command::Resize(size);
self.process(&command)
}
pub fn policy(&mut self, policy: PaperPolicy) -> PaperClientResult<()> {
let command = Command::Policy(policy);
self.process(&command)
}
pub fn status(&mut self) -> PaperClientResult<Status> {
self.process_status(&Command::Status)
}
fn process(&mut self, command: &Command<'_>) -> PaperClientResult<()> {
match self
.send(command)
.and_then(|_| self.receive(command))
{
Ok(response) => {
self.reconnect_attempts = 0;
Ok(response)
},
Err(PaperClientError::InvalidResponse) => {
self.reconnect_attempts += 1;
self.reconnect()?;
self.process(command)
},
err => err,
}
}
fn process_value(&mut self, command: &Command<'_>) -> PaperClientResult<PaperValue> {
match self
.send(command)
.and_then(|_| self.receive_value(command))
{
Ok(response) => {
self.reconnect_attempts = 0;
Ok(response)
},
Err(PaperClientError::InvalidResponse) => {
self.reconnect_attempts += 1;
self.reconnect()?;
self.process_value(command)
},
err => err,
}
}
fn process_has(&mut self, command: &Command<'_>) -> PaperClientResult<bool> {
match self
.send(command)
.and_then(|_| self.receive_has(command))
{
Ok(response) => {
self.reconnect_attempts = 0;
Ok(response)
},
Err(PaperClientError::InvalidResponse) => {
self.reconnect_attempts += 1;
self.reconnect()?;
self.process_has(command)
},
err => err,
}
}
fn process_size(&mut self, command: &Command<'_>) -> PaperClientResult<u32> {
match self
.send(command)
.and_then(|_| self.receive_size(command))
{
Ok(response) => {
self.reconnect_attempts = 0;
Ok(response)
},
Err(PaperClientError::InvalidResponse) => {
self.reconnect_attempts += 1;
self.reconnect()?;
self.process_size(command)
},
err => err,
}
}
fn process_status(&mut self, command: &Command<'_>) -> PaperClientResult<Status> {
match self
.send(command)
.and_then(|_| self.receive_status(command))
{
Ok(response) => {
self.reconnect_attempts = 0;
Ok(response)
},
Err(PaperClientError::InvalidResponse) => {
self.reconnect_attempts += 1;
self.reconnect()?;
self.process_status(command)
},
err => err,
}
}
fn send(&mut self, command: &Command<'_>) -> PaperClientResult<()> {
command
.write(&mut self.stream)
.map_err(|err| match err {
StreamError::InvalidStream => PaperClientError::Disconnected,
_ => PaperClientError::InvalidCommand,
})
}
fn receive(&mut self, command: &Command<'_>) -> PaperClientResult<()> {
command.parse_reader(&mut self.stream)
}
fn receive_value(&mut self, command: &Command<'_>) -> PaperClientResult<PaperValue> {
command.parse_buf_reader(&mut self.stream)
}
fn receive_has(&mut self, command: &Command<'_>) -> PaperClientResult<bool> {
command.parse_has_reader(&mut self.stream)
}
fn receive_size(&mut self, command: &Command<'_>) -> PaperClientResult<u32> {
command.parse_size_reader(&mut self.stream)
}
fn receive_status(&mut self, command: &Command<'_>) -> PaperClientResult<Status> {
command.parse_status_reader(&mut self.stream)
}
fn handshake(&mut self) -> PaperClientResult<()> {
let mut reader = StreamReader::new(&mut self.stream);
let is_ok = reader
.read_bool()
.map_err(|_| PaperClientError::UnreachableServer)?;
match is_ok {
true => Ok(()),
false => Err(PaperClientError::from_reader(reader)),
}
}
fn reconnect(&mut self) -> PaperClientResult<()> {
if self.reconnect_attempts > RECONNECT_MAX_ATTEMPTS {
return Err(PaperClientError::Disconnected);
}
self.stream = init_stream(&self.addr)?;
self.handshake()?;
if let Some(token) = self.auth_token.clone() {
self.auth(token)?;
}
Ok(())
}
}
fn init_stream(addr: &str) -> PaperClientResult<TcpStream> {
let stream = TcpStream::connect(addr).map_err(|_| PaperClientError::UnreachableServer)?;
if stream.set_nodelay(true).is_err() {
return Err(PaperClientError::Internal);
}
Ok(stream)
}