| /* |
| * 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 "lib.rs.h" |
| |
| namespace iggy { |
| |
| using LoginInfo = ffi::LoginInfo; |
| |
| 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 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)) {} |
| }; |
| |
| // TODO(slbotbm): Add rust bindings for Identifier that will use IdKind |
| /** |
| * @brief Identifies whether an identifier is numeric or text-based. |
| * |
| * @note Reserved for future identifier bindings and not currently passed |
| * across the Rust FFI. |
| */ |
| class IdKind final : private detail::StringTag<IdKind> { |
| public: |
| /** @brief Uses a numeric identifier represented as a 32-bit integer. */ |
| static IdKind Numeric() { return IdKind("numeric"); } |
| |
| /** @brief Uses a string identifier represented by its text value. */ |
| static IdKind String() { return IdKind("string"); } |
| |
| /** |
| * @brief Returns the value passed to the client implementation. |
| * @return Identifier kind name. |
| */ |
| std::string_view IdKindValue() const { return Value(); } |
| |
| private: |
| explicit IdKind(std::string id_kind) : detail::StringTag<IdKind>(std::move(id_kind)) {} |
| }; |
| |
| /** |
| * @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> |
| rust::Vec<std::uint8_t> to_little_endian_bytes(const Value value) { |
| rust::Vec<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 rust::Vec<std::uint8_t> to_bool_bytes(const bool value) { |
| rust::Vec<std::uint8_t> bytes{}; |
| bytes.push_back(static_cast<std::uint8_t>(value ? 1 : 0)); |
| |
| return bytes; |
| } |
| |
| inline rust::Vec<std::uint8_t> to_key_bytes(const std::string_view key) { |
| rust::Vec<std::uint8_t> bytes{}; |
| bytes.reserve(key.size()); |
| for (const char character : key) { |
| bytes.push_back(static_cast<std::uint8_t>(character)); |
| } |
| |
| return bytes; |
| } |
| |
| inline iggy::ffi::HeaderField to_header_field(const iggy::ffi::HeaderKind kind, rust::Vec<std::uint8_t> value) { |
| iggy::ffi::HeaderField field{}; |
| field.kind = static_cast<std::uint8_t>(kind); |
| field.value = std::move(value); |
| |
| return field; |
| } |
| |
| /// An option key is always `String`-kinded. Only the value kind varies per key. |
| inline iggy::ffi::HeaderEntry to_option_entry(const std::string_view key, |
| const iggy::ffi::HeaderKind value_kind, |
| rust::Vec<std::uint8_t> value) { |
| iggy::ffi::HeaderEntry entry{}; |
| entry.key = to_header_field(iggy::ffi::HeaderKind::String, to_key_bytes(key)); |
| entry.value = to_header_field(value_kind, std::move(value)); |
| |
| return entry; |
| } |
| |
| } // namespace detail |
| |
| /** |
| * @brief Creates topic option entries for `Client::create_topic(...)`. |
| * |
| * 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. |
| * |
| * Use `Client::describe_options("topic")` to retrieve the supported keys, |
| * value kinds, and defaults. Binary transport errors do not identify a rejected |
| * key, so the catalog is the discovery mechanism for those transports. |
| * |
| * @note These options are accepted only during topic creation. |
| * `Client::update_topic(...)` 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 iggy::ffi::HeaderEntry SegmentSize(const std::uint64_t bytes) { |
| return detail::to_option_entry("segment_size", iggy::ffi::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 iggy::ffi::HeaderEntry EnforceFsync(const bool enabled) { |
| return detail::to_option_entry("enforce_fsync", iggy::ffi::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 iggy::ffi::HeaderEntry MessagesRequiredToSave(const std::uint32_t messages) { |
| return detail::to_option_entry("messages_required_to_save", iggy::ffi::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 iggy::ffi::HeaderEntry SizeOfMessagesRequiredToSave(const std::uint64_t bytes) { |
| return detail::to_option_entry("size_of_messages_required_to_save", iggy::ffi::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 iggy::ffi::HeaderEntry PreallocateSegments(const bool enabled) { |
| return detail::to_option_entry("preallocate_segments", iggy::ffi::HeaderKind::Bool, |
| detail::to_bool_bytes(enabled)); |
| } |
| |
| private: |
| TopicOption() = delete; |
| }; |
| |
| /** |
| * @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 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(); |
| |
| 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 |