Skip to main content

sara_core/model/
metadata.rs

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