http_tunnel_common/models/
connection.rs

1use serde::{Deserialize, Serialize};
2
3/// Connection metadata tracked in DynamoDB for active WebSocket connections
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ConnectionMetadata {
6    /// API Gateway WebSocket connection ID
7    pub connection_id: String,
8
9    /// Unique tunnel ID assigned to this connection (path segment)
10    pub tunnel_id: String,
11
12    /// Full public URL (https://{domain}/{tunnel_id})
13    pub public_url: String,
14
15    /// Timestamp when connection was established (Unix epoch seconds)
16    pub created_at: i64,
17
18    /// TTL timestamp for DynamoDB auto-deletion (Unix epoch seconds)
19    pub ttl: i64,
20
21    /// Optional metadata about the client
22    #[serde(default)]
23    pub client_info: Option<ClientInfo>,
24}
25
26impl ConnectionMetadata {
27    /// Create a new connection metadata entry
28    pub fn new(
29        connection_id: String,
30        tunnel_id: String,
31        public_url: String,
32        created_at: i64,
33        ttl: i64,
34    ) -> Self {
35        Self {
36            connection_id,
37            tunnel_id,
38            public_url,
39            created_at,
40            ttl,
41            client_info: None,
42        }
43    }
44
45    /// Create a connection with client info
46    pub fn with_client_info(mut self, client_info: ClientInfo) -> Self {
47        self.client_info = Some(client_info);
48        self
49    }
50}
51
52/// Information about the client agent
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ClientInfo {
55    /// Client version string
56    pub version: String,
57
58    /// Platform/OS information
59    pub platform: String,
60}
61
62impl ClientInfo {
63    /// Create new client info
64    pub fn new(version: String, platform: String) -> Self {
65        Self { version, platform }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_connection_metadata_creation() {
75        let metadata = ConnectionMetadata::new(
76            "conn_123".to_string(),
77            "abc123def456".to_string(),
78            "https://abc123def456.tunnel.example.com".to_string(),
79            1234567890,
80            1234574090,
81        );
82
83        assert_eq!(metadata.connection_id, "conn_123");
84        assert_eq!(metadata.tunnel_id, "abc123def456");
85        assert_eq!(metadata.created_at, 1234567890);
86        assert_eq!(metadata.ttl, 1234574090);
87        assert!(metadata.client_info.is_none());
88    }
89
90    #[test]
91    fn test_connection_metadata_with_client_info() {
92        let client_info = ClientInfo::new("1.0.0".to_string(), "linux-x86_64".to_string());
93
94        let metadata = ConnectionMetadata::new(
95            "conn_123".to_string(),
96            "abc123def456".to_string(),
97            "https://abc123def456.tunnel.example.com".to_string(),
98            1234567890,
99            1234574090,
100        )
101        .with_client_info(client_info);
102
103        assert!(metadata.client_info.is_some());
104        let info = metadata.client_info.unwrap();
105        assert_eq!(info.version, "1.0.0");
106        assert_eq!(info.platform, "linux-x86_64");
107    }
108
109    #[test]
110    fn test_connection_metadata_serialization() {
111        let metadata = ConnectionMetadata::new(
112            "conn_abc".to_string(),
113            "xyz789".to_string(),
114            "https://xyz789.tunnel.example.com".to_string(),
115            1234567890,
116            1234574090,
117        );
118
119        let json = serde_json::to_string(&metadata).unwrap();
120        assert!(json.contains(r#""connection_id":"conn_abc"#));
121        assert!(json.contains(r#""tunnel_id":"xyz789"#));
122
123        let parsed: ConnectionMetadata = serde_json::from_str(&json).unwrap();
124        assert_eq!(parsed.connection_id, metadata.connection_id);
125        assert_eq!(parsed.tunnel_id, metadata.tunnel_id);
126        assert_eq!(parsed.created_at, metadata.created_at);
127        assert_eq!(parsed.ttl, metadata.ttl);
128    }
129
130    #[test]
131    fn test_client_info_serialization() {
132        let info = ClientInfo::new("2.1.0".to_string(), "darwin-arm64".to_string());
133
134        let json = serde_json::to_string(&info).unwrap();
135        assert!(json.contains(r#""version":"2.1.0"#));
136        assert!(json.contains(r#""platform":"darwin-arm64"#));
137
138        let parsed: ClientInfo = serde_json::from_str(&json).unwrap();
139        assert_eq!(parsed.version, "2.1.0");
140        assert_eq!(parsed.platform, "darwin-arm64");
141    }
142
143    #[test]
144    fn test_connection_metadata_with_serialized_client_info() {
145        let client_info = ClientInfo::new("1.5.0".to_string(), "windows-x86_64".to_string());
146        let metadata = ConnectionMetadata::new(
147            "conn_123".to_string(),
148            "abc123".to_string(),
149            "https://abc123.tunnel.example.com".to_string(),
150            1234567890,
151            1234574090,
152        )
153        .with_client_info(client_info);
154
155        let json = serde_json::to_string(&metadata).unwrap();
156        let parsed: ConnectionMetadata = serde_json::from_str(&json).unwrap();
157
158        assert!(parsed.client_info.is_some());
159        let info = parsed.client_info.unwrap();
160        assert_eq!(info.version, "1.5.0");
161        assert_eq!(info.platform, "windows-x86_64");
162    }
163
164    #[test]
165    fn test_connection_metadata_default_client_info() {
166        let json = r#"{
167            "connection_id": "conn_123",
168            "tunnel_id": "abc123",
169            "public_url": "https://tunnel.example.com/abc123",
170            "created_at": 1234567890,
171            "ttl": 1234574090
172        }"#;
173
174        let parsed: ConnectionMetadata = serde_json::from_str(json).unwrap();
175        assert!(parsed.client_info.is_none());
176    }
177}