align cpp sdk with rust, add docs
diff --git a/foreign/cpp/include/iggy.hpp b/foreign/cpp/include/iggy.hpp index f615ee3..6a4b1e2 100644 --- a/foreign/cpp/include/iggy.hpp +++ b/foreign/cpp/include/iggy.hpp
@@ -101,7 +101,16 @@ */ class IggyException : public std::runtime_error { public: + /** + * @brief Creates an Iggy exception from a null-terminated message. + * @param message Error description. + */ explicit IggyException(const char *message) : std::runtime_error(message) {} + + /** + * @brief Creates an Iggy exception from an owning string. + * @param message Error description. + */ explicit IggyException(const std::string &message) : std::runtime_error(message) {} }; @@ -159,7 +168,10 @@ */ class Identifier final { public: + /** @brief Maximum encoded length of a name-based identifier. */ static constexpr std::size_t kMaxIdentifierLength = 255; + + /** @brief Selects the representation stored by an Identifier. */ enum class Kind : std::uint8_t { Numeric, String }; /** @@ -207,69 +219,185 @@ std::variant<std::uint32_t, std::string> value_; }; +/** + * @brief Controls whether a user may authenticate. + */ enum class UserStatus : std::uint8_t { - Active = 1, - Inactive = 2, + Active = 1, ///< The user may authenticate. + Inactive = 2, ///< Authentication for the user is rejected. }; +/** + * @brief Cluster-wide permissions assigned to a user. + * + * Global grants apply without naming individual streams or topics. Management + * grants include the corresponding read grants. Stream and topic grants form a + * hierarchy: managing streams includes managing topics, reading streams + * includes reading topics, and reading topics includes polling messages and + * managing consumer groups. Managing streams or topics also authorizes sending + * messages; SendMessages() can grant sending without management permission. + * + * A default-constructed value has every flag disabled. The setters record the + * supplied flags without expanding implied grants; the server applies the + * hierarchy when authorizing a request. + */ class GlobalPermissions final { public: + /** + * @brief Returns the configured cluster-management flag. + * @return Configured flag value. + */ [[nodiscard]] bool ManageServers() const noexcept { return manage_servers_; } + + /** + * @brief Returns the configured server-information read flag. + * @return Configured flag value. + */ [[nodiscard]] bool ReadServers() const noexcept { return read_servers_; } + + /** + * @brief Returns the configured user-management flag. + * @return Configured flag value. + */ [[nodiscard]] bool ManageUsers() const noexcept { return manage_users_; } + + /** + * @brief Returns the configured user-information read flag. + * @return Configured flag value. + */ [[nodiscard]] bool ReadUsers() const noexcept { return read_users_; } + + /** + * @brief Returns the configured all-stream management flag. + * @return Configured flag value. + */ [[nodiscard]] bool ManageStreams() const noexcept { return manage_streams_; } + + /** + * @brief Returns the configured all-stream read flag. + * @return Configured flag value. + */ [[nodiscard]] bool ReadStreams() const noexcept { return read_streams_; } + + /** + * @brief Returns the configured all-topic management flag. + * @return Configured flag value. + */ [[nodiscard]] bool ManageTopics() const noexcept { return manage_topics_; } + + /** + * @brief Returns the configured all-topic read flag. + * @return Configured flag value. + */ [[nodiscard]] bool ReadTopics() const noexcept { return read_topics_; } + + /** + * @brief Returns the configured all-topic message-polling flag. + * @return Configured flag value. + */ [[nodiscard]] bool PollMessages() const noexcept { return poll_messages_; } + + /** + * @brief Returns the configured all-topic message-sending flag. + * @return Configured flag value. + */ [[nodiscard]] bool SendMessages() const noexcept { return send_messages_; } + /** + * @brief Enables or disables cluster-management permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetManageServers(bool enabled) { manage_servers_ = enabled; return *this; } + /** + * @brief Enables or disables permission to read server information. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetReadServers(bool enabled) { read_servers_ = enabled; return *this; } + /** + * @brief Enables or disables user-management permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetManageUsers(bool enabled) { manage_users_ = enabled; return *this; } + /** + * @brief Enables or disables permission to read user information. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetReadUsers(bool enabled) { read_users_ = enabled; return *this; } + /** + * @brief Enables or disables management permission for every stream. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetManageStreams(bool enabled) { manage_streams_ = enabled; return *this; } + /** + * @brief Enables or disables read permission for every stream. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetReadStreams(bool enabled) { read_streams_ = enabled; return *this; } + /** + * @brief Enables or disables management permission for every topic. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetManageTopics(bool enabled) { manage_topics_ = enabled; return *this; } + /** + * @brief Enables or disables read permission for every topic. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetReadTopics(bool enabled) { read_topics_ = enabled; return *this; } + /** + * @brief Enables or disables polling permission for every topic. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetPollMessages(bool enabled) { poll_messages_ = enabled; return *this; } + /** + * @brief Enables or disables sending permission for every topic. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ GlobalPermissions &SetSendMessages(bool enabled) { send_messages_ = enabled; return *this; @@ -293,28 +421,78 @@ bool send_messages_{}; }; +/** + * @brief Permissions extending a user's access to one topic. + * + * These flags grant access in addition to enclosing stream and global grants; + * a disabled flag does not revoke access granted at a broader scope. Managing + * a topic includes reading it. Reading a topic includes polling messages and + * managing its consumer groups. Managing a topic also authorizes sending; + * SetSendMessages() can grant sending without management permission. + * + * A default-constructed value has every flag disabled. + */ class TopicPermissions final { public: + /** + * @brief Returns the configured topic-management flag. + * @return Configured flag value. + */ [[nodiscard]] bool ManageTopic() const noexcept { return manage_topic_; } + + /** + * @brief Returns the configured topic-read flag. + * @return Configured flag value. + */ [[nodiscard]] bool ReadTopic() const noexcept { return read_topic_; } + + /** + * @brief Returns the configured message-polling flag. + * @return Configured flag value. + */ [[nodiscard]] bool PollMessages() const noexcept { return poll_messages_; } + + /** + * @brief Returns the configured message-sending flag. + * @return Configured flag value. + */ [[nodiscard]] bool SendMessages() const noexcept { return send_messages_; } + /** + * @brief Enables or disables topic-management permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ TopicPermissions &SetManageTopic(bool enabled) { manage_topic_ = enabled; return *this; } + /** + * @brief Enables or disables topic-read permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ TopicPermissions &SetReadTopic(bool enabled) { read_topic_ = enabled; return *this; } + /** + * @brief Enables or disables message-polling permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ TopicPermissions &SetPollMessages(bool enabled) { poll_messages_ = enabled; return *this; } + /** + * @brief Enables or disables message-sending permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ TopicPermissions &SetSendMessages(bool enabled) { send_messages_ = enabled; return *this; @@ -332,46 +510,127 @@ bool send_messages_{}; }; +/** + * @brief Permissions extending a user's access to one stream. + * + * Stream grants apply to the stream identified by the containing Permissions + * map. Topic-specific grants are keyed by numeric topic ID. These flags extend + * broader grants and cannot revoke permissions granted globally. Managing a + * stream includes reading it and managing its topics; reading a stream includes + * reading its topics. Managing a stream or its topics also authorizes sending; + * SetSendMessages() can grant sending without management permission. + * + * A default-constructed value has every flag disabled and no topic entries. + */ class StreamPermissions final { public: + /** + * @brief Returns the configured stream-management flag. + * @return Configured flag value. + */ [[nodiscard]] bool ManageStream() const noexcept { return manage_stream_; } + + /** + * @brief Returns the configured stream-read flag. + * @return Configured flag value. + */ [[nodiscard]] bool ReadStream() const noexcept { return read_stream_; } + + /** + * @brief Returns the configured management flag for all stream topics. + * @return Configured flag value. + */ [[nodiscard]] bool ManageTopics() const noexcept { return manage_topics_; } + + /** + * @brief Returns the configured read flag for all stream topics. + * @return Configured flag value. + */ [[nodiscard]] bool ReadTopics() const noexcept { return read_topics_; } + + /** + * @brief Returns the configured polling flag for all stream topics. + * @return Configured flag value. + */ [[nodiscard]] bool PollMessages() const noexcept { return poll_messages_; } + + /** + * @brief Returns the configured sending flag for all stream topics. + * @return Configured flag value. + */ [[nodiscard]] bool SendMessages() const noexcept { return send_messages_; } + + /** + * @brief Returns topic-specific grants keyed by numeric topic ID. + * @return Map owned by this value. + */ [[nodiscard]] const std::map<std::uint32_t, TopicPermissions> &Topics() const noexcept { return topics_; } + /** + * @brief Enables or disables stream-management permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ StreamPermissions &SetManageStream(bool enabled) { manage_stream_ = enabled; return *this; } + /** + * @brief Enables or disables stream-read permission. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ StreamPermissions &SetReadStream(bool enabled) { read_stream_ = enabled; return *this; } + /** + * @brief Enables or disables management permission for all stream topics. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ StreamPermissions &SetManageTopics(bool enabled) { manage_topics_ = enabled; return *this; } + /** + * @brief Enables or disables read permission for all stream topics. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ StreamPermissions &SetReadTopics(bool enabled) { read_topics_ = enabled; return *this; } + /** + * @brief Enables or disables polling permission for all stream topics. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ StreamPermissions &SetPollMessages(bool enabled) { poll_messages_ = enabled; return *this; } + /** + * @brief Enables or disables sending permission for all stream topics. + * @param enabled Requested flag value. + * @return Reference to this permissions object. + */ StreamPermissions &SetSendMessages(bool enabled) { send_messages_ = enabled; return *this; } + /** + * @brief Replaces the topic-specific permission map. + * @param topics Grants keyed by numeric topic ID. + * @return Reference to this permissions object. + */ StreamPermissions &SetTopics(std::map<std::uint32_t, TopicPermissions> topics) { topics_ = std::move(topics); return *this; @@ -392,16 +651,46 @@ std::map<std::uint32_t, TopicPermissions> topics_; }; +/** + * @brief Complete permission assignment for a user. + * + * Global permissions apply cluster-wide. Stream entries are keyed by numeric + * stream ID and add narrower grants, including optional topic-specific grants. + * Narrower scopes extend broader scopes and do not deny an inherited grant. + * + * A default-constructed value contains no grants. Passing such a value to + * CreateUser() assigns an explicit but empty permission set; passing + * `std::nullopt` assigns no permission object. + */ class Permissions final { public: + /** + * @brief Returns the cluster-wide grants. + * @return Global permissions owned by this value. + */ [[nodiscard]] const GlobalPermissions &Global() const noexcept { return global_; } + + /** + * @brief Returns stream-specific grants keyed by numeric stream ID. + * @return Map owned by this value. + */ [[nodiscard]] const std::map<std::uint32_t, StreamPermissions> &Streams() const noexcept { return streams_; } + /** + * @brief Replaces the cluster-wide grants. + * @param global New global permissions. + * @return Reference to this permissions object. + */ Permissions &SetGlobal(GlobalPermissions global) { global_ = global; return *this; } + /** + * @brief Replaces the stream-specific grants. + * @param streams Grants keyed by numeric stream ID. + * @return Reference to this permissions object. + */ Permissions &SetStreams(std::map<std::uint32_t, StreamPermissions> streams) { streams_ = std::move(streams); return *this; @@ -418,58 +707,6 @@ std::map<std::uint32_t, StreamPermissions> streams_; }; -class UserInfo final { - public: - [[nodiscard]] std::uint32_t Id() const noexcept { return id_; } - [[nodiscard]] std::uint64_t CreatedAt() const noexcept { return created_at_; } - [[nodiscard]] UserStatus Status() const noexcept { return status_; } - [[nodiscard]] const std::string &Username() const noexcept { return username_; } - - private: - UserInfo(std::uint32_t id, std::uint64_t created_at, UserStatus status, std::string username) - : id_(id), created_at_(created_at), status_(status), username_(std::move(username)) {} - - static UserInfo FromFfi(ffi::UserInfo user); - - friend class IggyBlockingClient; - - std::uint32_t id_; - std::uint64_t created_at_; - UserStatus status_; - std::string username_; -}; - -class UserInfoDetails final { - public: - [[nodiscard]] std::uint32_t Id() const noexcept { return id_; } - [[nodiscard]] std::uint64_t CreatedAt() const noexcept { return created_at_; } - [[nodiscard]] UserStatus Status() const noexcept { return status_; } - [[nodiscard]] const std::string &Username() const noexcept { return username_; } - [[nodiscard]] const std::optional<::iggy::Permissions> &Permissions() const noexcept { return permissions_; } - - private: - UserInfoDetails(std::uint32_t id, - std::uint64_t created_at, - UserStatus status, - std::string username, - std::optional<::iggy::Permissions> permissions) - : id_(id), - created_at_(created_at), - status_(status), - username_(std::move(username)), - permissions_(std::move(permissions)) {} - - static UserInfoDetails FromFfi(ffi::UserInfoDetails user); - - friend class IggyBlockingClient; - - std::uint32_t id_; - std::uint64_t created_at_; - UserStatus status_; - std::string username_; - std::optional<::iggy::Permissions> permissions_; -}; - /** * @brief Identifies the owner of a stored consumer offset. * @@ -479,6 +716,7 @@ */ class Consumer final { public: + /** @brief Selects an individual consumer or consumer-group identity. */ enum class Kind : std::uint8_t { Single, Group }; /** @@ -882,12 +1120,141 @@ friend class TopicDetails; friend class Stream; friend class StreamDetails; + friend class UserInfo; + friend class UserInfoDetails; std::map<std::string, HeaderField> explicit_; std::map<std::string, HeaderField> derived_; }; /** + * @brief Snapshot of basic user metadata. + * + * GetUsers() returns one value for each user visible to the caller. This + * summary omits permissions. User IDs remain assigned to the same user until + * that user is deleted. CreatedAt() is expressed in microseconds since the + * Unix epoch. + */ +class UserInfo final { + public: + /** + * @brief Returns the server-assigned numeric user ID. + * @return Numeric user ID. + */ + [[nodiscard]] std::uint32_t Id() const noexcept { return id_; } + + /** + * @brief Returns the creation timestamp. + * @return Timestamp in microseconds since the Unix epoch. + */ + [[nodiscard]] std::uint64_t CreatedAt() const noexcept { return created_at_; } + + /** + * @brief Returns whether the user is active or inactive. + * @return Current user status observed for this request. + */ + [[nodiscard]] UserStatus Status() const noexcept { return status_; } + + /** + * @brief Returns the unique user name. + * @return Name owned by this value. + */ + [[nodiscard]] const std::string &Username() const noexcept { return username_; } + + [[nodiscard]] const ResourceOptions &Options() const noexcept { return options_; } + + private: + UserInfo(std::uint32_t id, + std::uint64_t created_at, + UserStatus status, + std::string username, + ResourceOptions options) + : id_(id), + created_at_(created_at), + status_(status), + username_(std::move(username)), + options_(std::move(options)) {} + + static UserInfo FromFfi(ffi::UserInfo user); + + friend class IggyBlockingClient; + + std::uint32_t id_; + std::uint64_t created_at_; + UserStatus status_; + std::string username_; + ResourceOptions options_; +}; + +/** + * @brief Snapshot of user metadata and assigned permissions. + * + * GetUser() and CreateUser() return this detailed form. Permissions() is empty + * when the user has no permission object. An engaged Permissions value can + * still contain no enabled grants. + */ +class UserInfoDetails final { + public: + /** + * @brief Returns the server-assigned numeric user ID. + * @return Numeric user ID. + */ + [[nodiscard]] std::uint32_t Id() const noexcept { return id_; } + + /** + * @brief Returns the creation timestamp. + * @return Timestamp in microseconds since the Unix epoch. + */ + [[nodiscard]] std::uint64_t CreatedAt() const noexcept { return created_at_; } + + /** + * @brief Returns whether the user is active or inactive. + * @return Current user status observed for this request. + */ + [[nodiscard]] UserStatus Status() const noexcept { return status_; } + + /** + * @brief Returns the unique user name. + * @return Name owned by this value. + */ + [[nodiscard]] const std::string &Username() const noexcept { return username_; } + + /** + * @brief Returns the user's explicit permission assignment. + * @return Empty when no permission object is assigned; otherwise the + * permissions owned by this value. + */ + [[nodiscard]] const std::optional<::iggy::Permissions> &Permissions() const noexcept { return permissions_; } + + [[nodiscard]] const ResourceOptions &Options() const noexcept { return options_; } + + private: + UserInfoDetails(std::uint32_t id, + std::uint64_t created_at, + UserStatus status, + std::string username, + std::optional<::iggy::Permissions> permissions, + ResourceOptions options) + : id_(id), + created_at_(created_at), + status_(status), + username_(std::move(username)), + permissions_(std::move(permissions)), + options_(std::move(options)) {} + + static UserInfoDetails FromFfi(ffi::UserInfoDetails user); + + friend class IggyBlockingClient; + + std::uint32_t id_; + std::uint64_t created_at_; + UserStatus status_; + std::string username_; + std::optional<::iggy::Permissions> permissions_; + ResourceOptions options_; +}; + +/** * @brief Snapshot of one topic's metadata and aggregate statistics. * * GetStream() returns one of these values for each observed topic. It owns its @@ -1546,10 +1913,31 @@ std::vector<ConsumerGroupMember> members_; }; +/** + * @brief Identifies one consumer-group membership of a connected client. + * + * ClientInfoDetails returns these numeric identifiers for each membership + * observed by the server. The membership can change immediately after the + * client information is retrieved. + */ class ConsumerGroupInfo final { public: + /** + * @brief Returns the numeric ID of the member group's stream. + * @return Numeric stream ID. + */ [[nodiscard]] std::uint32_t StreamId() const noexcept { return stream_id_; } + + /** + * @brief Returns the numeric ID of the member group's topic. + * @return Numeric topic ID. + */ [[nodiscard]] std::uint32_t TopicId() const noexcept { return topic_id_; } + + /** + * @brief Returns the numeric consumer group ID. + * @return Numeric consumer group ID. + */ [[nodiscard]] std::uint32_t GroupId() const noexcept { return group_id_; } private: @@ -1565,12 +1953,45 @@ std::uint32_t group_id_; }; +/** + * @brief Snapshot summary of a client connection known to the server. + * + * GetClients() returns one summary for each connection observed by the server. + * A client is a transport connection, not an Iggy user. Connections can close, + * authenticate, or change consumer-group membership immediately after the + * request completes. + */ class ClientInfo final { public: + /** + * @brief Returns the server-assigned connection ID. + * @return Numeric client ID accepted by GetClient() while the connection + * remains known to the server. + */ [[nodiscard]] std::uint32_t ClientId() const noexcept { return client_id_; } + + /** + * @brief Returns the authenticated user ID for this connection. + * @return Empty when the client has not authenticated. + */ [[nodiscard]] const std::optional<std::uint32_t> &UserId() const noexcept { return user_id_; } + + /** + * @brief Returns the remote address reported by the server. + * @return Address owned by this value. + */ [[nodiscard]] const std::string &Address() const noexcept { return address_; } + + /** + * @brief Returns the transport name reported by the server. + * @return Transport name owned by this value. + */ [[nodiscard]] const std::string &Transport() const noexcept { return transport_; } + + /** + * @brief Returns the number of consumer groups joined by this client. + * @return Membership count observed for this request. + */ [[nodiscard]] std::uint32_t ConsumerGroupsCount() const noexcept { return consumer_groups_count_; } private: @@ -1596,13 +2017,48 @@ std::uint32_t consumer_groups_count_; }; +/** + * @brief Snapshot of a client connection and its consumer-group memberships. + * + * GetMe() and GetClient() return this detailed form. The connection state and + * memberships are not live and can change immediately after retrieval. + */ class ClientInfoDetails final { public: + /** + * @brief Returns the server-assigned connection ID. + * @return Numeric client ID. + */ [[nodiscard]] std::uint32_t ClientId() const noexcept { return client_id_; } + + /** + * @brief Returns the authenticated user ID for this connection. + * @return Empty when the client has not authenticated. + */ [[nodiscard]] const std::optional<std::uint32_t> &UserId() const noexcept { return user_id_; } + + /** + * @brief Returns the remote address reported by the server. + * @return Address owned by this value. + */ [[nodiscard]] const std::string &Address() const noexcept { return address_; } + + /** + * @brief Returns the transport name reported by the server. + * @return Transport name owned by this value. + */ [[nodiscard]] const std::string &Transport() const noexcept { return transport_; } + + /** + * @brief Returns the server-reported consumer-group membership count. + * @return Membership count observed for this request. + */ [[nodiscard]] std::uint32_t ConsumerGroupsCount() const noexcept { return consumer_groups_count_; } + + /** + * @brief Returns the observed consumer-group memberships. + * @return Membership identifiers owned by this value. + */ [[nodiscard]] const std::vector<ConsumerGroupInfo> &ConsumerGroups() const noexcept { return consumer_groups_; } private: @@ -1631,13 +2087,49 @@ std::vector<ConsumerGroupInfo> consumer_groups_; }; +/** + * @brief Cache counters for one stream, topic, and partition. + * + * Stats::CacheMetrics() contains these entries when the server implementation + * reports partition cache metrics. The current VSR server returns an empty + * cache-metrics collection. + */ class CacheMetricEntry final { public: + /** + * @brief Returns the numeric stream ID for this cache entry. + * @return Numeric stream ID. + */ [[nodiscard]] std::uint32_t StreamId() const noexcept { return stream_id_; } + + /** + * @brief Returns the numeric topic ID for this cache entry. + * @return Numeric topic ID. + */ [[nodiscard]] std::uint32_t TopicId() const noexcept { return topic_id_; } + + /** + * @brief Returns the numeric partition ID for this cache entry. + * @return Numeric partition ID. + */ [[nodiscard]] std::uint32_t PartitionId() const noexcept { return partition_id_; } + + /** + * @brief Returns the cumulative number of cache hits reported by the server. + * @return Cache hit count. + */ [[nodiscard]] std::uint64_t Hits() const noexcept { return hits_; } + + /** + * @brief Returns the cumulative number of cache misses reported by the server. + * @return Cache miss count. + */ [[nodiscard]] std::uint64_t Misses() const noexcept { return misses_; } + + /** + * @brief Returns the server-reported ratio of hits to total cache lookups. + * @return Cache hit ratio. + */ [[nodiscard]] float HitRatio() const noexcept { return hit_ratio_; } private: @@ -1666,35 +2158,187 @@ float hit_ratio_; }; +/** + * @brief Snapshot of server process, storage, and resource statistics. + * + * GetStats() returns process and host measurements from the serving server, + * together with metadata totals observed for one request. Values are not a + * live or transactional view. CPU measurements depend on the server's sampling + * history, and the first sample on a serving thread can report zero. Memory + * totals honor an effective cgroup limit when one applies. Disk-space values + * describe the volume containing the configured data directory and can be zero + * when the server cannot probe that volume. + */ class Stats final { public: + /** + * @brief Returns the operating-system process ID of the server. + * @return Numeric process ID. + */ [[nodiscard]] std::uint32_t ProcessId() const noexcept { return process_id_; } + + /** + * @brief Returns the server process CPU usage. + * @return Process CPU usage as a percentage. + */ [[nodiscard]] float CpuUsage() const noexcept { return cpu_usage_; } + + /** + * @brief Returns total CPU usage for the available CPU set. + * @return Total CPU usage as a percentage. + */ [[nodiscard]] float TotalCpuUsage() const noexcept { return total_cpu_usage_; } + + /** + * @brief Returns server process memory usage. + * @return Process memory usage in bytes. + */ [[nodiscard]] std::uint64_t MemoryUsage() const noexcept { return memory_usage_; } + + /** + * @brief Returns total host or effective cgroup memory. + * @return Total memory in bytes. + */ [[nodiscard]] std::uint64_t TotalMemory() const noexcept { return total_memory_; } + + /** + * @brief Returns available host or effective cgroup memory. + * @return Available memory in bytes. + */ [[nodiscard]] std::uint64_t AvailableMemory() const noexcept { return available_memory_; } + + /** + * @brief Returns server process uptime. + * @return Process uptime in microseconds. + */ [[nodiscard]] std::uint64_t RunTimeMicros() const noexcept { return run_time_micros_; } + + /** + * @brief Returns the server process start time. + * @return Timestamp in microseconds since the Unix epoch. + */ [[nodiscard]] std::uint64_t StartTimeEpochMicros() const noexcept { return start_time_epoch_micros_; } + + /** + * @brief Returns the server process read-byte count. + * @return Number of bytes read by the process. + */ [[nodiscard]] std::uint64_t ReadBytes() const noexcept { return read_bytes_; } + + /** + * @brief Returns the server process written-byte count. + * @return Number of bytes written by the process. + */ [[nodiscard]] std::uint64_t WrittenBytes() const noexcept { return written_bytes_; } + + /** + * @brief Returns the aggregate retained message size. + * @return Retained message size in bytes. + */ [[nodiscard]] std::uint64_t MessagesSizeBytes() const noexcept { return messages_size_bytes_; } + + /** + * @brief Returns the observed number of streams. + * @return Stream count. + */ [[nodiscard]] std::uint32_t StreamsCount() const noexcept { return streams_count_; } + + /** + * @brief Returns the observed number of topics. + * @return Topic count. + */ [[nodiscard]] std::uint32_t TopicsCount() const noexcept { return topics_count_; } + + /** + * @brief Returns the observed number of partitions. + * @return Partition count. + */ [[nodiscard]] std::uint32_t PartitionsCount() const noexcept { return partitions_count_; } + + /** + * @brief Returns the observed number of partition segments. + * @return Segment count. + */ [[nodiscard]] std::uint32_t SegmentsCount() const noexcept { return segments_count_; } + + /** + * @brief Returns the observed number of retained messages. + * @return Message count. + */ [[nodiscard]] std::uint64_t MessagesCount() const noexcept { return messages_count_; } + + /** + * @brief Returns the number of client connections observed by the server. + * @return Client connection count. + */ [[nodiscard]] std::uint32_t ClientsCount() const noexcept { return clients_count_; } + + /** + * @brief Returns the observed number of consumer groups. + * @return Consumer group count. + */ [[nodiscard]] std::uint32_t ConsumerGroupsCount() const noexcept { return consumer_groups_count_; } + + /** + * @brief Returns the server host name. + * @return Host name owned by this value. + */ [[nodiscard]] const std::string &Hostname() const noexcept { return hostname_; } + + /** + * @brief Returns the server operating-system name. + * @return Operating-system name owned by this value. + */ [[nodiscard]] const std::string &OsName() const noexcept { return os_name_; } + + /** + * @brief Returns the server operating-system version. + * @return Operating-system version owned by this value. + */ [[nodiscard]] const std::string &OsVersion() const noexcept { return os_version_; } + + /** + * @brief Returns the server kernel version. + * @return Kernel version owned by this value. + */ [[nodiscard]] const std::string &KernelVersion() const noexcept { return kernel_version_; } + + /** + * @brief Returns the human-readable Iggy server version. + * @return Version string owned by this value. + */ [[nodiscard]] const std::string &IggyServerVersion() const noexcept { return iggy_server_version_; } + + /** + * @brief Returns the numeric semantic version when reported by the server. + * @return `major * 1,000,000 + minor * 1,000 + patch`, or `std::nullopt` + * when the server does not report a numeric version. + */ [[nodiscard]] const std::optional<std::uint32_t> &ServerSemver() const noexcept { return server_semver_; } + + /** + * @brief Returns partition cache metrics reported by the server. + * @return Entries owned by this value. The current VSR server returns an + * empty collection. + */ [[nodiscard]] const std::vector<CacheMetricEntry> &CacheMetrics() const noexcept { return cache_metrics_; } + + /** + * @brief Returns the number of threads in the server process. + * @return Process thread count. + */ [[nodiscard]] std::uint32_t ThreadsCount() const noexcept { return threads_count_; } + + /** + * @brief Returns free space on the server data-directory volume. + * @return Free space in bytes, or zero when the probe is unavailable. + */ [[nodiscard]] std::uint64_t FreeDiskSpace() const noexcept { return free_disk_space_; } + + /** + * @brief Returns total space on the server data-directory volume. + * @return Total space in bytes, or zero when the probe is unavailable. + */ [[nodiscard]] std::uint64_t TotalDiskSpace() const noexcept { return total_disk_space_; } private: @@ -1988,8 +2632,25 @@ std::uint64_t expiry_value_; }; -enum class Durability : std::uint8_t { Replicated, Persisted }; +/** + * @brief Storage guarantee required before an operation reports completion. + * + * Both policies persist data through the replicated journal. Replicated waits + * for quorum commit without an additional stable-storage barrier. Persisted + * also requires recoverable stable-storage copies on the quorum. Topic message + * durability and consumer-offset durability are configured independently. + */ +enum class Durability : std::uint8_t { + Replicated, ///< Wait for quorum commit. + Persisted, ///< Wait for quorum commit backed by stable storage. +}; +/** + * @brief Returns the protocol option name for a durability policy. + * @param durability Durability policy to encode. + * @return `replicated` or `persisted`. + * @throws std::invalid_argument if @p durability is not a declared enumerator. + */ constexpr std::string_view to_string(const Durability durability) { switch (durability) { case Durability::Replicated: @@ -2010,6 +2671,7 @@ */ class TopicCreateOptions final { public: + /** @brief Creates options with every setting left to the server default. */ TopicCreateOptions() = default; /** @@ -2279,6 +2941,7 @@ */ class TopicUpdateOptions final { public: + /** @brief Creates an update that leaves every topic setting unchanged. */ TopicUpdateOptions() = default; /** @@ -2401,6 +3064,7 @@ */ class StreamUpdateOptions final { public: + /** @brief Creates an update with no requested stream settings. */ StreamUpdateOptions() = default; /** @@ -2445,6 +3109,61 @@ }; /** + * @brief Options for updating a user. + * + * Use this class to supply user settings to UpdateUser(). Updating a user + * patches only the supplied settings; omitted settings remain unchanged. + * Currently, Iggy does not support updating user settings, so the server + * rejects every supplied setting. The raw entries are retained for + * compatibility with future server versions that add mutable user settings. + */ +class UserUpdateOptions final { + public: + /** @brief Creates an update with no requested user settings. */ + UserUpdateOptions() = default; + + /** + * @brief Returns the requested user settings as key-value pairs. + * @return Ordered map of setting names and values. + * @note The server currently rejects all user settings. + */ + [[nodiscard]] const std::map<std::string, std::string> &RawEntries() const noexcept { return raw_; } + + /** + * @brief Adds or replaces requested user settings. + * @param entries Setting names and values to add. + * @return Reference to this options object. + * @note The server currently rejects all user settings. + */ + UserUpdateOptions &SetRawEntries(const std::map<std::string, std::string> &entries) { + for (const auto &entry : entries) { + raw_.insert_or_assign(entry.first, entry.second); + } + return *this; + } + /** + * @brief Adds or replaces requested user settings. + * @param entries Setting names and values to move into this options object. + * @return Reference to this options object. + * @see SetRawEntries(const std::map<std::string, std::string>&) + * @note The server currently rejects all user settings. + */ + UserUpdateOptions &SetRawEntries(std::map<std::string, std::string> &&entries) { + while (!entries.empty()) { + auto node = entries.extract(entries.begin()); + raw_.erase(node.key()); + raw_.insert(std::move(node)); + } + return *this; + } + + private: + std::map<std::string, std::string> raw_; + + friend class IggyBlockingClient; +}; + +/** * @brief Starting position for polling messages. * * @note The strategy kind and value are passed across the Rust FFI as a pair. @@ -2722,16 +3441,108 @@ */ void Logout(); + /** + * @brief Retrieves one user by numeric ID or name. + * + * An authenticated user may retrieve its own account without the global + * read-users grant. Reading another account requires read-users or + * manage-users permission. The result is a snapshot and includes the + * target user's optional permission assignment. + * + * @param user User to retrieve, addressed by numeric ID or name. + * @return Details for the requested user. + * @throws IggyException if the client is unavailable or unauthenticated; + * the identifier is invalid; the user does not exist; the caller + * lacks permission; or the request fails. + */ UserInfoDetails GetUser(const Identifier &user); + + /** + * @brief Lists user summaries visible to the authenticated caller. + * + * The summaries omit permissions. Use GetUser() to retrieve one user's + * permission assignment. + * + * @return User summaries observed by the server for this request. + * @throws IggyException if the client is unavailable or unauthenticated; + * the caller lacks read-users or manage-users permission; or the + * request fails. + */ std::vector<UserInfo> GetUsers(); + + /** + * @brief Creates a user account. + * + * User names must contain between 3 and 50 bytes and be unique. Passwords + * must contain between 3 and 100 bytes. An inactive account is created but + * cannot authenticate. Passing `std::nullopt` assigns no permission object; + * passing a default-constructed Permissions assigns an explicit permission + * object with no enabled grants. + * + * A failed or unknown transport outcome can leave the user created. Query + * the account by name before retrying this request. + * + * @param username Unique user name. + * @param password Initial user password. + * @param status Initial authentication status. + * @param permissions Optional permission assignment. + * @return Details of the newly created user. + * @throws IggyException if the client is unavailable or unauthenticated; + * the name, password, status, or permissions are invalid; the name + * is already in use; the caller lacks manage-users permission; or + * the request fails. + */ UserInfoDetails CreateUser(std::string username, std::string password, UserStatus status, const std::optional<Permissions> &permissions = std::nullopt); + + /** + * @brief Deletes a user account. + * + * The root user cannot be deleted. Deleting another user also removes its + * permission assignment and personal access tokens. A failed or unknown + * transport outcome can leave the deletion committed; look up the user + * before retrying. + * + * @param user User to delete, addressed by numeric ID or name. + * @throws IggyException if the client is unavailable or unauthenticated; + * the identifier is invalid; the user does not exist or is the + * root user; the caller lacks manage-users permission; or the + * request fails. + */ void DeleteUser(const Identifier &user); + + /** + * @brief Changes a user's name, status, and mutable settings. + * + * A supplied name must contain between 3 and 50 bytes and remain unique. + * Setting the status to inactive prevents subsequent authentication. + * Passing `std::nullopt` for the name or status leaves that field + * unchanged. The supplied UserUpdateOptions changes only the settings it + * contains; settings left unset retain their current values. User settings + * are currently not mutable, so the options object must be empty. Passing + * `std::nullopt` for both fields and an empty options object is accepted as + * a no-op. + * + * A failed or unknown transport outcome can leave the update committed. + * Retrieve the user before retrying with different values. + * + * @param user User to update, addressed by numeric ID or name. + * @param username New unique name, or `std::nullopt` to retain the name. + * @param status New authentication status, or `std::nullopt` to retain the + * status. + * @param options User update options. + * @throws IggyException if the client is unavailable or unauthenticated; + * an identifier, name, status, or option is invalid; an option is + * unsupported; the user does not exist; the name is already in + * use; the caller lacks manage-users permission; or the request + * fails. + */ void UpdateUser(const Identifier &user, - std::optional<std::string> username = std::nullopt, - std::optional<UserStatus> status = std::nullopt); + std::optional<std::string> username, + std::optional<UserStatus> status, + const UserUpdateOptions &options); /** * @brief Creates a top-level stream in the cluster metadata. @@ -2950,6 +3761,28 @@ * request fails. */ void DeletePartitions(const Identifier &stream, const Identifier &topic, std::uint32_t partitions_count); + + /** + * @brief Deletes the oldest sealed segments from one partition. + * + * The active segment is never deleted. If fewer sealed segments exist than + * requested, every sealed segment is selected. A count of zero, or a + * partition with no sealed segments, succeeds without deleting data. The + * server commits a truncation watermark before local replicas remove the + * selected segment files. + * + * A failed or unknown transport outcome can leave the truncation committed. + * Inspect the partition before retrying this destructive request. + * + * @param stream Parent stream, addressed by numeric ID or name. + * @param topic Parent topic, addressed by numeric ID or name. + * @param partition_id Numeric partition ID. + * @param segments_count Maximum number of oldest sealed segments to delete. + * @throws IggyException if the client is unavailable or unauthenticated; + * an identifier or partition is invalid; the stream, topic, or + * partition does not exist; the caller lacks topic-management + * permission; or the request fails. + */ void DeleteSegments(const Identifier &stream, const Identifier &topic, std::uint32_t partition_id, @@ -3153,11 +3986,80 @@ const Identifier &topic, std::optional<std::uint32_t> partition_id = std::nullopt); + /** + * @brief Retrieves details for this client connection. + * + * The result includes consumer-group memberships observed for the current + * authenticated connection. HTTP is stateless and does not expose a + * persistent current connection, so the HTTP transport reports this + * operation as unavailable. + * + * @return Details for the current client connection. + * @throws IggyException if the client is unavailable or unauthenticated; + * the transport does not support the operation; or the request + * fails. + */ ClientInfoDetails GetMe(); + + /** + * @brief Retrieves one currently connected client by numeric ID. + * + * The result is a snapshot containing the connection's observed consumer- + * group memberships. The connection can disappear immediately after the + * request completes. + * + * @param client_id Server-assigned connection ID. + * @return Details for the requested client connection. + * @throws IggyException if the client is unavailable or unauthenticated; + * the requested connection does not exist; the caller lacks + * read-servers or manage-servers permission; or the request fails. + */ ClientInfoDetails GetClient(std::uint32_t client_id); + + /** + * @brief Lists client connections currently known to the server. + * + * Each entry is a snapshot summary and omits individual consumer-group + * identifiers. Use GetClient() for membership details. + * + * @return Client connection summaries observed for this request. + * @throws IggyException if the client is unavailable or unauthenticated; + * the caller lacks read-servers or manage-servers permission; or + * the request fails. + */ std::vector<ClientInfo> GetClients(); + /** + * @brief Requests an immediate flush of a partition's unsaved buffer. + * + * When supported, the server writes buffered messages to storage and also + * requests an operating-system synchronization when @p fsync is true. The + * current VSR server does not expose this maintenance primitive and returns + * a feature-unavailable error; its HTTP API has no matching route. + * + * @param stream Parent stream, addressed by numeric ID or name. + * @param topic Parent topic, addressed by numeric ID or name. + * @param partition_id Numeric partition ID. + * @param fsync Whether to request synchronization to stable storage. + * @throws IggyException if the client is unavailable or unauthenticated; + * an identifier or partition is invalid; the server or transport + * does not support the operation; the caller lacks permission; or + * the request fails. + */ void FlushUnsavedBuffer(const Identifier &stream, const Identifier &topic, std::uint32_t partition_id, bool fsync); + + /** + * @brief Retrieves server process, storage, and resource statistics. + * + * The returned fields are an observed snapshot and can change immediately. + * Process and host measurements describe the server that handles the + * request; metadata totals describe the state visible to that server. + * + * @return Statistics observed for this request. + * @throws IggyException if the client is unavailable or unauthenticated; + * the caller lacks read-servers or manage-servers permission; or + * the request fails. + */ Stats GetStats(); private:
diff --git a/foreign/cpp/src/client.cpp b/foreign/cpp/src/client.cpp index 0d101f8..bf01dce 100644 --- a/foreign/cpp/src/client.cpp +++ b/foreign/cpp/src/client.cpp
@@ -137,10 +137,13 @@ void IggyBlockingClient::UpdateUser(const Identifier &user, const std::optional<std::string> username, - const std::optional<UserStatus> status) { - RethrowAsIggyException([this, &user, &username, status] { + const std::optional<UserStatus> status, + const UserUpdateOptions &options) { + RethrowAsIggyException([this, &user, &username, status, &options] { + auto ffi_options = ToFfiRawOptions(options.RawEntries()); Handle()->update_user(user.ToFfi(), username.has_value(), username.value_or(""), status.has_value(), - static_cast<ffi::UserStatus>(status.value_or(UserStatus::Active))); + static_cast<ffi::UserStatus>(status.value_or(UserStatus::Active)), + std::move(ffi_options)); }); }
diff --git a/foreign/cpp/src/client.rs b/foreign/cpp/src/client.rs index 6ea8fdc..c74fcfd 100644 --- a/foreign/cpp/src/client.rs +++ b/foreign/cpp/src/client.rs
@@ -1164,6 +1164,7 @@ username: String, has_status: bool, status: ffi::UserStatus, + options: Vec<ffi::HeaderEntry>, ) -> Result<(), String> { let rust_user_id = RustIdentifier::try_from(user_id) .map_err(|error| format!("Could not update user: invalid user identifier: {error}"))?; @@ -1171,6 +1172,9 @@ .then(|| RustUserStatus::try_from(status)) .transpose() .map_err(|error| format!("Could not update user '{rust_user_id}': {error}"))?; + let raw = ffi_options_to_raw(options) + .map_err(|error| format!("Could not update user '{rust_user_id}': {error}"))?; + let rust_options = UserUpdateOptions { raw }; RUNTIME.block_on(async { self.inner @@ -1178,7 +1182,7 @@ &rust_user_id, has_username.then_some(username.as_str()), rust_status, - &UserUpdateOptions::default(), + &rust_options, ) .await .map_err(|error| format!("Could not update user '{rust_user_id}': {error}"))?;
diff --git a/foreign/cpp/src/lib.rs b/foreign/cpp/src/lib.rs index 3ed6d11..8dda770 100644 --- a/foreign/cpp/src/lib.rs +++ b/foreign/cpp/src/lib.rs
@@ -382,6 +382,7 @@ created_at: u64, status: UserStatus, username: String, + options: Vec<HeaderEntry>, } struct UserInfoDetails { @@ -391,6 +392,7 @@ username: String, has_permissions: bool, permissions: Permissions, + options: Vec<HeaderEntry>, } struct LoginInfo { @@ -656,6 +658,7 @@ username: String, has_status: bool, status: UserStatus, + options: Vec<HeaderEntry>, ) -> Result<()>; fn update_permissions( self: &Client,
diff --git a/foreign/cpp/src/type_conversion.rs b/foreign/cpp/src/type_conversion.rs index 0d95a69..9c55c78 100644 --- a/foreign/cpp/src/type_conversion.rs +++ b/foreign/cpp/src/type_conversion.rs
@@ -158,6 +158,7 @@ created_at: user.created_at.as_micros(), status: ffi::UserStatus::from(user.status), username: user.username, + options: resource_options_to_ffi(&user.options, true), } } } @@ -172,6 +173,7 @@ username: user.username, has_permissions, permissions: ffi::Permissions::from(user.permissions.unwrap_or_default()), + options: resource_options_to_ffi(&user.options, true), } } }
diff --git a/foreign/cpp/src/type_conversions.cpp b/foreign/cpp/src/type_conversions.cpp index fc7594c..7306e26 100644 --- a/foreign/cpp/src/type_conversions.cpp +++ b/foreign/cpp/src/type_conversions.cpp
@@ -136,7 +136,8 @@ UserInfo UserInfo::FromFfi(ffi::UserInfo user) { return UserInfo(user.id, user.created_at, static_cast<UserStatus>(user.status), - std::string(user.username.c_str(), user.username.size())); + std::string(user.username.c_str(), user.username.size()), + ResourceOptions::FromFfi(std::move(user.options), rust::Vec<ffi::HeaderEntry>{})); } UserInfoDetails UserInfoDetails::FromFfi(ffi::UserInfoDetails user) { @@ -145,7 +146,8 @@ permissions = ::iggy::Permissions::FromFfi(std::move(user.permissions)); } return UserInfoDetails(user.id, user.created_at, static_cast<UserStatus>(user.status), - std::string(user.username.c_str(), user.username.size()), std::move(permissions)); + std::string(user.username.c_str(), user.username.size()), std::move(permissions), + ResourceOptions::FromFfi(std::move(user.options), rust::Vec<ffi::HeaderEntry>{})); } ffi::Identifier Identifier::ToFfi() const { @@ -338,7 +340,7 @@ std::vector<CacheMetricEntry> cache_metrics; cache_metrics.reserve(stats.cache_metrics.size()); for (auto &entry : stats.cache_metrics) { - cache_metrics.push_back(CacheMetricEntry::FromFfi(std::move(entry))); + cache_metrics.push_back(CacheMetricEntry::FromFfi(entry)); } return Stats(stats.process_id, stats.cpu_usage, stats.total_cpu_usage, stats.memory_usage, stats.total_memory, stats.available_memory, stats.run_time_micros, stats.start_time_epoch_micros, stats.read_bytes,
diff --git a/foreign/cpp/tests/e2e/client.cpp b/foreign/cpp/tests/e2e/client.cpp index 720770b..808e972 100644 --- a/foreign/cpp/tests/e2e/client.cpp +++ b/foreign/cpp/tests/e2e/client.cpp
@@ -632,18 +632,22 @@ const auto created = CreateUser(root_client, username, "secret123", iggy::UserStatus::Active); auto client = GetLoggedOutHighLevelClient(); - ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive), + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{}), std::exception); ASSERT_NO_THROW(client.Connect()); - ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive), + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{}), std::exception); ASSERT_NO_THROW(client.Login("iggy", "iggy")); ASSERT_NO_THROW(client.Logout()); - ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive), + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{}), std::exception); ASSERT_NO_THROW(client.Login("iggy", "iggy")); ASSERT_NO_THROW(client.Disconnect()); - ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive), + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{}), std::exception); const auto fetched = root_client.GetUser(iggy::Identifier::String(username)); @@ -660,12 +664,12 @@ const std::string proposed_username = GetRandomName(50); const auto unknown_id = std::numeric_limits<std::uint32_t>::max(); - ASSERT_THROW( - client.UpdateUser(iggy::Identifier::String(unknown_username), proposed_username, iggy::UserStatus::Inactive), - std::exception); - ASSERT_THROW( - client.UpdateUser(iggy::Identifier::Numeric(unknown_id), GetRandomName(50), iggy::UserStatus::Inactive), - std::exception); + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(unknown_username), proposed_username, + iggy::UserStatus::Inactive, iggy::UserUpdateOptions{}), + std::exception); + ASSERT_THROW(client.UpdateUser(iggy::Identifier::Numeric(unknown_id), GetRandomName(50), iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{}), + std::exception); ASSERT_THROW(client.GetUser(iggy::Identifier::String(proposed_username)), std::exception); } @@ -676,7 +680,8 @@ const std::string replacement = GetRandomName(50); ASSERT_NO_THROW({ const auto created = CreateUser(client, username, "secret123", iggy::UserStatus::Active); - ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{})); RenameTrackedUser(username, replacement); ASSERT_THROW(client.GetUser(iggy::Identifier::String(username)), std::exception); @@ -694,8 +699,8 @@ const std::string replacement = GetRandomName(50); ASSERT_NO_THROW({ const auto created = CreateUser(client, username, "secret123", iggy::UserStatus::Inactive); - ASSERT_NO_THROW( - client.UpdateUser(iggy::Identifier::Numeric(created.Id()), replacement, iggy::UserStatus::Active)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::Numeric(created.Id()), replacement, + iggy::UserStatus::Active, iggy::UserUpdateOptions{})); RenameTrackedUser(username, replacement); const auto by_id = client.GetUser(iggy::Identifier::Numeric(created.Id())); @@ -717,13 +722,14 @@ ASSERT_NO_THROW({ const auto created = CreateUser(client, username, "secret123", iggy::UserStatus::Active); - ASSERT_NO_THROW( - client.UpdateUser(iggy::Identifier::Numeric(created.Id()), std::nullopt, iggy::UserStatus::Inactive)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::Numeric(created.Id()), std::nullopt, + iggy::UserStatus::Inactive, iggy::UserUpdateOptions{})); const auto status_updated = client.GetUser(iggy::Identifier::Numeric(created.Id())); EXPECT_EQ(status_updated.Username(), username); EXPECT_EQ(status_updated.Status(), iggy::UserStatus::Inactive); - ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::Numeric(created.Id()), replacement, std::nullopt)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::Numeric(created.Id()), replacement, std::nullopt, + iggy::UserUpdateOptions{})); RenameTrackedUser(username, replacement); const auto username_updated = client.GetUser(iggy::Identifier::Numeric(created.Id())); @@ -744,11 +750,11 @@ ASSERT_NO_THROW({ const auto first = CreateUser(client, first_username, "secret123", iggy::UserStatus::Active); const auto second = CreateUser(client, second_username, "secret123", iggy::UserStatus::Active); - ASSERT_NO_THROW( - client.UpdateUser(iggy::Identifier::String(first_username), first_replacement, iggy::UserStatus::Active)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(first_username), first_replacement, + iggy::UserStatus::Active, iggy::UserUpdateOptions{})); RenameTrackedUser(first_username, first_replacement); - ASSERT_NO_THROW( - client.UpdateUser(iggy::Identifier::String(second_username), second_replacement, iggy::UserStatus::Active)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(second_username), second_replacement, + iggy::UserStatus::Active, iggy::UserUpdateOptions{})); RenameTrackedUser(second_username, second_replacement); const auto fetched_first = client.GetUser(iggy::Identifier::String(first_replacement)); @@ -770,7 +776,8 @@ const auto created = CreateUser(client, source, "secret123", iggy::UserStatus::Active); for (const auto &replacement : invalid_usernames) { - ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(source), replacement, iggy::UserStatus::Active), + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(source), replacement, iggy::UserStatus::Active, + iggy::UserUpdateOptions{}), std::exception); } const auto fetched = client.GetUser(iggy::Identifier::String(source)); @@ -793,7 +800,7 @@ ASSERT_NO_THROW({ const auto created = CreateUser(client, sources[index], "secret123", iggy::UserStatus::Active); ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(sources[index]), replacements[index], - iggy::UserStatus::Active)); + iggy::UserStatus::Active, iggy::UserUpdateOptions{})); RenameTrackedUser(sources[index], replacements[index]); const auto fetched = client.GetUser(iggy::Identifier::String(replacements[index])); EXPECT_EQ(fetched.Id(), created.Id()); @@ -815,7 +822,9 @@ const auto created = CreateUser(client, username, "secret123", iggy::UserStatus::Active); for (const auto status : statuses) { const std::string replacement = GetRandomName(50); - ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), replacement, status), std::exception); + ASSERT_THROW( + client.UpdateUser(iggy::Identifier::String(username), replacement, status, iggy::UserUpdateOptions{}), + std::exception); ASSERT_THROW(client.GetUser(iggy::Identifier::String(replacement)), std::exception); } const auto fetched = client.GetUser(iggy::Identifier::String(username)); @@ -833,9 +842,9 @@ ASSERT_NO_THROW({ const auto target = CreateUser(client, target_username, "secret123", iggy::UserStatus::Active); const auto conflict = CreateUser(client, conflict_username, "secret123", iggy::UserStatus::Inactive); - ASSERT_THROW( - client.UpdateUser(iggy::Identifier::String(target_username), conflict_username, iggy::UserStatus::Inactive), - std::exception); + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(target_username), conflict_username, + iggy::UserStatus::Inactive, iggy::UserUpdateOptions{}), + std::exception); const auto fetched_target = client.GetUser(iggy::Identifier::String(target_username)); const auto fetched_conflict = client.GetUser(iggy::Identifier::String(conflict_username)); EXPECT_EQ(fetched_target.Id(), target.Id()); @@ -851,7 +860,8 @@ const std::string username = GetRandomName(50); ASSERT_NO_THROW({ const auto created = CreateUser(client, username, "secret123", iggy::UserStatus::Active); - ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(username), username, iggy::UserStatus::Inactive)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(username), username, iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{})); const auto fetched = client.GetUser(iggy::Identifier::String(username)); EXPECT_EQ(fetched.Id(), created.Id()); EXPECT_EQ(fetched.Status(), iggy::UserStatus::Inactive); @@ -872,8 +882,8 @@ permissions.SetGlobal(global); ASSERT_NO_THROW({ const auto created = CreateUser(root_client, username, "known-secret", iggy::UserStatus::Active, permissions); - ASSERT_NO_THROW( - root_client.UpdateUser(iggy::Identifier::String(username), replacement, iggy::UserStatus::Active)); + ASSERT_NO_THROW(root_client.UpdateUser(iggy::Identifier::String(username), replacement, + iggy::UserStatus::Active, iggy::UserUpdateOptions{})); RenameTrackedUser(username, replacement); const auto fetched = root_client.GetUser(iggy::Identifier::String(replacement)); EXPECT_EQ(fetched.Id(), created.Id()); @@ -895,10 +905,12 @@ auto user_client = GetLoggedOutHighLevelClient(); const std::string username = GetRandomName(50); ASSERT_NO_THROW({ CreateUser(root_client, username, "known-secret", iggy::UserStatus::Active); }); - ASSERT_NO_THROW(root_client.UpdateUser(iggy::Identifier::String(username), username, iggy::UserStatus::Inactive)); + ASSERT_NO_THROW(root_client.UpdateUser(iggy::Identifier::String(username), username, iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{})); ASSERT_NO_THROW(user_client.Connect()); ASSERT_THROW(user_client.Login(username, "known-secret"), std::exception); - ASSERT_NO_THROW(root_client.UpdateUser(iggy::Identifier::String(username), username, iggy::UserStatus::Active)); + ASSERT_NO_THROW(root_client.UpdateUser(iggy::Identifier::String(username), username, iggy::UserStatus::Active, + iggy::UserUpdateOptions{})); ASSERT_NO_THROW(user_client.Login(username, "known-secret")); } @@ -909,8 +921,8 @@ const std::string new_username = GetRandomName(50); ASSERT_NO_THROW({ const auto first = CreateUser(client, old_username, "secret123", iggy::UserStatus::Active); - ASSERT_NO_THROW( - client.UpdateUser(iggy::Identifier::String(old_username), new_username, iggy::UserStatus::Active)); + ASSERT_NO_THROW(client.UpdateUser(iggy::Identifier::String(old_username), new_username, + iggy::UserStatus::Active, iggy::UserUpdateOptions{})); RenameTrackedUser(old_username, new_username); const auto second = CreateUser(client, old_username, "secret123", iggy::UserStatus::Active); EXPECT_EQ(client.GetUser(iggy::Identifier::String(new_username)).Username(), new_username); @@ -925,7 +937,8 @@ const std::string username = GetRandomName(50); ASSERT_NO_THROW({ const auto target = CreateUser(client, username, "secret123", iggy::UserStatus::Active); - ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), "iggy", iggy::UserStatus::Inactive), + ASSERT_THROW(client.UpdateUser(iggy::Identifier::String(username), "iggy", iggy::UserStatus::Inactive, + iggy::UserUpdateOptions{}), std::exception); const auto root = client.GetUser(iggy::Identifier::String("iggy")); const auto fetched = client.GetUser(iggy::Identifier::String(username)); @@ -955,10 +968,10 @@ ASSERT_NO_THROW(caller_client->connect()); ASSERT_NO_THROW(caller_client->login_user(caller_username, "secret123")); ASSERT_THROW(caller_client->update_user(make_string_identifier(target_username), true, GetRandomName(50), true, - iggy::ffi::UserStatus::Inactive), + iggy::ffi::UserStatus::Inactive, {}), std::exception); ASSERT_THROW(caller_client->update_user(make_string_identifier(caller_username), true, GetRandomName(50), true, - iggy::ffi::UserStatus::Inactive), + iggy::ffi::UserStatus::Inactive, {}), std::exception); iggy::ffi::UserInfoDetails fetched{}; ASSERT_NO_THROW({ fetched = root_client->get_user(make_string_identifier(target_username)); }); @@ -984,7 +997,7 @@ ASSERT_NO_THROW(manager_client->connect()); ASSERT_NO_THROW(manager_client->login_user(manager_username, "secret123")); ASSERT_NO_THROW(manager_client->update_user(make_string_identifier(target_username), true, replacement, true, - iggy::ffi::UserStatus::Inactive)); + iggy::ffi::UserStatus::Inactive, {})); RenameTrackedUser(target_username, replacement); iggy::ffi::UserInfoDetails fetched{}; ASSERT_NO_THROW({ fetched = root_client->get_user(make_string_identifier(replacement)); });
diff --git a/foreign/cpp/tests/unit/unit_tests.cpp b/foreign/cpp/tests/unit/unit_tests.cpp index 916ffdb..16af7af 100644 --- a/foreign/cpp/tests/unit/unit_tests.cpp +++ b/foreign/cpp/tests/unit/unit_tests.cpp
@@ -365,7 +365,8 @@ EXPECT_THROW((void)client.GetUsers(), iggy::IggyException); EXPECT_THROW((void)client.CreateUser("user", "secret123", iggy::UserStatus::Active), iggy::IggyException); EXPECT_THROW(client.DeleteUser(stream), iggy::IggyException); - EXPECT_THROW(client.UpdateUser(stream, "updated-user", iggy::UserStatus::Active), iggy::IggyException); + EXPECT_THROW(client.UpdateUser(stream, "updated-user", iggy::UserStatus::Active, iggy::UserUpdateOptions{}), + iggy::IggyException); EXPECT_THROW(client.CreateStream("stream"), iggy::IggyException); EXPECT_THROW(client.UpdateStream(stream, "updated-stream"), iggy::IggyException); EXPECT_THROW(client.GetStreams(), iggy::IggyException);