blob: c4efeb48923288db83967d8fb7d9404c0fcbd42d [file]
use crate::client::{
Client, ConsumerGroupClient, ConsumerOffsetClient, MessageClient, PartitionClient,
PersonalAccessTokenClient, StreamClient, SystemClient, TopicClient, UserClient,
};
use crate::consumer::Consumer;
use crate::error::IggyError;
use crate::identifier::Identifier;
use crate::locking::IggySharedMut;
use crate::locking::IggySharedMutFn;
use crate::messages::send_messages::{Message, Partitioning};
use crate::models::client_info::{ClientInfo, ClientInfoDetails};
use crate::models::consumer_group::{ConsumerGroup, ConsumerGroupDetails};
use crate::models::consumer_offset_info::ConsumerOffsetInfo;
use crate::models::identity_info::IdentityInfo;
use crate::models::messages::PolledMessages;
use crate::models::personal_access_token::{PersonalAccessTokenInfo, RawPersonalAccessToken};
use crate::models::stats::Stats;
use crate::models::stream::{Stream, StreamDetails};
use crate::models::topic::{Topic, TopicDetails};
use crate::models::user_info::{UserInfo, UserInfoDetails};
use crate::partitioner::Partitioner;
use crate::tcp::client::TcpClient;
use crate::utils::crypto::Encryptor;
use async_broadcast::Receiver;
use async_dropper::AsyncDrop;
use async_trait::async_trait;
use bytes::Bytes;
use std::fmt::Debug;
use std::sync::Arc;
use tracing::info;
use crate::clients::builder::IggyClientBuilder;
use crate::clients::consumer::IggyConsumerBuilder;
use crate::clients::producer::IggyProducerBuilder;
use crate::compression::compression_algorithm::CompressionAlgorithm;
use crate::diagnostic::DiagnosticEvent;
use crate::messages::poll_messages::PollingStrategy;
use crate::models::permissions::Permissions;
use crate::models::user_status::UserStatus;
use crate::utils::expiry::IggyExpiry;
use crate::utils::personal_access_token_expiry::PersonalAccessTokenExpiry;
use crate::utils::topic_size::MaxTopicSize;
/// The main client struct which implements all the `Client` traits and wraps the underlying low-level client for the specific transport.
///
/// It also provides the additional builders for the standalone consumer, consumer group, and producer.
#[derive(Debug)]
#[allow(dead_code)]
pub struct IggyClient {
client: IggySharedMut<Box<dyn Client>>,
partitioner: Option<Arc<dyn Partitioner>>,
encryptor: Option<Arc<dyn Encryptor>>,
}
impl Default for IggyClient {
fn default() -> Self {
IggyClient::new(Box::<TcpClient>::default())
}
}
impl IggyClient {
/// Creates a new `IggyClientBuilder`.
pub fn builder() -> IggyClientBuilder {
IggyClientBuilder::new()
}
/// Creates a new `IggyClientBuilder`.
pub fn builder_from_connection_string(
connection_string: &str,
) -> Result<IggyClientBuilder, IggyError> {
IggyClientBuilder::from_connection_string(connection_string)
}
/// Creates a new `IggyClient` with the provided client implementation for the specific transport.
pub fn new(client: Box<dyn Client>) -> Self {
let client = IggySharedMut::new(client);
IggyClient {
client,
partitioner: None,
encryptor: None,
}
}
pub fn from_connection_string(connection_string: &str) -> Result<Self, IggyError> {
let client = Box::new(TcpClient::from_connection_string(connection_string)?);
Ok(IggyClient::new(client))
}
/// Creates a new `IggyClient` with the provided client implementation for the specific transport and the optional implementations for the `partitioner` and `encryptor`.
pub fn create(
client: Box<dyn Client>,
partitioner: Option<Arc<dyn Partitioner>>,
encryptor: Option<Arc<dyn Encryptor>>,
) -> Self {
if partitioner.is_some() {
info!("Partitioner is enabled.");
}
if encryptor.is_some() {
info!("Client-side encryption is enabled.");
}
let client = IggySharedMut::new(client);
IggyClient {
client,
partitioner,
encryptor,
}
}
/// Returns the underlying client implementation for the specific transport.
pub fn client(&self) -> IggySharedMut<Box<dyn Client>> {
self.client.clone()
}
/// Returns the builder for the standalone consumer.
pub fn consumer(
&self,
name: &str,
stream: &str,
topic: &str,
partition: u32,
) -> Result<IggyConsumerBuilder, IggyError> {
Ok(IggyConsumerBuilder::new(
self.client.clone(),
name.to_owned(),
Consumer::new(name.try_into()?),
stream.try_into()?,
topic.try_into()?,
Some(partition),
self.encryptor.clone(),
None,
))
}
/// Returns the builder for the consumer group.
pub fn consumer_group(
&self,
name: &str,
stream: &str,
topic: &str,
) -> Result<IggyConsumerBuilder, IggyError> {
Ok(IggyConsumerBuilder::new(
self.client.clone(),
name.to_owned(),
Consumer::group(name.try_into()?),
stream.try_into()?,
topic.try_into()?,
None,
self.encryptor.clone(),
None,
))
}
/// Returns the builder for the producer.
pub fn producer(&self, stream: &str, topic: &str) -> Result<IggyProducerBuilder, IggyError> {
Ok(IggyProducerBuilder::new(
self.client.clone(),
stream.try_into()?,
stream.to_owned(),
topic.try_into()?,
topic.to_owned(),
self.encryptor.clone(),
None,
))
}
}
#[async_trait]
impl Client for IggyClient {
async fn connect(&self) -> Result<(), IggyError> {
self.client.read().await.connect().await
}
async fn disconnect(&self) -> Result<(), IggyError> {
self.client.read().await.disconnect().await
}
async fn subscribe_events(&self) -> Receiver<DiagnosticEvent> {
self.client.read().await.subscribe_events().await
}
}
#[async_trait]
impl UserClient for IggyClient {
async fn get_user(&self, user_id: &Identifier) -> Result<Option<UserInfoDetails>, IggyError> {
self.client.read().await.get_user(user_id).await
}
async fn get_users(&self) -> Result<Vec<UserInfo>, IggyError> {
self.client.read().await.get_users().await
}
async fn create_user(
&self,
username: &str,
password: &str,
status: UserStatus,
permissions: Option<Permissions>,
) -> Result<UserInfoDetails, IggyError> {
self.client
.read()
.await
.create_user(username, password, status, permissions)
.await
}
async fn delete_user(&self, user_id: &Identifier) -> Result<(), IggyError> {
self.client.read().await.delete_user(user_id).await
}
async fn update_user(
&self,
user_id: &Identifier,
username: Option<&str>,
status: Option<UserStatus>,
) -> Result<(), IggyError> {
self.client
.read()
.await
.update_user(user_id, username, status)
.await
}
async fn update_permissions(
&self,
user_id: &Identifier,
permissions: Option<Permissions>,
) -> Result<(), IggyError> {
self.client
.read()
.await
.update_permissions(user_id, permissions)
.await
}
async fn change_password(
&self,
user_id: &Identifier,
current_password: &str,
new_password: &str,
) -> Result<(), IggyError> {
self.client
.read()
.await
.change_password(user_id, current_password, new_password)
.await
}
async fn login_user(&self, username: &str, password: &str) -> Result<IdentityInfo, IggyError> {
self.client
.read()
.await
.login_user(username, password)
.await
}
async fn logout_user(&self) -> Result<(), IggyError> {
self.client.read().await.logout_user().await
}
}
#[async_trait]
impl PersonalAccessTokenClient for IggyClient {
async fn get_personal_access_tokens(&self) -> Result<Vec<PersonalAccessTokenInfo>, IggyError> {
self.client.read().await.get_personal_access_tokens().await
}
async fn create_personal_access_token(
&self,
name: &str,
expiry: PersonalAccessTokenExpiry,
) -> Result<RawPersonalAccessToken, IggyError> {
self.client
.read()
.await
.create_personal_access_token(name, expiry)
.await
}
async fn delete_personal_access_token(&self, name: &str) -> Result<(), IggyError> {
self.client
.read()
.await
.delete_personal_access_token(name)
.await
}
async fn login_with_personal_access_token(
&self,
token: &str,
) -> Result<IdentityInfo, IggyError> {
self.client
.read()
.await
.login_with_personal_access_token(token)
.await
}
}
#[async_trait]
impl SystemClient for IggyClient {
async fn get_stats(&self) -> Result<Stats, IggyError> {
self.client.read().await.get_stats().await
}
async fn get_me(&self) -> Result<ClientInfoDetails, IggyError> {
self.client.read().await.get_me().await
}
async fn get_client(&self, client_id: u32) -> Result<Option<ClientInfoDetails>, IggyError> {
self.client.read().await.get_client(client_id).await
}
async fn get_clients(&self) -> Result<Vec<ClientInfo>, IggyError> {
self.client.read().await.get_clients().await
}
async fn ping(&self) -> Result<(), IggyError> {
self.client.read().await.ping().await
}
}
#[async_trait]
impl StreamClient for IggyClient {
async fn get_stream(&self, stream_id: &Identifier) -> Result<Option<StreamDetails>, IggyError> {
self.client.read().await.get_stream(stream_id).await
}
async fn get_streams(&self) -> Result<Vec<Stream>, IggyError> {
self.client.read().await.get_streams().await
}
async fn create_stream(
&self,
name: &str,
stream_id: Option<u32>,
) -> Result<StreamDetails, IggyError> {
self.client
.read()
.await
.create_stream(name, stream_id)
.await
}
async fn update_stream(&self, stream_id: &Identifier, name: &str) -> Result<(), IggyError> {
self.client
.read()
.await
.update_stream(stream_id, name)
.await
}
async fn delete_stream(&self, stream_id: &Identifier) -> Result<(), IggyError> {
self.client.read().await.delete_stream(stream_id).await
}
async fn purge_stream(&self, stream_id: &Identifier) -> Result<(), IggyError> {
self.client.read().await.purge_stream(stream_id).await
}
}
#[async_trait]
impl TopicClient for IggyClient {
async fn get_topic(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
) -> Result<Option<TopicDetails>, IggyError> {
self.client
.read()
.await
.get_topic(stream_id, topic_id)
.await
}
async fn get_topics(&self, stream_id: &Identifier) -> Result<Vec<Topic>, IggyError> {
self.client.read().await.get_topics(stream_id).await
}
async fn create_topic(
&self,
stream_id: &Identifier,
name: &str,
partitions_count: u32,
compression_algorithm: CompressionAlgorithm,
replication_factor: Option<u8>,
topic_id: Option<u32>,
message_expiry: IggyExpiry,
max_topic_size: MaxTopicSize,
) -> Result<TopicDetails, IggyError> {
self.client
.read()
.await
.create_topic(
stream_id,
name,
partitions_count,
compression_algorithm,
replication_factor,
topic_id,
message_expiry,
max_topic_size,
)
.await
}
async fn update_topic(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
name: &str,
compression_algorithm: CompressionAlgorithm,
replication_factor: Option<u8>,
message_expiry: IggyExpiry,
max_topic_size: MaxTopicSize,
) -> Result<(), IggyError> {
self.client
.read()
.await
.update_topic(
stream_id,
topic_id,
name,
compression_algorithm,
replication_factor,
message_expiry,
max_topic_size,
)
.await
}
async fn delete_topic(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
) -> Result<(), IggyError> {
self.client
.read()
.await
.delete_topic(stream_id, topic_id)
.await
}
async fn purge_topic(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
) -> Result<(), IggyError> {
self.client
.read()
.await
.purge_topic(stream_id, topic_id)
.await
}
}
#[async_trait]
impl PartitionClient for IggyClient {
async fn create_partitions(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
partitions_count: u32,
) -> Result<(), IggyError> {
self.client
.read()
.await
.create_partitions(stream_id, topic_id, partitions_count)
.await
}
async fn delete_partitions(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
partitions_count: u32,
) -> Result<(), IggyError> {
self.client
.read()
.await
.delete_partitions(stream_id, topic_id, partitions_count)
.await
}
}
#[async_trait]
impl MessageClient for IggyClient {
async fn poll_messages(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
partition_id: Option<u32>,
consumer: &Consumer,
strategy: &PollingStrategy,
count: u32,
auto_commit: bool,
) -> Result<PolledMessages, IggyError> {
if count == 0 {
return Err(IggyError::InvalidMessagesCount);
}
let mut polled_messages = self
.client
.read()
.await
.poll_messages(
stream_id,
topic_id,
partition_id,
consumer,
strategy,
count,
auto_commit,
)
.await?;
if let Some(ref encryptor) = self.encryptor {
for message in &mut polled_messages.messages {
let payload = encryptor.decrypt(&message.payload)?;
message.payload = Bytes::from(payload);
message.length = message.payload.len() as u32;
}
}
Ok(polled_messages)
}
async fn send_messages(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
partitioning: &Partitioning,
messages: &mut [Message],
) -> Result<(), IggyError> {
if messages.is_empty() {
return Err(IggyError::InvalidMessagesCount);
}
if let Some(encryptor) = &self.encryptor {
for message in &mut *messages {
message.payload = Bytes::from(encryptor.encrypt(&message.payload)?);
message.length = message.payload.len() as u32;
}
}
self.client
.read()
.await
.send_messages(stream_id, topic_id, partitioning, messages)
.await
}
}
#[async_trait]
impl ConsumerOffsetClient for IggyClient {
async fn store_consumer_offset(
&self,
consumer: &Consumer,
stream_id: &Identifier,
topic_id: &Identifier,
partition_id: Option<u32>,
offset: u64,
) -> Result<(), IggyError> {
self.client
.read()
.await
.store_consumer_offset(consumer, stream_id, topic_id, partition_id, offset)
.await
}
async fn get_consumer_offset(
&self,
consumer: &Consumer,
stream_id: &Identifier,
topic_id: &Identifier,
partition_id: Option<u32>,
) -> Result<Option<ConsumerOffsetInfo>, IggyError> {
self.client
.read()
.await
.get_consumer_offset(consumer, stream_id, topic_id, partition_id)
.await
}
}
#[async_trait]
impl ConsumerGroupClient for IggyClient {
async fn get_consumer_group(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
group_id: &Identifier,
) -> Result<Option<ConsumerGroupDetails>, IggyError> {
self.client
.read()
.await
.get_consumer_group(stream_id, topic_id, group_id)
.await
}
async fn get_consumer_groups(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
) -> Result<Vec<ConsumerGroup>, IggyError> {
self.client
.read()
.await
.get_consumer_groups(stream_id, topic_id)
.await
}
async fn create_consumer_group(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
name: &str,
group_id: Option<u32>,
) -> Result<ConsumerGroupDetails, IggyError> {
self.client
.read()
.await
.create_consumer_group(stream_id, topic_id, name, group_id)
.await
}
async fn delete_consumer_group(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
group_id: &Identifier,
) -> Result<(), IggyError> {
self.client
.read()
.await
.delete_consumer_group(stream_id, topic_id, group_id)
.await
}
async fn join_consumer_group(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
group_id: &Identifier,
) -> Result<(), IggyError> {
self.client
.read()
.await
.join_consumer_group(stream_id, topic_id, group_id)
.await
}
async fn leave_consumer_group(
&self,
stream_id: &Identifier,
topic_id: &Identifier,
group_id: &Identifier,
) -> Result<(), IggyError> {
self.client
.read()
.await
.leave_consumer_group(stream_id, topic_id, group_id)
.await
}
}
#[async_trait]
impl AsyncDrop for IggyClient {
async fn async_drop(&mut self) {
let _ = self.client.read().await.logout_user().await;
}
}