kafrust 0.2.28

A pure Rust Kafka client with no librdkafka or C client binding dependency.
Documentation
mod common;

use kafrust::{
    AdminClient, ClientConfig, Error, PartitionReassignment, PartitionReassignmentOptions,
    PartitionReassignmentQuery,
};
use std::time::Duration;

#[tokio::main]
async fn main() -> kafrust::Result<()> {
    let bootstrap_servers = common::bootstrap_servers_from_env();
    let topic = std::env::var("KAFRUST_REASSIGNMENT_TOPIC")
        .or_else(|_| std::env::var("KAFRUST_TOPIC"))
        .unwrap_or_else(|_| "kafrust-smoke-multi".to_owned());
    let partition = parse_partition()?;
    let replicas = parse_replicas()?;
    if replicas.is_empty() {
        return Err(Error::Unsupported(
            "KAFRUST_REASSIGNMENT_REPLICAS must contain at least one broker ID",
        ));
    }

    let config = common::apply_security(
        ClientConfig::new(bootstrap_servers).client_id("kafrust-admin-reassignment-example"),
    )?;
    let admin = AdminClient::new(config);
    let options = PartitionReassignmentOptions::new().timeout(Duration::from_secs(30));
    let request = [PartitionReassignment::new(&topic).partition(partition, replicas.clone())];
    let altered = admin
        .alter_partition_reassignments(&request, options)
        .await?;
    if !altered.is_success() {
        let code = altered
            .topics()
            .iter()
            .flat_map(|topic| topic.partitions())
            .find(|partition| !partition.is_success())
            .map(|partition| partition.error_code())
            .unwrap_or(altered.error_code());
        return Err(Error::Broker {
            code,
            context: format!("alter partition reassignment for {topic}-{partition}"),
        });
    }
    println!("submitted partition reassignment for {topic}-{partition} to replicas {replicas:?}");

    let query = [PartitionReassignmentQuery::new(&topic).partition(partition)];
    for _ in 0..60 {
        let status = admin
            .list_partition_reassignments(Some(&query), options)
            .await?;
        if !status.is_success() {
            return Err(Error::Broker {
                code: status.error_code(),
                context: format!("list partition reassignment for {topic}-{partition}"),
            });
        }
        let still_running = status.topics().iter().any(|listed_topic| {
            listed_topic.name() == topic
                && listed_topic
                    .partitions()
                    .iter()
                    .any(|listed_partition| listed_partition.partition_index() == partition)
        });
        if !still_running {
            println!("partition reassignment for {topic}-{partition} completed");
            return Ok(());
        }
        tokio::time::sleep(Duration::from_millis(500)).await;
    }

    Err(Error::Unsupported(
        "partition reassignment did not complete within the smoke-test deadline",
    ))
}

fn parse_partition() -> kafrust::Result<i32> {
    let Ok(value) = std::env::var("KAFRUST_REASSIGNMENT_PARTITION") else {
        return Ok(0);
    };
    value
        .parse()
        .map_err(|_| Error::Unsupported("KAFRUST_REASSIGNMENT_PARTITION must be an integer"))
}

fn parse_replicas() -> kafrust::Result<Vec<i32>> {
    let value =
        std::env::var("KAFRUST_REASSIGNMENT_REPLICAS").unwrap_or_else(|_| "3,1,2".to_owned());
    value
        .split(',')
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(|value| {
            value.parse().map_err(|_| {
                Error::Unsupported("KAFRUST_REASSIGNMENT_REPLICAS must be CSV integers")
            })
        })
        .collect()
}