| /* |
| * 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. |
| */ |
| |
| #pragma once |
| |
| #include <cstddef> |
| /** |
| * @file iggy.hpp |
| * @brief Public C++ API for the Apache Iggy client. |
| */ |
| |
| #include <chrono> |
| #include <cstdint> |
| #include <limits> |
| #include <optional> |
| #include <stdexcept> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "lib.rs.h" |
| |
| namespace iggy { |
| |
| class IggyBlockingClient; |
| class LoginInfo; |
| class Partition; |
| class Topic; |
| class TopicDetails; |
| class Stream; |
| class StreamDetails; |
| |
| /** |
| * @brief Exception thrown when an Iggy client operation fails. |
| */ |
| class IggyException : public std::runtime_error { |
| public: |
| explicit IggyException(const char *message) : std::runtime_error(message) {} |
| explicit IggyException(const std::string &message) : std::runtime_error(message) {} |
| }; |
| |
| /** |
| * @brief Details returned after a successful login. |
| * |
| * The value identifies the authenticated user. HTTP logins also return the |
| * access token retained by the client for later requests. Stateful transports |
| * do not return an access token. The token is a credential: callers must not |
| * write it to logs or expose it to untrusted code. |
| */ |
| class LoginInfo final { |
| public: |
| /** |
| * @brief Returns the numeric ID of the authenticated user. |
| * @return Numeric user ID. |
| */ |
| std::uint32_t UserId() const noexcept { return user_id_; } |
| |
| /** |
| * @brief Returns the HTTP access token when the login returned one. |
| * @return Empty when the selected transport does not use an access token. |
| * A returned string view remains valid while this LoginInfo object |
| * is not modified or destroyed. |
| */ |
| std::optional<std::string_view> AccessToken() const noexcept { |
| if (!access_token_) { |
| return std::nullopt; |
| } |
| return *access_token_; |
| } |
| |
| /** |
| * @brief Returns the access-token expiry when a token was returned. |
| * @return Empty when no access token was returned; otherwise the |
| * server-provided expiry value. |
| */ |
| std::optional<std::uint64_t> AccessTokenExpiry() const noexcept { return access_token_expiry_; } |
| |
| private: |
| LoginInfo(std::uint32_t user_id, |
| std::optional<std::string> access_token, |
| std::optional<std::uint64_t> access_token_expiry) |
| : user_id_(user_id), access_token_(std::move(access_token)), access_token_expiry_(access_token_expiry) {} |
| |
| static LoginInfo FromFfi(ffi::LoginInfo login_info); |
| |
| friend class IggyBlockingClient; |
| |
| std::uint32_t user_id_; |
| std::optional<std::string> access_token_; |
| std::optional<std::uint64_t> access_token_expiry_; |
| }; |
| |
| /** |
| * @brief Identifier for a server resource. |
| * |
| * An identifier is either a numeric server ID or a name. The factories enforce |
| * the protocol's non-empty, 255-byte name limit. Numeric zero is valid because |
| * the server can assign zero as a resource ID. |
| */ |
| class Identifier final { |
| public: |
| enum class Kind { Numeric, String }; |
| |
| /** |
| * @brief Creates a numeric identifier. |
| * @param id Numeric server ID. |
| * @return Identifier that addresses @p id. |
| */ |
| static Identifier Numeric(std::uint32_t id) { return Identifier(Kind::Numeric, id); } |
| |
| /** |
| * @brief Creates a name-based identifier. |
| * @param name Resource name. |
| * @return Identifier that addresses @p name. |
| * @throws IggyException if @p name is empty or exceeds 255 bytes. |
| */ |
| static Identifier String(std::string name) { |
| if (name.empty() || name.size() > 255) { |
| throw IggyException("Identifier name must contain 1 to 255 bytes"); |
| } |
| return Identifier(Kind::String, std::move(name)); |
| } |
| |
| /** |
| * @brief Returns this identifier's representation. |
| * @return Kind::Numeric or Kind::String. |
| */ |
| Kind Type() const noexcept { return kind_; } |
| |
| /** |
| * @brief Returns the identifier payload. |
| * @return Numeric ID for Kind::Numeric, or a view of the name for |
| * Kind::String. The string view remains valid while this |
| * Identifier object is not modified or destroyed. |
| */ |
| std::variant<std::uint32_t, std::string_view> Value() const noexcept { |
| if (kind_ == Kind::Numeric) { |
| return std::get<std::uint32_t>(value_); |
| } |
| return std::string_view(std::get<std::string>(value_)); |
| } |
| |
| private: |
| Identifier(Kind kind, std::variant<std::uint32_t, std::string> value) : kind_(kind), value_(std::move(value)) {} |
| |
| static Identifier FromFfi(ffi::Identifier identifier); |
| ffi::Identifier ToFfi() const; |
| |
| friend class IggyBlockingClient; |
| |
| Kind kind_; |
| std::variant<std::uint32_t, std::string> value_; |
| }; |
| |
| /** |
| * @brief Type tag for a HeaderField payload. |
| * |
| * The tag and bytes use Iggy's header codec. Numeric payloads are little |
| * endian. Create TopicOption values instead of encoding catalog options by |
| * hand; use HeaderField directly only for application-defined headers. |
| */ |
| enum class HeaderKind : std::uint8_t { |
| Raw = 1, |
| String = 2, |
| Bool = 3, |
| Int8 = 4, |
| Int16 = 5, |
| Int32 = 6, |
| Int64 = 7, |
| Int128 = 8, |
| Uint8 = 9, |
| Uint16 = 10, |
| Uint32 = 11, |
| Uint64 = 12, |
| Uint128 = 13, |
| Float32 = 14, |
| Float64 = 15, |
| }; |
| |
| /** |
| * @brief One typed header key or value. |
| * |
| * HeaderField maps directly to Rust's header-field representation. Create() |
| * preserves the supplied bytes and does not verify that they match @p kind. |
| * Invalid key or value encodings are rejected when a request is sent. |
| */ |
| class HeaderField final { |
| public: |
| /** |
| * @brief Creates a typed header field from wire-encoded bytes. |
| * @param kind Type tag for @p value. |
| * @param value Payload encoded according to @p kind. |
| * @return Header field containing the supplied type and bytes. |
| */ |
| static HeaderField Create(HeaderKind kind, std::vector<std::uint8_t> value) { |
| return HeaderField(kind, std::move(value)); |
| } |
| |
| /** |
| * @brief Returns the wire type of Value(). |
| * @return Header type tag. |
| */ |
| HeaderKind Kind() const noexcept { return kind_; } |
| |
| /** |
| * @brief Returns bytes owned by this field. |
| * @return Payload encoded according to Kind(). |
| */ |
| const std::vector<std::uint8_t> &Value() const noexcept { return value_; } |
| |
| private: |
| HeaderField(HeaderKind kind, std::vector<std::uint8_t> value) : kind_(kind), value_(std::move(value)) {} |
| |
| static HeaderField FromFfi(ffi::HeaderField field); |
| static ffi::HeaderField ToFfi(HeaderField field); |
| |
| friend class HeaderEntry; |
| |
| HeaderKind kind_; |
| std::vector<std::uint8_t> value_; |
| }; |
| |
| /** |
| * @brief One typed header key-value pair. |
| * |
| * This maps directly to Rust's HeaderEntry. Topic options cross the C++ bridge |
| * as header entries because their keys and values use the same typed codec as |
| * message user headers. |
| */ |
| class HeaderEntry final { |
| public: |
| /** |
| * @brief Creates a header entry from its typed key and value. |
| * @param key Typed entry key. |
| * @param value Typed entry value. |
| * @return Header entry containing @p key and @p value. |
| */ |
| static HeaderEntry Create(HeaderField key, HeaderField value) { |
| return HeaderEntry(std::move(key), std::move(value)); |
| } |
| |
| /** |
| * @brief Returns the typed key. |
| * @return Key owned by this entry. |
| */ |
| const HeaderField &Key() const noexcept { return key_; } |
| |
| /** |
| * @brief Returns the typed value. |
| * @return Value owned by this entry. |
| */ |
| const HeaderField &Value() const noexcept { return value_; } |
| |
| private: |
| HeaderEntry(HeaderField key, HeaderField value) : key_(std::move(key)), value_(std::move(value)) {} |
| |
| static HeaderEntry FromFfi(ffi::HeaderEntry entry); |
| static ffi::HeaderEntry ToFfi(HeaderEntry entry); |
| |
| friend class ResourceOptions; |
| |
| HeaderField key_; |
| HeaderField value_; |
| }; |
| |
| /** |
| * @brief Creation options attached to a stream or topic. |
| * |
| * This maps to Rust's ResourceOptions. Explicit() contains entries supplied by |
| * the creating client. Derived() contains values the server resolved from its |
| * configuration at admission time. Derived values describe that resource's |
| * history and may differ if the resource is recreated under another server |
| * configuration. |
| * |
| * Construct creation options with, for example, |
| * `ResourceOptions::Explicit({TopicOption::SegmentSize(1024 * 1024)})`. |
| * Only explicit entries are sent; derived entries returned by Options() are |
| * not resubmitted. |
| */ |
| class ResourceOptions final { |
| public: |
| /** |
| * @brief Creates an empty option collection. |
| * @return Resource options with no explicit or derived entries. |
| */ |
| static ResourceOptions Empty() { return ResourceOptions({}); } |
| |
| /** |
| * @brief Creates request options from entries selected by the caller. |
| * @param entries Explicit option entries. |
| * @return Resource options that will submit @p entries. |
| */ |
| static ResourceOptions Explicit(std::vector<HeaderEntry> entries); |
| |
| /** |
| * @brief Returns entries supplied explicitly at resource creation. |
| * @return Explicit entries owned by this option collection. |
| */ |
| const std::vector<HeaderEntry> &Explicit() const noexcept { return explicit_; } |
| |
| /** |
| * @brief Returns entries derived from configured defaults at admission. |
| * @return Derived entries owned by this option collection. |
| * @note Stream responses currently expose explicit entries only, so this |
| * collection is empty for Stream and StreamDetails. |
| */ |
| const std::vector<HeaderEntry> &Derived() const noexcept { return derived_; } |
| |
| private: |
| explicit ResourceOptions(std::vector<HeaderEntry> explicit_entries) : explicit_(std::move(explicit_entries)) {} |
| ResourceOptions(std::vector<HeaderEntry> explicit_entries, std::vector<HeaderEntry> derived_entries) |
| : explicit_(std::move(explicit_entries)), derived_(std::move(derived_entries)) {} |
| |
| static ResourceOptions FromFfi(rust::Vec<ffi::HeaderEntry> explicit_entries, |
| rust::Vec<ffi::HeaderEntry> derived_entries); |
| static rust::Vec<ffi::HeaderEntry> ToFfi(ResourceOptions options); |
| |
| friend class IggyBlockingClient; |
| friend class Topic; |
| friend class TopicDetails; |
| friend class Stream; |
| friend class StreamDetails; |
| |
| std::vector<HeaderEntry> explicit_; |
| std::vector<HeaderEntry> derived_; |
| }; |
| |
| /** |
| * @brief Topic summary returned within StreamDetails. |
| * |
| * A Topic owns its string fields and contains no Rust bridge values. It |
| * describes server state observed during the enclosing stream read, not a live |
| * view. Its aggregate statistics and partition count can change immediately |
| * after that request completes. |
| * |
| * Options() distinguishes values selected by the creating client from values |
| * the server derived during admission. Partition details require GetTopic(). |
| */ |
| class Topic final { |
| public: |
| /** |
| * @brief Returns the numeric topic ID assigned within its stream. |
| * @return Numeric topic ID. |
| */ |
| std::uint32_t Id() const noexcept { return id_; } |
| |
| /** |
| * @brief Returns the server creation timestamp. |
| * @return Timestamp in microseconds. |
| */ |
| std::uint64_t CreatedAt() const noexcept { return created_at_; } |
| |
| /** |
| * @brief Returns the topic name. |
| * @return Name owned by this value. |
| */ |
| const std::string &Name() const noexcept { return name_; } |
| |
| /** |
| * @brief Returns the aggregate retained topic size. |
| * @return Size in bytes. |
| */ |
| std::uint64_t SizeBytes() const noexcept { return size_bytes_; } |
| |
| /** |
| * @brief Returns the server-encoded message retention value. |
| * @return Retention value in microseconds or a protocol sentinel. |
| */ |
| std::uint64_t MessageExpiry() const noexcept { return message_expiry_; } |
| |
| /** |
| * @brief Returns the server-selected storage compression algorithm. |
| * @return Algorithm name owned by this value. |
| */ |
| const std::string &CompressionAlgorithm() const noexcept { return compression_algorithm_; } |
| |
| /** |
| * @brief Returns the configured maximum retained topic size. |
| * @return Maximum size in bytes. |
| */ |
| std::uint64_t MaxTopicSize() const noexcept { return max_topic_size_; } |
| |
| /** |
| * @brief Returns the aggregate number of retained messages. |
| * @return Message count. |
| */ |
| std::uint64_t MessagesCount() const noexcept { return messages_count_; } |
| |
| /** |
| * @brief Returns the number of partitions belonging to this topic. |
| * @return Partition count. |
| */ |
| std::uint32_t PartitionsCount() const noexcept { return partitions_count_; } |
| |
| /** |
| * @brief Returns topic creation options and their admission provenance. |
| * @return Options owned by this value. |
| */ |
| const ResourceOptions &Options() const noexcept { return options_; } |
| |
| private: |
| Topic(std::uint32_t id, |
| std::uint64_t created_at, |
| std::string name, |
| std::uint64_t size_bytes, |
| std::uint64_t message_expiry, |
| std::string compression_algorithm, |
| std::uint64_t max_topic_size, |
| std::uint64_t messages_count, |
| std::uint32_t partitions_count, |
| ResourceOptions options) |
| : id_(id), |
| created_at_(created_at), |
| name_(std::move(name)), |
| size_bytes_(size_bytes), |
| message_expiry_(message_expiry), |
| compression_algorithm_(std::move(compression_algorithm)), |
| max_topic_size_(max_topic_size), |
| messages_count_(messages_count), |
| partitions_count_(partitions_count), |
| options_(std::move(options)) {} |
| |
| static Topic FromFfi(ffi::Topic topic); |
| |
| friend class IggyBlockingClient; |
| friend class StreamDetails; |
| |
| std::uint32_t id_; |
| std::uint64_t created_at_; |
| std::string name_; |
| std::uint64_t size_bytes_; |
| std::uint64_t message_expiry_; |
| std::string compression_algorithm_; |
| std::uint64_t max_topic_size_; |
| std::uint64_t messages_count_; |
| std::uint32_t partitions_count_; |
| ResourceOptions options_; |
| }; |
| |
| /** |
| * @brief Partition metadata returned within TopicDetails. |
| * |
| * This is an observed summary, not a live partition handle. Offsets and |
| * statistics can change immediately after GetTopic() returns. |
| */ |
| class Partition final { |
| public: |
| /** |
| * @brief Returns the numeric partition ID within its topic. |
| * @return Numeric partition ID. |
| */ |
| std::uint32_t Id() const noexcept { return id_; } |
| |
| /** |
| * @brief Returns the server creation timestamp. |
| * @return Timestamp in microseconds. |
| */ |
| std::uint64_t CreatedAt() const noexcept { return created_at_; } |
| |
| /** |
| * @brief Returns the number of retained storage segments. |
| * @return Segment count. |
| */ |
| std::uint32_t SegmentsCount() const noexcept { return segments_count_; } |
| |
| /** |
| * @brief Returns the current server-observed message offset. |
| * @return Current message offset. |
| */ |
| std::uint64_t CurrentOffset() const noexcept { return current_offset_; } |
| |
| /** |
| * @brief Returns the retained partition size. |
| * @return Size in bytes. |
| */ |
| std::uint64_t SizeBytes() const noexcept { return size_bytes_; } |
| |
| /** |
| * @brief Returns the number of retained messages. |
| * @return Message count. |
| */ |
| std::uint64_t MessagesCount() const noexcept { return messages_count_; } |
| |
| private: |
| Partition(std::uint32_t id, |
| std::uint64_t created_at, |
| std::uint32_t segments_count, |
| std::uint64_t current_offset, |
| std::uint64_t size_bytes, |
| std::uint64_t messages_count) |
| : id_(id), |
| created_at_(created_at), |
| segments_count_(segments_count), |
| current_offset_(current_offset), |
| size_bytes_(size_bytes), |
| messages_count_(messages_count) {} |
| |
| static Partition FromFfi(ffi::Partition partition); |
| |
| friend class TopicDetails; |
| |
| std::uint32_t id_; |
| std::uint64_t created_at_; |
| std::uint32_t segments_count_; |
| std::uint64_t current_offset_; |
| std::uint64_t size_bytes_; |
| std::uint64_t messages_count_; |
| }; |
| |
| /** |
| * @brief Topic metadata and partition summaries returned by topic detail calls. |
| * |
| * Partitions() contains one observed summary per partition. It does not expose |
| * segment metadata, messages, consumer offsets, or consumer-group membership. |
| */ |
| class TopicDetails final { |
| public: |
| /** |
| * @brief Returns the numeric topic ID within its stream. |
| * @return Numeric topic ID. |
| */ |
| std::uint32_t Id() const noexcept { return id_; } |
| |
| /** |
| * @brief Returns the server creation timestamp. |
| * @return Timestamp in microseconds. |
| */ |
| std::uint64_t CreatedAt() const noexcept { return created_at_; } |
| |
| /** |
| * @brief Returns the topic name. |
| * @return Name owned by this value. |
| */ |
| const std::string &Name() const noexcept { return name_; } |
| |
| /** |
| * @brief Returns the aggregate retained topic size. |
| * @return Size in bytes. |
| */ |
| std::uint64_t SizeBytes() const noexcept { return size_bytes_; } |
| |
| /** |
| * @brief Returns the server-encoded message retention value. |
| * @return Retention value in microseconds or a protocol sentinel. |
| */ |
| std::uint64_t MessageExpiry() const noexcept { return message_expiry_; } |
| |
| /** |
| * @brief Returns the storage compression algorithm selected for this topic. |
| * @return Algorithm name owned by this value. |
| */ |
| const std::string &CompressionAlgorithm() const noexcept { return compression_algorithm_; } |
| |
| /** |
| * @brief Returns the maximum retained size configured for this topic. |
| * @return Maximum size in bytes. |
| */ |
| std::uint64_t MaxTopicSize() const noexcept { return max_topic_size_; } |
| |
| /** |
| * @brief Returns the aggregate number of retained messages. |
| * @return Message count. |
| */ |
| std::uint64_t MessagesCount() const noexcept { return messages_count_; } |
| |
| /** |
| * @brief Returns the number of partitions belonging to this topic. |
| * @return Partition count. |
| */ |
| std::uint32_t PartitionsCount() const noexcept { return partitions_count_; } |
| |
| /** |
| * @brief Returns partition summaries. |
| * @return Summaries owned by this value. |
| */ |
| const std::vector<Partition> &Partitions() const noexcept { return partitions_; } |
| |
| /** |
| * @brief Returns topic creation options and their admission provenance. |
| * @return Options owned by this value. |
| */ |
| const ResourceOptions &Options() const noexcept { return options_; } |
| |
| private: |
| TopicDetails(std::uint32_t id, |
| std::uint64_t created_at, |
| std::string name, |
| std::uint64_t size_bytes, |
| std::uint64_t message_expiry, |
| std::string compression_algorithm, |
| std::uint64_t max_topic_size, |
| std::uint64_t messages_count, |
| std::uint32_t partitions_count, |
| std::vector<Partition> partitions, |
| ResourceOptions options) |
| : id_(id), |
| created_at_(created_at), |
| name_(std::move(name)), |
| size_bytes_(size_bytes), |
| message_expiry_(message_expiry), |
| compression_algorithm_(std::move(compression_algorithm)), |
| max_topic_size_(max_topic_size), |
| messages_count_(messages_count), |
| partitions_count_(partitions_count), |
| partitions_(std::move(partitions)), |
| options_(std::move(options)) {} |
| |
| static TopicDetails FromFfi(ffi::TopicDetails topic); |
| |
| friend class IggyBlockingClient; |
| |
| std::uint32_t id_; |
| std::uint64_t created_at_; |
| std::string name_; |
| std::uint64_t size_bytes_; |
| std::uint64_t message_expiry_; |
| std::string compression_algorithm_; |
| std::uint64_t max_topic_size_; |
| std::uint64_t messages_count_; |
| std::uint32_t partitions_count_; |
| std::vector<Partition> partitions_; |
| ResourceOptions options_; |
| }; |
| |
| namespace detail { |
| /** @brief Internal base for string-backed option types. */ |
| template <typename Tag> |
| class StringTag { |
| protected: |
| explicit StringTag(std::string value) : value_(std::move(value)) {} |
| ~StringTag() = default; |
| |
| std::string_view Value() const { return value_; } |
| |
| private: |
| std::string value_; |
| }; |
| |
| } // namespace detail |
| |
| /** |
| * @brief Snapshot of one stream's metadata and aggregate statistics. |
| * |
| * CreateStream() and GetStream() return this value after converting the Rust |
| * bridge response to ordinary C++ fields. It owns its name and topic |
| * collection, so no bridge-owned string or container leaks through the public |
| * API. |
| * |
| * The value describes the stream state observed by the server for one request. |
| * It is not a live view or an atomic snapshot of later stream, topic, or |
| * message activity. SizeBytes(), MessagesCount(), TopicsCount(), and Topics() |
| * can become stale immediately after the request completes when another client |
| * changes the stream. |
| * |
| * A newly created stream has no topics or messages, so CreateStream() returns |
| * zero for SizeBytes(), MessagesCount(), and TopicsCount(), with an empty |
| * Topics() collection. GetStream() returns the same aggregate fields and one |
| * Topic summary for each observed topic. |
| * |
| * Stream IDs identify a stream for its lifetime and remain stable when it is |
| * renamed. CreatedAt() is the server timestamp, in microseconds, recorded when |
| * the stream was created. |
| */ |
| class StreamDetails final { |
| public: |
| /** |
| * @brief Returns the numeric ID assigned by the server. |
| * |
| * This value can be passed to GetStream() while the stream exists. It is |
| * unchanged by a stream rename. |
| */ |
| std::uint32_t Id() const noexcept { return id_; } |
| |
| /** |
| * @brief Returns the server-recorded creation timestamp. |
| * @return Timestamp in microseconds. |
| */ |
| std::uint64_t CreatedAt() const noexcept { return created_at_; } |
| |
| /** |
| * @brief Returns the unique stream name observed by the server. |
| * @return Reference owned by this value. It remains valid until this |
| * StreamDetails object is modified or destroyed. |
| */ |
| const std::string &Name() const noexcept { return name_; } |
| |
| /** |
| * @brief Returns the aggregate retained size of all stream topics. |
| * @return Size in bytes observed by the server for this request. |
| */ |
| std::uint64_t SizeBytes() const noexcept { return size_bytes_; } |
| |
| /** |
| * @brief Returns the aggregate number of messages in all stream topics. |
| * @return Message count observed by the server for this request. |
| */ |
| std::uint64_t MessagesCount() const noexcept { return messages_count_; } |
| |
| /** |
| * @brief Returns the number of topics belonging to the stream. |
| * @return Topic count observed by the server for this request. |
| */ |
| std::uint32_t TopicsCount() const noexcept { return topics_count_; } |
| |
| /** |
| * @brief Returns the topic summaries observed by the server. |
| * @return Topic values owned by this StreamDetails object. |
| */ |
| const std::vector<Topic> &Topics() const noexcept { return topics_; } |
| |
| /** |
| * @brief Returns explicit stream creation options. |
| * @return Options owned by this value. |
| * @note The current bridge does not return derived stream options. |
| */ |
| const ResourceOptions &Options() const noexcept { return options_; } |
| |
| private: |
| StreamDetails(std::uint32_t id, |
| std::uint64_t created_at, |
| std::string name, |
| std::uint64_t size_bytes, |
| std::uint64_t messages_count, |
| std::uint32_t topics_count, |
| std::vector<Topic> topics, |
| ResourceOptions options) |
| : id_(id), |
| created_at_(created_at), |
| name_(std::move(name)), |
| size_bytes_(size_bytes), |
| messages_count_(messages_count), |
| topics_count_(topics_count), |
| topics_(std::move(topics)), |
| options_(std::move(options)) {} |
| |
| static StreamDetails FromFfi(ffi::StreamDetails stream); |
| |
| friend class IggyBlockingClient; |
| |
| std::uint32_t id_; |
| std::uint64_t created_at_; |
| std::string name_; |
| std::uint64_t size_bytes_; |
| std::uint64_t messages_count_; |
| std::uint32_t topics_count_; |
| std::vector<Topic> topics_; |
| ResourceOptions options_; |
| }; |
| |
| /** |
| * @brief Summary of a stream returned by GetStreams(). |
| * |
| * Unlike StreamDetails, this value does not include topic summaries. Its |
| * aggregate statistics describe the state observed by the server for one |
| * request and can become stale when another client modifies the stream. |
| */ |
| class Stream final { |
| public: |
| /** |
| * @brief Returns the numeric ID assigned by the server. |
| * @return Numeric stream ID. |
| */ |
| std::uint32_t Id() const noexcept { return id_; } |
| |
| /** |
| * @brief Returns the server-recorded creation timestamp. |
| * @return Timestamp in microseconds. |
| */ |
| std::uint64_t CreatedAt() const noexcept { return created_at_; } |
| |
| /** |
| * @brief Returns the stream name. |
| * @return Name owned by this value. |
| */ |
| const std::string &Name() const noexcept { return name_; } |
| |
| /** |
| * @brief Returns the aggregate retained stream size. |
| * @return Size in bytes. |
| */ |
| std::uint64_t SizeBytes() const noexcept { return size_bytes_; } |
| |
| /** |
| * @brief Returns the aggregate number of retained stream messages. |
| * @return Message count. |
| */ |
| std::uint64_t MessagesCount() const noexcept { return messages_count_; } |
| |
| /** |
| * @brief Returns the number of topics belonging to the stream. |
| * @return Topic count. |
| */ |
| std::uint32_t TopicsCount() const noexcept { return topics_count_; } |
| |
| /** |
| * @brief Returns explicit stream creation options. |
| * @return Options owned by this value. |
| * @note The current bridge does not return derived stream options. |
| */ |
| const ResourceOptions &Options() const noexcept { return options_; } |
| |
| private: |
| Stream(std::uint32_t id, |
| std::uint64_t created_at, |
| std::string name, |
| std::uint64_t size_bytes, |
| std::uint64_t messages_count, |
| std::uint32_t topics_count, |
| ResourceOptions options) |
| : id_(id), |
| created_at_(created_at), |
| name_(std::move(name)), |
| size_bytes_(size_bytes), |
| messages_count_(messages_count), |
| topics_count_(topics_count), |
| options_(std::move(options)) {} |
| |
| static Stream FromFfi(ffi::Stream stream); |
| |
| friend class IggyBlockingClient; |
| |
| std::uint32_t id_; |
| std::uint64_t created_at_; |
| std::string name_; |
| std::uint64_t size_bytes_; |
| std::uint64_t messages_count_; |
| std::uint32_t topics_count_; |
| ResourceOptions options_; |
| }; |
| |
| /** |
| * @brief Compression algorithm used for topic messages. |
| * |
| * Selects whether messages in a topic are stored as-is or compressed with |
| * gzip. |
| * |
| * @note The value is passed across the Rust FFI as a string. The Rust client |
| * rejects unsupported values. |
| */ |
| class CompressionAlgorithm final : private detail::StringTag<CompressionAlgorithm> { |
| public: |
| /** @brief Returns the uncompressed storage option. */ |
| static CompressionAlgorithm None() { return CompressionAlgorithm("none"); } |
| |
| /** @brief Returns the gzip compression option. */ |
| static CompressionAlgorithm Gzip() { return CompressionAlgorithm("gzip"); } |
| |
| /** |
| * @brief Returns the value passed to the client implementation. |
| * @return Compression algorithm name. |
| */ |
| std::string_view CompressionAlgorithmValue() const { return Value(); } |
| |
| private: |
| explicit CompressionAlgorithm(std::string algorithm) |
| : detail::StringTag<CompressionAlgorithm>(std::move(algorithm)) {} |
| }; |
| |
| /** |
| * @brief Compression algorithm used for system snapshot archives. |
| * |
| * Selects how snapshot data is compressed in the generated archive. |
| * |
| * @note The value is passed across the Rust FFI as a string. The Rust client |
| * rejects unsupported values. |
| */ |
| class SnapshotCompression final : private detail::StringTag<SnapshotCompression> { |
| public: |
| /** @brief Returns the uncompressed storage option. */ |
| static SnapshotCompression Stored() { return SnapshotCompression("stored"); } |
| |
| /** @brief Returns the Deflate compression option. */ |
| static SnapshotCompression Deflated() { return SnapshotCompression("deflated"); } |
| |
| /** @brief Uses bzip2 for better compression with slower processing. */ |
| static SnapshotCompression Bzip2() { return SnapshotCompression("bzip2"); } |
| |
| /** @brief Uses Zstandard for fast compression and decompression. */ |
| static SnapshotCompression Zstd() { return SnapshotCompression("zstd"); } |
| |
| /** @brief Uses LZMA for high compression, especially for larger files. */ |
| static SnapshotCompression Lzma() { return SnapshotCompression("lzma"); } |
| |
| /** @brief Uses XZ for LZMA-like compression with faster decompression. */ |
| static SnapshotCompression Xz() { return SnapshotCompression("xz"); } |
| |
| /** |
| * @brief Returns the value passed to the client implementation. |
| * @return Snapshot compression algorithm name. |
| */ |
| std::string_view SnapshotCompressionValue() const { return Value(); } |
| |
| private: |
| explicit SnapshotCompression(std::string snapshot_compression) |
| : detail::StringTag<SnapshotCompression>(std::move(snapshot_compression)) {} |
| }; |
| |
| /** |
| * @brief Selects data to include in a system snapshot. |
| * |
| * @note Each selected value is passed across the Rust FFI as a string. The |
| * Rust client rejects unsupported values. |
| */ |
| class SystemSnapshotType final : private detail::StringTag<SystemSnapshotType> { |
| public: |
| /** @brief Includes an overview of the file-system structure. */ |
| static SystemSnapshotType FilesystemOverview() { return SystemSnapshotType("filesystem_overview"); } |
| |
| /** @brief Includes currently running processes. */ |
| static SystemSnapshotType ProcessList() { return SystemSnapshotType("process_list"); } |
| |
| /** @brief Includes CPU, memory, and other resource usage statistics. */ |
| static SystemSnapshotType ResourceUsage() { return SystemSnapshotType("resource_usage"); } |
| |
| /** @brief Includes the test snapshot used for development and testing. */ |
| static SystemSnapshotType Test() { return SystemSnapshotType("test"); } |
| |
| /** @brief Includes server logs from the configured logging directory. */ |
| static SystemSnapshotType ServerLogs() { return SystemSnapshotType("server_logs"); } |
| |
| /** @brief Includes server configuration. */ |
| static SystemSnapshotType ServerConfig() { return SystemSnapshotType("server_config"); } |
| |
| /** @brief Includes all available snapshot data. */ |
| static SystemSnapshotType All() { return SystemSnapshotType("all"); } |
| |
| /** |
| * @brief Returns the value passed to the client implementation. |
| * @return System snapshot type name. |
| */ |
| std::string_view SnapshotTypeValue() const { return Value(); } |
| |
| private: |
| explicit SystemSnapshotType(std::string snapshot_type) |
| : detail::StringTag<SystemSnapshotType>(std::move(snapshot_type)) {} |
| }; |
| |
| /** |
| * @brief Maximum retained size of a topic. |
| * |
| * A topic may use the server default, have no size limit, or use an explicit |
| * byte limit. |
| * |
| * @note The value is passed across the Rust FFI as a string. The Rust parser |
| * accepts server_default, unlimited, and decimal byte counts. Zero maps |
| * to server_default, and std::numeric_limits<std::uint64_t>::max() maps |
| * to unlimited. The Rust client rejects unsupported values. |
| */ |
| class MaxTopicSize final : private detail::StringTag<MaxTopicSize> { |
| public: |
| /** @brief Returns the server-default size option. */ |
| static MaxTopicSize ServerDefault() { return MaxTopicSize("server_default"); } |
| |
| /** @brief Returns the unlimited size option. */ |
| static MaxTopicSize Unlimited() { return MaxTopicSize("unlimited"); } |
| |
| /** |
| * @brief Creates an explicit topic size limit. |
| * @param bytes Maximum topic size in bytes. |
| * @return Server-default size for zero, unlimited size for |
| * std::numeric_limits<std::uint64_t>::max(), or the requested limit. |
| * @note The configured limit cannot be smaller than the server segment size. |
| */ |
| static MaxTopicSize FromBytes(std::uint64_t bytes) { |
| if (bytes == 0) { |
| return ServerDefault(); |
| } |
| if (bytes == std::numeric_limits<std::uint64_t>::max()) { |
| return Unlimited(); |
| } |
| return MaxTopicSize(std::to_string(bytes)); |
| } |
| |
| /** |
| * @brief Returns the value passed to the client implementation. |
| * @return Topic size option or decimal byte count. |
| */ |
| std::string_view MaxTopicSizeValue() const { return Value(); } |
| |
| private: |
| explicit MaxTopicSize(std::string max_topic_size) : detail::StringTag<MaxTopicSize>(std::move(max_topic_size)) {} |
| }; |
| |
| /** |
| * @brief Message retention policy for a topic. |
| * |
| * @note The expiry kind and value are passed across the Rust FFI as a pair. |
| * The Rust client rejects unsupported kinds. |
| */ |
| class Expiry final { |
| public: |
| /** @brief Returns the server-default expiry policy. */ |
| static Expiry ServerDefault() { return Expiry("server_default", 0); } |
| |
| /** |
| * @brief Keeps messages until another operation removes them, such as |
| * topic deletion. |
| */ |
| static Expiry NeverExpire() { return Expiry("never_expire", std::numeric_limits<std::uint64_t>::max()); } |
| |
| /** |
| * @brief Creates a time-based expiry policy. |
| * @param micros Message lifetime in microseconds. |
| * @return Time-based expiry policy. |
| */ |
| static Expiry Duration(std::uint64_t micros) { return Expiry("duration", micros); } |
| |
| /** |
| * @brief Returns the expiry policy kind. |
| * @return One of server_default, never_expire, or duration. |
| */ |
| std::string_view ExpiryKind() const { return expiry_kind_; } |
| |
| /** |
| * @brief Returns the value associated with the expiry policy. |
| * @return Duration in microseconds for Duration(), zero for ServerDefault(), |
| * or std::numeric_limits<std::uint64_t>::max() for NeverExpire(). |
| */ |
| std::uint64_t ExpiryValue() const { return expiry_value_; } |
| |
| private: |
| explicit Expiry(std::string expiry_kind, std::uint64_t expiry_value) |
| : expiry_kind_(std::move(expiry_kind)), expiry_value_(expiry_value) {} |
| |
| std::string expiry_kind_; |
| std::uint64_t expiry_value_; |
| }; |
| |
| /** |
| * @brief Starting position for polling messages. |
| * |
| * @note The strategy kind and value are passed across the Rust FFI as a pair. |
| * The Rust client rejects unsupported kinds. |
| */ |
| class PollingStrategy final { |
| public: |
| /** |
| * @brief Starts polling at a message offset. |
| * @param value Message offset. |
| * @return Offset-based polling strategy. |
| */ |
| static PollingStrategy Offset(std::uint64_t value) { return PollingStrategy("offset", value); } |
| |
| /** |
| * @brief Starts polling at a timestamp. |
| * @param value Timestamp value expected by the Iggy protocol. |
| * @return Timestamp-based polling strategy. |
| */ |
| static PollingStrategy Timestamp(std::uint64_t value) { return PollingStrategy("timestamp", value); } |
| |
| /** @brief Starts polling with the first message in the partition. */ |
| static PollingStrategy First() { return PollingStrategy("first", 0); } |
| |
| /** @brief Starts polling with the last available message in the partition. */ |
| static PollingStrategy Last() { return PollingStrategy("last", 0); } |
| |
| /** |
| * @brief Returns a strategy that starts after the stored consumer offset. |
| * @note Typically used with automatic offset commits enabled. |
| */ |
| static PollingStrategy Next() { return PollingStrategy("next", 0); } |
| |
| /** |
| * @brief Returns the polling strategy kind. |
| * @return One of offset, timestamp, first, last, or next. |
| */ |
| std::string_view PollingStrategyKind() const { return polling_strategy_kind_; } |
| |
| /** |
| * @brief Returns the value associated with the polling strategy. |
| * @return Offset or timestamp for parameterized strategies; otherwise zero. |
| */ |
| std::uint64_t PollingStrategyValue() const { return polling_strategy_value_; } |
| |
| private: |
| explicit PollingStrategy(std::string kind, std::uint64_t value) |
| : polling_strategy_kind_(std::move(kind)), polling_strategy_value_(value) {} |
| |
| std::string polling_strategy_kind_; |
| std::uint64_t polling_strategy_value_; |
| }; |
| |
| namespace detail { |
| |
| /// Numeric option values are little-endian on the wire. Encoded byte by byte so |
| /// a big-endian host produces the same block as a little-endian one. |
| template <typename Value> |
| std::vector<std::uint8_t> to_little_endian_bytes(const Value value) { |
| std::vector<std::uint8_t> bytes{}; |
| bytes.reserve(sizeof(Value)); |
| for (std::size_t index{}; index < sizeof(Value); ++index) { |
| bytes.push_back(static_cast<std::uint8_t>((value >> (index * 8)) & 0xFF)); |
| } |
| |
| return bytes; |
| } |
| |
| inline std::vector<std::uint8_t> to_bool_bytes(const bool value) { |
| std::vector<std::uint8_t> bytes{}; |
| bytes.push_back(static_cast<std::uint8_t>(value ? 1 : 0)); |
| |
| return bytes; |
| } |
| |
| inline std::vector<std::uint8_t> to_key_bytes(const std::string_view key) { |
| std::vector<std::uint8_t> bytes{}; |
| bytes.reserve(key.size()); |
| for (const char character : key) { |
| bytes.push_back(static_cast<std::uint8_t>(character)); |
| } |
| |
| return bytes; |
| } |
| |
| /// An option key is always `String`-kinded. Only the value kind varies per key. |
| inline HeaderEntry to_option_entry(const std::string_view key, |
| const HeaderKind value_kind, |
| std::vector<std::uint8_t> value) { |
| return HeaderEntry::Create(HeaderField::Create(HeaderKind::String, to_key_bytes(key)), |
| HeaderField::Create(value_kind, std::move(value))); |
| } |
| |
| } // namespace detail |
| |
| /** |
| * @brief Creates catalog entries for ResourceOptions::Explicit(). |
| * |
| * Each factory encodes one key from the server's topic option catalog using |
| * that key's required value kind. The server rejects unknown keys and values |
| * encoded with a different kind. |
| * |
| * For example, pass |
| * `ResourceOptions::Explicit({TopicOption::SegmentSize(1024 * 1024)})` to |
| * CreateTopic(). The server rejects unknown keys and values encoded with a |
| * different kind. |
| * |
| * @note These options are accepted only during topic creation. UpdateTopic() |
| * rejects them because they define how partition storage is created. |
| * Changing them later could leave existing and new segments with |
| * different storage settings. |
| */ |
| class TopicOption final { |
| public: |
| /** |
| * @brief Set the size at which this topic's segments rotate. |
| * |
| * Must be a multiple of 512 bytes, at least 1 MiB, and no larger than the |
| * server's segment ceiling. |
| * |
| * @param bytes Segment size in bytes. |
| * @return Encoded topic option entry. |
| */ |
| static HeaderEntry SegmentSize(const std::uint64_t bytes) { |
| return detail::to_option_entry("segment_size", HeaderKind::Uint64, detail::to_little_endian_bytes(bytes)); |
| } |
| |
| /** |
| * @brief Choose whether writes to this topic's partitions are fsynced. |
| * |
| * @param enabled Whether partition writes are fsynced. |
| * @return Encoded topic option entry. |
| */ |
| static HeaderEntry EnforceFsync(const bool enabled) { |
| return detail::to_option_entry("enforce_fsync", HeaderKind::Bool, detail::to_bool_bytes(enabled)); |
| } |
| |
| /** |
| * @brief Flush the journal once it holds this many messages. |
| * |
| * Must be non-zero. Paired with |
| * `SizeOfMessagesRequiredToSave(bytes)`: whichever threshold trips |
| * first flushes. |
| * |
| * @param messages Message count at which to flush the journal. |
| * @return Encoded topic option entry. |
| */ |
| static HeaderEntry MessagesRequiredToSave(const std::uint32_t messages) { |
| return detail::to_option_entry("messages_required_to_save", HeaderKind::Uint32, |
| detail::to_little_endian_bytes(messages)); |
| } |
| |
| /** |
| * @brief Flush the journal once it holds this many bytes. |
| * |
| * Capped at 1 GiB: a threshold above the largest a segment may be never |
| * trips, and the journal does not survive a crash. |
| * |
| * @param bytes Byte count at which to flush the journal. |
| * @return Encoded topic option entry. |
| */ |
| static HeaderEntry SizeOfMessagesRequiredToSave(const std::uint64_t bytes) { |
| return detail::to_option_entry("size_of_messages_required_to_save", HeaderKind::Uint64, |
| detail::to_little_endian_bytes(bytes)); |
| } |
| |
| /** |
| * @brief Choose whether a segment's bytes are reserved on disk when it is created. |
| * |
| * Reserves `segment_size * partitions_count` up front, which the server |
| * caps at 64 GiB per topic. |
| * |
| * @param enabled Whether to reserve segment storage on disk. |
| * @return Encoded topic option entry. |
| */ |
| static HeaderEntry PreallocateSegments(const bool enabled) { |
| return detail::to_option_entry("preallocate_segments", HeaderKind::Bool, detail::to_bool_bytes(enabled)); |
| } |
| |
| private: |
| TopicOption() = delete; |
| }; |
| |
| /** |
| * @brief Owning client connection to an Apache Iggy server. |
| * |
| * Create instances with Builder or FromConnectionString(). The client owns a |
| * handle to the underlying Rust client. Destroying the C++ object releases that |
| * handle. The Rust client aborts its heartbeat task when it is dropped. |
| * |
| * Builder initializes a TCP client. To use QUIC, HTTP, or WebSocket, create the |
| * client with FromConnectionString(). |
| * |
| * @code{.cpp} |
| * auto client{iggy::IggyBlockingClient::Builder() |
| * .WithServerAddress("127.0.0.1:8090") |
| * .Build()}; |
| * client.Connect(); |
| * client.Login("iggy", "iggy"); |
| * client.Shutdown(); |
| * @endcode |
| */ |
| class IggyBlockingClient final { |
| public: |
| class Builder; |
| |
| /** @brief IggyBlockingClient is move-only. */ |
| IggyBlockingClient(const IggyBlockingClient &) = delete; |
| IggyBlockingClient &operator=(const IggyBlockingClient &) = delete; |
| |
| /** |
| * @brief Transfers ownership of a client. |
| * @param other Client whose connection ownership is transferred. |
| * |
| * The moved-from client may be destroyed or assigned a new value, but must |
| * not be used for client operations. |
| */ |
| IggyBlockingClient(IggyBlockingClient &&other) noexcept; |
| |
| /** |
| * @brief Replaces this client by taking ownership from another client. |
| * @param other Client whose connection ownership is transferred. |
| * @return Reference to this client. |
| * |
| * Any Rust client handle currently owned by this object is released first. |
| * Call Shutdown() before replacing a connected client. The moved-from |
| * client must not be used for client operations. |
| */ |
| IggyBlockingClient &operator=(IggyBlockingClient &&other) noexcept; |
| |
| /** |
| * @brief Releases the handle to the underlying Rust client. |
| * |
| * Dropping the underlying Rust client aborts its heartbeat task. Cleanup |
| * errors cannot be reported from the destructor. |
| */ |
| ~IggyBlockingClient(); |
| |
| /** |
| * @brief Creates a client from an Iggy connection string. |
| * |
| * Connection strings use one of these forms: |
| * |
| * - `iggy://<credentials>@<host>:<port>[?<options>]` for TCP. |
| * - `iggy+tcp://<credentials>@<host>:<port>[?<options>]` for TCP. |
| * - `iggy+quic://<credentials>@<host>:<port>[?<options>]` for QUIC. |
| * - `iggy+http://<credentials>@<host>:<port>[?<options>]` for HTTP. |
| * - `iggy+ws://<credentials>@<host>:<port>[?<options>]` for WebSocket. |
| * |
| * Credentials are either `<username>:<password>` or a personal access |
| * token. Multiple query parameters are separated with `&`. |
| * |
| * Connection string examples: |
| * |
| * - Username and password: |
| * `iggy+tcp://iggy:iggy@127.0.0.1:8090` |
| * - Personal access token: |
| * `iggy+tcp://iggypat-1234567890abcdef@127.0.0.1:8090` |
| * - TCP with TLS: |
| * `iggy+tcp://iggy:iggy@localhost:8090?tls=true&tls_domain=localhost` |
| * |
| * TCP accepts these query parameters: |
| * |
| * - `tls=<bool>` |
| * - `tls_domain=<string>` |
| * - `tls_ca_file=<path>` |
| * - `reconnection_retries=<uint32|unlimited>` |
| * - `reconnection_interval=<duration>` |
| * - `reestablish_after=<duration>` |
| * - `heartbeat_interval=<duration>` |
| * - `nodelay=<bool>` |
| * |
| * QUIC accepts these query parameters: |
| * |
| * - `response_buffer_size=<uint64>` |
| * - `max_concurrent_bidi_streams=<uint64>` |
| * - `datagram_send_buffer_size=<uint64>` |
| * - `initial_mtu=<uint16>` |
| * - `send_window=<uint64>` |
| * - `receive_window=<uint64>` |
| * - `keep_alive_interval=<uint64>` |
| * - `max_idle_timeout=<uint64>` |
| * - `validate_certificate=<bool>` |
| * - `heartbeat_interval=<duration>` |
| * - `reconnection_max_retries=<uint32|unlimited>` |
| * - `reconnection_interval=<duration>` |
| * - `reconnection_reestablish_after=<duration>` |
| * |
| * HTTP accepts these query parameters: |
| * |
| * - `heartbeat_interval=<duration>` |
| * - `retries=<uint32>` |
| * |
| * WebSocket accepts these query parameters: |
| * |
| * - `heartbeat_interval=<duration>` |
| * - `reconnection_retries=<uint32|unlimited>` |
| * - `reconnection_interval=<duration>` |
| * - `reestablish_after=<duration>` |
| * - `read_buffer_size=<unsigned integer>` |
| * - `write_buffer_size=<unsigned integer>` |
| * - `max_write_buffer_size=<unsigned integer>` |
| * - `max_message_size=<unsigned integer>` |
| * - `max_frame_size=<unsigned integer>` |
| * - `accept_unmasked_frames=<bool>` |
| * - `tls=<bool>` |
| * - `tls_domain=<string>` |
| * - `tls_ca_file=<path>` |
| * - `tls_validate_certificate=<bool>` |
| * |
| * Durations use Iggy duration syntax, such as `500ms`, `5s`, or `1min`. |
| * Boolean values are `true` or `false`. |
| * |
| * Credentials embedded in the connection string configure automatic login |
| * for Connect() and later reconnections. This method parses configuration |
| * but does not establish a network connection. |
| * |
| * @param connection_string Connection string containing client configuration. |
| * @return Configured, disconnected client. |
| * @throws IggyException if the connection string is invalid or the client |
| * cannot be created. |
| */ |
| static IggyBlockingClient FromConnectionString(std::string connection_string); |
| |
| /** |
| * @brief Connects to the configured Iggy server. |
| * |
| * Establishes the configured transport connection and starts heartbeat |
| * processing. If automatic login was configured, authentication is also |
| * performed. |
| * |
| * @note HTTP is stateless; connecting initializes heartbeat processing but |
| * does not open a persistent transport connection. |
| * @note Repeated calls do not start additional heartbeat tasks. An existing |
| * heartbeat task is reused while it is still running. |
| * @note The default reconnection limit is unlimited. If the server remains |
| * unavailable, this method keeps retrying and blocks the caller. Use |
| * WithReconnectionMaxRetries() to bound the wait. |
| * @throws IggyException if automatic authentication fails, or if a finite |
| * reconnection limit is configured and exhausted. |
| */ |
| void Connect(); |
| |
| /** |
| * @brief Disconnects from the configured Iggy server. |
| * |
| * Disconnect is temporary. It drops the active transport connection and |
| * changes the client state to disconnected, but keeps the client reusable. |
| * Call Connect() to establish a new connection. Configured automatic login |
| * is applied when reconnecting. |
| * |
| * @note Disconnect() does not stop the existing heartbeat task. With |
| * automatic login configured, a heartbeat may reconnect and |
| * authenticate the client in the background. |
| * @note The HTTP transport is stateless and treats this operation as a |
| * no-op. |
| * @throws IggyException if the client cannot disconnect cleanly. |
| * @see Shutdown() |
| */ |
| void Disconnect(); |
| |
| /** |
| * @brief Shuts down the client and its background tasks. |
| * |
| * Shutdown is terminal for stateful transports. It gracefully closes the |
| * active transport where supported, releases transport resources, and |
| * changes the client state to shutdown. Binary operations then fail with a |
| * client-shutdown error. The background heartbeat task stops when it next |
| * observes that error. Create a new client instead of reusing a shut-down |
| * client. |
| * |
| * @note The HTTP transport is stateless and treats this operation as a |
| * no-op. |
| * @throws IggyException if shutdown fails. |
| * @see Disconnect() |
| */ |
| void Shutdown(); |
| |
| /** |
| * @brief Authenticates with a username and password. |
| * |
| * For TCP, QUIC, and WebSocket, call Connect() first. A successful login |
| * leaves the transport connected and marks the session authenticated. For |
| * HTTP, the returned access token is stored by the client and used for |
| * subsequent authenticated requests. |
| * |
| * @param username Iggy user name. |
| * @param password Iggy user password. |
| * @return Information about the authenticated session. |
| * @throws IggyException if authentication fails. |
| */ |
| LoginInfo Login(std::string username, std::string password); |
| |
| /** |
| * @brief Ends the current authenticated session. |
| * |
| * Logout does not disconnect the transport. For binary transports, the |
| * client returns to the connected but unauthenticated state. For HTTP, the |
| * stored access token is cleared after the server accepts the logout. |
| * Protected operations require another successful Login() or an automatic |
| * login during reconnection. |
| * |
| * @throws IggyException if logout fails. |
| * @see Disconnect() |
| */ |
| void Logout(); |
| |
| /** |
| * @brief Creates a top-level stream in the cluster metadata. |
| * |
| * A stream is the top-level namespace for topics. This creates no topics, |
| * partitions, or messages. Its name must be unique, non-empty, and no more |
| * than 255 UTF-8 bytes. |
| * |
| * A transport failure after submission can leave the stream created. Look |
| * it up by name before retrying or choosing another name. |
| * |
| * @param name Unique stream name. |
| * @return Details of the newly created, topic-less stream. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * the name is invalid or already in use; the caller lacks |
| * stream-management permission; or the request fails. |
| */ |
| StreamDetails CreateStream(std::string name); |
| |
| /** |
| * @brief Renames a stream. |
| * |
| * @param stream Stream to rename, addressed by numeric ID or name. |
| * @param name New unique stream name. |
| * @throws IggyException if the client is unavailable, the caller lacks |
| * stream-management permission, either value is invalid, the stream |
| * does not exist, the name is already taken, or the request fails. |
| */ |
| void UpdateStream(const Identifier &stream, std::string name); |
| |
| /** |
| * @brief Lists stream summaries visible to the authenticated user. |
| * |
| * The summaries exclude per-topic details. Use GetStream() for those. |
| * @return Stream summaries visible to the authenticated user. |
| * @throws IggyException if the client is unavailable, the caller lacks |
| * permission to read streams, or the request fails. |
| */ |
| std::vector<Stream> GetStreams(); |
| |
| /** |
| * @brief Retrieves one stream by numeric ID or name. |
| * |
| * The result includes observed aggregate statistics and topic summaries. |
| * It does not include partition details or messages, and its statistics can |
| * become stale immediately after the request completes. |
| * |
| * @param stream Stream to retrieve. Numeric IDs remain stable if a stream |
| * is renamed. |
| * @return Details for the requested stream. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * the stream does not exist; the caller lacks read permission; or |
| * the metadata read fails. |
| */ |
| StreamDetails GetStream(const Identifier &stream); |
| |
| /** |
| * @brief Deletes a stream and all of its topics, partitions, and messages. |
| * |
| * This is irreversible. A transport failure after submission can leave the |
| * deletion committed, so query the stream before retrying this request. |
| * |
| * @param stream Stream to delete, addressed by numeric ID or name. |
| * @throws IggyException if the client is unavailable, the caller lacks |
| * stream-management permission, the stream does not exist, or the |
| * request fails. |
| */ |
| void DeleteStream(const Identifier &stream); |
| |
| /** |
| * @brief Removes all messages from every topic in a stream. |
| * |
| * The stream, its topics, and topic configuration remain available. A |
| * transport failure after submission can still leave the purge committed. |
| * @param stream Stream to purge, addressed by numeric ID or name. |
| * @throws IggyException if the client is unavailable, the caller lacks |
| * stream-management permission, the stream does not exist, or the |
| * request fails. |
| */ |
| void PurgeStream(const Identifier &stream); |
| |
| /** |
| * @brief Creates a topic and its initial partitions in a stream. |
| * |
| * A topic is the message namespace within a stream. The server creates |
| * exactly @p partitions_count partitions and returns their initial |
| * summaries. Its name is unique only within the selected stream. |
| * |
| * @p options must contain only entries selected by the caller. Use |
| * ResourceOptions::Explicit() with TopicOption factories for catalog |
| * options. The server resolves omitted keys from its configuration and |
| * returns those choices in TopicDetails::Options().Derived(). |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @param name Unique topic name within @p stream. |
| * @param partitions_count Number of initial partitions. |
| * @param compression_algorithm Storage compression setting. |
| * @param message_expiry Message retention setting. |
| * @param max_topic_size Maximum retained topic size. |
| * @param options Explicit catalog options for the new topic. |
| * @return Metadata and initial partition summaries for the created topic. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * an identifier, name, partition count, or option is invalid; the |
| * stream does not exist; the caller lacks topic-management |
| * permission; or the server rejects or cannot commit the write. |
| */ |
| TopicDetails CreateTopic(const Identifier &stream, |
| std::string name, |
| std::uint32_t partitions_count, |
| CompressionAlgorithm compression_algorithm = CompressionAlgorithm::None(), |
| Expiry message_expiry = Expiry::ServerDefault(), |
| MaxTopicSize max_topic_size = MaxTopicSize::ServerDefault(), |
| ResourceOptions options = ResourceOptions::Empty()); |
| |
| /** |
| * @brief Renames a topic and updates its mutable configuration. |
| * |
| * The partition count and creation-only options, including segment size, |
| * fsync policy, flush thresholds, and segment preallocation, cannot be |
| * changed here. Default arguments leave compression, expiry, and maximum |
| * size unchanged. The current update catalog exposes those three mutable |
| * settings only through their named parameters, so leave @p options empty. |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @param topic Topic to update, addressed by numeric ID or name. |
| * @param name New unique topic name within @p stream. |
| * @param compression_algorithm New compression setting, or the default to |
| * preserve the existing setting. |
| * @param message_expiry New retention setting, or the default to preserve |
| * the existing setting. |
| * @param max_topic_size New maximum size, or the default to preserve the |
| * existing setting. |
| * @param options Explicit mutable catalog options. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * an identifier, name, setting, or option is invalid; the stream or |
| * topic does not exist; the caller lacks permission; or the server |
| * rejects or cannot commit the write. |
| */ |
| void UpdateTopic(const Identifier &stream, |
| const Identifier &topic, |
| std::string name, |
| CompressionAlgorithm compression_algorithm = CompressionAlgorithm::None(), |
| Expiry message_expiry = Expiry::ServerDefault(), |
| MaxTopicSize max_topic_size = MaxTopicSize::ServerDefault(), |
| ResourceOptions options = ResourceOptions::Empty()); |
| |
| /** |
| * @brief Lists topic summaries in a stream. |
| * |
| * The returned summaries do not include partition details. Use GetTopic() |
| * when partition offsets, sizes, and segment counts are needed. |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @return Topic summaries visible to the authenticated user. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * the stream does not exist; the caller lacks read permission; or |
| * the metadata read fails. |
| */ |
| std::vector<Topic> GetTopics(const Identifier &stream); |
| |
| /** |
| * @brief Retrieves one topic and its partition summaries. |
| * |
| * The result is an observed metadata read. Partition offsets and retained |
| * statistics can change immediately after this call returns. |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @param topic Topic to retrieve, addressed by numeric ID or name. |
| * @return Topic metadata and one summary per partition. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * the stream or topic does not exist; the caller lacks read |
| * permission; or the metadata read fails. |
| */ |
| TopicDetails GetTopic(const Identifier &stream, const Identifier &topic); |
| |
| /** |
| * @brief Deletes a topic, its partitions, and retained messages. |
| * |
| * A failed or unknown transport outcome can leave the deletion committed. |
| * Query the topic before retrying a destructive request. |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @param topic Topic to delete, addressed by numeric ID or name. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * the stream or topic does not exist; the caller lacks |
| * topic-management permission; or the server rejects or cannot |
| * commit the write. |
| */ |
| void DeleteTopic(const Identifier &stream, const Identifier &topic); |
| |
| /** |
| * @brief Removes retained messages from every partition of a topic. |
| * |
| * The topic, its partitions, names, and configuration remain. New messages |
| * can be sent after a purge. A failed or unknown transport outcome can |
| * still leave the purge committed. |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @param topic Topic to purge, addressed by numeric ID or name. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * the stream or topic does not exist; the caller lacks |
| * topic-management permission; or the server rejects or cannot |
| * commit the write. |
| */ |
| void PurgeTopic(const Identifier &stream, const Identifier &topic); |
| |
| /** |
| * @brief Adds partitions to a topic. |
| * |
| * New partitions receive IDs after the topic's existing partitions. The |
| * requested count must be between 1 and 1000. A transport failure after |
| * submission can still leave the partitions created. |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @param topic Topic to extend, addressed by numeric ID or name. |
| * @param partitions_count Number of partitions to add. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * an identifier or count is invalid; the stream or topic does not |
| * exist; the caller lacks topic-management permission; or the |
| * request fails. |
| */ |
| void CreatePartitions(const Identifier &stream, const Identifier &topic, std::uint32_t partitions_count); |
| |
| /** |
| * @brief Deletes the highest-numbered partitions from a topic. |
| * |
| * The deleted partitions and their retained messages are removed. The |
| * requested count must be between 1 and 1000. A transport failure after |
| * submission can still leave the deletion committed. |
| * |
| * @param stream Parent stream, addressed by numeric ID or name. |
| * @param topic Topic to shrink, addressed by numeric ID or name. |
| * @param partitions_count Number of partitions to delete. |
| * @throws IggyException if the client is unavailable or unauthenticated; |
| * an identifier or count is invalid; the stream or topic does not |
| * exist; the caller lacks topic-management permission; or the |
| * request fails. |
| */ |
| void DeletePartitions(const Identifier &stream, const Identifier &topic, std::uint32_t partitions_count); |
| |
| private: |
| explicit IggyBlockingClient(ffi::Client *client); |
| |
| template <typename Operation> |
| static decltype(auto) RethrowAsIggyException(Operation &&operation) { |
| try { |
| return std::forward<Operation>(operation)(); |
| } catch (const std::exception &error) { |
| throw IggyException(error.what()); |
| } |
| } |
| |
| ffi::Client *Handle() const; |
| void Reset() noexcept; |
| |
| ffi::Client *client_; |
| }; |
| |
| /** |
| * @brief Fluent builder for IggyBlockingClient. |
| * |
| * The builder creates TCP clients only. Use |
| * IggyBlockingClient::FromConnectionString() to select another transport. |
| * Configuration methods return the builder by reference and may be chained. |
| * Unless documented otherwise, settings are validated and applied by Build(). |
| */ |
| class IggyBlockingClient::Builder final { |
| public: |
| /** |
| * @brief Creates a builder with the default TCP endpoint, 127.0.0.1:8090. |
| * |
| * Automatic login and TLS are disabled. Reconnection is enabled with |
| * unlimited retries, a one-second retry interval, and a five-second delay |
| * before reestablishing a previously working connection. The heartbeat |
| * interval is five seconds. TCP_NODELAY is disabled. Build() always returns |
| * a disconnected client. |
| */ |
| Builder(); |
| |
| /** |
| * @brief Sets the TCP server address. |
| * |
| * The address is trimmed and validated during Build(). Host names, IPv4, |
| * and bracketed IPv6 are accepted. A non-zero port is required. |
| * |
| * @param server_address Server address in host:port form. |
| * @return Reference to this builder. |
| * @throws IggyException if @p server_address is empty. |
| * @note Build() throws IggyException if the address is invalid. |
| */ |
| Builder &WithServerAddress(std::string server_address); |
| |
| /** |
| * @brief Enables automatic authentication with user credentials. |
| * |
| * The credentials are used whenever Connect() establishes a connection, |
| * including reconnections. This replaces a previously configured personal |
| * access token. |
| * |
| * @param username Iggy user name. |
| * @param password Iggy user password. |
| * @return Reference to this builder. |
| * @throws IggyException if either credential is empty. |
| * @see IggyBlockingClient::Connect() |
| * @see IggyBlockingClient::Login() |
| */ |
| Builder &WithAutoLogin(std::string username, std::string password); |
| |
| /** |
| * @brief Enables automatic authentication with a personal access token. |
| * |
| * The token is used whenever Connect() establishes a connection, including |
| * reconnections. This replaces previously configured username and password |
| * credentials. |
| * |
| * @param token Personal access token. |
| * @return Reference to this builder. |
| * @throws IggyException if the token is empty. |
| * @see IggyBlockingClient::Connect() |
| */ |
| Builder &WithPersonalAccessToken(std::string token); |
| |
| /** |
| * @brief Sets the maximum number of reconnection attempts. |
| * |
| * Reconnection is enabled by default. A value of zero disables retries |
| * after the initial connection attempt. This replaces a previous call to |
| * WithoutReconnectionLimit(). |
| * |
| * @param retries Maximum number of attempts. |
| * @return Reference to this builder. |
| */ |
| Builder &WithReconnectionMaxRetries(std::uint32_t retries); |
| |
| /** |
| * @brief Removes the limit on reconnection attempts. |
| * |
| * This is the default and replaces a previous finite retry limit. |
| * |
| * @return Reference to this builder. |
| */ |
| Builder &WithoutReconnectionLimit(); |
| |
| /** |
| * @brief Sets the delay between reconnection attempts. |
| * |
| * The default interval is one second. This interval applies between failed |
| * connection attempts. |
| * |
| * @param interval Non-negative reconnection interval. |
| * @return Reference to this builder. |
| * @throws IggyException if @p interval is negative. |
| */ |
| Builder &WithReconnectionInterval(std::chrono::microseconds interval); |
| |
| /** |
| * @brief Sets the delay before restoring a lost established connection. |
| * |
| * The default delay is five seconds. This cooldown is distinct from the |
| * interval between failed connection attempts. |
| * |
| * @param duration Non-negative delay. |
| * @return Reference to this builder. |
| * @throws IggyException if @p duration is negative. |
| */ |
| Builder &WithReestablishAfter(std::chrono::microseconds duration); |
| |
| /** |
| * @brief Enables or disables TLS. |
| * |
| * TLS is disabled by default. TLS domain, CA file, and certificate |
| * validation settings require TLS to be enabled. |
| * |
| * @param enabled Whether TLS is enabled. |
| * @return Reference to this builder. |
| */ |
| Builder &WithTlsEnabled(bool enabled = true); |
| |
| /** |
| * @brief Sets the domain used for TLS server-name verification. |
| * |
| * When omitted, the domain is derived from the configured server address. |
| * Build() throws IggyException if this is set while TLS is disabled. |
| * |
| * @param domain TLS domain name. |
| * @return Reference to this builder. |
| * @throws IggyException if @p domain is empty. |
| */ |
| Builder &WithTlsDomain(std::string domain); |
| |
| /** |
| * @brief Sets the certificate-authority file used by TLS. |
| * |
| * When omitted, system root certificates are used. This setting has no |
| * effect unless TLS is enabled. Build() throws IggyException if a path is |
| * set while TLS is disabled. |
| * |
| * @param path Path to a PEM-encoded certificate-authority file. |
| * @return Reference to this builder. |
| * @throws IggyException if @p path is empty. |
| */ |
| Builder &WithTlsCaFile(std::string path); |
| |
| /** |
| * @brief Enables or disables TLS certificate validation. |
| * |
| * Certificate validation is enabled by default. Disabling it accepts |
| * certificates without verifying their trust chain or server identity and |
| * should be limited to controlled development environments. This setting |
| * requires TLS; Build() throws IggyException if certificate validation is |
| * configured while TLS is disabled. |
| * |
| * @param enabled Whether the server certificate is validated. |
| * @return Reference to this builder. |
| */ |
| Builder &WithTlsCertificateValidation(bool enabled = true); |
| |
| /** |
| * @brief Enables TCP_NODELAY on the client socket. |
| * |
| * TCP_NODELAY disables Nagle's algorithm to reduce latency for small |
| * writes, potentially increasing packet count. It is disabled by default. |
| * |
| * @return Reference to this builder. |
| */ |
| Builder &WithNoDelay(); |
| |
| /** |
| * @brief Builds an owning Iggy blocking client. |
| * |
| * Build() validates the TCP configuration and creates an independent |
| * client. The builder is not consumed and may be reused. The returned |
| * client is always disconnected; call IggyBlockingClient::Connect() |
| * explicitly before using operations that require a connection. |
| * |
| * @return Configured client. |
| * @throws IggyException if validation or client creation fails. |
| */ |
| IggyBlockingClient Build() const; |
| |
| private: |
| std::string server_address_{}; |
| ffi::AutoLoginKind auto_login_kind_{ffi::AutoLoginKind::Disabled}; |
| std::string auto_login_username_{}; |
| std::string auto_login_password_{}; |
| std::string personal_access_token_{}; |
| std::optional<std::uint32_t> reconnection_max_retries_{}; |
| std::optional<std::uint64_t> reconnection_interval_micros_{}; |
| std::optional<std::uint64_t> reestablish_after_micros_{}; |
| bool tls_enabled_{}; |
| std::string tls_domain_{}; |
| std::string tls_ca_file_{}; |
| std::optional<bool> tls_validate_certificate_{}; |
| bool no_delay_{}; |
| }; |
| |
| } // namespace iggy |