agtrace_types/domain/
project.rs

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