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#[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 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#[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#[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 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#[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#[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 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#[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;