use std::collections::HashMap;
use std::fmt;
use client::{self, KafkaClient};
pub use client::{Compression};
use error::Result;
use utils::TopicPartitionOffset;
use ref_slice::ref_slice;
pub const DEFAULT_ACK_TIMEOUT: i32 = 30 * 1000;
pub const DEFAULT_REQUIRED_ACKS: i16 = 1;
pub trait AsBytes {
fn as_bytes(&self) -> &[u8];
}
impl AsBytes for () {
fn as_bytes(&self) -> &[u8] { &[] }
}
impl AsBytes for String {
fn as_bytes(&self) -> &[u8] { self.as_ref() }
}
impl AsBytes for Vec<u8> {
fn as_bytes(&self) -> &[u8] { self.as_ref() }
}
impl<'a> AsBytes for &'a [u8] {
fn as_bytes(&self) -> &[u8] { self }
}
impl<'a> AsBytes for &'a str {
fn as_bytes(&self) -> &[u8] { str::as_bytes(self) }
}
pub struct Record<'a, K, V> {
pub key: K,
pub value: V,
pub topic: &'a str,
pub partition: i32,
}
impl<'a, K, V> Record<'a, K, V> {
#[inline]
pub fn from_key_value(topic: &'a str, key: K, value: V) -> Record<'a, K, V> {
Record { key: key, value: value, topic: topic, partition: -1 }
}
#[inline]
pub fn with_partition(mut self, partition: i32) -> Self {
self.partition = partition;
self
}
}
impl<'a, V> Record<'a, (), V> {
#[inline]
pub fn from_value(topic: &'a str, value: V) -> Record<'a, (), V> {
Record { key: (), value: value, topic: topic, partition: -1 }
}
}
impl<'a, K: fmt::Debug, V: fmt::Debug> fmt::Debug for Record<'a, K, V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Record {{ topic: {}, partition: {}, key: {:?}, value: {:?} }}",
self.topic, self.partition, self.key, self.value)
}
}
pub struct Producer<P = DefaultPartitioner> {
client: KafkaClient,
state: State<P>,
config: Config,
}
struct State<P> {
partition_ids: HashMap<String, Vec<i32>>,
partitioner: P,
}
struct Config {
ack_timeout: i32,
required_acks: i16,
}
impl Producer {
pub fn from_client(client: KafkaClient) -> Builder<DefaultPartitioner> {
Builder::new(Some(client), Vec::new())
}
pub fn from_hosts(hosts: Vec<String>) -> Builder<DefaultPartitioner> {
Builder::new(None, hosts)
}
pub fn client(self) -> KafkaClient {
self.client
}
}
impl<P: Partitioner> Producer<P> {
pub fn send<'a, K, V>(&mut self, rec: &Record<'a, K, V>)
-> Result<()>
where K: AsBytes, V: AsBytes
{
let mut rs = try!(self.send_all(ref_slice(rec)));
assert_eq!(1, rs.len());
rs.pop().unwrap().offset.map(|_| ())
}
pub fn send_all<'a, K, V>(&mut self, recs: &[Record<'a, K, V>])
-> Result<Vec<TopicPartitionOffset>>
where K: AsBytes, V: AsBytes
{
let partitioner = &mut self.state.partitioner;
let partition_ids = &self.state.partition_ids;
let client = &mut self.client;
let config = &self.config;
client.produce_messages(
config.required_acks, config.ack_timeout,
recs.into_iter().map(|r| {
let mut m = client::ProduceMessage {
key: to_option(r.key.as_bytes()),
value: to_option(r.value.as_bytes()),
topic: r.topic,
partition: r.partition,
};
partitioner.partition(Topics::new(partition_ids), &mut m);
m
}))
}
}
fn to_option(data: &[u8]) -> Option<&[u8]> {
if data.is_empty() {
None
} else {
Some(data)
}
}
impl<P> State<P> {
fn new(client: &mut KafkaClient, partitioner: P) -> Result<State<P>> {
let ts = client.topics();
let mut ids = HashMap::with_capacity(ts.len());
for t in ts {
ids.insert(t.name().to_owned(), t.partition_ids());
}
Ok(State{partition_ids: ids, partitioner: partitioner})
}
}
pub struct Builder<P = DefaultPartitioner> {
client: Option<KafkaClient>,
hosts: Vec<String>,
compression: Compression,
ack_timeout: i32,
required_acks: i16,
partitioner: P,
}
impl Builder {
fn new(client: Option<KafkaClient>, hosts: Vec<String>)
-> Builder<DefaultPartitioner>
{
let mut b = Builder {
client: client,
hosts: hosts,
compression: client::DEFAULT_COMPRESSION,
ack_timeout: DEFAULT_ACK_TIMEOUT,
required_acks: DEFAULT_REQUIRED_ACKS,
partitioner: DefaultPartitioner::default(),
};
if let Some(ref c) = b.client {
b.compression = c.compression();
}
b
}
pub fn with_compression(mut self, compression: Compression) -> Self {
self.compression = compression;
self
}
pub fn with_ack_timeout(mut self, timeout: i32) -> Self {
self.ack_timeout = timeout;
self
}
pub fn with_required_acks(mut self, required_acks: i16) -> Self {
self.required_acks = required_acks;
self
}
}
impl<P> Builder<P> {
pub fn with_partitioner<Q: Partitioner>(self, partitioner: Q) -> Builder<Q> {
Builder {
client: self.client,
hosts: self.hosts,
compression: self.compression,
ack_timeout: self.ack_timeout,
required_acks: self.required_acks,
partitioner: partitioner,
}
}
pub fn create(self) -> Result<Producer<P>> {
let mut client = match self.client {
Some(client) => client,
None => {
let mut client = KafkaClient::new(self.hosts);
try!(client.load_metadata_all());
client
}
};
client.set_compression(self.compression);
let state = try!(State::new(&mut client, self.partitioner));
let config = Config {
ack_timeout: self.ack_timeout,
required_acks: self.required_acks,
};
Ok(Producer{
client: client,
state: state,
config: config,
})
}
}
pub struct Topics<'a> {
partition_ids: &'a HashMap<String, Vec<i32>>,
}
impl<'a> Topics<'a> {
fn new(partition_ids: &'a HashMap<String, Vec<i32>>) -> Topics<'a> {
Topics { partition_ids: partition_ids }
}
pub fn partition_ids(&self, topic: &'a str) -> Option<&[i32]> {
self.partition_ids.get(topic).map(|ps| &ps[..])
}
}
pub trait Partitioner {
fn partition(&mut self, topics: Topics, msg: &mut client::ProduceMessage);
}
pub struct DefaultPartitioner {
cntr: u32
}
impl Partitioner for DefaultPartitioner {
#[allow(unused_variables)]
fn partition(&mut self, topics: Topics, rec: &mut client::ProduceMessage) {
if rec.partition < 0 {
match topics.partition_ids(rec.topic) {
Some(ps) if !ps.is_empty() => {
rec.partition = ps[self.cntr as usize % ps.len()];
self.cntr = self.cntr.wrapping_add(1);
}
_ => {}
}
}
}
}
impl Default for DefaultPartitioner {
fn default() -> Self {
DefaultPartitioner { cntr: 0 }
}
}