// ConsumerGroupHeartbeat v0/v1 (key 68) — the single KIP-848 group
// membership RPC: join, reconcile, and leave all ride one request the
// member re-issues every heartbeat interval. Pinned from
// ConsumerGroupHeartbeatRequest.json / ConsumerGroupHeartbeatResponse.json
// in the Kafka 4.3 tree: v1 adds SubscribedTopicRegex and the
// INVALID_REGULAR_EXPRESSION error; flexible since v0.
//
// Member semantics: epoch 0 joins the group (the coordinator answers with
// the member id and the next epoch), -1 leaves gracefully, -2 marks a
// static member that will rejoin. The client generates its member id and
// keeps it for the process lifetime.
///|
/// One topic's partitions in a heartbeat assignment (topic-id addressed).
pub(all) struct HeartbeatTopicPartitions {
topic_id : Uuid
partitions : Array[Int]
} derive(@debug.Debug)
///|
/// Encode a ConsumerGroupHeartbeat request body. Optional fields the
/// member leaves unchanged send None (the server keeps its state); the
/// owned-partition list is sent as null (unchanged), so the server
/// drives assignment.
pub fn encode_consumer_group_heartbeat_request(
version : Int,
group_id : String,
member_id : String,
member_epoch : Int,
instance_id? : String? = None,
rack_id? : String? = None,
rebalance_timeout_ms? : Int = -1,
subscribed_topic_names? : Array[String]? = None,
subscribed_topic_regex? : String? = None,
server_assignor? : String? = None,
) -> Bytes raise {
if version < 0 || version > 1 {
raise ProtocolError::ProtocolError(
"moonkafka implements ConsumerGroupHeartbeat v0-v1, got v\{version}",
)
}
let body = @buf.Encoder::new()
body.write_compact_string(group_id)
body.write_compact_string(member_id)
body.write_i32(member_epoch)
body.write_compact_nullable_string(instance_id)
body.write_compact_nullable_string(rack_id)
body.write_i32(rebalance_timeout_ms)
match subscribed_topic_names {
Some(names) => {
body.write_compact_len(names.length())
for name in names {
body.write_compact_string(name)
}
}
None => body.write_compact_len(-1)
}
if version >= 1 {
body.write_compact_nullable_string(subscribed_topic_regex)
}
body.write_compact_nullable_string(server_assignor)
// Owned topic partitions: a nullable struct — null ("unchanged") is a
// single -1 byte, not an array length.
body.write_i8(-1)
body.write_tag_buffer()
body.to_bytes()
}
///|
/// One member heartbeat outcome: identity/epoch reconciliation, the
/// server's heartbeat interval, and the (possibly empty) assignment.
pub struct ConsumerGroupHeartbeatResult {
error_code : Int
error_message : String?
member_id : String
member_epoch : Int
heartbeat_interval_ms : Int
assignment : Array[HeartbeatTopicPartitions]
} derive(@debug.Debug)
///|
/// Decode a ConsumerGroupHeartbeat response body.
pub fn decode_consumer_group_heartbeat_response(
d : @buf.Decoder,
) -> (ConsumerGroupHeartbeatResult, Int) raise {
let throttle = d.read_i32()
let error_code = d.read_i16()
let error_message = d.read_compact_nullable_string()
let member_id = d.read_compact_nullable_string().unwrap_or("")
let member_epoch = d.read_i32()
let heartbeat_interval_ms = d.read_i32()
let assignment : Array[HeartbeatTopicPartitions] = []
let has_assignment = d.read_i8()
if has_assignment >= 0 {
let topics = d.read_compact_len()
for _ in 0.. ConsumerGroupHeartbeatResult {
let version = self.api_version(API_CONSUMER_GROUP_HEARTBEAT)
let d = self.request(
API_CONSUMER_GROUP_HEARTBEAT,
version,
encode_consumer_group_heartbeat_request(
version,
group_id,
member_id,
member_epoch,
instance_id~,
rebalance_timeout_ms~,
subscribed_topic_names~,
subscribed_topic_regex~,
server_assignor~,
),
timeout_ms~,
)
let (result, throttle) = decode_consumer_group_heartbeat_response(d)
self.note_throttle(throttle)
result
}