surrealcs 0.4.4

The SurrealCS client code for SurrealDB
Documentation
//! The Any transaction allows you to do anything at any state. This is more dangerous as we do not get the
//! checking at compile times but it enables the ProxyKv to be inserted into systems that may not be flexibile
//! enough to support the typestate pattern.
use super::bridge::SendToTransactionClientActor;
use super::interface::{Any, Transaction};
use nanoservices_utils::errors::NanoServiceError;
use surrealcs_kernel::messages::server::interface::{ServerMessage, ServerTransactionMessage};

impl Transaction<Any> {
	/// starts the transaction by sending the initial transaction operation to the server.
	///
	/// # Arguments
	/// * `operation`: the initial transaction operation to send to the server
	///
	/// # Returns
	/// A tuple containing the response from the server and the in progress transaction
	#[allow(dead_code)]
	pub async fn begin<T: SendToTransactionClientActor>(
		&mut self,
		operation: ServerTransactionMessage,
	) -> Result<ServerTransactionMessage, NanoServiceError> {
		let message = ServerMessage::BeginTransaction(operation);
		let transaction = T::send_to_transaction_client_actor(self, message).await?;
		Ok(transaction)
	}

	/// Sends a transaction operation to the server.
	///
	/// # Arguments
	/// * `message`: the transaction operation to send to the server
	///
	/// # Returns
	/// The response message from the server
	#[allow(dead_code)]
	pub async fn send<T: SendToTransactionClientActor>(
		&mut self,
		message: ServerTransactionMessage,
	) -> Result<ServerTransactionMessage, NanoServiceError> {
		let message = ServerMessage::SendOperation(message);
		T::send_to_transaction_client_actor(self, message).await
	}

	/// Commits the transaction.
	///
	/// # Returns
	/// A tuple containing the response message from the server and the committed transaction
	#[allow(dead_code)]
	pub async fn commit<T: SendToTransactionClientActor>(
		&mut self,
	) -> Result<ServerTransactionMessage, NanoServiceError> {
		let message = ServerMessage::CommitTransaction;
		let outcome = T::send_to_transaction_client_actor(self, message).await?;
		Ok(outcome)
	}

	/// Rolls back the transaction.
	///
	/// # Returns
	/// The rolled back transaction
	#[allow(dead_code)]
	pub async fn rollback<T: SendToTransactionClientActor>(
		&mut self,
	) -> Result<(), NanoServiceError> {
		let message = ServerMessage::RollbackTransaction;
		// TODO => consider returning the rolled back transaction message
		let rollback_result = T::send_to_transaction_client_actor(self, message).await;

		// This is allowed for the Any transaction as we might be calling the rollback on a transaction that has not been started.
		match rollback_result {
			Ok(_) => {}
			Err(e) => {
				if e.message == *"error sending transaction to key value store: no ID provided" {
				} else {
					return Err(e);
				}
			}
		}
		Ok(())
	}
}

#[cfg(test)]
mod tests {

	use super::*;
	use crate::generate_mock_handle;
	use std::future::Future;
	use std::marker::PhantomData;
	use surrealcs_kernel::messages::server::interface::{ServerMessage, ServerTransactionMessage};
	use surrealcs_kernel::messages::server::kv_operations::{MessageGet, MessagePut, ResponseGet};
	use tokio::sync::mpsc;

	#[tokio::test]
	async fn test_begin() {
		generate_mock_handle! {
			struct MockHandle;
			match ServerMessage::BeginTransaction(message) => {
				match message {
					ServerTransactionMessage::Get(message) => {
						assert_eq!(message.key, b"keys".to_vec());
					}
					_ => {
						panic!("message not as expected: {:?}", message);
					}
				}
			}
			return Ok(ServerTransactionMessage::ResponseGet(
				ResponseGet { value: Some(b"value".to_vec()) }
			));
		}

		// define the channel for the actor
		let (tx, _rx) = mpsc::channel(32);

		// define the channel for the handle
		let (_h_tx, h_rx) = mpsc::channel(32);

		let mut transaction = Transaction::<Any> {
			client_id: 1,
			server_id: None,
			receiver: h_rx,
			sender: tx,
			connection_id: "connection_id".to_string(),
			transaction_id: "transaction_id".to_string(),
			state: PhantomData,
		};

		let message = ServerTransactionMessage::Get(MessageGet {
			key: b"keys".to_vec(),
			version: None,
		});

		let outcome = transaction.begin::<MockHandle>(message).await.unwrap();

		let response = match outcome {
			ServerTransactionMessage::ResponseGet(response) => response,
			_ => panic!("wrong message type"),
		};

		assert_eq!(response.value, Some(b"value".to_vec()));
	}

	#[tokio::test]
	async fn test_send() {
		generate_mock_handle! {
			struct MockHandle;
			match ServerMessage::SendOperation(message) => {
				match message {
					ServerTransactionMessage::Put(message) => {
						assert_eq!(message.key, b"keys".to_vec());
						assert_eq!(message.value, b"value".to_vec());
					}
					_ => {
						panic!("message not as expected: {:?}", message);
					}
				}
			}
			return Ok(ServerTransactionMessage::EmptyResponse);
		}

		// define the channel for the actor
		let (tx, _rx) = mpsc::channel(32);

		// define the channel for the handle
		let (_h_tx, h_rx) = mpsc::channel(32);

		let mut transaction = Transaction::<Any> {
			client_id: 1,
			server_id: None,
			receiver: h_rx,
			sender: tx,
			connection_id: "connection_id".to_string(),
			transaction_id: "transaction_id".to_string(),
			state: PhantomData,
		};

		let message = ServerTransactionMessage::Put(MessagePut {
			key: b"keys".to_vec(),
			value: b"value".to_vec(),
			version: None,
		});

		let outcome = transaction.send::<MockHandle>(message).await.unwrap();
		match outcome {
			ServerTransactionMessage::EmptyResponse => (),
			_ => panic!("wrong message type"),
		};
	}

	#[tokio::test]
	async fn test_commit() {
		generate_mock_handle! {
			struct MockHandle;
			match ServerMessage::CommitTransaction => {
			}
			return Ok(ServerTransactionMessage::EmptyResponse);
		}

		// define the channel for the actor
		let (tx, _rx) = mpsc::channel(32);

		// define the channel for the handle
		let (_h_tx, h_rx) = mpsc::channel(32);

		let mut transaction = Transaction::<Any> {
			client_id: 1,
			server_id: None,
			receiver: h_rx,
			sender: tx,
			connection_id: "connection_id".to_string(),
			transaction_id: "transaction_id".to_string(),
			state: PhantomData,
		};

		transaction.commit::<MockHandle>().await.unwrap();
	}

	#[tokio::test]
	async fn test_rollback() {
		generate_mock_handle! {
			struct MockHandle;
			match ServerMessage::RollbackTransaction => {
			}
			return Ok(ServerTransactionMessage::ResponseRolledBack(
				true
			));
		}

		// define the channel for the actor
		let (tx, _rx) = mpsc::channel(32);

		// define the channel for the handle
		let (_h_tx, h_rx) = mpsc::channel(32);

		let mut transaction = Transaction::<Any> {
			client_id: 1,
			server_id: None,
			receiver: h_rx,
			sender: tx,
			connection_id: "connection_id".to_string(),
			transaction_id: "transaction_id".to_string(),
			state: PhantomData,
		};

		transaction.rollback::<MockHandle>().await.unwrap();
	}
}