Skip to main content

agtrace_types/domain/
project.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::path::{Path, PathBuf};
4
5/// Project identifier computed from canonical project root path via SHA256
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(transparent)]
8pub struct ProjectHash(String);
9
10impl ProjectHash {
11    /// Create a new ProjectHash from a string (typically hex digest)
12    pub fn new(hash: impl Into<String>) -> Self {
13        Self(hash.into())
14    }
15
16    /// Get the hash as a string slice
17    pub fn as_str(&self) -> &str {
18        &self.0
19    }
20}
21
22impl fmt::Display for ProjectHash {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28impl From<String> for ProjectHash {
29    fn from(s: String) -> Self {
30        Self(s)
31    }
32}
33
34impl From<&str> for ProjectHash {
35    fn from(s: &str) -> Self {
36        Self(s.to_string())
37    }
38}
39
40impl AsRef<str> for ProjectHash {
41    fn as_ref(&self) -> &str {
42        &self.0
43    }
44}
45
46/// Canonical project root path
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(transparent)]
49pub struct ProjectRoot(PathBuf);
50
51impl ProjectRoot {
52    /// Create a new ProjectRoot from a path
53    pub fn new(path: impl Into<PathBuf>) -> Self {
54        Self(path.into())
55    }
56
57    /// Get the root path as a Path reference
58    pub fn as_path(&self) -> &Path {
59        &self.0
60    }
61
62    /// Get the path as a string (lossy UTF-8 conversion)
63    pub fn to_string_lossy(&self) -> String {
64        self.0.to_string_lossy().to_string()
65    }
66}
67
68impl fmt::Display for ProjectRoot {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "{}", self.0.display())
71    }
72}
73
74impl From<PathBuf> for ProjectRoot {
75    fn from(path: PathBuf) -> Self {
76        Self(path)
77    }
78}
79
80impl From<&Path> for ProjectRoot {
81    fn from(path: &Path) -> Self {
82        Self(path.to_path_buf())
83    }
84}
85
86impl AsRef<Path> for ProjectRoot {
87    fn as_ref(&self) -> &Path {
88        &self.0
89    }
90}
91
92/// Repository identifier computed from git common directory path via SHA256
93///
94/// Used for git worktree support: multiple worktrees of the same repository
95/// share the same RepositoryHash while having different ProjectHash values.
96/// None for non-git directories.
97#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
98#[serde(transparent)]
99pub struct RepositoryHash(String);
100
101impl RepositoryHash {
102    pub fn new(hash: impl Into<String>) -> Self {
103        Self(hash.into())
104    }
105
106    pub fn as_str(&self) -> &str {
107        &self.0
108    }
109}
110
111impl fmt::Display for RepositoryHash {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{}", self.0)
114    }
115}
116
117impl From<String> for RepositoryHash {
118    fn from(s: String) -> Self {
119        Self(s)
120    }
121}
122
123impl From<&str> for RepositoryHash {
124    fn from(s: &str) -> Self {
125        Self(s.to_string())
126    }
127}
128
129impl AsRef<str> for RepositoryHash {
130    fn as_ref(&self) -> &str {
131        &self.0
132    }
133}
134
135/// Project scope for indexing and filtering sessions
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub enum ProjectScope {
138    /// Scan all projects without filtering
139    All,
140    /// Scan specific project by hash
141    Specific(ProjectHash),
142}
143
144impl ProjectScope {
145    /// Get optional project hash for filtering
146    pub fn hash(&self) -> Option<&ProjectHash> {
147        match self {
148            ProjectScope::All => None,
149            ProjectScope::Specific(hash) => Some(hash),
150        }
151    }
152}