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