You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by "slbotbm (via GitHub)" <gi...@apache.org> on 2026/10/04 10:23:12 UTC
[PR] feat(cpp): add more functions to high level client [iggy]
slbotbm opened a new pull request, #4397:
URL: https://github.com/apache/iggy/pull/4397
## Which issue does this PR address?
<!--
We generally require a GitHub issue for all bug fixes and enhancements. Link it with GitHub syntax, keep the line that applies and delete the other:
- `Closes #123` auto-closes the issue when this PR merges (full fix).
- `Relates to #123` links without closing (partial or related work).
-->
Closes #
Relates to #2100
## Rationale
<!--
Why is this change needed? If the issue explains it well, a one-liner is fine.
-->
Expose the final set of functions offered by cpp bridge in the high level client
## What changed?
Added the following functions and their related data structs:
- SendMessages
- PollMessages
- DescribeOptions
- Ping
- HeartbeatInterval
- Snapshot
- SendBinaryRequest
- UpdatePermissions
- ChangePassword
- GetClusterMetadata
Also translated tests to use high level client, deleting those that tested bridge code and were not possible to test in the high level client.
## Local Execution
- Passed
- Pre-commit hooks ran
<!--
You must run your code locally before submitting.
"Relying on CI" is not acceptable - PRs from authors who haven't run the code will be closed.
Did you have `prek` installed? It runs automatically on commit and covers all project languages. See [CONTRIBUTING.md](https://github.com/apache/iggy/blob/master/CONTRIBUTING.md).
-->
## AI Usage
If AI tools were used, please answer:
1. Which tools? codex
2. Scope of usage? code generation
3. How did you verify the generated code works correctly? read through the code
4. Can you explain every line of the code if asked? yes
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "github-actions[bot] (via GitHub) via commits" <co...@iggy.apache.org>.
github-actions[bot] commented on PR #4397:
URL: https://github.com/apache/iggy/pull/4397#issuecomment-6755446832
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs.
If you need a review, please ensure CI is green and the PR is rebased on the latest master. Don't hesitate to ping the maintainers - either `@core` on Discord or by mentioning them directly here on the PR.
Thank you for your contribution!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "hubcio (via GitHub)" <gi...@apache.org>.
hubcio commented on PR #4397:
URL: https://github.com/apache/iggy/pull/4397#issuecomment-6061764610
since purge is going away (removal pr goes up today), no need to port the purge tests in `stream.cpp` / `topic.cpp`. you can just drop them here.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "slbotbm (via GitHub)" <gi...@apache.org>.
slbotbm commented on PR #4397:
URL: https://github.com/apache/iggy/pull/4397#issuecomment-5978999099
#3995 is a blocker for this. If author does not respond in some days, I'll drive that PR to completion
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #4397:
URL: https://github.com/apache/iggy/pull/4397#issuecomment-6066365045
## [Codecov](https://app.codecov.io/gh/apache/iggy/pull/4397?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
:x: Patch coverage is `71.64179%` with `57 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 87.82%. Comparing base ([`09420cf`](https://app.codecov.io/gh/apache/iggy/commit/09420cf4dc7b5d9cfe77dd71a63db365216b733e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`966dc68`](https://app.codecov.io/gh/apache/iggy/commit/966dc68c5c0f77ea27be6890785d3678af595c5b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
| [Files with missing lines](https://app.codecov.io/gh/apache/iggy/pull/4397?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Patch % | Lines |
|---|---|---|
| [foreign/cpp/src/client.cpp](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Fclient.cpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL2NsaWVudC5jcHA=) | 56.32% | [9 Missing and 29 partials :warning: ](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [foreign/cpp/src/type\_conversions.cpp](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Ftype_conversions.cpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL3R5cGVfY29udmVyc2lvbnMuY3Bw) | 56.66% | [0 Missing and 13 partials :warning: ](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [foreign/cpp/include/iggy.hpp](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Finclude%2Figgy.hpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvaW5jbHVkZS9pZ2d5LmhwcA==) | 91.93% | [0 Missing and 5 partials :warning: ](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [foreign/cpp/src/type\_conversion.rs](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Ftype_conversion.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL3R5cGVfY29udmVyc2lvbi5ycw==) | 90.00% | [1 Missing :warning: ](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
<details><summary>Additional details and impacted files</summary>
```diff
@@ Coverage Diff @@
## master #4397 +/- ##
==========================================
Coverage 87.82% 87.82%
Complexity 1607 1607
==========================================
Files 1302 1302
Lines 252088 252231 +143
Branches 212381 212436 +55
==========================================
+ Hits 221394 221524 +130
+ Misses 25408 25376 -32
- Partials 5286 5331 +45
```
| [Components](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | |
|---|---|---|
| [Rust Core](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `89.63% <ø> (ø)` | |
| [Java SDK](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `68.81% <ø> (ø)` | |
| [C# SDK](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `61.43% <ø> (ø)` | |
| [Python SDK](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `91.25% <ø> (ø)` | |
| [PHP SDK](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `85.67% <ø> (ø)` | |
| [Node SDK](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `94.98% <ø> (ø)` | |
| [Go SDK](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `70.22% <ø> (ø)` | |
| [C++ SDK](https://app.codecov.io/gh/apache/iggy/pull/4397/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `86.81% <71.64%> (+0.20%)` | :arrow_up: |
| [Files with missing lines](https://app.codecov.io/gh/apache/iggy/pull/4397?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | |
|---|---|---|
| [foreign/cpp/src/client.rs](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Fclient.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL2NsaWVudC5ycw==) | `89.70% <100.00%> (-0.31%)` | :arrow_down: |
| [foreign/cpp/src/lib.rs](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Flib.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL2xpYi5ycw==) | `100.00% <ø> (ø)` | |
| [foreign/cpp/src/type\_conversion.rs](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Ftype_conversion.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL3R5cGVfY29udmVyc2lvbi5ycw==) | `96.56% <90.00%> (-0.93%)` | :arrow_down: |
| [foreign/cpp/include/iggy.hpp](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Finclude%2Figgy.hpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvaW5jbHVkZS9pZ2d5LmhwcA==) | `89.84% <91.93%> (+3.46%)` | :arrow_up: |
| [foreign/cpp/src/type\_conversions.cpp](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Ftype_conversions.cpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL3R5cGVfY29udmVyc2lvbnMuY3Bw) | `68.33% <56.66%> (+8.51%)` | :arrow_up: |
| [foreign/cpp/src/client.cpp](https://app.codecov.io/gh/apache/iggy/pull/4397?src=pr&el=tree&filepath=foreign%2Fcpp%2Fsrc%2Fclient.cpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-Zm9yZWlnbi9jcHAvc3JjL2NsaWVudC5jcHA=) | `69.04% <56.32%> (-1.45%)` | :arrow_down: |
</details>
<details><summary> :rocket: New features to boost your workflow: </summary>
- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "github-actions[bot] (via GitHub) via commits" <co...@iggy.apache.org>.
github-actions[bot] commented on code in PR #4397:
URL: https://github.com/apache/iggy/pull/4397#discussion_r4437898608
##########
foreign/cpp/src/type_conversions.cpp:
##########
@@ -849,4 +849,37 @@ MetadataChanges MetadataChanges::FromFfi(ffi::MetadataChanges changes) {
return result;
}
+SendMessagesConfirmation SendMessagesConfirmation::FromFfi(ffi::SendMessagesConfirmation confirmation) {
+ return SendMessagesConfirmation(confirmation.stream_id, confirmation.topic_id, confirmation.partition_id,
+ confirmation.base_offset);
+}
+
+SendMessagesResponse SendMessagesResponse::FromFfi(const ffi::SendMessagesResponse &response) {
+ std::vector<SendMessagesConfirmation> confirmations;
+ confirmations.reserve(response.confirmations.size());
+ for (const auto &confirmation : response.confirmations) {
+ confirmations.push_back(SendMessagesConfirmation::FromFfi(confirmation));
+ }
+ return SendMessagesResponse(std::move(confirmations));
+}
+
+TransportEndpoints TransportEndpoints::FromFfi(ffi::TransportEndpoints endpoints) {
+ return TransportEndpoints(endpoints.tcp, endpoints.quic, endpoints.http, endpoints.websocket);
+}
+
+ClusterNode ClusterNode::FromFfi(ffi::ClusterNode node) {
Review Comment:
warning: `ClusterNode::FromFfi()` drops `ffi::ClusterNode::replica_id`, so a `GetClusterMetadata()` caller cannot learn the ID that `SendBinaryRequestToReplica()` takes. Expose `ReplicaId()` and pass `node.replica_id` through the conversion.
##########
foreign/cpp/include/iggy.hpp:
##########
@@ -780,6 +789,17 @@ class Permissions final {
* A consumer offset belongs either to an individual consumer or to a consumer
* group. Create a value with Single() or Group(), then pass it to the consumer
* offset operations on IggyBlockingClient.
+ *
+ * Two callers naming the same identifier share one offset, so under
+ * PollingStrategy::Next() with auto-commit each sees only the messages the
+ * other has not read yet. Give every independent consumer its own name.
+ *
+ * @code{.cpp}
+ * const auto polled{client.PollMessages(
+ * stream, topic, std::nullopt,
Review Comment:
warning: The `Consumer` example passes `std::nullopt` to `PollMessages()`, but the overload rejects an empty partition with `partition_id is required`, so the example always throws. Pass a concrete partition ID in the example and take `std::uint32_t` instead of `std::optional` in the overload.
##########
foreign/cpp/include/iggy.hpp:
##########
@@ -7427,6 +7746,181 @@ class IggyBlockingClient final {
*/
Stats GetStats();
+ /**
+ * @brief Sends a non-empty batch of messages to one topic.
+ *
+ * The partitioning strategy selects one destination for the entire batch.
+ * Message and partitioning constraints are validated before the request is
+ * sent. The Rust SDK assigns generated IDs to messages whose ID is zero;
+ * the C++ input objects are not modified.
+ *
+ * Delivery is at least once. A failed or unknown transport outcome can
+ * leave the batch committed, so retrying may append duplicates. Successful
+ * responses can contain no confirmations; see SendMessagesResponse.
+ *
+ * @param stream Parent stream, addressed by numeric ID or name.
+ * @param topic Destination topic, addressed by numeric ID or name.
+ * @param partitioning Strategy used to select the destination partition.
+ * @param messages Messages to send. The collection must not be empty.
+ * @return Available per-partition commit confirmations.
+ * @throws IggyException if the client is unavailable or unauthenticated;
+ * an identifier, partitioning strategy, message, or batch is
+ * invalid; the stream, topic, or partition does not exist; the
+ * caller lacks send permission; or the request fails.
+ */
+ SendMessagesResponse SendMessages(const Identifier &stream,
+ const Identifier &topic,
+ const Partitioning &partitioning,
+ const std::vector<IggyMessageToSend> &messages);
+
+ /**
+ * @brief Reads one partition with a message count and optional on-read commit.
+ *
+ * A partition is required. For additional read limits or a continuation,
+ * use the overload taking PollOptions.
+ */
+ PolledMessages PollMessages(const Identifier &stream,
+ const Identifier &topic,
+ std::optional<std::uint32_t> partition_id,
+ const Consumer &consumer,
+ const PollingStrategy &strategy,
+ std::uint32_t count,
+ bool auto_commit);
+
+ /**
+ * @brief Retrieves the server's option catalog for one resource type.
+ *
+ * The catalog describes accepted create-option keys, their HeaderKind
+ * encodings, defaults, and descriptions. A supported scope with no option
+ * keys returns an empty collection.
+ *
+ * @param scope Resource scope. Must be `topic`, `stream`, or `user`.
+ * @return Option specifications for the requested scope.
+ * @throws IggyException if the client is unavailable or unauthenticated;
+ * @p scope is invalid; or the request fails.
+ */
+ std::vector<OptionSpec> DescribeOptions(std::string scope);
+
+ /**
+ * @brief Checks whether the configured server can answer a request.
+ *
+ * Ping does not require authentication and returns no server data.
+ *
+ * @throws IggyException if the client is unavailable or the request fails.
+ */
+ void Ping();
+
+ /**
+ * @brief Returns the client's configured heartbeat interval.
+ * @return Non-zero interval in microseconds.
+ * @note This reads client configuration and does not contact the server.
+ * @throws IggyException if this client has been moved from.
+ */
+ std::chrono::microseconds HeartbeatInterval();
+
+ /**
+ * @brief Captures server diagnostics as a ZIP archive.
+ *
+ * Duplicate snapshot types are removed. SystemSnapshotType::All() must be
+ * the only requested type and expands to all production diagnostic
+ * sections. The server runs only one snapshot collection at a time; a
+ * concurrent request fails. A section that cannot be captured can be
+ * omitted while the remaining archive is still returned.
+ *
+ * @param compression Compression method used for ZIP entries.
+ * @param types Non-empty collection of diagnostic sections to capture.
+ * @return Complete ZIP archive bytes owned by the caller.
+ * @throws IggyException if the client is unavailable or unauthenticated;
+ * the caller lacks read-servers or manage-servers permission; the
+ * requested types are invalid; another snapshot is in progress;
+ * archive creation fails; or the request fails.
+ */
+ std::vector<std::uint8_t> Snapshot(const SnapshotCompression &compression,
+ const std::vector<SystemSnapshotType> &types);
+
+ /**
+ * @brief Sends one command through the raw Iggy binary protocol.
+ *
+ * The caller supplies only the command body and receives only the response
+ * body; the configured transport adds and removes protocol framing. The
+ * command keeps its normal authentication, authorization, and mutation
+ * semantics. Session-control commands for login, logout, and registration
+ * are rejected so that the SDK's session state cannot be bypassed.
+ *
+ * @param code Iggy binary-protocol command code.
+ * @param payload Command body encoded for @p code.
+ * @return Raw response body without protocol framing.
+ * @throws IggyException if the transport is HTTP; @p code is a
+ * session-control or unsupported command; the payload is invalid;
+ * the command's access checks fail; or the request fails.
+ */
+ std::vector<std::uint8_t> SendBinaryRequest(std::uint32_t code, const std::vector<std::uint8_t> &payload);
+
+ /**
+ * @brief Sends a non-replicated raw command to a specific cluster replica.
+ * @throws IggyException for an unknown replica, a replicated or session-control command,
+ * or a failed request.
+ */
+ std::vector<std::uint8_t> SendBinaryRequestToReplica(std::uint32_t code,
Review Comment:
nit: The class doc and README list the methods that populate `IggyException::Code()` and `Kind()`, but this method and `SendMessages()`, `UpdatePermissions()`, `ChangePassword()`, and `SendBinaryRequest()` populate them too. Add the five.
##########
foreign/cpp/src/client.cpp:
##########
@@ -553,6 +570,124 @@ Stats IggyBlockingClient::GetStats() {
return RethrowAsIggyException([this] { return Stats::FromFfi(Handle()->get_stats()); });
}
+SendMessagesResponse IggyBlockingClient::SendMessages(const Identifier &stream,
+ const Identifier &topic,
+ const Partitioning &partitioning,
+ const std::vector<IggyMessageToSend> &messages) {
+ return RethrowAsIggyException([this, &stream, &topic, &partitioning, &messages](ffi::ErrorInfo &error_info) {
+ rust::Vec<ffi::IggyMessageToSend> ffi_messages;
+ ffi_messages.reserve(messages.size());
+ for (const auto &message : messages) {
+ ffi_messages.push_back(message.ToFfi());
+ }
+ rust::Vec<std::uint8_t> ffi_partitioning_value;
Review Comment:
simplification: The new wrappers repeat the byte-copy loop three times, here and at lines 647 and 661. Call the existing `ToFfiBytes()` helper at line 68 instead, which saves about twelve lines.
##########
foreign/cpp/include/iggy.hpp:
##########
@@ -998,10 +1038,11 @@ class IggyMessageToSend final {
* @note Payload and header constraints are validated when the message is
* sent, not by this function.
*/
- static IggyMessageToSend Create(std::vector<std::uint8_t> payload,
+ static IggyMessageToSend Create(std::string_view payload,
Review Comment:
warning: `Create()` takes `std::string_view` and copies the payload, so a caller with a byte vector cannot move it and pays one extra copy. Restore the by-value vector overload alongside the view.
##########
foreign/cpp/src/client.cpp:
##########
@@ -280,14 +280,32 @@ TopicDetails IggyBlockingClient::CreateTopic(const Identifier &stream,
}
if (auto value = options.Durability()) {
ffi_options.has_durability = true;
- ffi_options.durability = std::string(iggy::to_string(*value));
+ switch (*value) {
Review Comment:
simplification: Two identical switch blocks map `Durability` to `replicated` or `persisted`, here and at line 299. Restore the removed public `to_string(Durability)` helper and call it for both fields, which saves about nine lines.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "slbotbm (via GitHub) via commits" <co...@iggy.apache.org>.
slbotbm commented on PR #4397:
URL: https://github.com/apache/iggy/pull/4397#issuecomment-6583812008
Addressed all reviews except one which I think can be skipped. Also, I integrated the cargo tests into bazel, changed cpp version from cpp17 to cpp18, and deleted the low-level bridge tests.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "slbotbm (via GitHub) via commits" <co...@iggy.apache.org>.
slbotbm commented on code in PR #4397:
URL: https://github.com/apache/iggy/pull/4397#discussion_r4445125647
##########
foreign/cpp/src/client.cpp:
##########
@@ -280,14 +280,32 @@ TopicDetails IggyBlockingClient::CreateTopic(const Identifier &stream,
}
if (auto value = options.Durability()) {
ffi_options.has_durability = true;
- ffi_options.durability = std::string(iggy::to_string(*value));
+ switch (*value) {
Review Comment:
I think it is fine as it is
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "slbotbm (via GitHub)" <gi...@apache.org>.
slbotbm commented on PR #4397:
URL: https://github.com/apache/iggy/pull/4397#issuecomment-6066802670
removed purge apis in this pr
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "slbotbm (via GitHub) via commits" <co...@iggy.apache.org>.
slbotbm commented on PR #4397:
URL: https://github.com/apache/iggy/pull/4397#issuecomment-6565629503
/skill team-review
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(cpp): add more functions to high level client [iggy]
Posted by "spetz (via GitHub) via commits" <co...@iggy.apache.org>.
spetz commented on code in PR #4397:
URL: https://github.com/apache/iggy/pull/4397#discussion_r4446951907
##########
foreign/cpp/include/iggy.hpp:
##########
@@ -5738,6 +5808,305 @@ class PollingStrategy final {
std::uint64_t polling_strategy_value_;
};
+/**
+ * @brief Selects the destination partition for a batch of messages.
+ *
+ * Balanced() distributes batches across the topic's partitions. PartitionId()
+ * selects one partition explicitly. MessagesKey() hashes the supplied bytes
+ * modulo the topic's partition count, so changing that count can change the
+ * destination for a key.
+ *
+ * The selected strategy applies to the entire SendMessages() call. Validation
+ * is deferred until the batch is sent. In particular, a message key must
+ * contain between 1 and 255 bytes.
+ */
+class Partitioning final {
+ public:
+ /**
+ * @brief Selects partitions using the client's balanced strategy.
+ * @return Balanced partitioning with no value payload.
+ */
+ static Partitioning Balanced() { return Partitioning("balanced", {}); }
Review Comment:
No test calls `Partitioning::Balanced()` or `Partitioning::MessagesKey()`. Every `SendMessages` call in `foreign/cpp/tests` and `bdd/cpp` uses `PartitionId`, so the `balanced` and `messages_key` kinds and the 1 to 255 byte key rule at `foreign/cpp/src/client.rs:452` never run from C++. The only send round trip checks the confirmation count and `PartitionId()` (`foreign/cpp/tests/e2e/message.cpp:82`), so a wrong `BaseOffset()`, `StreamId()` or `TopicId()` mapping also passes.
##########
foreign/cpp/src/client.cpp:
##########
@@ -553,6 +570,105 @@ Stats IggyBlockingClient::GetStats() {
return RethrowAsIggyException([this] { return Stats::FromFfi(Handle()->get_stats()); });
}
+SendMessagesResponse IggyBlockingClient::SendMessages(const Identifier &stream,
+ const Identifier &topic,
+ const Partitioning &partitioning,
+ const std::vector<IggyMessageToSend> &messages) {
+ return RethrowAsIggyException([this, &stream, &topic, &partitioning, &messages](ffi::ErrorInfo &error_info) {
+ rust::Vec<ffi::IggyMessageToSend> ffi_messages;
+ ffi_messages.reserve(messages.size());
+ for (const auto &message : messages) {
+ ffi_messages.push_back(message.ToFfi());
Review Comment:
`SendMessages` takes the batch by const reference, so `ToFfi` copies every payload and header byte into a `rust::Vec` with `push_back` (`foreign/cpp/src/type_conversions.cpp:208`). In cxx 1.0.205 each `push_back` makes four out-of-line calls into Rust (`size`, `reserve_total`, `data`, `set_len`), so a 1 MB batch makes about four million calls across the bridge before anything is sent. Master had no caller of `IggyMessageToSend::ToFfi`, so this PR is what puts that loop on the main send path. The vector `Create` overload tells callers to move the payload to avoid a copy (`foreign/cpp/include/iggy.hpp:1060`), but the send copies it again anyway. `SendBinaryRequest` pays the same per-byte cost through `ToFfiBytes`.
##########
foreign/cpp/include/iggy.hpp:
##########
@@ -7427,6 +7798,181 @@ class IggyBlockingClient final {
*/
Stats GetStats();
+ /**
+ * @brief Sends a non-empty batch of messages to one topic.
+ *
+ * The partitioning strategy selects one destination for the entire batch.
+ * Message and partitioning constraints are validated before the request is
+ * sent. The Rust SDK assigns generated IDs to messages whose ID is zero;
+ * the C++ input objects are not modified.
+ *
+ * Delivery is at least once. A failed or unknown transport outcome can
+ * leave the batch committed, so retrying may append duplicates. Successful
+ * responses can contain no confirmations; see SendMessagesResponse.
+ *
+ * @param stream Parent stream, addressed by numeric ID or name.
+ * @param topic Destination topic, addressed by numeric ID or name.
+ * @param partitioning Strategy used to select the destination partition.
+ * @param messages Messages to send. The collection must not be empty.
+ * @return Available per-partition commit confirmations.
+ * @throws IggyException if the client is unavailable or unauthenticated;
+ * an identifier, partitioning strategy, message, or batch is
+ * invalid; the stream, topic, or partition does not exist; the
+ * caller lacks send permission; or the request fails.
+ */
+ SendMessagesResponse SendMessages(const Identifier &stream,
+ const Identifier &topic,
+ const Partitioning &partitioning,
+ const std::vector<IggyMessageToSend> &messages);
+
+ /**
+ * @brief Reads one partition with a message count and optional on-read commit.
+ *
+ * A partition is required. For additional read limits or a continuation,
+ * use the overload taking PollOptions.
+ */
+ PolledMessages PollMessages(const Identifier &stream,
Review Comment:
This overload takes the consumer fourth and a `bool auto_commit`, while every other consumer call in the class, including the two existing `PollMessages` overloads at `foreign/cpp/include/iggy.hpp:7573` and `foreign/cpp/include/iggy.hpp:7585`, takes the consumer first and a `CommitPolicy`. Its body at `foreign/cpp/src/client.cpp:596` only builds `PollOptions().SetCount(count)` with a commit policy and forwards, so it adds no capability, and once released the class keeps two argument orders for one name. Its doc also hides that `true` means `CommitPolicy::OnRead`, so a caller never learns that a lost reply blocks the partition with `PartitionLaneBlocked` until `AbandonOperation()`, which the other overload documents at `foreign/cpp/include/iggy.hpp:7558`.
##########
foreign/cpp/BUILD.bazel:
##########
@@ -61,7 +64,12 @@ genrule(
env -u PWD \
CARGO_TARGET_DIR="$$CARGO_TARGET_DIR" \
RUSTC="$$RUSTC" \
- "$$CARGO" build --quiet $$FLAGS
+ "$$CARGO" build --quiet --lib $$FLAGS
+
+ env -u PWD \
+ CARGO_TARGET_DIR="$$CARGO_TARGET_DIR" \
+ RUSTC="$$RUSTC" \
+ "$$CARGO" rustc --quiet --tests $$FLAGS -- --emit="link=$$OUT_TESTS"
Review Comment:
`rust_unit_tests` is an output of the same genrule that builds `libiggy_cpp.a`, so every target that depends on `//:iggy-cpp` now also compiles and links the Rust test binary. That includes the release build in CI (`.github/actions/cpp-bazel/pre-merge/action.yml:73`) and the BDD image (`bdd/cpp/Dockerfile:36`), which never run it, and a compile error in a `#[cfg(test)]` module now fails the library build as well.
##########
foreign/cpp/tests/e2e/client.cpp:
##########
@@ -2843,264 +2730,319 @@ TEST_F(E2E_Client, GetClientsReflectsAdditionalSession) {
EXPECT_TRUE(found_after);
}
-TEST_F(LowLevelE2E_Client, GetClusterMetadataBeforeLoginThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, GetClusterMetadataBeforeLoginThrows) {
RecordProperty("description",
"Rejects get_cluster_metadata before connect, after connect but before login, and after disconnect, "
"and serves it once authenticated.");
- iggy::ffi::Client *client = GetLoggedOutClient();
+ auto client = GetLoggedOutHighLevelClient();
- ASSERT_THROW(client->get_cluster_metadata(), std::exception);
- ASSERT_NO_THROW(client->connect());
+ ASSERT_THROW(client.GetClusterMetadata(), std::exception);
+ ASSERT_NO_THROW(client.Connect());
// The roster is private, so the read is auth-gated. No pre-login read is
// needed: a client that dialed a backup logs in there and the server
// forwards the register to the primary.
- ASSERT_THROW(client->get_cluster_metadata(), std::exception);
- ASSERT_NO_THROW(client->login_user("iggy", "iggy", error_info));
- iggy::ffi::ClusterMetadata metadata{};
- ASSERT_NO_THROW({ metadata = client->get_cluster_metadata(); });
- ASSERT_GE(metadata.nodes.size(), 1u);
- ASSERT_NO_THROW(client->disconnect());
- ASSERT_THROW(client->get_cluster_metadata(), std::exception);
+ ASSERT_THROW(client.GetClusterMetadata(), std::exception);
+ ASSERT_NO_THROW(client.Login("iggy", "iggy"));
+ ASSERT_NO_THROW({
+ const auto metadata = client.GetClusterMetadata();
+ ASSERT_GE(metadata.Nodes().size(), 1u);
+ });
+ ASSERT_NO_THROW(client.Disconnect());
+ ASSERT_THROW(client.GetClusterMetadata(), std::exception);
}
-TEST_F(LowLevelE2E_Client, GetClusterMetadataReturnsSingleNodeMetadata) {
+TEST_F(E2E_Client, GetClusterMetadataReturnsSingleNodeMetadata) {
RecordProperty("description",
"Returns the expected single-node cluster metadata shape from the default test server.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- iggy::ffi::ClusterMetadata metadata{};
- ASSERT_NO_THROW({ metadata = client->get_cluster_metadata(); });
+ const auto metadata = client.GetClusterMetadata();
- EXPECT_EQ(static_cast<std::string>(metadata.name), "single-node");
- ASSERT_EQ(metadata.nodes.size(), 1u);
+ EXPECT_EQ(metadata.Name(), "single-node");
+ ASSERT_EQ(metadata.Nodes().size(), 1u);
- const auto &node = metadata.nodes[0];
- EXPECT_FALSE(static_cast<std::string>(node.name).empty());
- EXPECT_FALSE(static_cast<std::string>(node.ip).empty());
- EXPECT_EQ(static_cast<std::string>(node.role), "leader");
- EXPECT_EQ(static_cast<std::string>(node.status), "healthy");
- EXPECT_NE(node.endpoints.tcp, 0u);
- EXPECT_NE(node.endpoints.http, 0u);
+ const auto &node = metadata.Nodes()[0];
+ EXPECT_EQ(node.ReplicaId(), 0u);
+ EXPECT_FALSE(node.Name().empty());
+ EXPECT_FALSE(node.Ip().empty());
+ EXPECT_EQ(node.Role(), "leader");
+ EXPECT_EQ(node.Status(), "healthy");
+ EXPECT_NE(node.Endpoints().Tcp(), 0u);
+ EXPECT_NE(node.Endpoints().Http(), 0u);
}
-TEST_F(LowLevelE2E_Client, GetClusterMetadataIsStableAcrossBackToBackCalls) {
+TEST_F(E2E_Client, GetClusterMetadataIsStableAcrossBackToBackCalls) {
RecordProperty("description", "Returns stable single-node cluster metadata across back-to-back calls.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- iggy::ffi::ClusterMetadata first_metadata{};
- iggy::ffi::ClusterMetadata second_metadata{};
- ASSERT_NO_THROW({
- first_metadata = client->get_cluster_metadata();
- second_metadata = client->get_cluster_metadata();
- });
+ const auto first_metadata = client.GetClusterMetadata();
+ const auto second_metadata = client.GetClusterMetadata();
- EXPECT_EQ(static_cast<std::string>(first_metadata.name), static_cast<std::string>(second_metadata.name));
- ASSERT_EQ(first_metadata.nodes.size(), 1u);
- ASSERT_EQ(second_metadata.nodes.size(), 1u);
+ EXPECT_EQ(first_metadata.Name(), second_metadata.Name());
+ ASSERT_EQ(first_metadata.Nodes().size(), 1u);
+ ASSERT_EQ(second_metadata.Nodes().size(), 1u);
- const auto &first_node = first_metadata.nodes[0];
- const auto &second_node = second_metadata.nodes[0];
- EXPECT_EQ(static_cast<std::string>(first_node.name), static_cast<std::string>(second_node.name));
- EXPECT_EQ(static_cast<std::string>(first_node.ip), static_cast<std::string>(second_node.ip));
- EXPECT_EQ(static_cast<std::string>(first_node.role), static_cast<std::string>(second_node.role));
- EXPECT_EQ(static_cast<std::string>(first_node.status), static_cast<std::string>(second_node.status));
- EXPECT_EQ(first_node.endpoints.tcp, second_node.endpoints.tcp);
- EXPECT_EQ(first_node.endpoints.quic, second_node.endpoints.quic);
- EXPECT_EQ(first_node.endpoints.http, second_node.endpoints.http);
- EXPECT_EQ(first_node.endpoints.websocket, second_node.endpoints.websocket);
+ const auto &first_node = first_metadata.Nodes()[0];
+ const auto &second_node = second_metadata.Nodes()[0];
+ EXPECT_EQ(first_node.ReplicaId(), second_node.ReplicaId());
+ EXPECT_EQ(first_node.Name(), second_node.Name());
+ EXPECT_EQ(first_node.Ip(), second_node.Ip());
+ EXPECT_EQ(first_node.Role(), second_node.Role());
+ EXPECT_EQ(first_node.Status(), second_node.Status());
+ EXPECT_EQ(first_node.Endpoints().Tcp(), second_node.Endpoints().Tcp());
+ EXPECT_EQ(first_node.Endpoints().Quic(), second_node.Endpoints().Quic());
+ EXPECT_EQ(first_node.Endpoints().Http(), second_node.Endpoints().Http());
+ EXPECT_EQ(first_node.Endpoints().Websocket(), second_node.Endpoints().Websocket());
}
-TEST_F(LowLevelE2E_Client, PingSucceedsForNewConnection) {
+TEST_F(E2E_Client, PingSucceedsForNewConnection) {
RecordProperty("description", "Successfully pings the server from a fresh unauthenticated client session.");
- iggy::ffi::Client *client = GetLoggedOutClient();
+ auto client = GetLoggedOutHighLevelClient();
// The VSR client has no lazy connect; ping still needs no authentication.
- ASSERT_NO_THROW(client->connect());
- ASSERT_NO_THROW(client->ping());
+ ASSERT_NO_THROW(client.Connect());
+ ASSERT_NO_THROW(client.Ping());
}
-TEST_F(LowLevelE2E_Client, HeartbeatIntervalReturnsDefaultValueForNewConnection) {
+TEST_F(E2E_Client, HeartbeatIntervalReturnsDefaultValueForNewConnection) {
RecordProperty("description",
"Returns the default heartbeat interval in microseconds for a fresh unauthenticated client.");
- constexpr std::uint64_t default_heartbeat_micros = 5'000'000ull;
- iggy::ffi::Client *client = GetLoggedOutClient();
+ constexpr std::int64_t default_heartbeat_micros = 5'000'000;
+ auto client = GetLoggedOutHighLevelClient();
- const auto heartbeat_interval = client->heartbeat_interval();
- EXPECT_EQ(heartbeat_interval, default_heartbeat_micros);
+ EXPECT_EQ(client.HeartbeatInterval().count(), default_heartbeat_micros);
}
-TEST_F(LowLevelE2E_Client, HeartbeatIntervalReturnsConfiguredValueFromConnectionString) {
+TEST_F(E2E_Client, HeartbeatIntervalReturnsConfiguredValueFromConnectionString) {
RecordProperty("description",
"Returns the configured heartbeat interval in microseconds from the connection string.");
- constexpr std::uint64_t configured_heartbeat_micros = 10'000'000ull;
- iggy::ffi::Client *client = nullptr;
- ASSERT_NO_THROW(
- { client = iggy::ffi::from_connection_string("iggy://iggy:iggy@127.0.0.1:8090?heartbeat_interval=10s"); });
- ASSERT_NE(client, nullptr);
- TrackClient(client);
+ constexpr std::int64_t configured_heartbeat_micros = 10'000'000;
+ auto client =
+ iggy::IggyBlockingClient::FromConnectionString("iggy://iggy:iggy@127.0.0.1:8090?heartbeat_interval=10s");
- const auto heartbeat_interval = client->heartbeat_interval();
- EXPECT_EQ(heartbeat_interval, configured_heartbeat_micros);
+ EXPECT_EQ(client.HeartbeatInterval().count(), configured_heartbeat_micros);
}
-TEST_F(LowLevelE2E_Client, SnapshotBeforeLoginThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, SnapshotBeforeLoginThrows) {
RecordProperty("description",
"Rejects snapshot before connect, after connect but before login, and after disconnect.");
- iggy::ffi::Client *client = GetLoggedOutClient();
+ auto client = GetLoggedOutHighLevelClient();
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"test"})), std::exception);
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()}),
+ std::exception);
- ASSERT_NO_THROW(client->connect());
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"test"})), std::exception);
- ASSERT_NO_THROW(client->login_user("iggy", "iggy", error_info));
- ASSERT_NO_THROW(client->disconnect());
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"test"})), std::exception);
+ ASSERT_NO_THROW(client.Connect());
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()}),
+ std::exception);
+ ASSERT_NO_THROW(client.Login("iggy", "iggy"));
+ ASSERT_NO_THROW(client.Disconnect());
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()}),
+ std::exception);
}
-TEST_F(LowLevelE2E_Client, SnapshotAllCombinedWithOtherTypeThrows) {
+TEST_F(E2E_Client, SnapshotAllCombinedWithOtherTypeThrows) {
RecordProperty("description", "Rejects combining the all snapshot type with any other snapshot type.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"all", "test"})), std::exception);
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(),
+ {iggy::SystemSnapshotType::All(), iggy::SystemSnapshotType::Test()}),
+ std::exception);
}
-TEST_F(LowLevelE2E_Client, SnapshotWithEmptySnapshotTypesThrows) {
+TEST_F(E2E_Client, SnapshotWithEmptySnapshotTypesThrows) {
RecordProperty("description", "Rejects an empty snapshot type list in the wrapper before sending.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<rust::String> snapshot_types;
- ASSERT_THROW(client->snapshot("deflated", snapshot_types), std::exception);
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), std::vector<iggy::SystemSnapshotType>{}),
+ std::exception);
}
-TEST_F(LowLevelE2E_Client, SnapshotReturnsNonEmptyBytes) {
+TEST_F(E2E_Client, SnapshotReturnsNonEmptyBytes) {
RecordProperty("description", "Returns a non-empty snapshot for a valid compression and snapshot type.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> snapshot_bytes;
- ASSERT_NO_THROW({ snapshot_bytes = client->snapshot("deflated", make_snapshot_types({"test"})); });
+ std::vector<std::uint8_t> snapshot_bytes;
+ ASSERT_NO_THROW({
+ snapshot_bytes = client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()});
+ });
EXPECT_FALSE(snapshot_bytes.empty());
}
-TEST_F(LowLevelE2E_Client, SnapshotWithInvalidCompressionThrows) {
- RecordProperty("description",
- "Rejects empty or invalid snapshot compression values in the wrapper before sending.");
- iggy::ffi::Client *client = GetLoggedInClient();
-
- ASSERT_THROW(client->snapshot("", make_snapshot_types({"test"})), std::exception);
- ASSERT_THROW(client->snapshot("invalid-compression", make_snapshot_types({"test"})), std::exception);
-}
-
-TEST_F(LowLevelE2E_Client, SnapshotWithInvalidSnapshotTypeThrows) {
- RecordProperty("description", "Rejects invalid snapshot type values in the wrapper before sending.");
- iggy::ffi::Client *client = GetLoggedInClient();
-
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"not-a-real-type"})), std::exception);
-}
-
-TEST_F(LowLevelE2E_Client, SendBinaryRequestPingReturnsEmptyBytes) {
+TEST_F(E2E_Client, SendBinaryRequestPingReturnsEmptyBytes) {
RecordProperty("description", "Returns an empty response body for a raw ping command with an empty payload.");
constexpr std::uint32_t ping_command_code = 1;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> empty_payload;
- rust::Vec<std::uint8_t> response;
- ASSERT_NO_THROW({
- iggy::ffi::ErrorInfo error_info{};
- response = client->send_binary_request(ping_command_code, empty_payload, error_info);
- });
+ const std::vector<std::uint8_t> empty_payload;
+ std::vector<std::uint8_t> response;
+ ASSERT_NO_THROW({ response = client.SendBinaryRequest(ping_command_code, empty_payload); });
EXPECT_TRUE(response.empty());
}
-TEST_F(LowLevelE2E_Client, SendBinaryRequestGetStatsReturnsNonEmptyBytes) {
+TEST_F(E2E_Client, SendBinaryRequestGetStatsReturnsNonEmptyBytes) {
RecordProperty("description",
"Returns a non-empty response body for a raw get-stats command with an empty payload.");
constexpr std::uint32_t get_stats_command_code = 10;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> empty_payload;
- rust::Vec<std::uint8_t> response;
- ASSERT_NO_THROW({
- iggy::ffi::ErrorInfo error_info{};
- response = client->send_binary_request(get_stats_command_code, empty_payload, error_info);
- });
+ const std::vector<std::uint8_t> empty_payload;
+ std::vector<std::uint8_t> response;
+ ASSERT_NO_THROW({ response = client.SendBinaryRequest(get_stats_command_code, empty_payload); });
EXPECT_FALSE(response.empty());
}
-TEST_F(LowLevelE2E_Client, SendBinaryRequestLoginUserCodeThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, SendBinaryRequestLoginUserCodeThrows) {
RecordProperty("description",
"Rejects the login-user session-control code client-side before it reaches the server.");
constexpr std::uint32_t login_user_command_code = 38;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> empty_payload;
- ASSERT_THROW(client->send_binary_request(login_user_command_code, empty_payload, error_info), std::exception);
+ const std::vector<std::uint8_t> empty_payload;
+ ASSERT_THROW(client.SendBinaryRequest(login_user_command_code, empty_payload), std::exception);
}
-TEST_F(LowLevelE2E_Client, SendBinaryRequestUnknownCommandCodeThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, SendBinaryRequestUnknownCommandCodeThrows) {
RecordProperty("description", "Rejects an unknown command code with an invalid-command error from the server.");
constexpr std::uint32_t unknown_command_code = 60000;
- constexpr std::uint32_t invalid_command_code = 3;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
+
+ const std::vector<std::uint8_t> empty_payload;
+ ASSERT_THROW(client.SendBinaryRequest(unknown_command_code, empty_payload), std::exception);
+}
+
+TEST_F(E2E_Client, ConsumerOffsetStoreGetDeleteRoundTrip) {
+ RecordProperty("description", "Stores a consumer offset, reads it back, and deletes it.");
+ const std::string stream_name = GetRandomName();
+ auto client = GetLoggedInHighLevelClient();
+
+ client.CreateStream(stream_name);
+ const auto stream = client.GetStream(iggy::Identifier::String(stream_name));
+ TrackStream(stream.Id());
+ const std::string topic_name = GetRandomName();
+ client.CreateTopic(iggy::Identifier::Numeric(stream.Id()), topic_name,
+ iggy::TopicCreateOptions().SetPartitionsCount(1));
+
+ std::vector<iggy::IggyMessageToSend> messages;
+ for (std::uint32_t i = 0; i < 5; i++) {
+ messages.push_back(
+ iggy::IggyMessageToSend::Create("offset-" + std::to_string(i), std::vector<iggy::HeaderEntry>()));
+ }
+ client.SendMessages(iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0),
+ iggy::Partitioning::PartitionId(0), messages);
+
+ const auto consumer = iggy::Consumer::Single(iggy::Identifier::String("offset-consumer"));
+ ASSERT_NO_THROW(client.StoreConsumerOffset(consumer, iggy::Identifier::Numeric(stream.Id()),
+ iggy::Identifier::Numeric(0), 0, 2));
+
+ auto stored =
+ client.GetConsumerOffset(consumer, iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0), 0);
+ EXPECT_EQ(stored.PartitionId(), 0u);
+ EXPECT_EQ(stored.StoredOffset(), 2u);
+
+ ASSERT_NO_THROW(
+ client.DeleteConsumerOffset(consumer, iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0), 0));
+
+ ASSERT_THROW(
+ client.GetConsumerOffset(consumer, iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0), 0),
+ iggy::IggyException);
+}
+
+TEST_F(E2E_Client, ConsumerOffsetKeepsDistinctConsumersApart) {
+ RecordProperty("description", "Each consumer owns its stored offset on the same partition.");
+ const std::string stream_name = GetRandomName();
+ auto client = GetLoggedInHighLevelClient();
+
+ client.CreateStream(stream_name);
+ const auto stream = client.GetStream(iggy::Identifier::String(stream_name));
+ TrackStream(stream.Id());
+ const std::string topic_name = GetRandomName();
+ client.CreateTopic(iggy::Identifier::Numeric(stream.Id()), topic_name,
+ iggy::TopicCreateOptions().SetPartitionsCount(1));
+
+ std::vector<iggy::IggyMessageToSend> messages;
+ for (std::uint32_t i = 0; i < 5; i++) {
+ messages.push_back(
+ iggy::IggyMessageToSend::Create("offset-" + std::to_string(i), std::vector<iggy::HeaderEntry>()));
+ }
+ client.SendMessages(iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0),
+ iggy::Partitioning::PartitionId(0), messages);
+
+ const auto consumer_a = iggy::Consumer::Single(iggy::Identifier::String("consumer-a"));
+ const auto consumer_b = iggy::Consumer::Single(iggy::Identifier::String("consumer-b"));
+ client.StoreConsumerOffset(consumer_a, iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0), 0, 1);
+ client.StoreConsumerOffset(consumer_b, iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0), 0, 3);
- rust::Vec<std::uint8_t> empty_payload;
- ASSERT_THROW(client->send_binary_request(unknown_command_code, empty_payload, error_info), std::exception);
- EXPECT_EQ(error_info.code, invalid_command_code);
- EXPECT_EQ(std::string(error_info.kind), "invalid_command");
- EXPECT_FALSE(error_info.has_operation_id);
- EXPECT_EQ(error_info.operation_id, 0u);
+ auto offset_a =
+ client.GetConsumerOffset(consumer_a, iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0), 0);
+ auto offset_b =
+ client.GetConsumerOffset(consumer_b, iggy::Identifier::Numeric(stream.Id()), iggy::Identifier::Numeric(0), 0);
+
+ EXPECT_EQ(offset_a.StoredOffset(), 1u);
+ EXPECT_EQ(offset_b.StoredOffset(), 3u);
}
-TEST_F(LowLevelE2E_Client, SendBinaryRequestToReplicaGetStatsReturnsNonEmptyBytes) {
+TEST_F(E2E_Client, StoreConsumerOffsetRejectsMissingPartitionId) {
+ RecordProperty("description", "Storing an offset needs an explicit partition.");
+ const std::string stream_name = GetRandomName();
+ auto client = GetLoggedInHighLevelClient();
+
+ client.CreateStream(stream_name);
+ const auto stream = client.GetStream(iggy::Identifier::String(stream_name));
+ TrackStream(stream.Id());
+ const std::string topic_name = GetRandomName();
+ client.CreateTopic(iggy::Identifier::Numeric(stream.Id()), topic_name,
+ iggy::TopicCreateOptions().SetPartitionsCount(1));
+
+ const auto consumer = iggy::Consumer::Single(iggy::Identifier::String("offset-consumer"));
+ ASSERT_THROW(client.StoreConsumerOffset(consumer, iggy::Identifier::Numeric(stream.Id()),
Review Comment:
The topic has one empty partition, and the server rejects offset 1 there with `InvalidOffset` even when the call names partition 0 (`core/partitions/src/iggy_partition.rs:5565`, which `foreign/cpp/tests/e2e/consumer_group.cpp:1203` relies on). The test checks only the exception type, so it passes whether or not a missing partition is refused, and a wrapper that sent partition 0 for `std::nullopt` would still be green.
##########
foreign/cpp/include/iggy.hpp:
##########
@@ -780,6 +790,17 @@ class Permissions final {
* A consumer offset belongs either to an individual consumer or to a consumer
* group. Create a value with Single() or Group(), then pass it to the consumer
* offset operations on IggyBlockingClient.
+ *
+ * Two callers naming the same identifier share one offset, so under
+ * PollingStrategy::Next() with auto-commit each sees only the messages the
+ * other has not read yet. Give every independent consumer its own name.
+ *
+ * @code{.cpp}
+ * const auto polled{client.PollMessages(
Review Comment:
The example polls as consumer group `my-group` without joining it, so the server rejects the call with `ConsumerGroupPartitionNotOwned` (5009). A group poll passes the fence at `core/server/src/dispatch/partition.rs:1297` only for a member that owns the partition, which is why the e2e group poll joins and waits for the assignment first (`foreign/cpp/tests/e2e/message.cpp:1460`). The paragraph above describes two callers sharing one offset under `Next()` with auto-commit, which holds for `Consumer::Single` and not for a group, so a reader who copies the example gets an exception instead of the split the text promises.
##########
foreign/cpp/src/client.cpp:
##########
@@ -280,14 +280,32 @@ TopicDetails IggyBlockingClient::CreateTopic(const Identifier &stream,
}
if (auto value = options.Durability()) {
ffi_options.has_durability = true;
- ffi_options.durability = std::string(iggy::to_string(*value));
+ switch (*value) {
Review Comment:
These two switches stand in for the public `iggy::to_string(Durability)` that this PR deletes from `iggy.hpp`, which breaks every caller of it and is not mentioned in the description. `Durability` is now the only option enum in the header without a `to_string`, and the field right below still calls `to_string(PartitionResizePolicy)` (`foreign/cpp/src/client.cpp:315`).
##########
foreign/cpp/include/iggy.hpp:
##########
@@ -7427,6 +7798,181 @@ class IggyBlockingClient final {
*/
Stats GetStats();
+ /**
+ * @brief Sends a non-empty batch of messages to one topic.
+ *
+ * The partitioning strategy selects one destination for the entire batch.
+ * Message and partitioning constraints are validated before the request is
+ * sent. The Rust SDK assigns generated IDs to messages whose ID is zero;
+ * the C++ input objects are not modified.
+ *
+ * Delivery is at least once. A failed or unknown transport outcome can
+ * leave the batch committed, so retrying may append duplicates. Successful
+ * responses can contain no confirmations; see SendMessagesResponse.
+ *
+ * @param stream Parent stream, addressed by numeric ID or name.
+ * @param topic Destination topic, addressed by numeric ID or name.
+ * @param partitioning Strategy used to select the destination partition.
+ * @param messages Messages to send. The collection must not be empty.
+ * @return Available per-partition commit confirmations.
+ * @throws IggyException if the client is unavailable or unauthenticated;
+ * an identifier, partitioning strategy, message, or batch is
+ * invalid; the stream, topic, or partition does not exist; the
+ * caller lacks send permission; or the request fails.
+ */
+ SendMessagesResponse SendMessages(const Identifier &stream,
+ const Identifier &topic,
+ const Partitioning &partitioning,
+ const std::vector<IggyMessageToSend> &messages);
+
+ /**
+ * @brief Reads one partition with a message count and optional on-read commit.
+ *
+ * A partition is required. For additional read limits or a continuation,
+ * use the overload taking PollOptions.
+ */
+ PolledMessages PollMessages(const Identifier &stream,
+ const Identifier &topic,
+ std::uint32_t partition_id,
+ const Consumer &consumer,
+ const PollingStrategy &strategy,
+ std::uint32_t count,
+ bool auto_commit);
+
+ /**
+ * @brief Retrieves the server's option catalog for one resource type.
+ *
+ * The catalog describes accepted create-option keys, their HeaderKind
+ * encodings, defaults, and descriptions. A supported scope with no option
+ * keys returns an empty collection.
+ *
+ * @param scope Resource scope. Must be `topic`, `stream`, or `user`.
+ * @return Option specifications for the requested scope.
+ * @throws IggyException if the client is unavailable or unauthenticated;
+ * @p scope is invalid; or the request fails.
+ */
+ std::vector<OptionSpec> DescribeOptions(std::string scope);
Review Comment:
Master already has `DescribeOptions(OptionsScope)` (`foreign/cpp/include/iggy.hpp:7766`), and this overload repeats its body (`foreign/cpp/src/client.cpp:603` against `foreign/cpp/src/client.cpp:550`) with a string the compiler cannot check. A misspelled scope now reaches the bridge and fails at run time, as `foreign/cpp/tests/e2e/topic.cpp:374` shows, where the enum made that impossible. The description lists `DescribeOptions` among the added functions, but it existed before this PR.
##########
foreign/cpp/tests/e2e/client.cpp:
##########
@@ -2843,264 +2730,319 @@ TEST_F(E2E_Client, GetClientsReflectsAdditionalSession) {
EXPECT_TRUE(found_after);
}
-TEST_F(LowLevelE2E_Client, GetClusterMetadataBeforeLoginThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, GetClusterMetadataBeforeLoginThrows) {
RecordProperty("description",
"Rejects get_cluster_metadata before connect, after connect but before login, and after disconnect, "
"and serves it once authenticated.");
- iggy::ffi::Client *client = GetLoggedOutClient();
+ auto client = GetLoggedOutHighLevelClient();
- ASSERT_THROW(client->get_cluster_metadata(), std::exception);
- ASSERT_NO_THROW(client->connect());
+ ASSERT_THROW(client.GetClusterMetadata(), std::exception);
+ ASSERT_NO_THROW(client.Connect());
// The roster is private, so the read is auth-gated. No pre-login read is
// needed: a client that dialed a backup logs in there and the server
// forwards the register to the primary.
- ASSERT_THROW(client->get_cluster_metadata(), std::exception);
- ASSERT_NO_THROW(client->login_user("iggy", "iggy", error_info));
- iggy::ffi::ClusterMetadata metadata{};
- ASSERT_NO_THROW({ metadata = client->get_cluster_metadata(); });
- ASSERT_GE(metadata.nodes.size(), 1u);
- ASSERT_NO_THROW(client->disconnect());
- ASSERT_THROW(client->get_cluster_metadata(), std::exception);
+ ASSERT_THROW(client.GetClusterMetadata(), std::exception);
+ ASSERT_NO_THROW(client.Login("iggy", "iggy"));
+ ASSERT_NO_THROW({
+ const auto metadata = client.GetClusterMetadata();
+ ASSERT_GE(metadata.Nodes().size(), 1u);
+ });
+ ASSERT_NO_THROW(client.Disconnect());
+ ASSERT_THROW(client.GetClusterMetadata(), std::exception);
}
-TEST_F(LowLevelE2E_Client, GetClusterMetadataReturnsSingleNodeMetadata) {
+TEST_F(E2E_Client, GetClusterMetadataReturnsSingleNodeMetadata) {
RecordProperty("description",
"Returns the expected single-node cluster metadata shape from the default test server.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- iggy::ffi::ClusterMetadata metadata{};
- ASSERT_NO_THROW({ metadata = client->get_cluster_metadata(); });
+ const auto metadata = client.GetClusterMetadata();
- EXPECT_EQ(static_cast<std::string>(metadata.name), "single-node");
- ASSERT_EQ(metadata.nodes.size(), 1u);
+ EXPECT_EQ(metadata.Name(), "single-node");
+ ASSERT_EQ(metadata.Nodes().size(), 1u);
- const auto &node = metadata.nodes[0];
- EXPECT_FALSE(static_cast<std::string>(node.name).empty());
- EXPECT_FALSE(static_cast<std::string>(node.ip).empty());
- EXPECT_EQ(static_cast<std::string>(node.role), "leader");
- EXPECT_EQ(static_cast<std::string>(node.status), "healthy");
- EXPECT_NE(node.endpoints.tcp, 0u);
- EXPECT_NE(node.endpoints.http, 0u);
+ const auto &node = metadata.Nodes()[0];
+ EXPECT_EQ(node.ReplicaId(), 0u);
+ EXPECT_FALSE(node.Name().empty());
+ EXPECT_FALSE(node.Ip().empty());
+ EXPECT_EQ(node.Role(), "leader");
+ EXPECT_EQ(node.Status(), "healthy");
+ EXPECT_NE(node.Endpoints().Tcp(), 0u);
+ EXPECT_NE(node.Endpoints().Http(), 0u);
}
-TEST_F(LowLevelE2E_Client, GetClusterMetadataIsStableAcrossBackToBackCalls) {
+TEST_F(E2E_Client, GetClusterMetadataIsStableAcrossBackToBackCalls) {
RecordProperty("description", "Returns stable single-node cluster metadata across back-to-back calls.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- iggy::ffi::ClusterMetadata first_metadata{};
- iggy::ffi::ClusterMetadata second_metadata{};
- ASSERT_NO_THROW({
- first_metadata = client->get_cluster_metadata();
- second_metadata = client->get_cluster_metadata();
- });
+ const auto first_metadata = client.GetClusterMetadata();
+ const auto second_metadata = client.GetClusterMetadata();
- EXPECT_EQ(static_cast<std::string>(first_metadata.name), static_cast<std::string>(second_metadata.name));
- ASSERT_EQ(first_metadata.nodes.size(), 1u);
- ASSERT_EQ(second_metadata.nodes.size(), 1u);
+ EXPECT_EQ(first_metadata.Name(), second_metadata.Name());
+ ASSERT_EQ(first_metadata.Nodes().size(), 1u);
+ ASSERT_EQ(second_metadata.Nodes().size(), 1u);
- const auto &first_node = first_metadata.nodes[0];
- const auto &second_node = second_metadata.nodes[0];
- EXPECT_EQ(static_cast<std::string>(first_node.name), static_cast<std::string>(second_node.name));
- EXPECT_EQ(static_cast<std::string>(first_node.ip), static_cast<std::string>(second_node.ip));
- EXPECT_EQ(static_cast<std::string>(first_node.role), static_cast<std::string>(second_node.role));
- EXPECT_EQ(static_cast<std::string>(first_node.status), static_cast<std::string>(second_node.status));
- EXPECT_EQ(first_node.endpoints.tcp, second_node.endpoints.tcp);
- EXPECT_EQ(first_node.endpoints.quic, second_node.endpoints.quic);
- EXPECT_EQ(first_node.endpoints.http, second_node.endpoints.http);
- EXPECT_EQ(first_node.endpoints.websocket, second_node.endpoints.websocket);
+ const auto &first_node = first_metadata.Nodes()[0];
+ const auto &second_node = second_metadata.Nodes()[0];
+ EXPECT_EQ(first_node.ReplicaId(), second_node.ReplicaId());
+ EXPECT_EQ(first_node.Name(), second_node.Name());
+ EXPECT_EQ(first_node.Ip(), second_node.Ip());
+ EXPECT_EQ(first_node.Role(), second_node.Role());
+ EXPECT_EQ(first_node.Status(), second_node.Status());
+ EXPECT_EQ(first_node.Endpoints().Tcp(), second_node.Endpoints().Tcp());
+ EXPECT_EQ(first_node.Endpoints().Quic(), second_node.Endpoints().Quic());
+ EXPECT_EQ(first_node.Endpoints().Http(), second_node.Endpoints().Http());
+ EXPECT_EQ(first_node.Endpoints().Websocket(), second_node.Endpoints().Websocket());
}
-TEST_F(LowLevelE2E_Client, PingSucceedsForNewConnection) {
+TEST_F(E2E_Client, PingSucceedsForNewConnection) {
RecordProperty("description", "Successfully pings the server from a fresh unauthenticated client session.");
- iggy::ffi::Client *client = GetLoggedOutClient();
+ auto client = GetLoggedOutHighLevelClient();
// The VSR client has no lazy connect; ping still needs no authentication.
- ASSERT_NO_THROW(client->connect());
- ASSERT_NO_THROW(client->ping());
+ ASSERT_NO_THROW(client.Connect());
+ ASSERT_NO_THROW(client.Ping());
}
-TEST_F(LowLevelE2E_Client, HeartbeatIntervalReturnsDefaultValueForNewConnection) {
+TEST_F(E2E_Client, HeartbeatIntervalReturnsDefaultValueForNewConnection) {
RecordProperty("description",
"Returns the default heartbeat interval in microseconds for a fresh unauthenticated client.");
- constexpr std::uint64_t default_heartbeat_micros = 5'000'000ull;
- iggy::ffi::Client *client = GetLoggedOutClient();
+ constexpr std::int64_t default_heartbeat_micros = 5'000'000;
+ auto client = GetLoggedOutHighLevelClient();
- const auto heartbeat_interval = client->heartbeat_interval();
- EXPECT_EQ(heartbeat_interval, default_heartbeat_micros);
+ EXPECT_EQ(client.HeartbeatInterval().count(), default_heartbeat_micros);
}
-TEST_F(LowLevelE2E_Client, HeartbeatIntervalReturnsConfiguredValueFromConnectionString) {
+TEST_F(E2E_Client, HeartbeatIntervalReturnsConfiguredValueFromConnectionString) {
RecordProperty("description",
"Returns the configured heartbeat interval in microseconds from the connection string.");
- constexpr std::uint64_t configured_heartbeat_micros = 10'000'000ull;
- iggy::ffi::Client *client = nullptr;
- ASSERT_NO_THROW(
- { client = iggy::ffi::from_connection_string("iggy://iggy:iggy@127.0.0.1:8090?heartbeat_interval=10s"); });
- ASSERT_NE(client, nullptr);
- TrackClient(client);
+ constexpr std::int64_t configured_heartbeat_micros = 10'000'000;
+ auto client =
+ iggy::IggyBlockingClient::FromConnectionString("iggy://iggy:iggy@127.0.0.1:8090?heartbeat_interval=10s");
- const auto heartbeat_interval = client->heartbeat_interval();
- EXPECT_EQ(heartbeat_interval, configured_heartbeat_micros);
+ EXPECT_EQ(client.HeartbeatInterval().count(), configured_heartbeat_micros);
}
-TEST_F(LowLevelE2E_Client, SnapshotBeforeLoginThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, SnapshotBeforeLoginThrows) {
RecordProperty("description",
"Rejects snapshot before connect, after connect but before login, and after disconnect.");
- iggy::ffi::Client *client = GetLoggedOutClient();
+ auto client = GetLoggedOutHighLevelClient();
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"test"})), std::exception);
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()}),
+ std::exception);
- ASSERT_NO_THROW(client->connect());
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"test"})), std::exception);
- ASSERT_NO_THROW(client->login_user("iggy", "iggy", error_info));
- ASSERT_NO_THROW(client->disconnect());
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"test"})), std::exception);
+ ASSERT_NO_THROW(client.Connect());
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()}),
+ std::exception);
+ ASSERT_NO_THROW(client.Login("iggy", "iggy"));
+ ASSERT_NO_THROW(client.Disconnect());
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()}),
+ std::exception);
}
-TEST_F(LowLevelE2E_Client, SnapshotAllCombinedWithOtherTypeThrows) {
+TEST_F(E2E_Client, SnapshotAllCombinedWithOtherTypeThrows) {
RecordProperty("description", "Rejects combining the all snapshot type with any other snapshot type.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"all", "test"})), std::exception);
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(),
+ {iggy::SystemSnapshotType::All(), iggy::SystemSnapshotType::Test()}),
+ std::exception);
}
-TEST_F(LowLevelE2E_Client, SnapshotWithEmptySnapshotTypesThrows) {
+TEST_F(E2E_Client, SnapshotWithEmptySnapshotTypesThrows) {
RecordProperty("description", "Rejects an empty snapshot type list in the wrapper before sending.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<rust::String> snapshot_types;
- ASSERT_THROW(client->snapshot("deflated", snapshot_types), std::exception);
+ ASSERT_THROW(client.Snapshot(iggy::SnapshotCompression::Deflated(), std::vector<iggy::SystemSnapshotType>{}),
+ std::exception);
}
-TEST_F(LowLevelE2E_Client, SnapshotReturnsNonEmptyBytes) {
+TEST_F(E2E_Client, SnapshotReturnsNonEmptyBytes) {
RecordProperty("description", "Returns a non-empty snapshot for a valid compression and snapshot type.");
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> snapshot_bytes;
- ASSERT_NO_THROW({ snapshot_bytes = client->snapshot("deflated", make_snapshot_types({"test"})); });
+ std::vector<std::uint8_t> snapshot_bytes;
+ ASSERT_NO_THROW({
+ snapshot_bytes = client.Snapshot(iggy::SnapshotCompression::Deflated(), {iggy::SystemSnapshotType::Test()});
+ });
EXPECT_FALSE(snapshot_bytes.empty());
}
-TEST_F(LowLevelE2E_Client, SnapshotWithInvalidCompressionThrows) {
- RecordProperty("description",
- "Rejects empty or invalid snapshot compression values in the wrapper before sending.");
- iggy::ffi::Client *client = GetLoggedInClient();
-
- ASSERT_THROW(client->snapshot("", make_snapshot_types({"test"})), std::exception);
- ASSERT_THROW(client->snapshot("invalid-compression", make_snapshot_types({"test"})), std::exception);
-}
-
-TEST_F(LowLevelE2E_Client, SnapshotWithInvalidSnapshotTypeThrows) {
- RecordProperty("description", "Rejects invalid snapshot type values in the wrapper before sending.");
- iggy::ffi::Client *client = GetLoggedInClient();
-
- ASSERT_THROW(client->snapshot("deflated", make_snapshot_types({"not-a-real-type"})), std::exception);
-}
-
-TEST_F(LowLevelE2E_Client, SendBinaryRequestPingReturnsEmptyBytes) {
+TEST_F(E2E_Client, SendBinaryRequestPingReturnsEmptyBytes) {
RecordProperty("description", "Returns an empty response body for a raw ping command with an empty payload.");
constexpr std::uint32_t ping_command_code = 1;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> empty_payload;
- rust::Vec<std::uint8_t> response;
- ASSERT_NO_THROW({
- iggy::ffi::ErrorInfo error_info{};
- response = client->send_binary_request(ping_command_code, empty_payload, error_info);
- });
+ const std::vector<std::uint8_t> empty_payload;
+ std::vector<std::uint8_t> response;
+ ASSERT_NO_THROW({ response = client.SendBinaryRequest(ping_command_code, empty_payload); });
EXPECT_TRUE(response.empty());
}
-TEST_F(LowLevelE2E_Client, SendBinaryRequestGetStatsReturnsNonEmptyBytes) {
+TEST_F(E2E_Client, SendBinaryRequestGetStatsReturnsNonEmptyBytes) {
RecordProperty("description",
"Returns a non-empty response body for a raw get-stats command with an empty payload.");
constexpr std::uint32_t get_stats_command_code = 10;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> empty_payload;
- rust::Vec<std::uint8_t> response;
- ASSERT_NO_THROW({
- iggy::ffi::ErrorInfo error_info{};
- response = client->send_binary_request(get_stats_command_code, empty_payload, error_info);
- });
+ const std::vector<std::uint8_t> empty_payload;
+ std::vector<std::uint8_t> response;
+ ASSERT_NO_THROW({ response = client.SendBinaryRequest(get_stats_command_code, empty_payload); });
EXPECT_FALSE(response.empty());
}
-TEST_F(LowLevelE2E_Client, SendBinaryRequestLoginUserCodeThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, SendBinaryRequestLoginUserCodeThrows) {
RecordProperty("description",
"Rejects the login-user session-control code client-side before it reaches the server.");
constexpr std::uint32_t login_user_command_code = 38;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
- rust::Vec<std::uint8_t> empty_payload;
- ASSERT_THROW(client->send_binary_request(login_user_command_code, empty_payload, error_info), std::exception);
+ const std::vector<std::uint8_t> empty_payload;
+ ASSERT_THROW(client.SendBinaryRequest(login_user_command_code, empty_payload), std::exception);
}
-TEST_F(LowLevelE2E_Client, SendBinaryRequestUnknownCommandCodeThrows) {
- iggy::ffi::ErrorInfo error_info{};
+TEST_F(E2E_Client, SendBinaryRequestUnknownCommandCodeThrows) {
RecordProperty("description", "Rejects an unknown command code with an invalid-command error from the server.");
constexpr std::uint32_t unknown_command_code = 60000;
- constexpr std::uint32_t invalid_command_code = 3;
- iggy::ffi::Client *client = GetLoggedInClient();
+ auto client = GetLoggedInHighLevelClient();
+
+ const std::vector<std::uint8_t> empty_payload;
+ ASSERT_THROW(client.SendBinaryRequest(unknown_command_code, empty_payload), std::exception);
Review Comment:
The README now promises `Code()` and `Kind()` for `SendMessages` and `SendBinaryRequest` (`foreign/cpp/README.md:110`), but this port dropped the checks of code 3, kind `invalid_command` and the empty operation ID that the bridge version of the test made. No test reads `Code()` or `Kind()` from an exception of either call, so a wrapper that stops passing `ErrorInfo` to the bridge still passes the suite.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@iggy.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org