1use std::sync::Arc;
42
43use bytes::Bytes;
44use magnetar_proto::schema::{Schema, SchemaError};
45use magnetar_proto::{IncomingMessage, MessageId, pb};
46use magnetar_runtime_tokio::{Consumer, Producer};
47
48use crate::PulsarClient;
49use crate::client::PulsarError;
50
51pub struct TypedProducer<S: Schema, P: crate::ProducerApi = Producer> {
63 inner: P,
64 schema: Arc<S>,
65}
66
67impl<S: Schema, P: crate::ProducerApi + std::fmt::Debug> std::fmt::Debug for TypedProducer<S, P> {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.debug_struct("TypedProducer")
70 .field("inner", &self.inner)
71 .field("schema_type", &self.schema.schema_type())
72 .finish()
73 }
74}
75
76impl<S: Schema, P: crate::ProducerApi> TypedProducer<S, P> {
77 #[must_use]
79 pub fn inner(&self) -> &P {
80 &self.inner
81 }
82
83 pub async fn send(
93 &self,
94 value: &S::Owned,
95 key: Option<String>,
96 ) -> Result<MessageId, PulsarError> {
97 self.warm_broker_schema().await?;
98 let bytes = self.schema.encode(value).map_err(schema_to_pulsar)?;
99 let mut msg = crate::OutgoingMessage::with_payload(bytes);
105 if let Some(k) = key {
106 msg = msg.key(k);
107 }
108 let id = crate::ProducerApi::send(&self.inner, msg)
109 .await
110 .map_err(|err| PulsarError::Other(format!("send: {err}")))?;
111 Ok(id)
112 }
113
114 async fn warm_broker_schema(&self) -> Result<(), PulsarError> {
119 if self.schema.needs_broker_schema() {
120 let resolved = crate::ProducerApi::get_schema(&self.inner, None)
121 .await
122 .map_err(|err| PulsarError::Other(format!("get_schema: {err}")))?;
123 self.schema.store_resolved_schema(resolved);
124 }
125 Ok(())
126 }
127
128 pub async fn close(self) -> Result<(), PulsarError> {
130 crate::ProducerApi::close_owned(self.inner)
131 .await
132 .map_err(|err| PulsarError::Other(format!("close: {err}")))
133 }
134
135 #[must_use]
137 pub fn topic(&self) -> String {
138 crate::ProducerApi::topic(&self.inner)
139 }
140
141 #[must_use]
144 pub fn name(&self) -> String {
145 crate::ProducerApi::name(&self.inner)
146 }
147
148 #[must_use]
150 pub fn compression(&self) -> magnetar_proto::types::CompressionKind {
151 crate::ProducerApi::compression(&self.inner)
152 }
153
154 #[must_use]
156 pub fn is_connected(&self) -> bool {
157 crate::ProducerApi::is_connected(&self.inner)
158 }
159
160 #[must_use]
162 pub fn is_closed(&self) -> bool {
163 crate::ProducerApi::is_closed(&self.inner)
164 }
165
166 #[must_use]
168 pub fn stats(&self) -> magnetar_proto::ProducerStats {
169 crate::ProducerApi::stats(&self.inner)
170 }
171
172 #[must_use]
175 pub fn last_disconnected_timestamp(&self) -> Option<std::time::SystemTime> {
176 crate::ProducerApi::last_disconnected_timestamp(&self.inner)
177 }
178
179 #[must_use]
181 pub fn last_sequence_id(&self) -> i64 {
182 crate::ProducerApi::last_sequence_id(&self.inner)
183 }
184
185 #[must_use]
188 pub fn last_sequence_id_published(&self) -> i64 {
189 crate::ProducerApi::last_sequence_id_published(&self.inner)
190 }
191
192 #[must_use]
194 pub fn pending_count(&self) -> usize {
195 crate::ProducerApi::pending_count(&self.inner)
196 }
197
198 #[must_use]
200 pub fn batch_len(&self) -> usize {
201 crate::ProducerApi::batch_len(&self.inner)
202 }
203
204 #[must_use]
206 pub fn batch_bytes(&self) -> usize {
207 crate::ProducerApi::batch_bytes(&self.inner)
208 }
209
210 pub async fn flush(&self) -> Result<(), PulsarError> {
213 crate::ProducerApi::flush(&self.inner)
214 .await
215 .map_err(|err| PulsarError::Other(format!("flush: {err}")))
216 }
217}
218
219impl<S: Schema> TypedProducer<S, Producer> {
220 pub fn new_message(&self) -> TypedMessageBuilder<'_, S> {
229 TypedMessageBuilder {
230 producer: self,
231 msg: crate::OutgoingMessage::default(),
232 }
233 }
234}
235
236#[derive(Debug)]
242pub struct TypedMessageBuilder<'a, S: Schema> {
243 producer: &'a TypedProducer<S, Producer>,
244 msg: crate::OutgoingMessage,
245}
246
247impl<S: Schema> TypedMessageBuilder<'_, S> {
248 #[must_use]
250 pub fn key(mut self, key: impl Into<String>) -> Self {
251 self.msg = self.msg.key(key);
252 self
253 }
254
255 #[must_use]
257 pub fn ordering_key(mut self, key: impl Into<Bytes>) -> Self {
258 self.msg = self.msg.ordering_key(key);
259 self
260 }
261
262 #[must_use]
264 pub fn event_time_ms(mut self, ts: u64) -> Self {
265 self.msg = self.msg.event_time_ms(ts);
266 self
267 }
268
269 #[must_use]
271 pub fn property(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
272 self.msg = self.msg.property(key, value);
273 self
274 }
275
276 #[must_use]
278 pub fn deliver_at_ms(mut self, ts_ms: i64) -> Self {
279 self.msg = self.msg.deliver_at_ms(ts_ms);
280 self
281 }
282
283 #[must_use]
286 pub fn deliver_after_ms(mut self, now_ms: i64, delay_ms: i64) -> Self {
287 self.msg = self.msg.deliver_after_ms(now_ms, delay_ms);
288 self
289 }
290
291 #[must_use]
293 pub fn replication_clusters(mut self, clusters: Vec<String>) -> Self {
294 self.msg = self.msg.replication_clusters(clusters);
295 self
296 }
297
298 #[must_use]
300 pub fn disable_replication(mut self) -> Self {
301 self.msg = self.msg.disable_replication();
302 self
303 }
304
305 #[must_use]
307 pub fn txn(mut self, txn_id: magnetar_proto::TxnId) -> Self {
308 self.msg = self.msg.txn(txn_id);
309 self
310 }
311
312 pub async fn send(self, value: &S::Owned) -> Result<MessageId, PulsarError> {
317 self.producer.warm_broker_schema().await?;
318 let bytes = self
319 .producer
320 .schema
321 .encode(value)
322 .map_err(schema_to_pulsar)?;
323 let mut with_payload = self.msg.value(bytes);
324 crate::inject_otel_context(&mut with_payload.properties);
325 let id = self
326 .producer
327 .inner
328 .send(with_payload.into())
329 .await
330 .map_err(PulsarError::Client)?;
331 Ok(id)
332 }
333}
334
335pub struct TypedProducerBuilder<'a, S: Schema, E: crate::Engine = crate::TokioEngine> {
345 client: &'a PulsarClient<E>,
346 topic: String,
347 schema: Arc<S>,
348 name: Option<String>,
349 compression: magnetar_proto::types::CompressionKind,
350 batching: Option<(usize, usize)>,
351 chunking: bool,
352 properties: Vec<(String, String)>,
353 initial_sequence_id: Option<u64>,
354 access_mode: pb::ProducerAccessMode,
355 send_timeout: Option<std::time::Duration>,
356 batching_max_publish_delay: Option<std::time::Duration>,
357 encryptor: Option<Arc<dyn magnetar_runtime_tokio::MessageEncryptor>>,
362}
363
364impl<S: Schema, E: crate::Engine> std::fmt::Debug for TypedProducerBuilder<'_, S, E> {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 f.debug_struct("TypedProducerBuilder")
367 .field("topic", &self.topic)
368 .field("schema_type", &self.schema.schema_type())
369 .field("name", &self.name)
370 .finish()
371 }
372}
373
374impl<'a, S: Schema, E: crate::Engine> TypedProducerBuilder<'a, S, E> {
375 pub(crate) fn new(client: &'a PulsarClient<E>, topic: String, schema: Arc<S>) -> Self {
376 Self {
377 client,
378 topic,
379 schema,
380 name: None,
381 compression: magnetar_proto::types::CompressionKind::None,
382 batching: None,
383 chunking: false,
384 properties: Vec::new(),
385 initial_sequence_id: None,
386 access_mode: pb::ProducerAccessMode::Shared,
387 send_timeout: None,
388 batching_max_publish_delay: None,
389 encryptor: None,
390 }
391 }
392
393 #[must_use]
395 pub fn name(mut self, name: impl Into<String>) -> Self {
396 self.name = Some(name.into());
397 self
398 }
399
400 #[must_use]
402 pub fn compression(mut self, kind: magnetar_proto::types::CompressionKind) -> Self {
403 self.compression = kind;
404 self
405 }
406
407 #[must_use]
409 pub fn batching(mut self, max_messages: usize, max_bytes: usize) -> Self {
410 self.batching = Some((max_messages, max_bytes));
411 self
412 }
413
414 #[must_use]
416 pub fn chunking(mut self, enable: bool) -> Self {
417 self.chunking = enable;
418 self
419 }
420
421 #[must_use]
423 pub fn property(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
424 self.properties.push((key.into(), value.into()));
425 self
426 }
427
428 #[must_use]
430 pub fn initial_sequence_id(mut self, id: u64) -> Self {
431 self.initial_sequence_id = Some(id);
432 self
433 }
434
435 #[must_use]
437 pub fn access_mode(mut self, mode: pb::ProducerAccessMode) -> Self {
438 self.access_mode = mode;
439 self
440 }
441
442 #[must_use]
444 pub fn send_timeout(mut self, timeout: std::time::Duration) -> Self {
445 self.send_timeout = Some(timeout);
446 self
447 }
448
449 #[must_use]
451 pub fn batching_max_publish_delay(mut self, delay: std::time::Duration) -> Self {
452 self.batching_max_publish_delay = Some(delay);
453 self
454 }
455}
456
457impl<S: Schema, E: crate::Engine> TypedProducerBuilder<'_, S, E>
458where
459 E::ClientState: crate::CreateProducerApi + crate::BrokerMetadataApi,
460{
461 pub async fn create(
476 self,
477 ) -> Result<TypedProducer<S, <E::ClientState as crate::CreateProducerApi>::Producer>, PulsarError>
478 {
479 if self.encryptor.is_some() {
480 return Err(PulsarError::Other(
481 "TypedProducerBuilder::create() refuses a configured encryptor — \
482 use create_with_encryption() to honor the PIP-4 encryptor"
483 .to_owned(),
484 ));
485 }
486 let schema_pb = pb::Schema {
487 name: self.topic.clone(),
488 schema_data: self.schema.schema_data(),
489 r#type: self.schema.schema_type() as i32,
490 properties: self
491 .schema
492 .properties()
493 .into_iter()
494 .map(|(key, value)| pb::KeyValue { key, value })
495 .collect(),
496 };
497 let mut builder = self
498 .client
499 .producer(self.topic)
500 .schema(schema_pb)
501 .compression(self.compression)
502 .chunking(self.chunking)
503 .access_mode(self.access_mode);
504 if let Some(n) = self.name {
505 builder = builder.name(n);
506 }
507 if let Some((max_msgs, max_bytes)) = self.batching {
508 builder = builder.batching(max_msgs, max_bytes);
509 }
510 for (k, v) in self.properties {
511 builder = builder.property(k, v);
512 }
513 if let Some(id) = self.initial_sequence_id {
514 builder = builder.initial_sequence_id(id);
515 }
516 if let Some(t) = self.send_timeout {
517 builder = builder.send_timeout(t);
518 }
519 if let Some(d) = self.batching_max_publish_delay {
520 builder = builder.batching_max_publish_delay(d);
521 }
522 let inner = builder.create().await?;
523 Ok(TypedProducer {
524 inner,
525 schema: self.schema,
526 })
527 }
528}
529
530impl<S: Schema> TypedProducerBuilder<'_, S, crate::TokioEngine> {
534 #[must_use]
537 pub fn encryption(
538 mut self,
539 encryptor: Arc<dyn magnetar_runtime_tokio::MessageEncryptor>,
540 ) -> Self {
541 self.encryptor = Some(encryptor);
542 self
543 }
544
545 pub async fn create_with_encryption(self) -> Result<TypedProducer<S>, PulsarError> {
549 let schema_pb = pb::Schema {
550 name: self.topic.clone(),
551 schema_data: self.schema.schema_data(),
552 r#type: self.schema.schema_type() as i32,
553 properties: self
554 .schema
555 .properties()
556 .into_iter()
557 .map(|(key, value)| pb::KeyValue { key, value })
558 .collect(),
559 };
560 let mut builder = self
561 .client
562 .producer(self.topic)
563 .schema(schema_pb)
564 .compression(self.compression)
565 .chunking(self.chunking)
566 .access_mode(self.access_mode);
567 if let Some(n) = self.name {
568 builder = builder.name(n);
569 }
570 if let Some((max_msgs, max_bytes)) = self.batching {
571 builder = builder.batching(max_msgs, max_bytes);
572 }
573 for (k, v) in self.properties {
574 builder = builder.property(k, v);
575 }
576 if let Some(id) = self.initial_sequence_id {
577 builder = builder.initial_sequence_id(id);
578 }
579 if let Some(t) = self.send_timeout {
580 builder = builder.send_timeout(t);
581 }
582 if let Some(d) = self.batching_max_publish_delay {
583 builder = builder.batching_max_publish_delay(d);
584 }
585 if let Some(e) = self.encryptor {
586 builder = builder.encryption(e);
587 }
588 let inner = builder.create_with_encryption().await?;
589 Ok(TypedProducer {
590 inner,
591 schema: self.schema,
592 })
593 }
594}
595
596pub type TypedMessageListener<S> = Arc<dyn Fn(&TypedMessage<S>) + Send + Sync>;
604
605pub struct TypedMessage<S: Schema> {
607 pub message_id: MessageId,
609 pub value: S::Owned,
611 pub payload: Bytes,
614 pub raw: IncomingMessage,
616}
617
618impl<S: Schema> std::fmt::Debug for TypedMessage<S>
619where
620 S::Owned: std::fmt::Debug,
621{
622 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
623 f.debug_struct("TypedMessage")
624 .field("message_id", &self.message_id)
625 .field("value", &self.value)
626 .field("payload_len", &self.payload.len())
627 .field("raw", &self.raw)
628 .finish()
629 }
630}
631
632pub struct TypedConsumer<S: Schema, C: crate::ConsumerApi = Consumer> {
656 inner: C,
657 schema: Arc<S>,
658}
659
660impl<S: Schema, C: crate::ConsumerApi + std::fmt::Debug> std::fmt::Debug for TypedConsumer<S, C> {
661 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
662 f.debug_struct("TypedConsumer")
663 .field("inner", &self.inner)
664 .field("schema_type", &self.schema.schema_type())
665 .finish()
666 }
667}
668
669impl<S: Schema, C: crate::ConsumerApi> TypedConsumer<S, C> {
670 #[must_use]
672 pub fn inner(&self) -> &C {
673 &self.inner
674 }
675
676 pub async fn ack(&self, message_id: MessageId) -> Result<(), PulsarError> {
678 crate::ConsumerApi::ack(&self.inner, message_id)
679 .await
680 .map_err(|err| PulsarError::Other(format!("ack: {err}")))
681 }
682
683 pub async fn close(self) -> Result<(), PulsarError> {
685 crate::ConsumerApi::close_owned(self.inner)
686 .await
687 .map_err(|err| PulsarError::Other(format!("close: {err}")))
688 }
689
690 #[must_use]
692 pub fn topic(&self) -> String {
693 crate::ConsumerApi::topic(&self.inner)
694 }
695
696 #[must_use]
698 pub fn subscription(&self) -> String {
699 crate::ConsumerApi::subscription(&self.inner)
700 }
701
702 #[must_use]
704 pub fn name(&self) -> String {
705 crate::ConsumerApi::name(&self.inner)
706 }
707
708 #[must_use]
710 pub fn is_connected(&self) -> bool {
711 crate::ConsumerApi::is_connected(&self.inner)
712 }
713
714 #[must_use]
716 pub fn is_closed(&self) -> bool {
717 crate::ConsumerApi::is_closed(&self.inner)
718 }
719
720 #[must_use]
722 pub fn stats(&self) -> magnetar_proto::ConsumerStats {
723 crate::ConsumerApi::stats(&self.inner)
724 }
725
726 #[must_use]
729 pub fn last_disconnected_timestamp(&self) -> Option<std::time::SystemTime> {
730 crate::ConsumerApi::last_disconnected_timestamp(&self.inner)
731 }
732
733 pub fn negative_ack(&self, message_id: MessageId) {
735 crate::ConsumerApi::negative_ack(&self.inner, message_id);
736 }
737
738 pub fn redeliver_unacked(&self) {
741 crate::ConsumerApi::redeliver_unacked(&self.inner);
742 }
743
744 pub async fn ack_cumulative(&self, message_id: MessageId) -> Result<(), PulsarError> {
746 crate::ConsumerApi::ack_cumulative(&self.inner, message_id)
747 .await
748 .map_err(|err| PulsarError::Other(format!("ack_cumulative: {err}")))
749 }
750
751 pub fn ack_grouped(&self, message_id: MessageId) {
754 crate::ConsumerApi::ack_grouped(&self.inner, message_id);
755 }
756
757 pub fn ack_grouped_cumulative(&self, message_id: MessageId) {
759 crate::ConsumerApi::ack_grouped_cumulative(&self.inner, message_id);
760 }
761
762 pub async fn ack_with_txn(
765 &self,
766 message_id: MessageId,
767 txn_id: magnetar_proto::TxnId,
768 ) -> Result<(), PulsarError> {
769 crate::ConsumerApi::ack_with_txn(&self.inner, message_id, txn_id)
770 .await
771 .map_err(|err| PulsarError::Other(format!("ack_with_txn: {err}")))
772 }
773
774 pub async fn ack_cumulative_with_txn(
777 &self,
778 message_id: MessageId,
779 txn_id: magnetar_proto::TxnId,
780 ) -> Result<(), PulsarError> {
781 crate::ConsumerApi::ack_cumulative_with_txn(&self.inner, message_id, txn_id)
782 .await
783 .map_err(|err| PulsarError::Other(format!("ack_cumulative_with_txn: {err}")))
784 }
785
786 pub async fn seek_to_earliest(&self) -> Result<(), PulsarError> {
788 crate::ConsumerApi::seek_to_earliest(&self.inner)
789 .await
790 .map_err(|err| PulsarError::Other(format!("seek_to_earliest: {err}")))
791 }
792
793 pub async fn seek_to_latest(&self) -> Result<(), PulsarError> {
795 crate::ConsumerApi::seek_to_latest(&self.inner)
796 .await
797 .map_err(|err| PulsarError::Other(format!("seek_to_latest: {err}")))
798 }
799
800 pub async fn last_message_id(&self) -> Result<MessageId, PulsarError> {
803 crate::ConsumerApi::last_message_id(&self.inner)
804 .await
805 .map_err(|err| PulsarError::Other(format!("last_message_id: {err}")))
806 }
807
808 pub async fn has_message_after(&self, cursor: MessageId) -> Result<bool, PulsarError> {
811 crate::ConsumerApi::has_message_after(&self.inner, cursor)
812 .await
813 .map_err(|err| PulsarError::Other(format!("has_message_after: {err}")))
814 }
815}
816
817impl<S: Schema> TypedConsumer<S, Consumer> {
831 pub async fn receive(&self) -> Result<TypedMessage<S>, PulsarError> {
842 if self.schema.needs_broker_schema() {
843 let resolved = self
844 .inner
845 .get_schema(None)
846 .await
847 .map_err(PulsarError::Client)?;
848 self.schema.store_resolved_schema(resolved);
849 }
850 let raw = self.inner.receive().await?;
851 let value = self.schema.decode(&raw.payload).map_err(schema_to_pulsar)?;
852 Ok(TypedMessage {
853 message_id: raw.message_id,
854 value,
855 payload: raw.payload.clone(),
856 raw,
857 })
858 }
859
860 pub fn pause(&self) {
862 self.inner.pause();
863 }
864
865 pub fn resume(&self) {
867 self.inner.resume();
868 }
869
870 #[must_use]
873 pub fn is_paused(&self) -> bool {
874 self.inner.is_paused()
875 }
876
877 #[must_use]
880 pub fn has_reached_end_of_topic(&self) -> bool {
881 self.inner.has_reached_end_of_topic()
882 }
883
884 #[must_use]
886 pub fn available_in_queue(&self) -> usize {
887 self.inner.available_in_queue()
888 }
889
890 #[must_use]
895 pub fn available_permits(&self) -> u32 {
896 self.inner.available_permits()
897 }
898
899 #[must_use]
902 pub fn has_received_any_message(&self) -> bool {
903 self.inner.has_received_any_message()
904 }
905
906 #[must_use]
909 pub fn is_inactive(&self) -> bool {
910 self.inner.is_inactive()
911 }
912
913 pub async fn ack_batch(&self, message_ids: Vec<MessageId>) -> Result<(), PulsarError> {
915 self.inner
916 .ack_batch(message_ids)
917 .await
918 .map_err(PulsarError::Client)
919 }
920
921 pub async fn unsubscribe(&self, force: bool) -> Result<(), PulsarError> {
925 self.inner
926 .unsubscribe(force)
927 .await
928 .map_err(PulsarError::Client)
929 }
930
931 pub async fn seek_to_message(&self, message_id: MessageId) -> Result<(), PulsarError> {
933 self.inner
934 .seek_to_message(message_id)
935 .await
936 .map_err(PulsarError::Client)
937 }
938
939 pub async fn seek_to_timestamp(&self, publish_time_ms: u64) -> Result<(), PulsarError> {
942 self.inner
943 .seek_to_timestamp(publish_time_ms)
944 .await
945 .map_err(PulsarError::Client)
946 }
947
948 pub fn flow(&self, permits: u32) {
950 self.inner.flow(permits);
951 }
952
953 pub async fn receive_with_timeout(
957 &self,
958 timeout: std::time::Duration,
959 ) -> Result<Option<TypedMessage<S>>, PulsarError> {
960 match self.inner.receive_with_timeout(timeout).await? {
961 Some(raw) => {
962 let value = self.schema.decode(&raw.payload).map_err(schema_to_pulsar)?;
963 Ok(Some(TypedMessage {
964 message_id: raw.message_id,
965 value,
966 payload: raw.payload.clone(),
967 raw,
968 }))
969 }
970 None => Ok(None),
971 }
972 }
973
974 pub async fn receive_batch(
977 &self,
978 max_messages: usize,
979 max_wait: std::time::Duration,
980 ) -> Result<Vec<TypedMessage<S>>, PulsarError> {
981 let raw_batch = self.inner.receive_batch(max_messages, max_wait).await?;
982 let mut out = Vec::with_capacity(raw_batch.len());
983 for raw in raw_batch {
984 let value = self.schema.decode(&raw.payload).map_err(schema_to_pulsar)?;
985 out.push(TypedMessage {
986 message_id: raw.message_id,
987 value,
988 payload: raw.payload.clone(),
989 raw,
990 });
991 }
992 Ok(out)
993 }
994
995 pub async fn receive_batch_with_bytes_cap(
998 &self,
999 max_messages: usize,
1000 max_bytes: usize,
1001 max_wait: std::time::Duration,
1002 ) -> Result<Vec<TypedMessage<S>>, PulsarError> {
1003 let raw_batch = self
1004 .inner
1005 .receive_batch_with_bytes_cap(max_messages, max_bytes, max_wait)
1006 .await?;
1007 let mut out = Vec::with_capacity(raw_batch.len());
1008 for raw in raw_batch {
1009 let value = self.schema.decode(&raw.payload).map_err(schema_to_pulsar)?;
1010 out.push(TypedMessage {
1011 message_id: raw.message_id,
1012 value,
1013 payload: raw.payload.clone(),
1014 raw,
1015 });
1016 }
1017 Ok(out)
1018 }
1019
1020 pub async fn ack_with_properties(
1023 &self,
1024 message_id: MessageId,
1025 properties: Vec<(String, i64)>,
1026 ) -> Result<(), PulsarError> {
1027 self.inner
1028 .ack_with_properties(message_id, properties)
1029 .await
1030 .map_err(PulsarError::Client)
1031 }
1032
1033 pub async fn ack_batch_with_txn(
1036 &self,
1037 message_ids: Vec<MessageId>,
1038 txn_id: magnetar_proto::TxnId,
1039 ) -> Result<(), PulsarError> {
1040 self.inner
1041 .ack_batch_with_txn(message_ids, txn_id)
1042 .await
1043 .map_err(PulsarError::Client)
1044 }
1045
1046 pub async fn ack_cumulative_with_properties(
1049 &self,
1050 message_id: MessageId,
1051 properties: Vec<(String, i64)>,
1052 ) -> Result<(), PulsarError> {
1053 self.inner
1054 .ack_cumulative_with_properties(message_id, properties)
1055 .await
1056 .map_err(PulsarError::Client)
1057 }
1058
1059 #[must_use]
1062 pub fn drain_dead_letter(&self) -> Vec<IncomingMessage> {
1063 self.inner.drain_dead_letter()
1064 }
1065
1066 pub async fn republish_dead_letters(
1075 &self,
1076 dlq_producer: &magnetar_runtime_tokio::Producer,
1077 ) -> Result<usize, PulsarError> {
1078 let mut extra_properties = Vec::new();
1079 crate::inject_otel_context(&mut extra_properties);
1080 self.inner
1081 .republish_dead_letters_with_properties(dlq_producer, extra_properties)
1082 .await
1083 .map_err(PulsarError::Client)
1084 }
1085
1086 pub async fn reconsume_later(
1095 &self,
1096 retry_producer: &magnetar_runtime_tokio::Producer,
1097 msg: magnetar_proto::IncomingMessage,
1098 delay: std::time::Duration,
1099 ) -> Result<(), PulsarError> {
1100 self.reconsume_later_with_properties(retry_producer, msg, Vec::new(), delay)
1103 .await
1104 }
1105
1106 pub async fn reconsume_later_with_properties(
1115 &self,
1116 retry_producer: &magnetar_runtime_tokio::Producer,
1117 msg: magnetar_proto::IncomingMessage,
1118 mut custom_properties: Vec<(String, String)>,
1119 delay: std::time::Duration,
1120 ) -> Result<(), PulsarError> {
1121 crate::inject_otel_context(&mut custom_properties);
1122 self.inner
1123 .reconsume_later_with_properties(retry_producer, msg, custom_properties, delay)
1124 .await
1125 .map_err(PulsarError::Client)
1126 }
1127}
1128
1129pub struct TypedConsumerBuilder<'a, S: Schema, E: crate::Engine = crate::TokioEngine> {
1138 client: &'a PulsarClient<E>,
1139 topic: String,
1140 schema: Arc<S>,
1141 subscription: Option<String>,
1142 sub_type: pb::command_subscribe::SubType,
1143 durable: bool,
1144 initial_position: pb::command_subscribe::InitialPosition,
1145 receiver_queue_size: usize,
1146 consumer_name: Option<String>,
1147 priority_level: Option<i32>,
1148 properties: Vec<(String, String)>,
1149 subscription_properties: Vec<(String, String)>,
1150 read_compacted: bool,
1151 negative_ack_redelivery_delay: Option<std::time::Duration>,
1152 ack_timeout: Option<std::time::Duration>,
1153 ack_group_time: Option<std::time::Duration>,
1154 dlq_policy: Option<(u32, Option<String>)>,
1155 max_pending_chunked_message: Option<usize>,
1156 auto_ack_oldest_chunked_message_on_queue_full: Option<bool>,
1157 expire_time_of_incomplete_chunked_message: Option<std::time::Duration>,
1158 key_shared: Option<magnetar_proto::KeySharedConfig>,
1159 start_message_id: Option<magnetar_proto::MessageId>,
1160 replicate_subscription_state: Option<bool>,
1161 force_topic_creation: Option<bool>,
1162 start_message_rollback_duration_sec: Option<u64>,
1163 listener: Option<TypedMessageListener<S>>,
1164}
1165
1166impl<S: Schema, E: crate::Engine> std::fmt::Debug for TypedConsumerBuilder<'_, S, E> {
1167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1168 f.debug_struct("TypedConsumerBuilder")
1169 .field("topic", &self.topic)
1170 .field("schema_type", &self.schema.schema_type())
1171 .field("subscription", &self.subscription)
1172 .field("sub_type", &self.sub_type)
1173 .field("durable", &self.durable)
1174 .finish()
1175 }
1176}
1177
1178impl<'a, S: Schema, E: crate::Engine> TypedConsumerBuilder<'a, S, E> {
1179 pub(crate) fn new(client: &'a PulsarClient<E>, topic: String, schema: Arc<S>) -> Self {
1180 Self {
1181 client,
1182 topic,
1183 schema,
1184 subscription: None,
1185 sub_type: pb::command_subscribe::SubType::Exclusive,
1186 durable: true,
1187 initial_position: pb::command_subscribe::InitialPosition::Latest,
1188 receiver_queue_size: 1000,
1189 consumer_name: None,
1190 priority_level: None,
1191 properties: Vec::new(),
1192 subscription_properties: Vec::new(),
1193 read_compacted: false,
1194 negative_ack_redelivery_delay: None,
1195 ack_timeout: None,
1196 ack_group_time: None,
1197 dlq_policy: None,
1198 max_pending_chunked_message: None,
1199 auto_ack_oldest_chunked_message_on_queue_full: None,
1200 expire_time_of_incomplete_chunked_message: None,
1201 key_shared: None,
1202 start_message_id: None,
1203 replicate_subscription_state: None,
1204 force_topic_creation: None,
1205 start_message_rollback_duration_sec: None,
1206 listener: None,
1207 }
1208 }
1209
1210 #[must_use]
1220 pub fn message_listener(mut self, listener: TypedMessageListener<S>) -> Self {
1221 self.listener = Some(listener);
1222 self
1223 }
1224
1225 #[must_use]
1228 pub fn name(mut self, name: impl Into<String>) -> Self {
1229 self.consumer_name = Some(name.into());
1230 self
1231 }
1232
1233 #[must_use]
1235 pub fn priority_level(mut self, level: i32) -> Self {
1236 self.priority_level = Some(level);
1237 self
1238 }
1239
1240 #[must_use]
1242 pub fn property(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1243 self.properties.push((key.into(), value.into()));
1244 self
1245 }
1246
1247 #[must_use]
1249 pub fn subscription_property(
1250 mut self,
1251 key: impl Into<String>,
1252 value: impl Into<String>,
1253 ) -> Self {
1254 self.subscription_properties
1255 .push((key.into(), value.into()));
1256 self
1257 }
1258
1259 #[must_use]
1261 pub fn read_compacted(mut self, on: bool) -> Self {
1262 self.read_compacted = on;
1263 self
1264 }
1265
1266 #[must_use]
1268 pub fn negative_ack_redelivery_delay(mut self, delay: std::time::Duration) -> Self {
1269 self.negative_ack_redelivery_delay = Some(delay);
1270 self
1271 }
1272
1273 #[must_use]
1275 pub fn ack_timeout(mut self, timeout: std::time::Duration) -> Self {
1276 self.ack_timeout = Some(timeout);
1277 self
1278 }
1279
1280 #[must_use]
1283 pub fn ack_group_time(mut self, window: std::time::Duration) -> Self {
1284 self.ack_group_time = Some(window);
1285 self
1286 }
1287
1288 #[must_use]
1290 pub fn dead_letter_policy(
1291 mut self,
1292 max_redeliver_count: u32,
1293 dead_letter_topic: Option<String>,
1294 ) -> Self {
1295 self.dlq_policy = Some((max_redeliver_count, dead_letter_topic));
1296 self
1297 }
1298
1299 #[must_use]
1301 pub fn max_pending_chunked_message(mut self, max: usize) -> Self {
1302 self.max_pending_chunked_message = Some(max);
1303 self
1304 }
1305
1306 #[must_use]
1308 pub fn auto_ack_oldest_chunked_message_on_queue_full(mut self, auto_ack: bool) -> Self {
1309 self.auto_ack_oldest_chunked_message_on_queue_full = Some(auto_ack);
1310 self
1311 }
1312
1313 #[must_use]
1315 pub fn expire_time_of_incomplete_chunked_message(
1316 mut self,
1317 expire: std::time::Duration,
1318 ) -> Self {
1319 self.expire_time_of_incomplete_chunked_message = Some(expire);
1320 self
1321 }
1322
1323 #[must_use]
1326 pub fn key_shared_policy(mut self, cfg: magnetar_proto::KeySharedConfig) -> Self {
1327 self.key_shared = Some(cfg);
1328 self
1329 }
1330
1331 #[must_use]
1333 pub fn start_message_id(mut self, id: magnetar_proto::MessageId) -> Self {
1334 self.start_message_id = Some(id);
1335 self
1336 }
1337
1338 #[must_use]
1340 pub fn replicate_subscription_state(mut self, on: bool) -> Self {
1341 self.replicate_subscription_state = Some(on);
1342 self
1343 }
1344
1345 #[must_use]
1347 pub fn force_topic_creation(mut self, on: bool) -> Self {
1348 self.force_topic_creation = Some(on);
1349 self
1350 }
1351
1352 #[must_use]
1355 pub fn start_message_rollback_duration(mut self, seconds: u64) -> Self {
1356 self.start_message_rollback_duration_sec = Some(seconds);
1357 self
1358 }
1359
1360 #[must_use]
1362 pub fn subscription(mut self, name: impl Into<String>) -> Self {
1363 self.subscription = Some(name.into());
1364 self
1365 }
1366
1367 #[doc(hidden)]
1372 #[must_use]
1373 pub fn chunk_knobs_for_test(
1374 &self,
1375 ) -> (Option<usize>, Option<bool>, Option<std::time::Duration>) {
1376 (
1377 self.max_pending_chunked_message,
1378 self.auto_ack_oldest_chunked_message_on_queue_full,
1379 self.expire_time_of_incomplete_chunked_message,
1380 )
1381 }
1382
1383 #[doc(hidden)]
1388 #[must_use]
1389 pub fn has_listener_for_test(&self) -> bool {
1390 self.listener.is_some()
1391 }
1392
1393 #[must_use]
1395 pub fn subscription_type(mut self, sub_type: pb::command_subscribe::SubType) -> Self {
1396 self.sub_type = sub_type;
1397 self
1398 }
1399
1400 #[must_use]
1402 pub fn durable(mut self, durable: bool) -> Self {
1403 self.durable = durable;
1404 self
1405 }
1406
1407 #[must_use]
1409 pub fn initial_position(mut self, position: pb::command_subscribe::InitialPosition) -> Self {
1410 self.initial_position = position;
1411 self
1412 }
1413
1414 #[must_use]
1416 pub fn receiver_queue_size(mut self, size: usize) -> Self {
1417 self.receiver_queue_size = size;
1418 self
1419 }
1420}
1421
1422impl<S: Schema, E: crate::Engine> TypedConsumerBuilder<'_, S, E>
1423where
1424 E::ClientState: crate::SubscribeApi,
1425{
1426 pub async fn subscribe(
1430 self,
1431 ) -> Result<TypedConsumer<S, <E::ClientState as crate::SubscribeApi>::Consumer>, PulsarError>
1432 {
1433 let subscription = self
1434 .subscription
1435 .ok_or_else(|| PulsarError::Config("subscription name is required".to_owned()))?;
1436 let schema_pb = pb::Schema {
1437 name: self.topic.clone(),
1438 schema_data: self.schema.schema_data(),
1439 r#type: self.schema.schema_type() as i32,
1440 properties: self
1441 .schema
1442 .properties()
1443 .into_iter()
1444 .map(|(key, value)| pb::KeyValue { key, value })
1445 .collect(),
1446 };
1447 let mut builder = self
1448 .client
1449 .consumer(self.topic)
1450 .subscription(subscription)
1451 .subscription_type(self.sub_type)
1452 .durable(self.durable)
1453 .initial_position(self.initial_position)
1454 .receiver_queue_size(self.receiver_queue_size)
1455 .read_compacted(self.read_compacted)
1456 .schema(schema_pb);
1457 if let Some(name) = self.consumer_name {
1458 builder = builder.name(name);
1459 }
1460 if let Some(level) = self.priority_level {
1461 builder = builder.priority_level(level);
1462 }
1463 for (k, v) in self.properties {
1464 builder = builder.property(k, v);
1465 }
1466 for (k, v) in self.subscription_properties {
1467 builder = builder.subscription_property(k, v);
1468 }
1469 if let Some(d) = self.negative_ack_redelivery_delay {
1470 builder = builder.negative_ack_redelivery_delay(d);
1471 }
1472 if let Some(t) = self.ack_timeout {
1473 builder = builder.ack_timeout(t);
1474 }
1475 if let Some(w) = self.ack_group_time {
1476 builder = builder.ack_group_time(w);
1477 }
1478 if let Some((max, topic_opt)) = self.dlq_policy {
1479 builder = builder.dead_letter_policy(max, topic_opt);
1480 }
1481 if let Some(max) = self.max_pending_chunked_message {
1482 builder = builder.max_pending_chunked_message(max);
1483 }
1484 if let Some(auto_ack) = self.auto_ack_oldest_chunked_message_on_queue_full {
1485 builder = builder.auto_ack_oldest_chunked_message_on_queue_full(auto_ack);
1486 }
1487 if let Some(expire) = self.expire_time_of_incomplete_chunked_message {
1488 builder = builder.expire_time_of_incomplete_chunked_message(expire);
1489 }
1490 if let Some(cfg) = self.key_shared {
1491 builder = builder.key_shared_policy(cfg);
1492 }
1493 if let Some(id) = self.start_message_id {
1494 builder = builder.start_message_id(id);
1495 }
1496 if let Some(on) = self.replicate_subscription_state {
1497 builder = builder.replicate_subscription_state(on);
1498 }
1499 if let Some(on) = self.force_topic_creation {
1500 builder = builder.force_topic_creation(on);
1501 }
1502 if let Some(sec) = self.start_message_rollback_duration_sec {
1503 builder = builder.start_message_rollback_duration(sec);
1504 }
1505 let inner = builder.subscribe().await?;
1506 Ok(TypedConsumer {
1507 inner,
1508 schema: self.schema,
1509 })
1510 }
1511}
1512
1513impl<S: Schema + Send + Sync + 'static, E: crate::Engine> TypedConsumerBuilder<'_, S, E>
1514where
1515 E::ClientState: crate::SubscribeApi,
1516 <E::ClientState as crate::SubscribeApi>::Consumer: Clone,
1517{
1518 pub async fn subscribe_with_listener(
1537 self,
1538 ) -> Result<crate::MessageListenerHandle, PulsarError> {
1539 let Some(listener) = self.listener.clone() else {
1540 return Err(PulsarError::Config(
1541 "subscribe_with_listener() requires a listener — \
1542 call message_listener(...) first (or use subscribe() for pull mode)"
1543 .to_owned(),
1544 ));
1545 };
1546 let typed = self.subscribe().await?;
1547 let schema = typed.schema.clone();
1548 if schema.needs_broker_schema() {
1552 let resolved = crate::ConsumerApi::get_schema(&typed.inner, None)
1553 .await
1554 .map_err(|err| PulsarError::Other(format!("get_schema: {err}")))?;
1555 schema.store_resolved_schema(resolved);
1556 }
1557 let handle = crate::consumer_listener::spawn_listener_loop(typed.inner, move |raw| {
1558 if let Ok(value) = schema.decode(&raw.payload) {
1563 let msg = TypedMessage {
1564 message_id: raw.message_id,
1565 value,
1566 payload: raw.payload.clone(),
1567 raw,
1568 };
1569 listener(&msg);
1570 }
1571 });
1572 Ok(handle)
1573 }
1574}
1575
1576fn schema_to_pulsar(err: SchemaError) -> PulsarError {
1577 PulsarError::Schema(err)
1578}