This is an automated email from the ASF dual-hosted git repository.
slbotbm pushed a commit to branch cpp-high-level-client-2
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/cpp-high-level-client-2 by
this push:
new 4d7af5d34 fixes
4d7af5d34 is described below
commit 4d7af5d34bca4647e678891112e16e8f90a03414
Author: Rimuksh Kansal <[email protected]>
AuthorDate: Mon Sep 21 16:02:54 2026 +0900
fixes
---
.../features/step_definitions/background_steps.cpp | 3 +--
foreign/cpp/include/iggy.hpp | 29 +++++++++++++-------
foreign/cpp/src/client.cpp | 31 ++++++++++++++++------
foreign/cpp/tests/e2e/consumer_group.cpp | 12 ++++-----
foreign/cpp/tests/unit/unit_tests.cpp | 27 +++++++++++++++++++
5 files changed, 76 insertions(+), 26 deletions(-)
diff --git a/bdd/cpp/features/step_definitions/background_steps.cpp
b/bdd/cpp/features/step_definitions/background_steps.cpp
index eaa0e7b9e..33fcb008e 100644
--- a/bdd/cpp/features/step_definitions/background_steps.cpp
+++ b/bdd/cpp/features/step_definitions/background_steps.cpp
@@ -37,8 +37,7 @@ namespace {
std::string required_env(const char *name) {
const char *value = std::getenv(name);
if (value == nullptr || *value == '\0') {
- throw std::runtime_error(std::string(name) +
- " must be set; run the suite via
scripts/run-bdd-tests.sh");
+ throw std::runtime_error(std::string(name) + " must be set; run the
suite via scripts/run-bdd-tests.sh");
}
return std::string(value);
}
diff --git a/foreign/cpp/include/iggy.hpp b/foreign/cpp/include/iggy.hpp
index eb93ed677..c213638a8 100644
--- a/foreign/cpp/include/iggy.hpp
+++ b/foreign/cpp/include/iggy.hpp
@@ -1137,8 +1137,8 @@ class ConsumerGroupMember final {
[[nodiscard]] std::uint32_t Id() const noexcept { return id_; }
/**
- * @brief Returns the number of partitions assigned to this member.
- * @return Number of entries in Partitions().
+ * @brief Returns the server-reported number of partitions assigned to
this member.
+ * @return Partition count reported by the server.
*/
[[nodiscard]] std::uint32_t PartitionsCount() const noexcept { return
partitions_count_; }
@@ -1237,8 +1237,8 @@ class ConsumerGroupDetails final {
[[nodiscard]] std::uint32_t PartitionsCount() const noexcept { return
partitions_count_; }
/**
- * @brief Returns the number of members in the group.
- * @return Number of entries in Members().
+ * @brief Returns the server-reported number of members in the group.
+ * @return Member count reported by the server.
*/
[[nodiscard]] std::uint32_t MembersCount() const noexcept { return
members_count_; }
@@ -2549,7 +2549,10 @@ class IggyBlockingClient final {
* @param stream Parent stream, addressed by numeric ID or name.
* @param topic Parent topic, addressed by numeric ID or name.
* @param offset Message offset to store.
- * @param partition_id Partition whose offset is stored.
+ * @param partition_id Partition whose offset is stored, or `std::nullopt`
+ * to omit the partition from the request. The maximum
+ * `std::uint32_t` value is rejected because it is reserved by the
+ * FFI representation.
* @throws IggyException if an identifier, partition, or offset is invalid;
* the resource does not exist; the client is unauthenticated; the
* caller lacks permission; or the request fails.
@@ -2558,7 +2561,7 @@ class IggyBlockingClient final {
const Identifier &stream,
const Identifier &topic,
std::uint64_t offset,
- std::uint32_t partition_id);
+ std::optional<std::uint32_t> partition_id =
std::nullopt);
/**
* @brief Retrieves the stored offset for a consumer or consumer group.
@@ -2574,7 +2577,10 @@ class IggyBlockingClient final {
* @param consumer Consumer identity that owns the offset.
* @param stream Parent stream, addressed by numeric ID or name.
* @param topic Parent topic, addressed by numeric ID or name.
- * @param partition_id Partition whose offset is retrieved.
+ * @param partition_id Partition whose offset is retrieved, or
+ * `std::nullopt` to omit the partition from the request. The
+ * maximum `std::uint32_t` value is rejected because it is reserved
+ * by the FFI representation.
* @return Partition state and the stored consumer offset.
* @throws IggyException if an identifier or partition is invalid; the
* resource or stored offset does not exist; the client is
@@ -2584,7 +2590,7 @@ class IggyBlockingClient final {
ConsumerOffsetInfo GetConsumerOffset(const Consumer &consumer,
const Identifier &stream,
const Identifier &topic,
- std::uint32_t partition_id);
+ std::optional<std::uint32_t>
partition_id = std::nullopt);
/**
* @brief Deletes the stored offset for a consumer or consumer group.
@@ -2601,7 +2607,10 @@ class IggyBlockingClient final {
* @param consumer Consumer identity that owns the offset.
* @param stream Parent stream, addressed by numeric ID or name.
* @param topic Parent topic, addressed by numeric ID or name.
- * @param partition_id Partition whose offset is deleted.
+ * @param partition_id Partition whose offset is deleted, or `std::nullopt`
+ * to omit the partition from the request. The maximum
+ * `std::uint32_t` value is rejected because it is reserved by the
+ * FFI representation.
* @throws IggyException if an identifier or partition is invalid; the
* resource or stored offset does not exist; the client is
* unauthenticated; the caller lacks permission; or the request
@@ -2610,7 +2619,7 @@ class IggyBlockingClient final {
void DeleteConsumerOffset(const Consumer &consumer,
const Identifier &stream,
const Identifier &topic,
- std::uint32_t partition_id);
+ std::optional<std::uint32_t> partition_id =
std::nullopt);
private:
explicit IggyBlockingClient(ffi::Client *client);
diff --git a/foreign/cpp/src/client.cpp b/foreign/cpp/src/client.cpp
index afe1b551c..b61b2fd2b 100644
--- a/foreign/cpp/src/client.cpp
+++ b/foreign/cpp/src/client.cpp
@@ -307,30 +307,45 @@ void IggyBlockingClient::StoreConsumerOffset(const
Consumer &consumer,
const Identifier &stream,
const Identifier &topic,
const std::uint64_t offset,
- const std::uint32_t partition_id)
{
+ const
std::optional<std::uint32_t> partition_id) {
RethrowAsIggyException([this, &consumer, &stream, &topic, offset,
partition_id] {
- Handle()->store_consumer_offset(stream.ToFfi(), topic.ToFfi(),
partition_id, std::string(consumer.KindName()),
- consumer.Id().ToFfi(), offset);
+ 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::uint32_t
partition_id) {
+ 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(), partition_id,
std::string(consumer.KindName()), consumer.Id().ToFfi()));
+ 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::uint32_t
partition_id) {
+ const
std::optional<std::uint32_t> partition_id) {
RethrowAsIggyException([this, &consumer, &stream, &topic, partition_id] {
- Handle()->delete_consumer_offset(stream.ToFfi(), topic.ToFfi(),
partition_id, std::string(consumer.KindName()),
- consumer.Id().ToFfi());
+ 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());
});
}
diff --git a/foreign/cpp/tests/e2e/consumer_group.cpp
b/foreign/cpp/tests/e2e/consumer_group.cpp
index 79b5730b7..87ca3f29a 100644
--- a/foreign/cpp/tests/e2e/consumer_group.cpp
+++ b/foreign/cpp/tests/e2e/consumer_group.cpp
@@ -1156,7 +1156,7 @@ TEST_F(E2E_ConsumerGroup,
DeleteConsumerGroupAndRecreateWithSameNameSucceeds) {
}
TEST_F(E2E_ConsumerGroup, StoreGetAndDeleteConsumerOffsetSucceeds) {
- RecordProperty("description", "Stores, retrieves, and deletes an
individual consumer offset.");
+ RecordProperty("description", "Retrieves a partition-0 offset with no
partition specified.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
@@ -1177,17 +1177,17 @@ TEST_F(E2E_ConsumerGroup,
StoreGetAndDeleteConsumerOffsetSucceeds) {
ASSERT_NO_THROW(client.StoreConsumerOffset(consumer,
iggy::Identifier::String(stream_name),
iggy::Identifier::String(topic_name), 0, 0));
- const auto offset = client.GetConsumerOffset(consumer,
iggy::Identifier::String(stream_name),
-
iggy::Identifier::String(topic_name), 0);
+ const auto offset =
+ client.GetConsumerOffset(consumer,
iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name));
EXPECT_EQ(offset.PartitionId(), 0u);
EXPECT_EQ(offset.CurrentOffset(), 0u);
EXPECT_EQ(offset.StoredOffset(), 0u);
ASSERT_NO_THROW(client.DeleteConsumerOffset(consumer,
iggy::Identifier::String(stream_name),
iggy::Identifier::String(topic_name), 0));
- ASSERT_THROW(client.GetConsumerOffset(consumer,
iggy::Identifier::String(stream_name),
-
iggy::Identifier::String(topic_name), 0),
- iggy::IggyException);
+ ASSERT_THROW(
+ client.GetConsumerOffset(consumer,
iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name)),
+ iggy::IggyException);
}
TEST_F(E2E_ConsumerGroup, StoreConsumerOffsetOnEmptyPartitionThrows) {
diff --git a/foreign/cpp/tests/unit/unit_tests.cpp
b/foreign/cpp/tests/unit/unit_tests.cpp
index b1b0be60d..410e91fbd 100644
--- a/foreign/cpp/tests/unit/unit_tests.cpp
+++ b/foreign/cpp/tests/unit/unit_tests.cpp
@@ -352,6 +352,7 @@ TEST(IggyBlockingClientTest, MovedFromOperationsThrow) {
const auto stream = iggy::Identifier::String("stream");
const auto topic = iggy::Identifier::String("topic");
+ const auto group = iggy::Identifier::String("group");
const auto consumer = iggy::Consumer::Single(iggy::Identifier::Numeric(1));
// Exercising the moved-from guard requires invoking every operation on
the valid but empty source object.
@@ -375,11 +376,37 @@ TEST(IggyBlockingClientTest, MovedFromOperationsThrow) {
EXPECT_THROW(client.PurgeTopic(stream, topic), iggy::IggyException);
EXPECT_THROW(client.CreatePartitions(stream, topic, 1),
iggy::IggyException);
EXPECT_THROW(client.DeletePartitions(stream, topic, 1),
iggy::IggyException);
+ EXPECT_THROW(client.CreateConsumerGroup(stream, topic, "group"),
iggy::IggyException);
+ EXPECT_THROW(client.GetConsumerGroup(stream, topic, group),
iggy::IggyException);
+ EXPECT_THROW(client.GetConsumerGroups(stream, topic), iggy::IggyException);
+ EXPECT_THROW(client.DeleteConsumerGroup(stream, topic, group),
iggy::IggyException);
+ EXPECT_THROW(client.JoinConsumerGroup(stream, topic, group),
iggy::IggyException);
+ EXPECT_THROW(client.LeaveConsumerGroup(stream, topic, group),
iggy::IggyException);
EXPECT_THROW(client.StoreConsumerOffset(consumer, stream, topic, 0, 0),
iggy::IggyException);
EXPECT_THROW(client.GetConsumerOffset(consumer, stream, topic, 0),
iggy::IggyException);
EXPECT_THROW(client.DeleteConsumerOffset(consumer, stream, topic, 0),
iggy::IggyException);
}
+TEST(IggyBlockingClientTest, ConsumerOffsetOperationsRejectMaximumPartitionId)
{
+ auto client = iggy::IggyBlockingClient::Builder().Build();
+ const auto stream = iggy::Identifier::String("stream");
+ const auto topic = iggy::Identifier::String("topic");
+ const auto consumer =
iggy::Consumer::Single(iggy::Identifier::Numeric(1));
+ const auto maximum_partition = std::numeric_limits<std::uint32_t>::max();
+ const auto expect_rejection = [](auto &&operation) {
+ try {
+ operation();
+ FAIL() << "Expected the maximum std::uint32_t partition_id to be
rejected";
+ } catch (const iggy::IggyException &error) {
+ EXPECT_STREQ(error.what(), "partition_id cannot be the maximum
std::uint32_t value");
+ }
+ };
+
+ expect_rejection([&] { client.StoreConsumerOffset(consumer, stream, topic,
0, maximum_partition); });
+ expect_rejection([&] { (void)client.GetConsumerOffset(consumer, stream,
topic, maximum_partition); });
+ expect_rejection([&] { client.DeleteConsumerOffset(consumer, stream,
topic, maximum_partition); });
+}
+
TEST(AutoLoginKindTest, HasStableDiscriminantsAndZeroInitializedDefault) {
EXPECT_EQ(static_cast<std::uint8_t>(iggy::ffi::AutoLoginKind::Disabled),
0u);
EXPECT_EQ(static_cast<std::uint8_t>(iggy::ffi::AutoLoginKind::UsernamePassword),
1u);