server/model/mod.rs
1//! # Data Model Module
2//!
3//! Core data models and message representations for Azure Service Bus operations.
4//! This module provides the fundamental data structures used throughout the Quetty
5//! application for representing Service Bus messages, their states, and associated metadata.
6//!
7//! ## Message Representation
8//!
9//! The primary model is [`MessageModel`], which provides a unified representation
10//! of Azure Service Bus messages that is optimized for terminal UI display and
11//! manipulation. It handles the complexities of Azure's message format while
12//! providing a clean, consistent interface.
13//!
14//! ## Key Features
15//!
16//! ### Flexible Message Body Handling
17//! - **JSON Messages** - Automatic parsing and validation of JSON message bodies
18//! - **Raw Text Messages** - Support for plain text and binary message content
19//! - **Lossless Conversion** - Preserves original message data during transformation
20//!
21//! ### Message State Management
22//! - **State Tracking** - Comprehensive message state representation
23//! - **State Transitions** - Support for all Azure Service Bus message states
24//! - **Status Visualization** - UI-friendly state representation
25//!
26//! ### Serialization Support
27//! - **JSON Export** - Full message serialization for export and analysis
28//! - **Timestamp Handling** - ISO8601 timestamp serialization
29//! - **Type Safety** - Strongly typed message components
30//!
31//! ## Core Types
32//!
33//! ### MessageModel
34//! The primary message representation containing all essential message data:
35//!
36//! ```no_run
37//! use quetty_server::model::{MessageModel, MessageState, BodyData};
38//! use azure_core::time::OffsetDateTime;
39//!
40//! // Create a new message model
41//! let message = MessageModel::new(
42//! 12345, // sequence number
43//! "msg-001".to_string(), // message ID
44//! OffsetDateTime::now_utc(), // enqueued timestamp
45//! 0, // delivery count
46//! MessageState::Active, // current state
47//! BodyData::RawString("Hello!".to_string()) // message body
48//! );
49//!
50//! println!("Message ID: {}", message.id);
51//! println!("State: {:?}", message.state);
52//! ```
53//!
54//! ### MessageState
55//! Represents all possible states of a Service Bus message:
56//!
57//! ```no_run
58//! use quetty_server::model::MessageState;
59//!
60//! let state = MessageState::Active;
61//! match state {
62//! MessageState::Active => println!("Message is available for processing"),
63//! MessageState::Deferred => println!("Message is deferred for later processing"),
64//! MessageState::Scheduled => println!("Message is scheduled for future delivery"),
65//! MessageState::DeadLettered => println!("Message is in dead letter queue"),
66//! MessageState::Completed => println!("Message processing completed"),
67//! MessageState::Abandoned => println!("Message processing abandoned"),
68//! }
69//! ```
70//!
71//! ### BodyData
72//! Flexible message body representation supporting both JSON and raw text:
73//!
74//! ```no_run
75//! use quetty_server::model::BodyData;
76//! use serde_json::json;
77//!
78//! // JSON message body
79//! let json_body = BodyData::ValidJson(json!({
80//! "type": "order",
81//! "id": 12345,
82//! "customer": "Jane Doe"
83//! }));
84//!
85//! // Raw text message body
86//! let text_body = BodyData::RawString("Plain text message".to_string());
87//!
88//! // Bodies serialize appropriately for JSON export
89//! let serialized = serde_json::to_string(&json_body)?;
90//! ```
91//!
92//! ## Message Conversion
93//!
94//! ### From Azure Service Bus Messages
95//! Automatic conversion from Azure SDK message types:
96//!
97//! ```no_run
98//! use quetty_server::model::MessageModel;
99//! use azservicebus::prelude::ServiceBusPeekedMessage;
100//! use std::convert::TryFrom;
101//!
102//! // Convert single message
103//! let azure_message: ServiceBusPeekedMessage = get_message_from_azure();
104//! let message_model = MessageModel::try_from(azure_message)?;
105//!
106//! // Convert batch of messages with error handling
107//! let azure_messages: Vec<ServiceBusPeekedMessage> = get_messages_from_azure();
108//! let valid_messages = MessageModel::try_convert_messages_collect(azure_messages);
109//! println!("Successfully converted {} messages", valid_messages.len());
110//! ```
111//!
112//! ### Error Handling
113//! Robust error handling for message conversion:
114//!
115//! ```no_run
116//! use quetty_server::model::{MessageModel, MessageModelError};
117//! use std::convert::TryFrom;
118//!
119//! match MessageModel::try_from(azure_message) {
120//! Ok(message) => {
121//! println!("Successfully converted message: {}", message.id);
122//! }
123//! Err(MessageModelError::MissingMessageId) => {
124//! eprintln!("Message is missing required ID field");
125//! }
126//! Err(MessageModelError::MissingMessageBody) => {
127//! eprintln!("Message is missing body content");
128//! }
129//! Err(MessageModelError::MissingDeliveryCount) => {
130//! eprintln!("Message is missing delivery count");
131//! }
132//! Err(MessageModelError::JsonError(e)) => {
133//! eprintln!("JSON parsing error: {}", e);
134//! }
135//! }
136//! ```
137//!
138//! ## Integration with UI
139//!
140//! Models are designed for seamless integration with the terminal UI:
141//!
142//! - **Display Optimization** - Fields optimized for table and detail views
143//! - **Sorting Support** - Comparable fields for list sorting
144//! - **Color Coding** - State-based visual indicators
145//! - **Export Support** - JSON serialization for data export
146//!
147//! ## Performance Considerations
148//!
149//! - **Efficient Conversion** - Minimal overhead in Azure message conversion
150//! - **Memory Optimization** - Appropriate use of owned vs. borrowed data
151//! - **Batch Processing** - Optimized batch conversion for large message sets
152//! - **Error Tolerance** - Graceful handling of malformed messages in batches
153//!
154//! ## Thread Safety
155//!
156//! All model types implement appropriate traits for concurrent use:
157//! - `Clone` for efficient copying
158//! - `Send` and `Sync` for thread safety
159//! - `Debug` for development and logging
160
161use azservicebus::prelude::ServiceBusPeekedMessage;
162use azservicebus::primitives::service_bus_message_state::ServiceBusMessageState;
163use azure_core::time::OffsetDateTime;
164use serde::Serialize;
165use serde::ser::Serializer;
166use serde_json::Value;
167use std::convert::TryFrom;
168
169/// Unified message model representing Azure Service Bus messages.
170///
171/// This struct provides a clean, consistent representation of Service Bus messages
172/// that is optimized for terminal UI display and manipulation. It handles the
173/// complexities of Azure's message format while providing type safety and
174/// serialization support.
175///
176/// # Fields
177///
178/// - `sequence` - Unique sequence number assigned by Azure Service Bus
179/// - `id` - Message identifier (typically a GUID)
180/// - `enqueued_at` - Timestamp when the message was enqueued
181/// - `delivery_count` - Number of delivery attempts for this message
182/// - `state` - Current state of the message in the queue
183/// - `body` - Message content (JSON or raw text)
184///
185/// # Examples
186///
187/// ## Creating a New Message Model
188/// ```no_run
189/// use quetty_server::model::{MessageModel, MessageState, BodyData};
190/// use azure_core::time::OffsetDateTime;
191///
192/// let message = MessageModel::new(
193/// 12345,
194/// "550e8400-e29b-41d4-a716-446655440000".to_string(),
195/// OffsetDateTime::now_utc(),
196/// 0,
197/// MessageState::Active,
198/// BodyData::RawString("Hello, Service Bus!".to_string())
199/// );
200///
201/// assert_eq!(message.sequence, 12345);
202/// assert_eq!(message.delivery_count, 0);
203/// assert_eq!(message.state, MessageState::Active);
204/// ```
205///
206/// ## Converting from Azure Messages
207/// ```no_run
208/// use quetty_server::model::MessageModel;
209/// use azservicebus::prelude::ServiceBusPeekedMessage;
210/// use std::convert::TryFrom;
211///
212/// // Convert single message with error handling
213/// let azure_message: ServiceBusPeekedMessage = get_azure_message();
214/// match MessageModel::try_from(azure_message) {
215/// Ok(message) => {
216/// println!("Converted message: {} (sequence: {})",
217/// message.id, message.sequence);
218/// }
219/// Err(e) => eprintln!("Conversion failed: {:?}", e),
220/// }
221/// ```
222///
223/// ## Batch Conversion
224/// ```no_run
225/// use quetty_server::model::MessageModel;
226/// use azservicebus::prelude::ServiceBusPeekedMessage;
227///
228/// let azure_messages: Vec<ServiceBusPeekedMessage> = get_azure_messages();
229/// let valid_messages = MessageModel::try_convert_messages_collect(azure_messages);
230///
231/// println!("Successfully converted {} messages", valid_messages.len());
232/// for message in valid_messages {
233/// println!(" - {} ({})", message.id, message.state);
234/// }
235/// ```
236///
237/// ## JSON Serialization
238/// ```no_run
239/// use quetty_server::model::{MessageModel, MessageState, BodyData};
240/// use serde_json::json;
241///
242/// let message = MessageModel {
243/// sequence: 12345,
244/// id: "test-message".to_string(),
245/// enqueued_at: OffsetDateTime::now_utc(),
246/// delivery_count: 0,
247/// state: MessageState::Active,
248/// body: BodyData::ValidJson(json!({"type": "test", "data": "value"})),
249/// };
250///
251/// // Serialize to JSON for export or API responses
252/// let json_string = serde_json::to_string_pretty(&message)?;
253/// println!("Exported message:\n{}", json_string);
254/// ```
255///
256/// # Thread Safety
257///
258/// `MessageModel` implements `Clone`, `Send`, and `Sync`, making it safe to share
259/// across threads and async tasks. This is essential for the concurrent nature
260/// of the terminal UI application.
261///
262/// # Performance Notes
263///
264/// - Cloning is relatively inexpensive due to reference counting for large data
265/// - Serialization is optimized for both human-readable and compact formats
266/// - Memory usage is minimized through efficient string storage
267#[derive(Serialize, Clone, PartialEq, Debug)]
268pub struct MessageModel {
269 /// Unique sequence number assigned by Azure Service Bus for message ordering
270 pub sequence: i64,
271 /// Message identifier, typically a GUID string
272 pub id: String,
273 /// UTC timestamp when the message was originally enqueued
274 #[serde(with = "azure_core::time::iso8601")]
275 pub enqueued_at: OffsetDateTime,
276 /// Number of times delivery has been attempted for this message
277 pub delivery_count: usize,
278 /// Current state of the message within the Service Bus queue
279 pub state: MessageState,
280 /// Message content, either parsed JSON or raw text
281 pub body: BodyData,
282}
283
284/// Represents the current state of a message within Azure Service Bus.
285///
286/// This enum maps to Azure Service Bus message states and provides additional
287/// states for local message lifecycle management. Each state represents a
288/// specific point in the message processing lifecycle with different implications
289/// for message availability and processing.
290///
291/// # State Transitions
292///
293/// Messages typically follow these state transitions:
294///
295/// ```text
296/// ┌─────────────────────────────────────────────────────────────┐
297/// │ Message Lifecycle │
298/// └─────────────────────────────────────────────────────────────┘
299///
300/// Enqueue
301/// │
302/// ▼
303/// ┌─────────┐ Defer ┌──────────┐
304/// │ Active │ ──────────────► │ Deferred │
305/// └─────────┘ └──────────┘
306/// │ │
307/// │ Complete │ Activate
308/// ▼ ▼
309/// ┌───────────┐ ┌─────────┐
310/// │ Completed │◄────────────────│ Active │
311/// └───────────┘ └─────────┘
312/// │ │
313/// │ │ Abandon/Retry Limit
314/// │ ▼
315/// │ ┌─────────────┐
316/// │ │ Abandoned / │
317/// │ │DeadLettered │
318/// │ └─────────────┘
319/// │
320/// ┌───────────┐ Schedule ┌───────────┐
321/// │ Scheduled │ ◄───────────────│ Active │
322/// └───────────┘ └───────────┘
323/// ```
324///
325/// # Examples
326///
327/// ## Checking Message State
328/// ```no_run
329/// use quetty_server::model::MessageState;
330///
331/// let state = MessageState::Active;
332///
333/// match state {
334/// MessageState::Active => {
335/// println!("Message is available for immediate processing");
336/// }
337/// MessageState::Deferred => {
338/// println!("Message is deferred - can be activated later");
339/// }
340/// MessageState::Scheduled => {
341/// println!("Message is scheduled for future delivery");
342/// }
343/// MessageState::DeadLettered => {
344/// println!("Message failed processing and is in dead letter queue");
345/// }
346/// MessageState::Completed => {
347/// println!("Message processing completed successfully");
348/// }
349/// MessageState::Abandoned => {
350/// println!("Message processing was abandoned");
351/// }
352/// }
353/// ```
354///
355/// ## State-Based Operations
356/// ```no_run
357/// use quetty_server::model::{MessageModel, MessageState};
358///
359/// fn can_process_message(message: &MessageModel) -> bool {
360/// matches!(message.state, MessageState::Active | MessageState::Deferred)
361/// }
362///
363/// fn requires_attention(message: &MessageModel) -> bool {
364/// matches!(message.state, MessageState::DeadLettered | MessageState::Abandoned)
365/// }
366///
367/// fn is_pending_delivery(message: &MessageModel) -> bool {
368/// matches!(message.state, MessageState::Scheduled)
369/// }
370///
371/// // Usage
372/// let message = get_message();
373/// if can_process_message(&message) {
374/// println!("Message {} is ready for processing", message.id);
375/// } else if requires_attention(&message) {
376/// println!("Message {} requires manual intervention", message.id);
377/// }
378/// ```
379///
380/// ## UI Display Logic
381/// ```no_run
382/// use quetty_server::model::MessageState;
383///
384/// fn get_state_color(state: &MessageState) -> &'static str {
385/// match state {
386/// MessageState::Active => "green",
387/// MessageState::Deferred => "yellow",
388/// MessageState::Scheduled => "blue",
389/// MessageState::DeadLettered => "red",
390/// MessageState::Completed => "gray",
391/// MessageState::Abandoned => "orange",
392/// }
393/// }
394///
395/// fn get_state_description(state: &MessageState) -> &'static str {
396/// match state {
397/// MessageState::Active => "Ready for processing",
398/// MessageState::Deferred => "Deferred for later",
399/// MessageState::Scheduled => "Scheduled delivery",
400/// MessageState::DeadLettered => "Failed processing",
401/// MessageState::Completed => "Processing complete",
402/// MessageState::Abandoned => "Processing abandoned",
403/// }
404/// }
405/// ```
406///
407/// # State Descriptions
408///
409/// - **Active** - Message is available in the queue for immediate processing
410/// - **Deferred** - Message has been deferred and can be retrieved by sequence number
411/// - **Scheduled** - Message is scheduled for delivery at a future time
412/// - **DeadLettered** - Message exceeded retry limits or failed validation
413/// - **Completed** - Message processing completed successfully
414/// - **Abandoned** - Message processing was explicitly abandoned
415///
416/// # Default State
417///
418/// The default state is `Active`, representing a newly enqueued message ready
419/// for processing.
420#[derive(Serialize, Clone, PartialEq, Debug, Default)]
421pub enum MessageState {
422 /// Message is available in the queue for immediate processing.
423 /// This is the most common state for messages awaiting consumption.
424 #[default]
425 Active,
426
427 /// Message has been deferred by a receiver and can be retrieved later
428 /// using its sequence number. Deferred messages don't count toward
429 /// the queue's active message count.
430 Deferred,
431
432 /// Message is scheduled for delivery at a specific future time.
433 /// It will automatically become Active when the scheduled time arrives.
434 Scheduled,
435
436 /// Message has been moved to the dead letter queue due to:
437 /// - Exceeding maximum delivery count
438 /// - Message TTL expiration
439 /// - Explicit dead lettering by receiver
440 /// - Message size limits or other validation failures
441 DeadLettered,
442
443 /// Message processing has been completed successfully.
444 /// This is a terminal state - the message will be removed from the queue.
445 Completed,
446
447 /// Message processing was abandoned by the receiver.
448 /// The message may be retried or moved to dead letter queue
449 /// depending on retry policies.
450 Abandoned,
451}
452
453impl MessageModel {
454 pub fn new(
455 sequence: i64,
456 id: String,
457 enqueued_at: OffsetDateTime,
458 delivery_count: usize,
459 state: MessageState,
460 body: BodyData,
461 ) -> Self {
462 Self {
463 sequence,
464 id,
465 enqueued_at,
466 delivery_count,
467 state,
468 body,
469 }
470 }
471
472 pub fn try_convert_messages_collect(
473 messages: Vec<ServiceBusPeekedMessage>,
474 ) -> Vec<MessageModel> {
475 let mut valid_models = Vec::new();
476
477 for msg in messages {
478 if let Ok(model) = MessageModel::try_from(msg) {
479 valid_models.push(model);
480 }
481 }
482
483 valid_models
484 }
485
486 fn parse_message_body(msg: &ServiceBusPeekedMessage) -> Result<BodyData, MessageModelError> {
487 let bytes = match msg.body() {
488 Ok(body) => body,
489 Err(_) => return Err(MessageModelError::MissingMessageBody),
490 };
491
492 match serde_json::from_slice::<Value>(bytes) {
493 Ok(val) => Ok(BodyData::ValidJson(val)),
494 Err(_) => Ok(BodyData::RawString(
495 String::from_utf8_lossy(bytes).into_owned(),
496 )),
497 }
498 }
499}
500
501/// Flexible representation of message body content supporting both JSON and raw text.
502///
503/// This enum handles the diverse nature of Service Bus message content, providing
504/// type-safe handling for both structured JSON data and plain text messages.
505/// The parser attempts JSON deserialization first, falling back to raw string
506/// storage to ensure no message content is lost.
507///
508/// # Variants
509///
510/// - **ValidJson** - Successfully parsed JSON content stored as `serde_json::Value`
511/// - **RawString** - Plain text or unparseable content stored as UTF-8 string
512///
513/// # Examples
514///
515/// ## Working with JSON Messages
516/// ```no_run
517/// use quetty_server::model::BodyData;
518/// use serde_json::{json, Value};
519///
520/// // Create JSON message body
521/// let json_body = BodyData::ValidJson(json!({
522/// "orderId": 12345,
523/// "customerId": "customer-001",
524/// "items": [
525/// {"productId": "prod-001", "quantity": 2},
526/// {"productId": "prod-002", "quantity": 1}
527/// ],
528/// "total": 149.99
529/// }));
530///
531/// // Access JSON content
532/// if let BodyData::ValidJson(value) = &json_body {
533/// if let Some(order_id) = value.get("orderId").and_then(|v| v.as_i64()) {
534/// println!("Processing order: {}", order_id);
535/// }
536///
537/// if let Some(items) = value.get("items").and_then(|v| v.as_array()) {
538/// println!("Order contains {} items", items.len());
539/// }
540/// }
541/// ```
542///
543/// ## Working with Text Messages
544/// ```no_run
545/// use quetty_server::model::BodyData;
546///
547/// // Create text message body
548/// let text_body = BodyData::RawString("Hello, Service Bus!".to_string());
549///
550/// // Access text content
551/// if let BodyData::RawString(content) = &text_body {
552/// println!("Message content: {}", content);
553///
554/// // Process text content
555/// let word_count = content.split_whitespace().count();
556/// println!("Message contains {} words", word_count);
557/// }
558/// ```
559///
560/// ## Pattern Matching for Different Content Types
561/// ```no_run
562/// use quetty_server::model::{BodyData, MessageModel};
563/// use serde_json::Value;
564///
565/// fn process_message(message: &MessageModel) {
566/// match &message.body {
567/// BodyData::ValidJson(json_value) => {
568/// println!("Processing JSON message");
569///
570/// // Handle different JSON message types
571/// match json_value.get("type").and_then(|v| v.as_str()) {
572/// Some("order") => process_order_message(json_value),
573/// Some("notification") => process_notification_message(json_value),
574/// Some("event") => process_event_message(json_value),
575/// _ => println!("Unknown JSON message type"),
576/// }
577/// }
578/// BodyData::RawString(text) => {
579/// println!("Processing text message: {}", text);
580///
581/// // Handle different text formats
582/// if text.starts_with("CMD:") {
583/// process_command_message(text);
584/// } else if text.contains("ERROR") {
585/// process_error_message(text);
586/// } else {
587/// process_plain_text_message(text);
588/// }
589/// }
590/// }
591/// }
592/// ```
593///
594/// ## Serialization Behavior
595/// ```no_run
596/// use quetty_server::model::BodyData;
597/// use serde_json::{json, to_string};
598///
599/// // JSON bodies serialize as their JSON content
600/// let json_body = BodyData::ValidJson(json!({"key": "value"}));
601/// let json_serialized = to_string(&json_body)?;
602/// assert_eq!(json_serialized, r#"{"key":"value"}"#);
603///
604/// // Text bodies serialize as quoted strings
605/// let text_body = BodyData::RawString("Hello, World!".to_string());
606/// let text_serialized = to_string(&text_body)?;
607/// assert_eq!(text_serialized, r#""Hello, World!""#);
608/// ```
609///
610/// ## Content Type Detection
611/// ```no_run
612/// use quetty_server::model::BodyData;
613///
614/// fn analyze_message_content(body: &BodyData) -> String {
615/// match body {
616/// BodyData::ValidJson(value) => {
617/// format!("JSON message with {} top-level fields",
618/// value.as_object().map(|o| o.len()).unwrap_or(0))
619/// }
620/// BodyData::RawString(text) => {
621/// let char_count = text.chars().count();
622/// let line_count = text.lines().count();
623/// format!("Text message: {} characters, {} lines", char_count, line_count)
624/// }
625/// }
626/// }
627/// ```
628///
629/// ## Creating from Raw Data
630/// ```no_run
631/// use quetty_server::model::BodyData;
632/// use serde_json::{from_str, Value};
633///
634/// fn create_body_from_bytes(data: &[u8]) -> BodyData {
635/// // Try to parse as UTF-8 first
636/// match std::str::from_utf8(data) {
637/// Ok(text) => {
638/// // Try to parse as JSON
639/// match from_str::<Value>(text) {
640/// Ok(json) => BodyData::ValidJson(json),
641/// Err(_) => BodyData::RawString(text.to_string()),
642/// }
643/// }
644/// Err(_) => {
645/// // Fallback to lossy UTF-8 conversion for binary data
646/// BodyData::RawString(String::from_utf8_lossy(data).into_owned())
647/// }
648/// }
649/// }
650/// ```
651///
652/// # Performance Notes
653///
654/// - JSON parsing is performed only once during message conversion
655/// - `Value` type provides efficient access to JSON structure without re-parsing
656/// - String storage uses Rust's efficient string handling
657/// - Cloning is optimized through reference counting for large JSON objects
658///
659/// # Thread Safety
660///
661/// `BodyData` implements `Clone`, `Send`, and `Sync`, making it safe for
662/// concurrent use across threads and async tasks.
663#[derive(Debug, Clone, PartialEq)]
664pub enum BodyData {
665 /// Successfully parsed JSON content.
666 ///
667 /// Contains a `serde_json::Value` that provides structured access
668 /// to the JSON data. This allows for efficient querying and
669 /// manipulation of JSON message content.
670 ValidJson(Value),
671
672 /// Raw text content that couldn't be parsed as JSON.
673 ///
674 /// Contains the original message content as a UTF-8 string.
675 /// This preserves all message data even for non-JSON content
676 /// or malformed JSON that couldn't be parsed.
677 RawString(String),
678}
679
680impl Serialize for BodyData {
681 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
682 where
683 S: Serializer,
684 {
685 match self {
686 BodyData::ValidJson(val) => val.serialize(serializer),
687 BodyData::RawString(s) => serializer.serialize_str(s),
688 }
689 }
690}
691
692#[derive(Debug)]
693pub enum MessageModelError {
694 MissingMessageId,
695 MissingMessageBody,
696 MissingDeliveryCount,
697 JsonError(serde_json::Error),
698}
699
700impl TryFrom<ServiceBusPeekedMessage> for MessageModel {
701 type Error = MessageModelError;
702
703 fn try_from(msg: ServiceBusPeekedMessage) -> Result<Self, Self::Error> {
704 let id = msg
705 .message_id()
706 .ok_or(MessageModelError::MissingMessageId)?
707 .to_string();
708
709 let body = MessageModel::parse_message_body(&msg)?;
710
711 let delivery_count = msg
712 .delivery_count()
713 .ok_or(MessageModelError::MissingDeliveryCount)? as usize;
714
715 // Map Azure message state to our internal MessageState enum
716 let state = match msg.state() {
717 ServiceBusMessageState::Active => MessageState::Active,
718 ServiceBusMessageState::Deferred => MessageState::Deferred,
719 ServiceBusMessageState::Scheduled => MessageState::Scheduled,
720 };
721
722 Ok(Self {
723 sequence: msg.sequence_number(),
724 id,
725 enqueued_at: msg.enqueued_time(),
726 delivery_count,
727 state,
728 body,
729 })
730 }
731}