1use crate::{
7 application::dto::{
8 priority_dto::{FromDto, ToDto},
9 session_id_dto::SessionIdDto,
10 stream_id_dto::StreamIdDto,
11 },
12 domain::{
13 DomainError,
14 events::{DomainEvent, EventId, PerformanceMetrics},
15 value_objects::{SessionId, StreamId},
16 },
17};
18use chrono::{DateTime, Utc};
19use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(tag = "event_type", rename_all = "snake_case")]
24pub enum DomainEventDto {
25 SessionActivated {
27 session_id: SessionIdDto,
28 timestamp: DateTime<Utc>,
29 },
30
31 SessionClosed {
33 session_id: SessionIdDto,
34 timestamp: DateTime<Utc>,
35 },
36
37 SessionExpired {
39 session_id: SessionIdDto,
40 timestamp: DateTime<Utc>,
41 },
42
43 SessionTimedOut {
45 session_id: SessionIdDto,
46 original_state: String, timeout_duration: u64,
48 timestamp: DateTime<Utc>,
49 },
50
51 SessionTimeoutExtended {
53 session_id: SessionIdDto,
54 additional_seconds: u64,
55 new_expires_at: DateTime<Utc>,
56 timestamp: DateTime<Utc>,
57 },
58
59 StreamCreated {
61 session_id: SessionIdDto,
62 stream_id: StreamIdDto,
63 timestamp: DateTime<Utc>,
64 },
65
66 StreamStarted {
68 session_id: SessionIdDto,
69 stream_id: StreamIdDto,
70 timestamp: DateTime<Utc>,
71 },
72
73 StreamCompleted {
75 session_id: SessionIdDto,
76 stream_id: StreamIdDto,
77 timestamp: DateTime<Utc>,
78 },
79
80 StreamFailed {
82 session_id: SessionIdDto,
83 stream_id: StreamIdDto,
84 error: String,
85 timestamp: DateTime<Utc>,
86 },
87
88 StreamCancelled {
90 session_id: SessionIdDto,
91 stream_id: StreamIdDto,
92 timestamp: DateTime<Utc>,
93 },
94
95 SkeletonGenerated {
97 session_id: SessionIdDto,
98 stream_id: StreamIdDto,
99 frame_size_bytes: u64,
100 timestamp: DateTime<Utc>,
101 },
102
103 PatchFramesGenerated {
105 session_id: SessionIdDto,
106 stream_id: StreamIdDto,
107 frame_count: usize,
108 total_bytes: u64,
109 highest_priority: u8,
110 timestamp: DateTime<Utc>,
111 },
112
113 FramesBatched {
115 session_id: SessionIdDto,
116 frame_count: usize,
117 timestamp: DateTime<Utc>,
118 },
119
120 PriorityThresholdAdjusted {
122 session_id: SessionIdDto,
123 old_threshold: u8,
124 new_threshold: u8,
125 reason: String,
126 timestamp: DateTime<Utc>,
127 },
128
129 StreamConfigUpdated {
131 session_id: SessionIdDto,
132 stream_id: StreamIdDto,
133 timestamp: DateTime<Utc>,
134 },
135
136 PerformanceMetricsRecorded {
138 session_id: SessionIdDto,
139 metrics: PerformanceMetricsDto,
140 timestamp: DateTime<Utc>,
141 },
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub struct PerformanceMetricsDto {
147 pub frames_per_second: f64,
148 pub bytes_per_second: f64,
149 pub average_frame_size: f64,
150 pub priority_distribution: PriorityDistributionDto,
151 pub latency_ms: Option<u64>,
152}
153
154pub type PriorityDistributionDto = crate::domain::events::PriorityDistribution;
157
158#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
160pub struct EventIdDto {
161 uuid: uuid::Uuid,
162}
163
164impl EventIdDto {
165 pub fn new(uuid: uuid::Uuid) -> Self {
167 Self { uuid }
168 }
169
170 pub fn uuid(self) -> uuid::Uuid {
172 self.uuid
173 }
174
175 pub fn generate() -> Self {
177 Self {
178 uuid: uuid::Uuid::new_v4(),
179 }
180 }
181}
182
183impl std::fmt::Display for EventIdDto {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 write!(f, "{}", self.uuid)
186 }
187}
188
189impl From<DomainEvent> for DomainEventDto {
192 fn from(event: DomainEvent) -> Self {
193 match event {
194 DomainEvent::SessionActivated {
195 session_id,
196 timestamp,
197 } => Self::SessionActivated {
198 session_id: session_id.to_dto(),
199 timestamp,
200 },
201 DomainEvent::SessionClosed {
202 session_id,
203 timestamp,
204 } => Self::SessionClosed {
205 session_id: session_id.to_dto(),
206 timestamp,
207 },
208 DomainEvent::SessionExpired {
209 session_id,
210 timestamp,
211 } => Self::SessionExpired {
212 session_id: session_id.to_dto(),
213 timestamp,
214 },
215 DomainEvent::StreamCreated {
216 session_id,
217 stream_id,
218 timestamp,
219 } => Self::StreamCreated {
220 session_id: session_id.to_dto(),
221 stream_id: stream_id.to_dto(),
222 timestamp,
223 },
224 DomainEvent::StreamStarted {
225 session_id,
226 stream_id,
227 timestamp,
228 } => Self::StreamStarted {
229 session_id: session_id.to_dto(),
230 stream_id: stream_id.to_dto(),
231 timestamp,
232 },
233 DomainEvent::StreamCompleted {
234 session_id,
235 stream_id,
236 timestamp,
237 } => Self::StreamCompleted {
238 session_id: session_id.to_dto(),
239 stream_id: stream_id.to_dto(),
240 timestamp,
241 },
242 DomainEvent::StreamFailed {
243 session_id,
244 stream_id,
245 error,
246 timestamp,
247 } => Self::StreamFailed {
248 session_id: session_id.to_dto(),
249 stream_id: stream_id.to_dto(),
250 error,
251 timestamp,
252 },
253 DomainEvent::StreamCancelled {
254 session_id,
255 stream_id,
256 timestamp,
257 } => Self::StreamCancelled {
258 session_id: session_id.to_dto(),
259 stream_id: stream_id.to_dto(),
260 timestamp,
261 },
262 DomainEvent::SkeletonGenerated {
263 session_id,
264 stream_id,
265 frame_size_bytes,
266 timestamp,
267 } => Self::SkeletonGenerated {
268 session_id: session_id.to_dto(),
269 stream_id: stream_id.to_dto(),
270 frame_size_bytes,
271 timestamp,
272 },
273 DomainEvent::PatchFramesGenerated {
274 session_id,
275 stream_id,
276 frame_count,
277 total_bytes,
278 highest_priority,
279 timestamp,
280 } => Self::PatchFramesGenerated {
281 session_id: session_id.to_dto(),
282 stream_id: stream_id.to_dto(),
283 frame_count,
284 total_bytes,
285 highest_priority,
286 timestamp,
287 },
288 DomainEvent::FramesBatched {
289 session_id,
290 frame_count,
291 timestamp,
292 } => Self::FramesBatched {
293 session_id: session_id.to_dto(),
294 frame_count,
295 timestamp,
296 },
297 DomainEvent::PriorityThresholdAdjusted {
298 session_id,
299 old_threshold,
300 new_threshold,
301 reason,
302 timestamp,
303 } => Self::PriorityThresholdAdjusted {
304 session_id: session_id.to_dto(),
305 old_threshold,
306 new_threshold,
307 reason,
308 timestamp,
309 },
310 DomainEvent::StreamConfigUpdated {
311 session_id,
312 stream_id,
313 timestamp,
314 } => Self::StreamConfigUpdated {
315 session_id: session_id.to_dto(),
316 stream_id: stream_id.to_dto(),
317 timestamp,
318 },
319 DomainEvent::PerformanceMetricsRecorded {
320 session_id,
321 metrics,
322 timestamp,
323 } => Self::PerformanceMetricsRecorded {
324 session_id: session_id.to_dto(),
325 metrics: metrics.into(),
326 timestamp,
327 },
328 DomainEvent::SessionTimedOut {
329 session_id,
330 original_state,
331 timeout_duration,
332 timestamp,
333 } => Self::SessionTimedOut {
334 session_id: session_id.to_dto(),
335 original_state: format!("{:?}", original_state),
336 timeout_duration,
337 timestamp,
338 },
339 DomainEvent::SessionTimeoutExtended {
340 session_id,
341 additional_seconds,
342 new_expires_at,
343 timestamp,
344 } => Self::SessionTimeoutExtended {
345 session_id: session_id.to_dto(),
346 additional_seconds,
347 new_expires_at,
348 timestamp,
349 },
350 }
351 }
352}
353
354impl ToDto<DomainEventDto> for DomainEvent {
355 fn to_dto(self) -> DomainEventDto {
356 DomainEventDto::from(self)
357 }
358}
359
360impl FromDto<DomainEventDto> for DomainEvent {
361 type Error = DomainError;
362
363 fn from_dto(dto: DomainEventDto) -> Result<Self, Self::Error> {
364 match dto {
365 DomainEventDto::SessionActivated {
366 session_id,
367 timestamp,
368 } => Ok(Self::SessionActivated {
369 session_id: SessionId::from_dto(session_id)?,
370 timestamp,
371 }),
372 DomainEventDto::SessionClosed {
373 session_id,
374 timestamp,
375 } => Ok(Self::SessionClosed {
376 session_id: SessionId::from_dto(session_id)?,
377 timestamp,
378 }),
379 DomainEventDto::SessionExpired {
380 session_id,
381 timestamp,
382 } => Ok(Self::SessionExpired {
383 session_id: SessionId::from_dto(session_id)?,
384 timestamp,
385 }),
386 DomainEventDto::StreamCreated {
387 session_id,
388 stream_id,
389 timestamp,
390 } => Ok(Self::StreamCreated {
391 session_id: SessionId::from_dto(session_id)?,
392 stream_id: StreamId::from_dto(stream_id)?,
393 timestamp,
394 }),
395 DomainEventDto::StreamStarted {
396 session_id,
397 stream_id,
398 timestamp,
399 } => Ok(Self::StreamStarted {
400 session_id: SessionId::from_dto(session_id)?,
401 stream_id: StreamId::from_dto(stream_id)?,
402 timestamp,
403 }),
404 DomainEventDto::StreamCompleted {
405 session_id,
406 stream_id,
407 timestamp,
408 } => Ok(Self::StreamCompleted {
409 session_id: SessionId::from_dto(session_id)?,
410 stream_id: StreamId::from_dto(stream_id)?,
411 timestamp,
412 }),
413 DomainEventDto::StreamFailed {
414 session_id,
415 stream_id,
416 error,
417 timestamp,
418 } => Ok(Self::StreamFailed {
419 session_id: SessionId::from_dto(session_id)?,
420 stream_id: StreamId::from_dto(stream_id)?,
421 error,
422 timestamp,
423 }),
424 DomainEventDto::StreamCancelled {
425 session_id,
426 stream_id,
427 timestamp,
428 } => Ok(Self::StreamCancelled {
429 session_id: SessionId::from_dto(session_id)?,
430 stream_id: StreamId::from_dto(stream_id)?,
431 timestamp,
432 }),
433 DomainEventDto::SkeletonGenerated {
434 session_id,
435 stream_id,
436 frame_size_bytes,
437 timestamp,
438 } => Ok(Self::SkeletonGenerated {
439 session_id: SessionId::from_dto(session_id)?,
440 stream_id: StreamId::from_dto(stream_id)?,
441 frame_size_bytes,
442 timestamp,
443 }),
444 DomainEventDto::PatchFramesGenerated {
445 session_id,
446 stream_id,
447 frame_count,
448 total_bytes,
449 highest_priority,
450 timestamp,
451 } => Ok(Self::PatchFramesGenerated {
452 session_id: SessionId::from_dto(session_id)?,
453 stream_id: StreamId::from_dto(stream_id)?,
454 frame_count,
455 total_bytes,
456 highest_priority,
457 timestamp,
458 }),
459 DomainEventDto::FramesBatched {
460 session_id,
461 frame_count,
462 timestamp,
463 } => Ok(Self::FramesBatched {
464 session_id: SessionId::from_dto(session_id)?,
465 frame_count,
466 timestamp,
467 }),
468 DomainEventDto::PriorityThresholdAdjusted {
469 session_id,
470 old_threshold,
471 new_threshold,
472 reason,
473 timestamp,
474 } => Ok(Self::PriorityThresholdAdjusted {
475 session_id: SessionId::from_dto(session_id)?,
476 old_threshold,
477 new_threshold,
478 reason,
479 timestamp,
480 }),
481 DomainEventDto::StreamConfigUpdated {
482 session_id,
483 stream_id,
484 timestamp,
485 } => Ok(Self::StreamConfigUpdated {
486 session_id: SessionId::from_dto(session_id)?,
487 stream_id: StreamId::from_dto(stream_id)?,
488 timestamp,
489 }),
490 DomainEventDto::PerformanceMetricsRecorded {
491 session_id,
492 metrics,
493 timestamp,
494 } => Ok(Self::PerformanceMetricsRecorded {
495 session_id: SessionId::from_dto(session_id)?,
496 metrics: metrics.try_into().map_err(|_| {
497 DomainError::InvalidInput("Invalid performance metrics".to_string())
498 })?,
499 timestamp,
500 }),
501 DomainEventDto::SessionTimedOut {
502 session_id,
503 original_state,
504 timeout_duration,
505 timestamp,
506 } => {
507 let state = match original_state.as_str() {
509 "Initializing" => crate::domain::events::SessionState::Initializing,
510 "Active" => crate::domain::events::SessionState::Active,
511 "Closing" => crate::domain::events::SessionState::Closing,
512 "Completed" => crate::domain::events::SessionState::Completed,
513 "Failed" => crate::domain::events::SessionState::Failed,
514 _ => {
515 return Err(DomainError::InvalidInput(format!(
516 "Invalid session state: {}",
517 original_state
518 )));
519 }
520 };
521 Ok(Self::SessionTimedOut {
522 session_id: SessionId::from_dto(session_id)?,
523 original_state: state,
524 timeout_duration,
525 timestamp,
526 })
527 }
528 DomainEventDto::SessionTimeoutExtended {
529 session_id,
530 additional_seconds,
531 new_expires_at,
532 timestamp,
533 } => Ok(Self::SessionTimeoutExtended {
534 session_id: SessionId::from_dto(session_id)?,
535 additional_seconds,
536 new_expires_at,
537 timestamp,
538 }),
539 }
540 }
541}
542
543impl From<PerformanceMetrics> for PerformanceMetricsDto {
544 fn from(metrics: PerformanceMetrics) -> Self {
545 Self {
546 frames_per_second: metrics.frames_per_second,
547 bytes_per_second: metrics.bytes_per_second,
548 average_frame_size: metrics.average_frame_size,
549 priority_distribution: metrics.priority_distribution,
550 latency_ms: metrics.latency_ms,
551 }
552 }
553}
554
555impl TryFrom<PerformanceMetricsDto> for PerformanceMetrics {
556 type Error = DomainError;
557
558 fn try_from(dto: PerformanceMetricsDto) -> Result<Self, Self::Error> {
559 Ok(Self {
560 frames_per_second: dto.frames_per_second,
561 bytes_per_second: dto.bytes_per_second,
562 average_frame_size: dto.average_frame_size,
563 priority_distribution: dto.priority_distribution,
564 latency_ms: dto.latency_ms,
565 })
566 }
567}
568
569impl From<EventId> for EventIdDto {
572 fn from(event_id: EventId) -> Self {
573 Self::new(event_id.inner())
574 }
575}
576
577impl From<EventIdDto> for EventId {
578 fn from(dto: EventIdDto) -> Self {
579 EventId::from_uuid(dto.uuid)
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586 use chrono::Utc;
587
588 #[test]
589 fn test_domain_event_dto_conversion() {
590 let session_id = SessionId::new();
591 let stream_id = StreamId::new();
592 let timestamp = Utc::now();
593
594 let domain_event = DomainEvent::StreamCreated {
595 session_id,
596 stream_id,
597 timestamp,
598 };
599
600 let dto = domain_event.clone().to_dto();
602
603 let converted = DomainEvent::from_dto(dto).unwrap();
605
606 assert_eq!(domain_event, converted);
607 }
608
609 #[test]
610 fn test_performance_metrics_dto_conversion() {
611 let metrics = PerformanceMetrics {
612 frames_per_second: 60.0,
613 bytes_per_second: 1024.0,
614 average_frame_size: 512.0,
615 priority_distribution: PriorityDistributionDto::default(),
616 latency_ms: Some(100),
617 };
618
619 let dto = PerformanceMetricsDto::from(metrics.clone());
620 let converted = PerformanceMetrics::try_from(dto).unwrap();
621
622 assert_eq!(metrics, converted);
623 }
624
625 #[test]
626 fn test_event_dto_serialization() {
627 let session_id = SessionId::new();
628 let event_dto = DomainEventDto::SessionActivated {
629 session_id: session_id.to_dto(),
630 timestamp: Utc::now(),
631 };
632
633 let serialized = serde_json::to_string(&event_dto).unwrap();
634 let deserialized: DomainEventDto = serde_json::from_str(&serialized).unwrap();
635
636 assert_eq!(event_dto, deserialized);
637 }
638
639 #[test]
640 fn test_event_id_dto_creation() {
641 let uuid = uuid::Uuid::new_v4();
642 let event_id_dto = EventIdDto::new(uuid);
643
644 assert_eq!(event_id_dto.uuid(), uuid);
645 }
646
647 #[test]
648 fn test_event_id_dto_generate() {
649 let event_id1 = EventIdDto::generate();
650 let event_id2 = EventIdDto::generate();
651
652 assert_ne!(event_id1, event_id2);
653 }
654
655 #[test]
656 fn test_event_id_dto_display() {
657 let uuid = uuid::Uuid::new_v4();
658 let event_id_dto = EventIdDto::new(uuid);
659
660 assert_eq!(event_id_dto.to_string(), uuid.to_string());
661 }
662
663 #[test]
664 fn test_event_id_dto_conversion() {
665 let event_id = EventId::new();
666 let dto = EventIdDto::from(event_id);
667 let converted_back = EventId::from(dto);
668
669 assert_eq!(event_id, converted_back);
670 }
671
672 #[test]
673 fn test_all_domain_event_dto_variants() {
674 let session_id = SessionId::new();
675 let stream_id = StreamId::new();
676 let timestamp = Utc::now();
677
678 let event1 = DomainEvent::SessionActivated {
680 session_id,
681 timestamp,
682 };
683 let dto1 = DomainEventDto::from(event1.clone());
684 let converted1 = DomainEvent::from_dto(dto1).unwrap();
685 assert_eq!(event1, converted1);
686
687 let event2 = DomainEvent::SessionClosed {
689 session_id,
690 timestamp,
691 };
692 let dto2 = DomainEventDto::from(event2.clone());
693 let converted2 = DomainEvent::from_dto(dto2).unwrap();
694 assert_eq!(event2, converted2);
695
696 let event3 = DomainEvent::SessionExpired {
698 session_id,
699 timestamp,
700 };
701 let dto3 = DomainEventDto::from(event3.clone());
702 let converted3 = DomainEvent::from_dto(dto3).unwrap();
703 assert_eq!(event3, converted3);
704
705 let event4 = DomainEvent::StreamCreated {
707 session_id,
708 stream_id,
709 timestamp,
710 };
711 let dto4 = DomainEventDto::from(event4.clone());
712 let converted4 = DomainEvent::from_dto(dto4).unwrap();
713 assert_eq!(event4, converted4);
714
715 let event5 = DomainEvent::StreamStarted {
717 session_id,
718 stream_id,
719 timestamp,
720 };
721 let dto5 = DomainEventDto::from(event5.clone());
722 let converted5 = DomainEvent::from_dto(dto5).unwrap();
723 assert_eq!(event5, converted5);
724
725 let event6 = DomainEvent::StreamCompleted {
727 session_id,
728 stream_id,
729 timestamp,
730 };
731 let dto6 = DomainEventDto::from(event6.clone());
732 let converted6 = DomainEvent::from_dto(dto6).unwrap();
733 assert_eq!(event6, converted6);
734 }
735
736 #[test]
737 fn test_stream_failed_event_conversion() {
738 let session_id = SessionId::new();
739 let stream_id = StreamId::new();
740 let timestamp = Utc::now();
741 let error = "Test error message".to_string();
742
743 let event = DomainEvent::StreamFailed {
744 session_id,
745 stream_id,
746 error: error.clone(),
747 timestamp,
748 };
749
750 let dto = DomainEventDto::from(event.clone());
751 let converted = DomainEvent::from_dto(dto).unwrap();
752
753 assert_eq!(event, converted);
754 }
755
756 #[test]
757 fn test_performance_metrics_with_none_latency() {
758 let metrics = PerformanceMetrics {
759 frames_per_second: 30.0,
760 bytes_per_second: 2048.0,
761 average_frame_size: 1024.0,
762 priority_distribution: PriorityDistributionDto::default(),
763 latency_ms: None,
764 };
765
766 let dto = PerformanceMetricsDto::from(metrics.clone());
767 let converted = PerformanceMetrics::try_from(dto).unwrap();
768
769 assert_eq!(metrics, converted);
770 assert_eq!(converted.latency_ms, None);
771 }
772
773 #[test]
774 fn test_complex_event_serialization() {
775 let session_id = SessionId::new();
776 let stream_id = StreamId::new();
777 let timestamp = Utc::now();
778
779 let event_dto = DomainEventDto::PatchFramesGenerated {
780 session_id: session_id.to_dto(),
781 stream_id: stream_id.to_dto(),
782 frame_count: 42,
783 total_bytes: 1024,
784 highest_priority: 100,
785 timestamp,
786 };
787
788 let serialized = serde_json::to_string(&event_dto).unwrap();
789 let deserialized: DomainEventDto = serde_json::from_str(&serialized).unwrap();
790
791 assert_eq!(event_dto, deserialized);
792 }
793
794 #[test]
795 fn test_performance_metrics_dto_fields() {
796 let dto = PerformanceMetricsDto {
797 frames_per_second: 60.0,
798 bytes_per_second: 4096.0,
799 average_frame_size: 256.0,
800 priority_distribution: PriorityDistributionDto::default(),
801 latency_ms: Some(50),
802 };
803
804 assert_eq!(dto.frames_per_second, 60.0);
805 assert_eq!(dto.bytes_per_second, 4096.0);
806 assert_eq!(dto.average_frame_size, 256.0);
807 assert_eq!(dto.latency_ms, Some(50));
808 }
809
810 #[test]
811 fn test_priority_threshold_adjusted_event() {
812 let session_id = SessionId::new();
813 let timestamp = Utc::now();
814
815 let event = DomainEvent::PriorityThresholdAdjusted {
816 session_id,
817 old_threshold: 50,
818 new_threshold: 75,
819 reason: "Performance optimization".to_string(),
820 timestamp,
821 };
822
823 let dto = DomainEventDto::from(event.clone());
824 let converted = DomainEvent::from_dto(dto).unwrap();
825
826 assert_eq!(event, converted);
827 }
828
829 #[test]
830 fn test_event_id_dto_hash() {
831 let uuid1 = uuid::Uuid::new_v4();
832 let uuid2 = uuid::Uuid::new_v4();
833
834 let event_id1 = EventIdDto::new(uuid1);
835 let event_id2 = EventIdDto::new(uuid2);
836 let event_id3 = EventIdDto::new(uuid1); use std::collections::HashSet;
839 let mut set = HashSet::new();
840 set.insert(event_id1.clone());
841 set.insert(event_id2);
842 set.insert(event_id3);
843
844 assert_eq!(set.len(), 2); }
846
847 #[test]
848 fn test_event_dto_clone() {
849 let session_id = SessionId::new();
850 let timestamp = Utc::now();
851
852 let original = DomainEventDto::SessionActivated {
853 session_id: session_id.to_dto(),
854 timestamp,
855 };
856
857 let cloned = original.clone();
858 assert_eq!(original, cloned);
859 }
860
861 #[test]
862 fn test_event_dto_debug() {
863 let session_id = SessionId::new();
864 let timestamp = Utc::now();
865
866 let event = DomainEventDto::SessionActivated {
867 session_id: session_id.to_dto(),
868 timestamp,
869 };
870
871 let debug_str = format!("{:?}", event);
872 assert!(debug_str.contains("SessionActivated"));
873 }
874}