sara_core/model/
metadata.rs

1//! Metadata structures for items and source tracking.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Tracks the file origin of an item for error reporting.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SourceLocation {
9    /// Repository path (absolute).
10    pub repository: PathBuf,
11
12    /// Relative path within repository.
13    pub file_path: PathBuf,
14
15    /// Optional Git commit/branch if reading from history.
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub git_ref: Option<String>,
18}
19
20impl SourceLocation {
21    /// Creates a new SourceLocation.
22    pub fn new(repository: impl Into<PathBuf>, file_path: impl Into<PathBuf>) -> Self {
23        Self {
24            repository: repository.into(),
25            file_path: file_path.into(),
26            git_ref: None,
27        }
28    }
29
30    /// Creates a new SourceLocation with a Git reference.
31    pub fn with_git_ref(
32        repository: impl Into<PathBuf>,
33        file_path: impl Into<PathBuf>,
34        git_ref: impl Into<String>,
35    ) -> Self {
36        Self {
37            repository: repository.into(),
38            file_path: file_path.into(),
39            git_ref: Some(git_ref.into()),
40        }
41    }
42
43    /// Returns the full path to the file.
44    pub fn full_path(&self) -> PathBuf {
45        self.repository.join(&self.file_path)
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_source_location_full_path() {
55        let loc = SourceLocation::new("/repo", "docs/SOL-001.md");
56        assert_eq!(loc.full_path(), PathBuf::from("/repo/docs/SOL-001.md"));
57    }
58
59    #[test]
60    fn test_source_location_with_git_ref() {
61        let loc = SourceLocation::with_git_ref("/repo", "docs/SOL-001.md", "main");
62        assert_eq!(loc.git_ref, Some("main".to_string()));
63    }
64}