Skip to main content

subc_daemon/
identity.rs

1use std::{fmt, path::Path};
2
3pub use cortexkit_paths::{IdentityError, ProjectRootId};
4
5/// Opaque session identifier supplied by the harness.
6///
7/// subc treats this as an uninterpreted string and only carries it through to
8/// modules that need per-session undo, backup, bash-task, or checkpoint state.
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10pub struct SessionId(String);
11
12impl SessionId {
13    /// Wrap a harness-supplied opaque session id without interpreting it.
14    pub fn new(value: impl Into<String>) -> Self {
15        Self(value.into())
16    }
17
18    /// Borrow the opaque session id string.
19    pub fn as_str(&self) -> &str {
20        &self.0
21    }
22
23    /// Consume the session id and return the opaque string.
24    pub fn into_string(self) -> String {
25        self.0
26    }
27}
28
29impl AsRef<str> for SessionId {
30    fn as_ref(&self) -> &str {
31        self.as_str()
32    }
33}
34
35impl fmt::Display for SessionId {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.write_str(&self.0)
38    }
39}
40
41impl From<&str> for SessionId {
42    fn from(value: &str) -> Self {
43        Self::new(value)
44    }
45}
46
47impl From<String> for SessionId {
48    fn from(value: String) -> Self {
49        Self::new(value)
50    }
51}
52
53impl From<SessionId> for String {
54    fn from(value: SessionId) -> Self {
55        value.into_string()
56    }
57}
58
59/// Identity carried by routed requests.
60///
61/// The session id scopes per-session state while the project root id scopes
62/// project-shared state and is suitable for lease/scheduler map keys.
63#[derive(Clone, Debug, PartialEq, Eq, Hash)]
64pub struct RequestIdentity {
65    pub session_id: SessionId,
66    pub project_root: ProjectRootId,
67}
68
69impl RequestIdentity {
70    pub fn new(session_id: impl Into<SessionId>, project_root: ProjectRootId) -> Self {
71        Self {
72            session_id: session_id.into(),
73            project_root,
74        }
75    }
76
77    pub fn from_path(
78        session_id: impl Into<SessionId>,
79        project_root: impl AsRef<Path>,
80    ) -> Result<Self, IdentityError> {
81        Ok(Self::new(
82            session_id,
83            ProjectRootId::from_path(project_root)?,
84        ))
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::{collections::HashMap, fs};
91
92    use super::*;
93    use subc_test_support::TestTempDir as TestDir;
94
95    #[test]
96    fn request_identity_is_hashable_as_hash_map_key() {
97        let temp = TestDir::new("hashmap");
98        let root = temp.path().join("project");
99        fs::create_dir(&root).expect("create project root");
100
101        let identity = RequestIdentity::from_path("ses_a", &root).expect("build request identity");
102        let same_identity = RequestIdentity::from_path(String::from("ses_a"), root.join("."))
103            .expect("build equivalent request identity");
104        let other_session = RequestIdentity::from_path("ses_b", &root)
105            .expect("build different-session request identity");
106
107        let mut entries = HashMap::new();
108        entries.insert(identity.clone(), "session A project state");
109
110        assert_eq!(
111            entries.get(&same_identity),
112            Some(&"session A project state")
113        );
114        assert_eq!(entries.get(&other_session), None);
115    }
116}