Skip to main content

relay_knowledge/domain/operations/software/
dependencies.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 dependency component from repository manifests and lockfiles.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct SoftwareComponent {
9    pub component_id: String,
10    pub repository_id: String,
11    pub source_scope: String,
12    pub ecosystem: String,
13    pub language_id: String,
14    pub name: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub requirement: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub resolved_version: Option<String>,
19    pub dependency_group: String,
20    pub source_kind: String,
21    pub relationship_state: String,
22    pub evidence_path: String,
23    pub evidence_line_range: RepositoryCodeRange,
24    pub confidence_basis_points: u16,
25    pub created_graph_version: GraphVersion,
26}
27
28impl SoftwareComponent {
29    /// Creates a validated component identity derived from dependency evidence.
30    pub fn new(input: SoftwareComponentInput) -> Result<Self, DomainError> {
31        let requirement = normalize_optional("requirement", input.requirement)?;
32        let resolved_version = normalize_optional("resolved_version", input.resolved_version)?;
33        let source_scope = required_text("source_scope", input.source_scope)?;
34        let ecosystem = required_text("ecosystem", input.ecosystem)?;
35        let name = required_text("component_name", input.name)?;
36        let dependency_group = required_text("dependency_group", input.dependency_group)?;
37        let source_kind = required_text("source_kind", input.source_kind)?;
38        let language_id = required_text("language_id", input.language_id)?;
39        let evidence_path = required_text("evidence_path", input.evidence_path)?;
40        let line_start = input.evidence_line_range.start.to_string();
41        let identity_version = resolved_version
42            .as_deref()
43            .or(requirement.as_deref())
44            .unwrap_or("unversioned");
45
46        Ok(Self {
47            component_id: stable_software_id(
48                "component",
49                [
50                    source_scope.as_str(),
51                    ecosystem.as_str(),
52                    name.as_str(),
53                    identity_version,
54                    dependency_group.as_str(),
55                    source_kind.as_str(),
56                    language_id.as_str(),
57                    evidence_path.as_str(),
58                    line_start.as_str(),
59                ],
60            ),
61            repository_id: required_text("repository_id", input.repository_id)?,
62            source_scope,
63            ecosystem,
64            name,
65            requirement,
66            resolved_version,
67            dependency_group,
68            source_kind,
69            relationship_state: required_text("relationship_state", input.relationship_state)?,
70            language_id,
71            evidence_path,
72            evidence_line_range: input.evidence_line_range,
73            confidence_basis_points: validate_confidence(input.confidence_basis_points)?,
74            created_graph_version: input.created_graph_version,
75        })
76    }
77}
78
79/// Constructor input for `SoftwareComponent`.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct SoftwareComponentInput {
82    pub repository_id: String,
83    pub source_scope: String,
84    pub ecosystem: String,
85    pub language_id: String,
86    pub name: String,
87    pub requirement: Option<String>,
88    pub resolved_version: Option<String>,
89    pub dependency_group: String,
90    pub source_kind: String,
91    pub relationship_state: String,
92    pub evidence_path: String,
93    pub evidence_line_range: RepositoryCodeRange,
94    pub confidence_basis_points: u16,
95    pub created_graph_version: GraphVersion,
96}
97
98/// Projected SDK or external API usage from unresolved import/include evidence.
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct SoftwareSdkUsage {
101    pub usage_id: String,
102    pub repository_id: String,
103    pub source_scope: String,
104    pub language_id: String,
105    pub module: String,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub target_hint: Option<String>,
108    pub resolution_state: String,
109    pub evidence_path: String,
110    pub evidence_line_range: RepositoryCodeRange,
111    pub confidence_basis_points: u16,
112    pub created_graph_version: GraphVersion,
113}
114
115impl SoftwareSdkUsage {
116    /// Creates a validated unresolved SDK/API usage candidate.
117    pub fn new(input: SoftwareSdkUsageInput) -> Result<Self, DomainError> {
118        let source_scope = required_text("source_scope", input.source_scope)?;
119        let language_id = required_text("language_id", input.language_id)?;
120        let module = required_text("module", input.module)?;
121        let target_hint = normalize_optional("target_hint", input.target_hint)?;
122        let resolution_state = required_text("resolution_state", input.resolution_state)?;
123        let evidence_path = required_text("evidence_path", input.evidence_path)?;
124        let line_start = input.evidence_line_range.start.to_string();
125
126        Ok(Self {
127            usage_id: stable_software_id(
128                "sdk_usage",
129                [
130                    source_scope.as_str(),
131                    language_id.as_str(),
132                    evidence_path.as_str(),
133                    module.as_str(),
134                    resolution_state.as_str(),
135                    line_start.as_str(),
136                ],
137            ),
138            repository_id: required_text("repository_id", input.repository_id)?,
139            source_scope,
140            language_id,
141            module,
142            target_hint,
143            resolution_state,
144            evidence_path,
145            evidence_line_range: input.evidence_line_range,
146            confidence_basis_points: validate_confidence(input.confidence_basis_points)?,
147            created_graph_version: input.created_graph_version,
148        })
149    }
150}
151
152/// Constructor input for `SoftwareSdkUsage`.
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct SoftwareSdkUsageInput {
155    pub repository_id: String,
156    pub source_scope: String,
157    pub language_id: String,
158    pub module: String,
159    pub target_hint: Option<String>,
160    pub resolution_state: String,
161    pub evidence_path: String,
162    pub evidence_line_range: RepositoryCodeRange,
163    pub confidence_basis_points: u16,
164    pub created_graph_version: GraphVersion,
165}
166
167/// Import/include evidence that uses a declared dependency component.
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169pub struct SoftwareDependencyUsage {
170    pub usage_id: String,
171    pub component_id: String,
172    pub repository_id: String,
173    pub source_scope: String,
174    pub ecosystem: String,
175    pub package_name: String,
176    pub language_id: String,
177    pub module: String,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub target_hint: Option<String>,
180    pub resolution_state: String,
181    pub evidence_path: String,
182    pub evidence_line_range: RepositoryCodeRange,
183    pub confidence_basis_points: u16,
184    pub created_graph_version: GraphVersion,
185}
186
187impl SoftwareDependencyUsage {
188    /// Creates a validated relationship between dependency metadata and import evidence.
189    pub fn new(input: SoftwareDependencyUsageInput) -> Result<Self, DomainError> {
190        let component_id = required_text("component_id", input.component_id)?;
191        let source_scope = required_text("source_scope", input.source_scope)?;
192        let ecosystem = required_text("ecosystem", input.ecosystem)?;
193        let package_name = required_text("package_name", input.package_name)?;
194        let language_id = required_text("language_id", input.language_id)?;
195        let module = required_text("module", input.module)?;
196        let target_hint = normalize_optional("target_hint", input.target_hint)?;
197        let resolution_state = required_text("resolution_state", input.resolution_state)?;
198        let evidence_path = required_text("evidence_path", input.evidence_path)?;
199        let line_start = input.evidence_line_range.start.to_string();
200
201        Ok(Self {
202            usage_id: stable_software_id(
203                "dependency_usage",
204                [
205                    source_scope.as_str(),
206                    component_id.as_str(),
207                    language_id.as_str(),
208                    evidence_path.as_str(),
209                    module.as_str(),
210                    line_start.as_str(),
211                ],
212            ),
213            component_id,
214            repository_id: required_text("repository_id", input.repository_id)?,
215            source_scope,
216            ecosystem,
217            package_name,
218            language_id,
219            module,
220            target_hint,
221            resolution_state,
222            evidence_path,
223            evidence_line_range: input.evidence_line_range,
224            confidence_basis_points: validate_confidence(input.confidence_basis_points)?,
225            created_graph_version: input.created_graph_version,
226        })
227    }
228}
229
230/// Constructor input for `SoftwareDependencyUsage`.
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct SoftwareDependencyUsageInput {
233    pub component_id: String,
234    pub repository_id: String,
235    pub source_scope: String,
236    pub ecosystem: String,
237    pub package_name: String,
238    pub language_id: String,
239    pub module: String,
240    pub target_hint: Option<String>,
241    pub resolution_state: String,
242    pub evidence_path: String,
243    pub evidence_line_range: RepositoryCodeRange,
244    pub confidence_basis_points: u16,
245    pub created_graph_version: GraphVersion,
246}
247
248#[cfg(test)]
249#[path = "dependencies_tests.rs"]
250mod tests;