Skip to content

Commit 09aca6b

Browse files
malletgumimischi
andauthored
Add headers to KafkaAcknowledgedMessage (#179)
When a message is being sent to Kafka the acknowledgement should also contains the headers. ### Motivation: Resolves #144 ### Modifications: Reused the parsing function from KafkaConsumer in the producer path to return headers in the ack. ### Result: Headers available in the acknowledgement for producers and still available for consumers as well. --------- Co-authored-by: Michael Gecht <[email protected]>
1 parent 9986c5b commit 09aca6b

File tree

4 files changed

+83
-81
lines changed

4 files changed

+83
-81
lines changed

Diff for: Sources/Kafka/KafkaAcknowledgedMessage.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public struct KafkaAcknowledgedMessage {
2727
public var value: ByteBuffer
2828
/// The offset of the message in its partition.
2929
public var offset: KafkaOffset
30+
/// The headers of the message.
31+
public var headers: [KafkaHeader]
3032

3133
/// Initialize ``KafkaAcknowledgedMessage`` from `rd_kafka_message_t` pointer.
3234
/// - Throws: A ``KafkaAcknowledgedMessageError`` for failed acknowledgements or malformed messages.
@@ -53,7 +55,7 @@ public struct KafkaAcknowledgedMessage {
5355
self.topic = topic
5456

5557
self.partition = KafkaPartition(rawValue: Int(rdKafkaMessage.partition))
56-
58+
self.headers = try RDKafkaClient.getHeaders(for: messagePointer)
5759
if let keyPointer = rdKafkaMessage.key {
5860
let keyBufferPointer = UnsafeRawBufferPointer(
5961
start: keyPointer,

Diff for: Sources/Kafka/KafkaConsumerMessage.swift

+1-80
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public struct KafkaConsumerMessage {
6666

6767
self.partition = KafkaPartition(rawValue: Int(rdKafkaMessage.partition))
6868

69-
self.headers = try Self.getHeaders(for: messagePointer)
69+
self.headers = try RDKafkaClient.getHeaders(for: messagePointer)
7070

7171
if let keyPointer = rdKafkaMessage.key {
7272
let keyBufferPointer = UnsafeRawBufferPointer(
@@ -91,82 +91,3 @@ extension KafkaConsumerMessage: Hashable {}
9191
// MARK: - KafkaConsumerMessage + Sendable
9292

9393
extension KafkaConsumerMessage: Sendable {}
94-
95-
// MARK: - Helpers
96-
97-
extension KafkaConsumerMessage {
98-
/// Extract ``KafkaHeader``s from a `rd_kafka_message_t` pointer.
99-
///
100-
/// - Parameters:
101-
/// - for: Pointer to the `rd_kafka_message_t` object to extract the headers from.
102-
private static func getHeaders(
103-
for messagePointer: UnsafePointer<rd_kafka_message_t>
104-
) throws -> [KafkaHeader] {
105-
var result: [KafkaHeader] = []
106-
var headers: OpaquePointer?
107-
108-
var readStatus = rd_kafka_message_headers(messagePointer, &headers)
109-
110-
if readStatus == RD_KAFKA_RESP_ERR__NOENT {
111-
// No Header Entries
112-
return result
113-
}
114-
115-
guard readStatus == RD_KAFKA_RESP_ERR_NO_ERROR else {
116-
throw KafkaError.rdKafkaError(wrapping: readStatus)
117-
}
118-
119-
guard let headers else {
120-
return result
121-
}
122-
123-
let headerCount = rd_kafka_header_cnt(headers)
124-
result.reserveCapacity(headerCount)
125-
126-
var headerIndex = 0
127-
128-
while readStatus != RD_KAFKA_RESP_ERR__NOENT && headerIndex < headerCount {
129-
var headerKeyPointer: UnsafePointer<CChar>?
130-
var headerValuePointer: UnsafeRawPointer?
131-
var headerValueSize = 0
132-
133-
readStatus = rd_kafka_header_get_all(
134-
headers,
135-
headerIndex,
136-
&headerKeyPointer,
137-
&headerValuePointer,
138-
&headerValueSize
139-
)
140-
141-
if readStatus == RD_KAFKA_RESP_ERR__NOENT {
142-
// No Header Entries
143-
return result
144-
}
145-
146-
guard readStatus == RD_KAFKA_RESP_ERR_NO_ERROR else {
147-
throw KafkaError.rdKafkaError(wrapping: readStatus)
148-
}
149-
150-
guard let headerKeyPointer else {
151-
fatalError("Found null pointer when reading KafkaConsumerMessage header key")
152-
}
153-
let headerKey = String(cString: headerKeyPointer)
154-
155-
var headerValue: ByteBuffer?
156-
if let headerValuePointer, headerValueSize > 0 {
157-
let headerValueBufferPointer = UnsafeRawBufferPointer(
158-
start: headerValuePointer,
159-
count: headerValueSize
160-
)
161-
headerValue = ByteBuffer(bytes: headerValueBufferPointer)
162-
}
163-
164-
let newHeader = KafkaHeader(key: headerKey, value: headerValue)
165-
result.append(newHeader)
166-
167-
headerIndex += 1
168-
}
169-
170-
return result
171-
}
172-
}

Diff for: Sources/Kafka/RDKafka/RDKafkaClient.swift

+76
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import Crdkafka
1616
import Dispatch
1717
import Logging
18+
import NIOCore
1819

1920
import class Foundation.JSONDecoder
2021

@@ -616,4 +617,79 @@ public final class RDKafkaClient: Sendable {
616617
func withKafkaHandlePointer<T>(_ body: (OpaquePointer) throws -> T) rethrows -> T {
617618
try body(self.kafkaHandle.pointer)
618619
}
620+
621+
/// Extract ``KafkaHeader``s from a `rd_kafka_message_t` pointer.
622+
///
623+
/// - Parameters:
624+
/// - for: Pointer to the `rd_kafka_message_t` object to extract the headers from.
625+
internal static func getHeaders(
626+
for messagePointer: UnsafePointer<rd_kafka_message_t>
627+
) throws -> [KafkaHeader] {
628+
var result: [KafkaHeader] = []
629+
var headers: OpaquePointer?
630+
631+
var readStatus = rd_kafka_message_headers(messagePointer, &headers)
632+
633+
if readStatus == RD_KAFKA_RESP_ERR__NOENT {
634+
// No Header Entries
635+
return result
636+
}
637+
638+
guard readStatus == RD_KAFKA_RESP_ERR_NO_ERROR else {
639+
throw KafkaError.rdKafkaError(wrapping: readStatus)
640+
}
641+
642+
guard let headers else {
643+
return result
644+
}
645+
646+
let headerCount = rd_kafka_header_cnt(headers)
647+
result.reserveCapacity(headerCount)
648+
649+
var headerIndex = 0
650+
651+
while readStatus != RD_KAFKA_RESP_ERR__NOENT && headerIndex < headerCount {
652+
var headerKeyPointer: UnsafePointer<CChar>?
653+
var headerValuePointer: UnsafeRawPointer?
654+
var headerValueSize = 0
655+
656+
readStatus = rd_kafka_header_get_all(
657+
headers,
658+
headerIndex,
659+
&headerKeyPointer,
660+
&headerValuePointer,
661+
&headerValueSize
662+
)
663+
664+
if readStatus == RD_KAFKA_RESP_ERR__NOENT {
665+
// No Header Entries
666+
return result
667+
}
668+
669+
guard readStatus == RD_KAFKA_RESP_ERR_NO_ERROR else {
670+
throw KafkaError.rdKafkaError(wrapping: readStatus)
671+
}
672+
673+
guard let headerKeyPointer else {
674+
fatalError("Found null pointer when reading KafkaConsumerMessage header key")
675+
}
676+
let headerKey = String(cString: headerKeyPointer)
677+
678+
var headerValue: ByteBuffer?
679+
if let headerValuePointer, headerValueSize > 0 {
680+
let headerValueBufferPointer = UnsafeRawBufferPointer(
681+
start: headerValuePointer,
682+
count: headerValueSize
683+
)
684+
headerValue = ByteBuffer(bytes: headerValueBufferPointer)
685+
}
686+
687+
let newHeader = KafkaHeader(key: headerKey, value: headerValue)
688+
result.append(newHeader)
689+
690+
headerIndex += 1
691+
}
692+
693+
return result
694+
}
619695
}

Diff for: Tests/KafkaTests/KafkaProducerTests.swift

+3
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ final class KafkaProducerTests: XCTestCase {
8282
}
8383

8484
let expectedTopic = "test-topic"
85+
let headers = [KafkaHeader(key: "some", value: ByteBuffer.init(string: "test"))]
8586
let message = KafkaProducerMessage(
8687
topic: expectedTopic,
88+
headers: headers,
8789
key: "key",
8890
value: "Hello, World!"
8991
)
@@ -118,6 +120,7 @@ final class KafkaProducerTests: XCTestCase {
118120
XCTAssertEqual(expectedTopic, receivedMessage.topic)
119121
XCTAssertEqual(ByteBuffer(string: message.key!), receivedMessage.key)
120122
XCTAssertEqual(ByteBuffer(string: message.value), receivedMessage.value)
123+
XCTAssertEqual(headers, receivedMessage.headers)
121124

122125
// Shutdown the serviceGroup
123126
await serviceGroup.triggerGracefulShutdown()

0 commit comments

Comments
 (0)