use crate::client::Response;
use crate::connection::Authenticated;
use crate::query::{WithInfo, WithoutInfo};
use crate::resource::AsResource;
use crate::{Connection, DatabaseStream, Query, Result};
use std::borrow::{Borrow, BorrowMut};
use std::marker::PhantomData;
use std::net::TcpStream;
enum Command {
Query = 0,
Create = 8,
Add = 9,
Replace = 12,
Store = 13,
}
pub struct CommandWithOptionalInput<'a, T>
where
T: DatabaseStream,
{
connection: &'a mut Connection<T, Authenticated>,
}
impl<'a, T> CommandWithOptionalInput<'a, T>
where
T: DatabaseStream,
{
fn new(connection: &'a mut Connection<T, Authenticated>) -> Self {
Self { connection }
}
pub fn with_input<'b, R: AsResource<'b>>(self, input: R) -> Result<String> {
self.connection.send_arg(&mut input.into_read())?;
self.connection.get_response()
}
pub fn without_input(self) -> Result<String> {
self.connection.skip_arg()?;
self.connection.get_response()
}
}
#[derive(Debug)]
pub struct Client<T>
where
T: DatabaseStream,
{
connection: Connection<T, Authenticated>,
}
impl Client<TcpStream> {
pub fn connect(host: &str, port: u16, user: &str, password: &str) -> Result<Client<TcpStream>> {
let stream = TcpStream::connect(&format!("{}:{}", host, port))?;
let connection = Connection::new(stream).authenticate(user, password)?;
Ok(Client::new(connection))
}
}
impl<T> Client<T>
where
T: DatabaseStream,
{
pub fn new(connection: Connection<T, Authenticated>) -> Self {
Self { connection }
}
pub fn execute(mut self, command: &str) -> Result<Response<T>> {
self.connection.send_arg(&mut command.as_bytes())?;
Ok(Response::new(self))
}
pub fn create(&mut self, name: &str) -> Result<CommandWithOptionalInput<T>> {
self.connection.send_cmd(Command::Create as u8)?;
self.connection.send_arg(&mut name.as_bytes())?;
Ok(CommandWithOptionalInput::new(&mut self.connection))
}
pub fn replace<'a>(&mut self, path: &str, input: impl AsResource<'a>) -> Result<String> {
self.connection.send_cmd(Command::Replace as u8)?;
self.connection.send_arg(&mut path.as_bytes())?;
self.connection.send_arg(&mut input.into_read())?;
self.connection.get_response()
}
pub fn store<'a>(&mut self, path: &str, input: impl AsResource<'a>) -> Result<String> {
self.connection.send_cmd(Command::Store as u8)?;
self.connection.send_arg(&mut path.as_bytes())?;
self.connection.send_arg(&mut input.into_read())?;
self.connection.get_response()
}
pub fn add<'a>(&mut self, path: &str, input: impl AsResource<'a>) -> Result<String> {
self.connection.send_cmd(Command::Add as u8)?;
self.connection.send_arg(&mut path.as_bytes())?;
self.connection.send_arg(&mut input.into_read())?;
self.connection.get_response()
}
pub fn query<'a, R: AsResource<'a>>(self, query: R) -> Result<QueryWithOptionalInfo<'a, T, R>> {
Ok(QueryWithOptionalInfo::new(self, query))
}
}
impl<T: DatabaseStream> Clone for Client<T> {
fn clone(&self) -> Self {
Self {
connection: self.connection.try_clone().unwrap(),
}
}
}
impl<T: DatabaseStream> Borrow<Connection<T, Authenticated>> for Client<T> {
fn borrow(&self) -> &Connection<T, Authenticated> {
&self.connection
}
}
impl<T: DatabaseStream> BorrowMut<Connection<T, Authenticated>> for Client<T> {
fn borrow_mut(&mut self) -> &mut Connection<T, Authenticated> {
&mut self.connection
}
}
pub struct QueryWithOptionalInfo<'a, T, R>
where
T: DatabaseStream,
R: AsResource<'a>,
{
phantom: PhantomData<&'a ()>,
client: Client<T>,
query: R,
}
impl<'a, T, R> QueryWithOptionalInfo<'a, T, R>
where
T: DatabaseStream,
R: AsResource<'a>,
{
fn new(client: Client<T>, query: R) -> Self {
Self {
phantom: Default::default(),
client,
query,
}
}
pub fn with_info(self) -> Result<Query<T, WithInfo>> {
let (mut client, _) = self.client.execute("SET QUERYINFO true")?.close()?;
let id = Self::query(&mut client, self.query)?;
Ok(Query::with_info(id, client))
}
pub fn without_info(self) -> Result<Query<T, WithoutInfo>> {
let (mut client, _) = self.client.execute("SET QUERYINFO false")?.close()?;
let id = Self::query(&mut client, self.query)?;
Ok(Query::without_info(id, client))
}
fn query(client: &mut Client<T>, query: R) -> Result<String> {
client.connection.send_cmd(Command::Query as u8)?;
client.connection.send_arg(&mut query.into_read())?;
client.connection.get_response()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::MockStream;
use crate::ClientError;
impl<T> Client<T>
where
T: DatabaseStream,
{
pub(crate) fn into_inner(self) -> Connection<T, Authenticated> {
self.connection
}
}
#[test]
fn test_formats_as_debug() {
format!("{:?}", Client::new(Connection::failing()));
}
#[test]
fn test_clones() {
let _ = Client::new(Connection::from_str("")).clone();
}
#[test]
fn test_borrows_as_connection() {
let _: &Connection<MockStream, Authenticated> = Client::new(Connection::from_str("test")).borrow();
}
#[test]
fn test_database_is_created_with_input() {
let mut client = Client::new(Connection::from_str("test\0"));
let info = client
.create("boy_sminem")
.unwrap()
.with_input("<wojak><pink_index>69</pink_index></wojak>")
.unwrap();
assert_eq!(
client.into_inner().into_inner().to_string(),
"\u{8}boy_sminem\u{0}<wojak><pink_index>69</pink_index></wojak>\u{0}".to_owned()
);
assert_eq!("test", info);
}
#[test]
fn test_database_is_created_without_input() {
let mut client = Client::new(Connection::from_str("test\0"));
let info = client.create("boy_sminem").unwrap().without_input().unwrap();
assert_eq!(
client.into_inner().into_inner().to_string(),
"\u{8}boy_sminem\u{0}\u{0}".to_owned()
);
assert_eq!("test", info);
}
#[test]
fn test_database_fails_to_create_with_failing_stream() {
let mut client = Client::new(Connection::failing());
let actual_error = client.create("boy_sminem").err().expect("Operation must fail");
assert!(matches!(actual_error, ClientError::Io(_)));
}
#[test]
fn test_resource_is_replaced() {
let mut client = Client::new(Connection::from_str("test\0"));
let info = client
.replace("boy_sminem", "<wojak><pink_index>69</pink_index></wojak>")
.unwrap();
assert_eq!(
client.into_inner().into_inner().to_string(),
"\u{c}boy_sminem\u{0}<wojak><pink_index>69</pink_index></wojak>\u{0}".to_owned()
);
assert_eq!("test", info);
}
#[test]
fn test_resource_fails_to_replace_with_failing_stream() {
let mut client = Client::new(Connection::failing());
let actual_error = client
.replace("boy_sminem", "<wojak><pink_index>69</pink_index></wojak>")
.expect_err("Operation must fail");
assert!(matches!(actual_error, ClientError::Io(_)));
}
#[test]
fn test_resource_is_stored() {
let mut client = Client::new(Connection::from_str("test\0"));
let info = client
.store("boy_sminem", "<wojak><pink_index>69</pink_index></wojak>")
.unwrap();
assert_eq!(
client.into_inner().into_inner().to_string(),
"\u{d}boy_sminem\u{0}<wojak><pink_index>69</pink_index></wojak>\u{0}".to_owned()
);
assert_eq!("test", info);
}
#[test]
fn test_resource_fails_to_store_with_failing_stream() {
let mut client = Client::new(Connection::failing());
let actual_error = client
.store("boy_sminem", "<wojak><pink_index>69</pink_index></wojak>")
.expect_err("Operation must fail");
assert!(matches!(actual_error, ClientError::Io(_)));
}
#[test]
fn test_resource_is_added() {
let mut client = Client::new(Connection::from_str("test\0"));
let info = client
.add("boy_sminem", "<wojak><pink_index>69</pink_index></wojak>")
.unwrap();
assert_eq!(
client.into_inner().into_inner().to_string(),
"\u{9}boy_sminem\u{0}<wojak><pink_index>69</pink_index></wojak>\u{0}".to_owned()
);
assert_eq!("test", info);
}
#[test]
fn test_resource_fails_to_add_with_failing_stream() {
let mut client = Client::new(Connection::failing());
let actual_error = client
.add("boy_sminem", "<wojak><pink_index>69</pink_index></wojak>")
.expect_err("Operation must fail");
assert!(matches!(actual_error, ClientError::Io(_)));
}
}