connector_client/
identity.rs1use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct AppIdentity {
9 pub app_instance_id: String,
10 pub app_id: String,
11 pub pid: u32,
12 pub started_at: u64,
13 #[serde(default)]
14 pub workspace_id: Option<String>,
15 #[serde(default)]
16 pub workspace_path: Option<PathBuf>,
17 pub inspection_protocol_version: u64,
18 #[serde(default)]
19 pub workflow_protocol_version: Option<u64>,
20}
21
22impl AppIdentity {
23 pub fn parse(value: Value) -> Result<Self, String> {
24 let identity: Self = serde_json::from_value(value)
25 .map_err(|e| format!("identity_unavailable: invalid endpoint identity: {e}"))?;
26 if identity.app_instance_id.is_empty() || identity.app_id.is_empty() || identity.pid == 0 {
27 return Err("identity_unavailable: incomplete endpoint identity".into());
28 }
29 Ok(identity)
30 }
31}
32
33pub fn workspace_matches(cwd: &Path, workspace: &Path) -> bool {
35 match (cwd.canonicalize(), workspace.canonicalize()) {
36 (Ok(cwd), Ok(workspace)) => cwd.starts_with(workspace),
37 _ => false,
38 }
39}
40
41pub fn instance_from_handle(handle: &str) -> Option<&str> {
42 handle
43 .split_once(":picker:")
44 .or_else(|| handle.split_once(":capture:"))
45 .or_else(|| handle.split_once(":artifact:"))
46 .map(|(instance, _)| instance)
47 .filter(|s| !s.is_empty())
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 #[test]
54 fn canonical_workspace_boundaries_and_symlinks() {
55 let root =
56 std::env::temp_dir().join(format!("connector-identity-{}", uuid::Uuid::new_v4()));
57 let app = root.join("app");
58 let apple = root.join("apple");
59 std::fs::create_dir_all(app.join("child")).unwrap();
60 std::fs::create_dir_all(&apple).unwrap();
61 assert!(workspace_matches(&app.join("child"), &app));
62 assert!(!workspace_matches(&apple, &app));
63 assert!(!workspace_matches(&app, &root.join("missing")));
64 #[cfg(unix)]
65 {
66 let alias = root.join("alias");
67 std::os::unix::fs::symlink(&app, &alias).unwrap();
68 assert!(workspace_matches(&alias.join("child"), &app));
69 }
70 std::fs::remove_dir_all(root).unwrap();
71 }
72}