blob: d74c9b8094019fa3fc38ebace18e76632616c1bf [file]
// 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.
use std::sync::Arc;
use ext_php_rs::{exception::PhpResult, php_class, php_impl, types::ZendCallable};
use futures::StreamExt;
use iggy::prelude::{
AutoCommit as RustAutoCommit, AutoCommitWhen as RustAutoCommitWhen,
IggyConsumer as RustIggyConsumer, IggyDuration,
};
use tokio::sync::Mutex;
use crate::error::to_php_exception;
use crate::message_iterator::MessageIterator;
use crate::receive_message::ReceiveMessage;
use crate::runtime::runtime;
/// A PHP class representing the Iggy consumer.
#[php_class]
#[php(name = "Iggy\\Consumer")]
pub struct IggyConsumer {
pub(crate) inner: Arc<Mutex<RustIggyConsumer>>,
pub(crate) name: String,
pub(crate) stream: String,
pub(crate) topic: String,
}
#[php_impl]
impl IggyConsumer {
/// Get the last consumed offset or null if no offset has been consumed yet.
pub fn get_last_consumed_offset(&self, partition_id: u32) -> Option<u64> {
self.with_consumer(|inner| inner.get_last_consumed_offset(partition_id))
}
/// Get the last stored offset or null if no offset has been stored yet.
pub fn get_last_stored_offset(&self, partition_id: u32) -> Option<u64> {
self.with_consumer(|inner| inner.get_last_stored_offset(partition_id))
}
/// Gets the name of the consumer group.
pub fn name(&self) -> String {
self.name.clone()
}
/// Gets the current partition id or 0 if no messages have been polled yet.
pub fn partition_id(&self) -> u32 {
self.with_consumer(RustIggyConsumer::partition_id)
}
/// Gets the stream identifier this consumer is configured for.
pub fn stream(&self) -> String {
self.stream.clone()
}
/// Gets the topic identifier this consumer is configured for.
pub fn topic(&self) -> String {
self.topic.clone()
}
/// Stores the provided offset for the provided partition id.
///
/// If partition_id is null, at least one message must have been polled first.
pub fn store_offset(&self, offset: u64, partition_id: Option<u32>) -> PhpResult {
let inner = self.inner.clone();
runtime().block_on(async move {
let inner = inner.lock().await;
let partition_id = Self::partition_id_or_current_after_poll(&inner, partition_id)?;
inner
.store_offset(offset, partition_id)
.await
.map_err(to_php_exception)
})
}
/// Deletes the stored offset for the provided partition id.
///
/// If partition_id is null, at least one message must have been polled first.
pub fn delete_offset(&self, partition_id: Option<u32>) -> PhpResult {
let inner = self.inner.clone();
runtime().block_on(async move {
let inner = inner.lock().await;
let partition_id = Self::partition_id_or_current_after_poll(&inner, partition_id)?;
inner
.delete_offset(partition_id)
.await
.map_err(to_php_exception)
})
}
/// Consumes messages with a PHP callback.
///
/// The callback is called as callback(ReceiveMessage $message). A finite limit is required.
///
/// With AutoCommit::when(), offsets may already be queued for commit before the
/// PHP callback runs. Use AutoCommit::disabled() and call storeOffset() after a
/// successful callback when at-least-once callback processing is required.
pub fn consume_messages(&self, callback: ZendCallable, limit: u32) -> PhpResult<u32> {
let mut consumed = 0;
while consumed < limit {
let Some(message) = self.next_message()? else {
break;
};
callback
.try_call(vec![&message])
.map_err(to_php_exception)?;
consumed += 1;
}
Ok(consumed)
}
/// Returns an iterator over messages for use with foreach.
pub fn iter_messages(&self) -> MessageIterator {
MessageIterator::new(self.inner.clone())
}
}
impl IggyConsumer {
fn with_consumer<T>(&self, f: impl FnOnce(&RustIggyConsumer) -> T) -> T {
let inner = self.inner.clone();
runtime().block_on(async move {
let inner = inner.lock().await;
f(&inner)
})
}
fn next_message(&self) -> PhpResult<Option<ReceiveMessage>> {
let inner = self.inner.clone();
runtime().block_on(async move {
let mut inner = inner.lock().await;
match inner.next().await {
Some(Ok(message)) => Ok(Some(ReceiveMessage {
inner: message.message,
partition_id: message.partition_id,
})),
Some(Err(err)) => Err(to_php_exception(err)),
None => Ok(None),
}
})
}
fn partition_id_or_current_after_poll(
inner: &RustIggyConsumer,
partition_id: Option<u32>,
) -> PhpResult<Option<u32>> {
if let Some(partition_id) = partition_id {
return Ok(Some(partition_id));
}
let current_partition_id = inner.partition_id();
if inner
.get_last_consumed_offset(current_partition_id)
.is_none()
{
return Err(to_php_exception(
"'partition_id' is required until at least one message has been polled",
));
}
Ok(Some(current_partition_id))
}
}
#[php_class]
#[php(name = "Iggy\\AutoCommit")]
#[derive(Clone, Copy)]
pub struct AutoCommit {
pub(crate) inner: RustAutoCommit,
}
#[php_impl]
impl AutoCommit {
pub fn disabled() -> Self {
Self {
inner: RustAutoCommit::Disabled,
}
}
pub fn interval(interval_micros: u64) -> Self {
Self {
inner: RustAutoCommit::Interval(IggyDuration::from(interval_micros)),
}
}
pub fn interval_or_when(interval_micros: u64, when: &AutoCommitWhen) -> Self {
Self {
inner: RustAutoCommit::IntervalOrWhen(IggyDuration::from(interval_micros), when.inner),
}
}
pub fn when(when: &AutoCommitWhen) -> Self {
Self {
inner: RustAutoCommit::When(when.inner),
}
}
}
impl From<&AutoCommit> for RustAutoCommit {
fn from(value: &AutoCommit) -> Self {
value.inner
}
}
#[php_class]
#[php(name = "Iggy\\AutoCommitWhen")]
#[derive(Clone, Copy)]
pub struct AutoCommitWhen {
pub(crate) inner: RustAutoCommitWhen,
}
#[php_impl]
impl AutoCommitWhen {
pub fn polling_messages() -> Self {
Self {
inner: RustAutoCommitWhen::PollingMessages,
}
}
pub fn consuming_all_messages() -> Self {
Self {
inner: RustAutoCommitWhen::ConsumingAllMessages,
}
}
pub fn consuming_each_message() -> Self {
Self {
inner: RustAutoCommitWhen::ConsumingEachMessage,
}
}
pub fn consuming_every_nth_message(n: u32) -> Self {
Self {
inner: RustAutoCommitWhen::ConsumingEveryNthMessage(n),
}
}
}
impl From<&AutoCommitWhen> for RustAutoCommitWhen {
fn from(value: &AutoCommitWhen) -> Self {
value.inner
}
}