Skip to content

Commit 84882ee

Browse files
committed
fix: rework name and version for virtual workspaces root node
1 parent c9a8615 commit 84882ee

2 files changed

Lines changed: 83 additions & 17 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public final class CargoProvider extends Provider {
6060
private static final ObjectMapper MAPPER =
6161
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
6262
private static final Logger log = LoggersFactory.getLogger(CargoProvider.class.getName());
63+
private static final String VIRTUAL_VERSION = "1.0.0";
6364
private static final String PACKAGE_NAME = "package.name";
6465
private static final String PACKAGE_VERSION = "package.version";
6566
private static final String PACKAGE_VERSION_WORKSPACE = "package.version.workspace";
@@ -69,14 +70,18 @@ public final class CargoProvider extends Provider {
6970
private final String cargoExecutable;
7071

7172
private void addDependencies(
72-
Sbom sbom, PackageURL root, Set<String> ignoredDeps, AnalysisType analysisType) {
73+
Sbom sbom,
74+
PackageURL root,
75+
Set<String> ignoredDeps,
76+
AnalysisType analysisType,
77+
ProjectInfo projectInfo) {
7378
try {
7479
CargoMetadata metadata = executeCargoMetadata();
7580
if (metadata != null && metadata.resolve() != null && metadata.resolve().nodes() != null) {
7681
// Build maps and find root once, reuse for better performance
7782
Map<String, CargoPackage> packageMap = buildPackageMap(metadata);
7883
Map<String, CargoNode> nodeMap = buildNodeMap(metadata);
79-
CargoNode rootNode = findRootNodeForAnalysis(metadata, nodeMap);
84+
CargoNode rootNode = findRootNodeForAnalysis(metadata, nodeMap, projectInfo);
8085

8186
if (rootNode == null) {
8287
return;
@@ -228,21 +233,21 @@ private Map<String, CargoNode> buildNodeMap(CargoMetadata metadata) {
228233
}
229234

230235
private CargoNode findRootNodeForAnalysis(
231-
CargoMetadata metadata, Map<String, CargoNode> nodeMap) {
236+
CargoMetadata metadata, Map<String, CargoNode> nodeMap, ProjectInfo projectInfo) {
232237
/* The package in the current working directory (if --manifest-path is not given).
233238
This is null if there is a virtual workspace. Otherwise, it is
234239
the Package ID of the package.
235240
*/
236241
String rootId = metadata.resolve().root();
237242
// Handle workspace-only projects (no root package)
238243
if (rootId == null) {
239-
return createRootNodeFromVirtualWorkspace(metadata, nodeMap);
244+
return createRootNodeFromVirtualWorkspace(metadata, nodeMap, projectInfo);
240245
}
241246
return nodeMap.get(rootId);
242247
}
243248

244249
private CargoNode createRootNodeFromVirtualWorkspace(
245-
CargoMetadata metadata, Map<String, CargoNode> nodeMap) {
250+
CargoMetadata metadata, Map<String, CargoNode> nodeMap, ProjectInfo projectInfo) {
246251
if (metadata.workspaceMembers() == null || metadata.workspaceMembers().isEmpty()) {
247252
log.warning("No workspace members found for workspace-only project");
248253
return null;
@@ -275,8 +280,8 @@ private CargoNode createRootNodeFromVirtualWorkspace(
275280
}
276281

277282
// Create a virtual root node with combined dependencies
278-
// Use the workspace name/version for the virtual root
279-
String virtualRootId = "virtual-workspace-root";
283+
// Use the actual workspace name/version from ProjectInfo
284+
String virtualRootId = String.format("%s#%s", projectInfo.name(), projectInfo.version());
280285
return new CargoNode(virtualRootId, null, new ArrayList<>(depMap.values()));
281286
}
282287

@@ -483,9 +488,9 @@ private Sbom createSbom(boolean includeTransitiveDependencies) throws IOExceptio
483488
Set<String> ignoredDeps = getIgnoredDependencies(tomlResult, cargoContent);
484489

485490
if (includeTransitiveDependencies) {
486-
addDependencies(sbom, root, ignoredDeps, AnalysisType.STACK);
491+
addDependencies(sbom, root, ignoredDeps, AnalysisType.STACK, projectInfo);
487492
} else {
488-
addDependencies(sbom, root, ignoredDeps, AnalysisType.COMPONENT);
493+
addDependencies(sbom, root, ignoredDeps, AnalysisType.COMPONENT, projectInfo);
489494
}
490495
return sbom;
491496
} catch (Exception e) {
@@ -513,9 +518,10 @@ private ProjectInfo parseCargoToml(TomlParseResult result) throws IOException {
513518
"Parsed project info: name="
514519
+ packageName
515520
+ ", version="
516-
+ (packageVersion != null ? packageVersion : "0.0.0"));
521+
+ (packageVersion != null ? packageVersion : VIRTUAL_VERSION));
517522
}
518-
return new ProjectInfo(packageName, packageVersion != null ? packageVersion : "0.0.0");
523+
return new ProjectInfo(
524+
packageName, packageVersion != null ? packageVersion : VIRTUAL_VERSION);
519525
}
520526
// Check for workspace section as fallback (when there's no [package] section)
521527
boolean hasWorkspace = result.contains("workspace");
@@ -527,9 +533,10 @@ private ProjectInfo parseCargoToml(TomlParseResult result) throws IOException {
527533
"Using workspace fallback: name="
528534
+ dirName
529535
+ ", version="
530-
+ (workspaceVersion != null ? workspaceVersion : "0.0.0"));
536+
+ (workspaceVersion != null ? workspaceVersion : VIRTUAL_VERSION));
531537
}
532-
return new ProjectInfo(dirName, workspaceVersion != null ? workspaceVersion : "0.0.0");
538+
return new ProjectInfo(
539+
dirName, workspaceVersion != null ? workspaceVersion : VIRTUAL_VERSION);
533540
}
534541
throw new IOException("Invalid Cargo.toml: no [package] or [workspace] section found");
535542
}

src/test/java/io/github/guacsec/trustifyda/providers/CargoProviderCargoParsingTest.java

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@ public void testPackageCargoTomlWithMissingVersion(@TempDir Path tempDir) throws
160160
var stackContent = provider.provideStack();
161161
String stackSbom = new String(stackContent.buffer);
162162

163-
// Should use default version "0.0.0"
163+
// Should use default version "1.0.0"
164164
assertTrue(stackSbom.contains("no-version-project"));
165-
assertTrue(stackSbom.contains("0.0.0"));
165+
assertTrue(stackSbom.contains("1.0.0"));
166166
}
167167

168168
@Test
@@ -188,7 +188,7 @@ public void testWorkspaceCargoTomlWithoutVersion(@TempDir Path tempDir) throws I
188188

189189
// Should use directory name and default version
190190
assertTrue(stackSbom.contains(tempDir.getFileName().toString()));
191-
assertTrue(stackSbom.contains("0.0.0"));
191+
assertTrue(stackSbom.contains("1.0.0"));
192192
}
193193

194194
@Test
@@ -351,7 +351,7 @@ public void testPackageWithWorkspaceCargoToml(@TempDir Path tempDir) throws IOEx
351351

352352
// Should NOT contain default version (which would indicate workspace parsing)
353353
assertFalse(
354-
componentContent.contains("0.0.0"),
354+
componentContent.contains("1.0.0"),
355355
"Should not contain default version from workspace parsing");
356356
}
357357

@@ -661,4 +661,63 @@ public void testDependencyKindsFilteringLogic() {
661661

662662
assertTrue(true, "Logic documentation test - see console output for details");
663663
}
664+
665+
@Test
666+
public void testVirtualRootUsesWorkspaceNameAndVersion(@TempDir Path tempDir) throws IOException {
667+
// Create a workspace Cargo.toml with specific name (directory name) and version
668+
Path cargoToml = tempDir.resolve("Cargo.toml");
669+
String content =
670+
"""
671+
[workspace]
672+
members = ["member1", "member2"]
673+
674+
[workspace.package]
675+
version = "2.5.0"
676+
edition = "2021"
677+
""";
678+
Files.writeString(cargoToml, content);
679+
680+
CargoProvider provider = new CargoProvider(cargoToml);
681+
682+
// Test that virtual root doesn't use hardcoded name anymore
683+
var stackContent = provider.provideStack();
684+
String stackSbom = new String(stackContent.buffer);
685+
686+
// Verify workspace name comes from directory name
687+
String expectedWorkspaceName = tempDir.getFileName().toString();
688+
assertTrue(
689+
stackSbom.contains(expectedWorkspaceName),
690+
"SBOM should contain workspace directory name: " + expectedWorkspaceName);
691+
692+
// Verify workspace version comes from workspace.package.version
693+
assertTrue(stackSbom.contains("2.5.0"), "SBOM should contain workspace version: 2.5.0");
694+
}
695+
696+
@Test
697+
public void testVirtualRootWithoutVersionUsesDefault(@TempDir Path tempDir) throws IOException {
698+
// Create a workspace Cargo.toml without version
699+
Path cargoToml = tempDir.resolve("Cargo.toml");
700+
String content =
701+
"""
702+
[workspace]
703+
members = ["api", "core"]
704+
705+
[workspace.package]
706+
edition = "2021"
707+
""";
708+
Files.writeString(cargoToml, content);
709+
710+
CargoProvider provider = new CargoProvider(cargoToml);
711+
712+
var stackContent = provider.provideStack();
713+
String stackSbom = new String(stackContent.buffer);
714+
715+
// Should use directory name and default version
716+
String expectedWorkspaceName = tempDir.getFileName().toString();
717+
assertTrue(
718+
stackSbom.contains(expectedWorkspaceName),
719+
"SBOM should contain workspace directory name: " + expectedWorkspaceName);
720+
721+
assertTrue(stackSbom.contains("1.0.0"), "SBOM should contain default version: 1.0.0");
722+
}
664723
}

0 commit comments

Comments
 (0)