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}