Skip to main content

relay_knowledge/domain/operations/software/
graph.rs

1use serde::{Deserialize, Serialize};
2
3use super::super::{DomainError, GraphVersion, RepositoryCodeRange, error::required_text};
4use super::validation::{normalize_optional, stable_software_id, validate_confidence};
5
6/// Projected repository file node for the software global model.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct SoftwareFile {
9    pub software_file_id: String,
10    pub repository_id: String,
11    pub source_scope: String,
12    pub path: String,
13    pub language_id: String,
14    pub file_role: String,
15    pub parse_status: String,
16    pub created_graph_version: GraphVersion,
17}
18
19impl SoftwareFile {
20    /// Creates a stable file node identity for a repository snapshot.
21    pub fn new(input: SoftwareFileInput) -> Result<Self, DomainError> {
22        let source_scope = required_text("source_scope", input.source_scope)?;
23        let path = required_text("software_file_path", input.path)?;
24
25        Ok(Self {
26            software_file_id: stable_software_id("file", [source_scope.as_str(), path.as_str()]),
27            repository_id: required_text("repository_id", input.repository_id)?,
28            source_scope,
29            path,
30            language_id: required_text("language_id", input.language_id)?,
31            file_role: required_text("file_role", input.file_role)?,
32            parse_status: required_text("parse_status", input.parse_status)?,
33            created_graph_version: input.created_graph_version,
34        })
35    }
36}
37
38/// Constructor input for `SoftwareFile`.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct SoftwareFileInput {
41    pub repository_id: String,
42    pub source_scope: String,
43    pub path: String,
44    pub language_id: String,
45    pub file_role: String,
46    pub parse_status: String,
47    pub created_graph_version: GraphVersion,
48}
49
50/// Topic extracted from repository documentation or the repository knowledge map.
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub struct SoftwareTopic {
53    pub topic_id: String,
54    pub repository_id: String,
55    pub source_scope: String,
56    pub name: String,
57    pub topic_kind: String,
58    pub source_path: String,
59    pub line_range: RepositoryCodeRange,
60    pub created_graph_version: GraphVersion,
61}
62
63impl SoftwareTopic {
64    /// Creates a stable topic identity tied to the source evidence location.
65    pub fn new(input: SoftwareTopicInput) -> Result<Self, DomainError> {
66        let source_scope = required_text("source_scope", input.source_scope)?;
67        let name = required_text("topic_name", input.name)?;
68        let topic_kind = required_text("topic_kind", input.topic_kind)?;
69        let source_path = required_text("topic_source_path", input.source_path)?;
70        let line_start = input.line_range.start.to_string();
71
72        Ok(Self {
73            topic_id: stable_software_id(
74                "topic",
75                [
76                    source_scope.as_str(),
77                    topic_kind.as_str(),
78                    source_path.as_str(),
79                    name.as_str(),
80                    line_start.as_str(),
81                ],
82            ),
83            repository_id: required_text("repository_id", input.repository_id)?,
84            source_scope,
85            name,
86            topic_kind,
87            source_path,
88            line_range: input.line_range,
89            created_graph_version: input.created_graph_version,
90        })
91    }
92}
93
94/// Constructor input for `SoftwareTopic`.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct SoftwareTopicInput {
97    pub repository_id: String,
98    pub source_scope: String,
99    pub name: String,
100    pub topic_kind: String,
101    pub source_path: String,
102    pub line_range: RepositoryCodeRange,
103    pub created_graph_version: GraphVersion,
104}
105
106/// Cross-domain relationship between projected software files, topics, components, and usages.
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108pub struct SoftwareRelationship {
109    pub relationship_id: String,
110    pub repository_id: String,
111    pub source_scope: String,
112    pub relationship_kind: String,
113    pub source_id: String,
114    pub source_kind: String,
115    pub target_id: String,
116    pub target_kind: String,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub target_hint: Option<String>,
119    pub resolution_state: String,
120    pub confidence_basis_points: u16,
121    pub confidence_tier: String,
122    pub evidence_path: String,
123    pub evidence_line_range: RepositoryCodeRange,
124    pub created_graph_version: GraphVersion,
125}
126
127impl SoftwareRelationship {
128    /// Creates a validated projected relationship without upgrading unresolved targets.
129    pub fn new(input: SoftwareRelationshipInput) -> Result<Self, DomainError> {
130        let source_scope = required_text("source_scope", input.source_scope)?;
131        let relationship_kind = required_text("relationship_kind", input.relationship_kind)?;
132        let source_id = required_text("relationship_source_id", input.source_id)?;
133        let target_id = required_text("relationship_target_id", input.target_id)?;
134        let evidence_path = required_text("evidence_path", input.evidence_path)?;
135        let line_start = input.evidence_line_range.start.to_string();
136
137        Ok(Self {
138            relationship_id: stable_software_id(
139                "relationship",
140                [
141                    source_scope.as_str(),
142                    relationship_kind.as_str(),
143                    source_id.as_str(),
144                    target_id.as_str(),
145                    evidence_path.as_str(),
146                    line_start.as_str(),
147                ],
148            ),
149            repository_id: required_text("repository_id", input.repository_id)?,
150            source_scope,
151            relationship_kind,
152            source_id,
153            source_kind: required_text("relationship_source_kind", input.source_kind)?,
154            target_id,
155            target_kind: required_text("relationship_target_kind", input.target_kind)?,
156            target_hint: normalize_optional("target_hint", input.target_hint)?,
157            resolution_state: required_text("resolution_state", input.resolution_state)?,
158            confidence_basis_points: validate_confidence(input.confidence_basis_points)?,
159            confidence_tier: required_text("confidence_tier", input.confidence_tier)?,
160            evidence_path,
161            evidence_line_range: input.evidence_line_range,
162            created_graph_version: input.created_graph_version,
163        })
164    }
165}
166
167/// Constructor input for `SoftwareRelationship`.
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct SoftwareRelationshipInput {
170    pub repository_id: String,
171    pub source_scope: String,
172    pub relationship_kind: String,
173    pub source_id: String,
174    pub source_kind: String,
175    pub target_id: String,
176    pub target_kind: String,
177    pub target_hint: Option<String>,
178    pub resolution_state: String,
179    pub confidence_basis_points: u16,
180    pub confidence_tier: String,
181    pub evidence_path: String,
182    pub evidence_line_range: RepositoryCodeRange,
183    pub created_graph_version: GraphVersion,
184}
185
186#[cfg(test)]
187#[path = "graph_tests.rs"]
188mod tests;