blob: bf01dce826a27ea14f00e549cbdc7065e7e5d5b9 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "iggy.hpp"
namespace iggy {
namespace {
rust::Vec<ffi::HeaderEntry> ToFfiRawOptions(const std::map<std::string, std::string> &raw_options) {
rust::Vec<ffi::HeaderEntry> ffi_options;
ffi_options.reserve(raw_options.size());
for (const auto &[key, value] : raw_options) {
ffi::HeaderEntry ffi_entry;
ffi_entry.key.kind = static_cast<std::uint8_t>(HeaderKind::String);
ffi_entry.key.value.reserve(key.size());
for (const char character : key) {
ffi_entry.key.value.push_back(static_cast<std::uint8_t>(character));
}
ffi_entry.value.kind = static_cast<std::uint8_t>(HeaderKind::String);
ffi_entry.value.value.reserve(value.size());
for (const char character : value) {
ffi_entry.value.value.push_back(static_cast<std::uint8_t>(character));
}
ffi_options.push_back(std::move(ffi_entry));
}
return ffi_options;
}
template <typename FfiOptions, typename Options>
void SetMutableTopicOptions(FfiOptions &ffi_options, const Options &options) {
if (const auto &value = options.CompressionAlgorithm()) {
ffi_options.has_compression_algorithm = true;
ffi_options.compression_algorithm = std::string(value->Value());
}
if (const auto &value = options.MessageExpiry()) {
ffi_options.has_message_expiry = true;
ffi_options.message_expiry_kind = std::string(value->Kind());
ffi_options.message_expiry_value = value->Value();
}
if (const auto &value = options.MaxTopicSize()) {
ffi_options.has_max_topic_size = true;
ffi_options.max_topic_size = std::string(value->Value());
}
ffi_options.raw_options = ToFfiRawOptions(options.RawEntries());
}
} // namespace
IggyBlockingClient::IggyBlockingClient(IggyBlockingClient &&other) noexcept
: client_(std::exchange(other.client_, nullptr)) {}
IggyBlockingClient &IggyBlockingClient::operator=(IggyBlockingClient &&other) noexcept {
if (this != &other) {
Reset();
client_ = std::exchange(other.client_, nullptr);
}
return *this;
}
IggyBlockingClient::~IggyBlockingClient() {
Reset();
}
IggyBlockingClient IggyBlockingClient::FromConnectionString(std::string connection_string) {
return RethrowAsIggyException(
[&connection_string] { return IggyBlockingClient(ffi::from_connection_string(connection_string)); });
}
void IggyBlockingClient::Connect() {
RethrowAsIggyException([this] { Handle()->connect(); });
}
void IggyBlockingClient::Disconnect() {
RethrowAsIggyException([this] { Handle()->disconnect(); });
}
void IggyBlockingClient::Shutdown() {
RethrowAsIggyException([this] { Handle()->shutdown(); });
}
LoginInfo IggyBlockingClient::Login(std::string username, std::string password) {
return RethrowAsIggyException(
[this, &username, &password] { return LoginInfo::FromFfi(Handle()->login_user(username, password)); });
}
void IggyBlockingClient::Logout() {
RethrowAsIggyException([this] { Handle()->logout_user(); });
}
UserInfoDetails IggyBlockingClient::GetUser(const Identifier &user) {
return RethrowAsIggyException([this, &user] { return UserInfoDetails::FromFfi(Handle()->get_user(user.ToFfi())); });
}
std::vector<UserInfo> IggyBlockingClient::GetUsers() {
return RethrowAsIggyException([this] {
std::vector<UserInfo> users;
auto ffi_users = Handle()->get_users();
users.reserve(ffi_users.size());
for (auto &user : ffi_users) {
users.push_back(UserInfo::FromFfi(std::move(user)));
}
return users;
});
}
UserInfoDetails IggyBlockingClient::CreateUser(std::string username,
std::string password,
UserStatus status,
const std::optional<Permissions> &permissions) {
return RethrowAsIggyException([this, &username, &password, status, &permissions] {
auto ffi_permissions = permissions ? permissions->ToFfi() : ffi::Permissions{};
return UserInfoDetails::FromFfi(Handle()->create_user(username, password, static_cast<ffi::UserStatus>(status),
permissions.has_value(), std::move(ffi_permissions)));
});
}
void IggyBlockingClient::DeleteUser(const Identifier &user) {
RethrowAsIggyException([this, &user] { Handle()->delete_user(user.ToFfi()); });
}
void IggyBlockingClient::UpdateUser(const Identifier &user,
const std::optional<std::string> username,
const std::optional<UserStatus> status,
const UserUpdateOptions &options) {
RethrowAsIggyException([this, &user, &username, status, &options] {
auto ffi_options = ToFfiRawOptions(options.RawEntries());
Handle()->update_user(user.ToFfi(), username.has_value(), username.value_or(""), status.has_value(),
static_cast<ffi::UserStatus>(status.value_or(UserStatus::Active)),
std::move(ffi_options));
});
}
StreamDetails IggyBlockingClient::CreateStream(std::string name) {
return RethrowAsIggyException([this, &name] { return StreamDetails::FromFfi(Handle()->create_stream(name)); });
}
void IggyBlockingClient::UpdateStream(const Identifier &stream, std::string name, const StreamUpdateOptions &options) {
RethrowAsIggyException([this, &stream, &name, &options] {
auto ffi_options = ToFfiRawOptions(options.RawEntries());
Handle()->update_stream(stream.ToFfi(), name, std::move(ffi_options));
});
}
std::vector<Stream> IggyBlockingClient::GetStreams() {
return RethrowAsIggyException([this] {
std::vector<Stream> streams;
auto ffi_streams = Handle()->get_streams();
streams.reserve(ffi_streams.size());
for (auto &stream : ffi_streams) {
streams.push_back(Stream::FromFfi(std::move(stream)));
}
return streams;
});
}
StreamDetails IggyBlockingClient::GetStream(const Identifier &stream) {
return RethrowAsIggyException(
[this, &stream] { return StreamDetails::FromFfi(Handle()->get_stream(stream.ToFfi())); });
}
void IggyBlockingClient::DeleteStream(const Identifier &stream) {
RethrowAsIggyException([this, &stream] { Handle()->delete_stream(stream.ToFfi()); });
}
void IggyBlockingClient::PurgeStream(const Identifier &stream) {
RethrowAsIggyException([this, &stream] { Handle()->purge_stream(stream.ToFfi()); });
}
TopicDetails IggyBlockingClient::CreateTopic(const Identifier &stream,
std::string name,
const TopicCreateOptions &options) {
return RethrowAsIggyException([this, &stream, &name, &options] {
ffi::TopicCreateOptions ffi_options{};
SetMutableTopicOptions(ffi_options, options);
if (auto value = options.PartitionsCount()) {
ffi_options.has_partitions_count = true;
ffi_options.partitions_count = *value;
} else {
ffi_options.has_partitions_count = false;
ffi_options.partitions_count = 0;
}
if (auto value = options.SegmentSize()) {
ffi_options.has_segment_size = true;
ffi_options.segment_size = *value;
} else {
ffi_options.has_segment_size = false;
ffi_options.segment_size = 0;
}
if (auto value = options.Durability()) {
ffi_options.has_durability = true;
ffi_options.durability = std::string(iggy::to_string(*value));
} else {
ffi_options.has_durability = false;
ffi_options.durability = "";
}
if (auto value = options.ConsumerOffsetDurability()) {
ffi_options.has_consumer_offset_durability = true;
ffi_options.consumer_offset_durability = std::string(iggy::to_string(*value));
} else {
ffi_options.has_consumer_offset_durability = false;
ffi_options.consumer_offset_durability = "";
}
if (auto value = options.MessagesRequiredToSave()) {
ffi_options.has_messages_required_to_save = true;
ffi_options.messages_required_to_save = *value;
} else {
ffi_options.has_messages_required_to_save = false;
ffi_options.messages_required_to_save = 0;
}
if (auto value = options.SizeOfMessagesRequiredToSave()) {
ffi_options.has_size_of_messages_required_to_save = true;
ffi_options.size_of_messages_required_to_save = *value;
} else {
ffi_options.has_size_of_messages_required_to_save = false;
ffi_options.size_of_messages_required_to_save = 0;
}
if (auto value = options.PreallocateSegments()) {
ffi_options.has_preallocate_segments = true;
ffi_options.preallocate_segments = *value;
} else {
ffi_options.has_preallocate_segments = false;
ffi_options.preallocate_segments = false;
}
return TopicDetails::FromFfi(Handle()->create_topic(stream.ToFfi(), name, std::move(ffi_options)));
});
}
void IggyBlockingClient::UpdateTopic(const Identifier &stream,
const Identifier &topic,
std::string name,
const TopicUpdateOptions &options) {
RethrowAsIggyException([this, &stream, &topic, &name, &options] {
ffi::TopicUpdateOptions ffi_options{};
SetMutableTopicOptions(ffi_options, options);
Handle()->update_topic(stream.ToFfi(), topic.ToFfi(), name, std::move(ffi_options));
});
}
std::vector<Topic> IggyBlockingClient::GetTopics(const Identifier &stream) {
return RethrowAsIggyException([this, &stream] {
std::vector<Topic> topics;
auto ffi_topics = Handle()->get_topics(stream.ToFfi());
topics.reserve(ffi_topics.size());
for (auto &topic : ffi_topics) {
topics.push_back(Topic::FromFfi(std::move(topic)));
}
return topics;
});
}
TopicDetails IggyBlockingClient::GetTopic(const Identifier &stream, const Identifier &topic) {
return RethrowAsIggyException(
[this, &stream, &topic] { return TopicDetails::FromFfi(Handle()->get_topic(stream.ToFfi(), topic.ToFfi())); });
}
void IggyBlockingClient::DeleteTopic(const Identifier &stream, const Identifier &topic) {
RethrowAsIggyException([this, &stream, &topic] { Handle()->delete_topic(stream.ToFfi(), topic.ToFfi()); });
}
void IggyBlockingClient::PurgeTopic(const Identifier &stream, const Identifier &topic) {
RethrowAsIggyException([this, &stream, &topic] { Handle()->purge_topic(stream.ToFfi(), topic.ToFfi()); });
}
void IggyBlockingClient::CreatePartitions(const Identifier &stream,
const Identifier &topic,
const std::uint32_t partitions_count) {
RethrowAsIggyException([this, &stream, &topic, partitions_count] {
Handle()->create_partitions(stream.ToFfi(), topic.ToFfi(), partitions_count);
});
}
void IggyBlockingClient::DeletePartitions(const Identifier &stream,
const Identifier &topic,
const std::uint32_t partitions_count) {
RethrowAsIggyException([this, &stream, &topic, partitions_count] {
Handle()->delete_partitions(stream.ToFfi(), topic.ToFfi(), partitions_count);
});
}
void IggyBlockingClient::DeleteSegments(const Identifier &stream,
const Identifier &topic,
const std::uint32_t partition_id,
const std::uint32_t segments_count) {
RethrowAsIggyException([this, &stream, &topic, partition_id, segments_count] {
Handle()->delete_segments(stream.ToFfi(), topic.ToFfi(), partition_id, segments_count);
});
}
ConsumerGroupDetails IggyBlockingClient::CreateConsumerGroup(const Identifier &stream,
const Identifier &topic,
std::string name) {
return RethrowAsIggyException([this, &stream, &topic, &name] {
return ConsumerGroupDetails::FromFfi(Handle()->create_consumer_group(stream.ToFfi(), topic.ToFfi(), name));
});
}
ConsumerGroupDetails IggyBlockingClient::GetConsumerGroup(const Identifier &stream,
const Identifier &topic,
const Identifier &group) {
return RethrowAsIggyException([this, &stream, &topic, &group] {
return ConsumerGroupDetails::FromFfi(
Handle()->get_consumer_group(stream.ToFfi(), topic.ToFfi(), group.ToFfi()));
});
}
std::vector<ConsumerGroup> IggyBlockingClient::GetConsumerGroups(const Identifier &stream, const Identifier &topic) {
return RethrowAsIggyException([this, &stream, &topic] {
std::vector<ConsumerGroup> groups;
auto ffi_groups = Handle()->get_consumer_groups(stream.ToFfi(), topic.ToFfi());
groups.reserve(ffi_groups.size());
for (auto &group : ffi_groups) {
groups.push_back(ConsumerGroup::FromFfi(std::move(group)));
}
return groups;
});
}
void IggyBlockingClient::DeleteConsumerGroup(const Identifier &stream,
const Identifier &topic,
const Identifier &group) {
RethrowAsIggyException([this, &stream, &topic, &group] {
Handle()->delete_consumer_group(stream.ToFfi(), topic.ToFfi(), group.ToFfi());
});
}
void IggyBlockingClient::JoinConsumerGroup(const Identifier &stream, const Identifier &topic, const Identifier &group) {
RethrowAsIggyException([this, &stream, &topic, &group] {
Handle()->join_consumer_group(stream.ToFfi(), topic.ToFfi(), group.ToFfi());
});
}
void IggyBlockingClient::LeaveConsumerGroup(const Identifier &stream,
const Identifier &topic,
const Identifier &group) {
RethrowAsIggyException([this, &stream, &topic, &group] {
Handle()->leave_consumer_group(stream.ToFfi(), topic.ToFfi(), group.ToFfi());
});
}
void IggyBlockingClient::StoreConsumerOffset(const Consumer &consumer,
const Identifier &stream,
const Identifier &topic,
const std::optional<std::uint32_t> partition_id,
const std::uint64_t offset) {
RethrowAsIggyException([this, &consumer, &stream, &topic, offset, partition_id] {
constexpr auto unspecified_partition_id = std::numeric_limits<std::uint32_t>::max();
if (partition_id == unspecified_partition_id) {
throw std::invalid_argument("partition_id cannot be the maximum std::uint32_t value");
}
const auto ffi_partition_id = partition_id.value_or(unspecified_partition_id);
Handle()->store_consumer_offset(stream.ToFfi(), topic.ToFfi(), ffi_partition_id,
std::string(consumer.KindName()), consumer.Id().ToFfi(), offset);
});
}
ConsumerOffsetInfo IggyBlockingClient::GetConsumerOffset(const Consumer &consumer,
const Identifier &stream,
const Identifier &topic,
const std::optional<std::uint32_t> partition_id) {
return RethrowAsIggyException([this, &consumer, &stream, &topic, partition_id] {
constexpr auto unspecified_partition_id = std::numeric_limits<std::uint32_t>::max();
if (partition_id == unspecified_partition_id) {
throw std::invalid_argument("partition_id cannot be the maximum std::uint32_t value");
}
const auto ffi_partition_id = partition_id.value_or(unspecified_partition_id);
return ConsumerOffsetInfo::FromFfi(Handle()->get_consumer_offset(
stream.ToFfi(), topic.ToFfi(), ffi_partition_id, std::string(consumer.KindName()), consumer.Id().ToFfi()));
});
}
void IggyBlockingClient::DeleteConsumerOffset(const Consumer &consumer,
const Identifier &stream,
const Identifier &topic,
const std::optional<std::uint32_t> partition_id) {
RethrowAsIggyException([this, &consumer, &stream, &topic, partition_id] {
constexpr auto unspecified_partition_id = std::numeric_limits<std::uint32_t>::max();
if (partition_id == unspecified_partition_id) {
throw std::invalid_argument("partition_id cannot be the maximum std::uint32_t value");
}
const auto ffi_partition_id = partition_id.value_or(unspecified_partition_id);
Handle()->delete_consumer_offset(stream.ToFfi(), topic.ToFfi(), ffi_partition_id,
std::string(consumer.KindName()), consumer.Id().ToFfi());
});
}
ClientInfoDetails IggyBlockingClient::GetMe() {
return RethrowAsIggyException([this] { return ClientInfoDetails::FromFfi(Handle()->get_me()); });
}
ClientInfoDetails IggyBlockingClient::GetClient(std::uint32_t client_id) {
return RethrowAsIggyException(
[this, client_id] { return ClientInfoDetails::FromFfi(Handle()->get_client(client_id)); });
}
std::vector<ClientInfo> IggyBlockingClient::GetClients() {
return RethrowAsIggyException([this] {
std::vector<ClientInfo> clients;
auto ffi_clients = Handle()->get_clients();
clients.reserve(ffi_clients.size());
for (auto &client : ffi_clients) {
clients.push_back(ClientInfo::FromFfi(std::move(client)));
}
return clients;
});
}
void IggyBlockingClient::FlushUnsavedBuffer(const Identifier &stream,
const Identifier &topic,
const std::uint32_t partition_id,
const bool fsync) {
RethrowAsIggyException([this, &stream, &topic, partition_id, fsync] {
Handle()->flush_unsaved_buffer(stream.ToFfi(), topic.ToFfi(), partition_id, fsync);
});
}
Stats IggyBlockingClient::GetStats() {
return RethrowAsIggyException([this] { return Stats::FromFfi(Handle()->get_stats()); });
}
IggyBlockingClient::IggyBlockingClient(ffi::Client *client) : client_(client) {
if (client_ == nullptr) {
throw IggyException("Could not create Iggy client");
}
}
ffi::Client *IggyBlockingClient::Handle() const {
if (client_ == nullptr) {
throw IggyException("Cannot use a moved-from IggyBlockingClient");
}
return client_;
}
void IggyBlockingClient::Reset() noexcept {
if (client_ == nullptr) {
return;
}
ffi::Client *client{std::exchange(client_, nullptr)};
ffi::delete_client(client);
}
IggyBlockingClient::Builder::Builder() = default;
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithServerAddress(std::string server_address) {
if (server_address.empty()) {
throw IggyException("Server address cannot be empty");
}
server_address_ = std::move(server_address);
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithAutoLogin(std::string username, std::string password) {
if (username.empty() || password.empty()) {
throw IggyException("Automatic login username and password cannot be empty");
}
auto_login_kind_ = ffi::AutoLoginKind::UsernamePassword;
auto_login_username_ = std::move(username);
auto_login_password_ = std::move(password);
personal_access_token_.clear();
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithPersonalAccessToken(std::string token) {
if (token.empty()) {
throw IggyException("Personal access token cannot be empty");
}
auto_login_kind_ = ffi::AutoLoginKind::PersonalAccessToken;
personal_access_token_ = std::move(token);
auto_login_username_.clear();
auto_login_password_.clear();
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithReconnectionMaxRetries(std::uint32_t retries) {
reconnection_max_retries_ = retries;
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithoutReconnectionLimit() {
reconnection_max_retries_.reset();
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithReconnectionInterval(std::chrono::microseconds interval) {
if (interval.count() < 0) {
throw IggyException("Reconnection interval cannot be negative");
}
reconnection_interval_micros_ = static_cast<std::uint64_t>(interval.count());
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithReestablishAfter(std::chrono::microseconds duration) {
if (duration.count() < 0) {
throw IggyException("Reestablish duration cannot be negative");
}
reestablish_after_micros_ = static_cast<std::uint64_t>(duration.count());
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithTlsEnabled(bool enabled) {
tls_enabled_ = enabled;
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithTlsDomain(std::string domain) {
if (domain.empty()) {
throw IggyException("TLS domain cannot be empty");
}
tls_domain_ = std::move(domain);
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithTlsCaFile(std::string path) {
if (path.empty()) {
throw IggyException("TLS CA file cannot be empty");
}
tls_ca_file_ = std::move(path);
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithTlsCertificateValidation(bool enabled) {
tls_validate_certificate_ = enabled;
return *this;
}
IggyBlockingClient::Builder &IggyBlockingClient::Builder::WithNoDelay() {
no_delay_ = true;
return *this;
}
IggyBlockingClient IggyBlockingClient::Builder::Build() const {
return IggyBlockingClient::RethrowAsIggyException([this] {
ffi::IggyClientConfig config{};
config.server_address = server_address_;
config.auto_login_kind = auto_login_kind_;
config.username = auto_login_username_;
config.password = auto_login_password_;
config.personal_access_token = personal_access_token_;
config.has_reconnection_max_retries = reconnection_max_retries_.has_value();
config.reconnection_max_retries = reconnection_max_retries_.value_or(0);
config.has_reconnection_interval = reconnection_interval_micros_.has_value();
config.reconnection_interval_micros = reconnection_interval_micros_.value_or(0);
config.has_reestablish_after = reestablish_after_micros_.has_value();
config.reestablish_after_micros = reestablish_after_micros_.value_or(0);
config.tls_enabled = tls_enabled_;
config.tls_domain = tls_domain_;
config.tls_ca_file = tls_ca_file_;
config.has_tls_validate_certificate = tls_validate_certificate_.has_value();
config.tls_validate_certificate = tls_validate_certificate_.value_or(false);
config.no_delay = no_delay_;
return IggyBlockingClient(ffi::new_connection(std::move(config)));
});
}
} // namespace iggy