Skip to main content

pjson_rs/application/dto/
id_dto.rs

1//! Generic ID Data Transfer Object for serialization
2//!
3//! Handles serialization/deserialization of `Id<T>` domain objects
4//! while keeping domain layer clean of serialization concerns.
5
6use crate::domain::value_objects::{Id, IdMarker, SessionMarker, StreamMarker};
7use serde::{Deserialize, Serialize};
8use std::marker::PhantomData;
9use uuid::Uuid;
10
11/// Generic serializable representation of `Id<T>` domain object.
12///
13/// The phantom marker is skipped during serialization, resulting in
14/// a transparent UUID representation in JSON.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(transparent)]
17pub struct IdDto<T: IdMarker> {
18    uuid: Uuid,
19    #[serde(skip)]
20    _marker: PhantomData<T>,
21}
22
23impl<T: IdMarker> IdDto<T> {
24    /// Create from UUID
25    #[must_use]
26    pub fn new(uuid: Uuid) -> Self {
27        Self {
28            uuid,
29            _marker: PhantomData,
30        }
31    }
32
33    /// Create from string with validation
34    ///
35    /// # Errors
36    ///
37    /// Returns `uuid::Error` if the string is not a valid UUID.
38    pub fn from_string(s: &str) -> Result<Self, uuid::Error> {
39        let uuid = Uuid::parse_str(s)?;
40        Ok(Self::new(uuid))
41    }
42
43    /// Get UUID value
44    #[must_use]
45    pub fn uuid(self) -> Uuid {
46        self.uuid
47    }
48
49    /// Get string representation
50    #[must_use]
51    pub fn as_string(self) -> String {
52        self.uuid.to_string()
53    }
54}
55
56impl<T: IdMarker> From<Id<T>> for IdDto<T> {
57    fn from(id: Id<T>) -> Self {
58        Self::new(id.as_uuid())
59    }
60}
61
62impl<T: IdMarker> From<IdDto<T>> for Id<T> {
63    fn from(dto: IdDto<T>) -> Self {
64        Id::from_uuid(dto.uuid)
65    }
66}
67
68impl<T: IdMarker> std::fmt::Display for IdDto<T> {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(f, "{}", self.uuid)
71    }
72}
73
74/// Type alias for session ID DTO
75pub type SessionIdDto = IdDto<SessionMarker>;
76
77/// Type alias for stream ID DTO
78pub type StreamIdDto = IdDto<StreamMarker>;
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::domain::value_objects::{SessionId, StreamId};
84
85    #[test]
86    fn test_session_id_dto_serialization() {
87        let session_id = SessionId::new();
88        let dto = SessionIdDto::from(session_id);
89
90        let json = serde_json::to_string(&dto).unwrap();
91        let deserialized: SessionIdDto = serde_json::from_str(&json).unwrap();
92
93        assert_eq!(deserialized.uuid(), dto.uuid());
94
95        let domain_session_id = Id::from(deserialized);
96        assert_eq!(domain_session_id.as_uuid(), session_id.as_uuid());
97    }
98
99    #[test]
100    fn test_stream_id_dto_serialization() {
101        let stream_id = StreamId::new();
102        let dto = StreamIdDto::from(stream_id);
103
104        let json = serde_json::to_string(&dto).unwrap();
105        let deserialized: StreamIdDto = serde_json::from_str(&json).unwrap();
106
107        assert_eq!(deserialized.uuid(), dto.uuid());
108    }
109
110    #[test]
111    fn test_id_dto_from_string() {
112        let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
113        let dto = SessionIdDto::from_string(uuid_str).unwrap();
114        assert_eq!(dto.as_string(), uuid_str);
115
116        assert!(SessionIdDto::from_string("invalid-uuid").is_err());
117    }
118
119    #[test]
120    fn test_conversion_traits() {
121        let session_id = SessionId::new();
122
123        let dto: SessionIdDto = session_id.into();
124        assert_eq!(dto.uuid(), session_id.as_uuid());
125
126        let converted = SessionId::from(dto);
127        assert_eq!(converted.as_uuid(), session_id.as_uuid());
128    }
129
130    #[test]
131    fn test_display() {
132        let dto = SessionIdDto::from_string("550e8400-e29b-41d4-a716-446655440000").unwrap();
133        assert_eq!(format!("{}", dto), "550e8400-e29b-41d4-a716-446655440000");
134    }
135}