1use crate::RemoteConnectionOptions;
2
3/// A normalized remote identity for matching live remote hosts against
4/// persisted remote metadata.
5///
6/// This mirrors workspace persistence identity semantics rather than full
7/// `RemoteConnectionOptions` equality, so runtime-only fields like SSH
8/// nicknames or Docker environment overrides do not affect matching.
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum RemoteConnectionIdentity {
11 Ssh {
12 host: String,
13 username: Option<String>,
14 port: Option<u16>,
15 },
16 Wsl {
17 distro_name: String,
18 user: Option<String>,
19 },
20 Docker {
21 container_id: String,
22 name: String,
23 remote_user: String,
24 },
25 #[cfg(any(test, feature = "test-support"))]
26 Mock { id: u64 },
27}
28
29impl From<&RemoteConnectionOptions> for RemoteConnectionIdentity {
30 fn from(options: &RemoteConnectionOptions) -> Self {
31 match options {
32 RemoteConnectionOptions::Ssh(options) => Self::Ssh {
33 host: options.host.to_string(),
34 username: options.username.clone(),
35 port: options.port,
36 },
37 RemoteConnectionOptions::Wsl(options) => Self::Wsl {
38 distro_name: options.distro_name.clone(),
39 user: options.user.clone(),
40 },
41 RemoteConnectionOptions::Docker(options) => Self::Docker {
42 container_id: options.container_id.clone(),
43 name: options.name.clone(),
44 remote_user: options.remote_user.clone(),
45 },
46 #[cfg(any(test, feature = "test-support"))]
47 RemoteConnectionOptions::Mock(options) => Self::Mock { id: options.id },
48 }
49 }
50}
51
52pub fn remote_connection_identity(options: &RemoteConnectionOptions) -> RemoteConnectionIdentity {
53 options.into()
54}
55
56pub fn same_remote_connection_identity(
57 left: Option<&RemoteConnectionOptions>,
58 right: Option<&RemoteConnectionOptions>,
59) -> bool {
60 match (left, right) {
61 (Some(left), Some(right)) => {
62 remote_connection_identity(left) == remote_connection_identity(right)
63 }
64 (None, None) => true,
65 _ => false,
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use std::collections::BTreeMap;
72
73 use super::*;
74 use crate::{DockerConnectionOptions, SshConnectionOptions, WslConnectionOptions};
75
76 #[test]
77 fn ssh_identity_ignores_non_persisted_runtime_fields() {
78 let left = RemoteConnectionOptions::Ssh(SshConnectionOptions {
79 host: "example.com".into(),
80 username: Some("anth".to_string()),
81 port: Some(2222),
82 password: Some("secret".to_string()),
83 args: Some(vec!["-v".to_string()]),
84 connection_timeout: Some(30),
85 nickname: Some("work".to_string()),
86 upload_binary_over_ssh: true,
87 ..Default::default()
88 });
89 let right = RemoteConnectionOptions::Ssh(SshConnectionOptions {
90 host: "example.com".into(),
91 username: Some("anth".to_string()),
92 port: Some(2222),
93 password: None,
94 args: None,
95 connection_timeout: None,
96 nickname: None,
97 upload_binary_over_ssh: false,
98 ..Default::default()
99 });
100
101 assert!(same_remote_connection_identity(Some(&left), Some(&right),));
102 }
103
104 #[test]
105 fn ssh_identity_distinguishes_persistence_key_fields() {
106 let left = RemoteConnectionOptions::Ssh(SshConnectionOptions {
107 host: "example.com".into(),
108 username: Some("anth".to_string()),
109 port: Some(2222),
110 ..Default::default()
111 });
112 let right = RemoteConnectionOptions::Ssh(SshConnectionOptions {
113 host: "example.com".into(),
114 username: Some("anth".to_string()),
115 port: Some(2223),
116 ..Default::default()
117 });
118
119 assert!(!same_remote_connection_identity(Some(&left), Some(&right),));
120 }
121
122 #[test]
123 fn wsl_identity_includes_user() {
124 let left = RemoteConnectionOptions::Wsl(WslConnectionOptions {
125 distro_name: "Ubuntu".to_string(),
126 user: Some("anth".to_string()),
127 });
128 let right = RemoteConnectionOptions::Wsl(WslConnectionOptions {
129 distro_name: "Ubuntu".to_string(),
130 user: Some("root".to_string()),
131 });
132
133 assert!(!same_remote_connection_identity(Some(&left), Some(&right),));
134 }
135
136 #[test]
137 fn docker_identity_ignores_non_persisted_runtime_fields() {
138 let left = RemoteConnectionOptions::Docker(DockerConnectionOptions {
139 name: "zed-dev".to_string(),
140 container_id: "container-123".to_string(),
141 remote_user: "anth".to_string(),
142 upload_binary_over_docker_exec: true,
143 use_podman: true,
144 remote_env: BTreeMap::from([("FOO".to_string(), "BAR".to_string())]),
145 });
146 let right = RemoteConnectionOptions::Docker(DockerConnectionOptions {
147 name: "zed-dev".to_string(),
148 container_id: "container-123".to_string(),
149 remote_user: "anth".to_string(),
150 upload_binary_over_docker_exec: false,
151 use_podman: false,
152 remote_env: BTreeMap::new(),
153 });
154
155 assert!(same_remote_connection_identity(Some(&left), Some(&right),));
156 }
157
158 #[test]
159 fn local_identity_matches_only_local_identity() {
160 let remote = RemoteConnectionOptions::Wsl(WslConnectionOptions {
161 distro_name: "Ubuntu".to_string(),
162 user: Some("anth".to_string()),
163 });
164
165 assert!(same_remote_connection_identity(None, None));
166 assert!(!same_remote_connection_identity(None, Some(&remote)));
167 }
168}