Skip to content

Commit 759ffc4

Browse files
committed
fix: update after review
1 parent ce40cee commit 759ffc4

13 files changed

Lines changed: 294 additions & 170 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/providers/RustProvider.java renamed to src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java

Lines changed: 19 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818

1919
import static io.github.guacsec.trustifyda.impl.ExhortApi.debugLoggingIsNeeded;
2020

21-
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
22-
import com.fasterxml.jackson.annotation.JsonProperty;
2321
import com.fasterxml.jackson.databind.ObjectMapper;
2422
import com.github.packageurl.PackageURL;
2523
import io.github.guacsec.trustifyda.Api;
2624
import io.github.guacsec.trustifyda.Provider;
2725
import io.github.guacsec.trustifyda.logging.LoggersFactory;
26+
import io.github.guacsec.trustifyda.providers.rust.model.CargoDep;
27+
import io.github.guacsec.trustifyda.providers.rust.model.CargoDepKind;
28+
import io.github.guacsec.trustifyda.providers.rust.model.CargoMetadata;
29+
import io.github.guacsec.trustifyda.providers.rust.model.CargoNode;
30+
import io.github.guacsec.trustifyda.providers.rust.model.DependencyInfo;
31+
import io.github.guacsec.trustifyda.providers.rust.model.ProjectInfo;
2832
import io.github.guacsec.trustifyda.sbom.Sbom;
2933
import io.github.guacsec.trustifyda.sbom.SbomFactory;
3034
import io.github.guacsec.trustifyda.tools.Ecosystem.Type;
@@ -38,7 +42,6 @@
3842
import java.util.HashMap;
3943
import java.util.HashSet;
4044
import java.util.LinkedHashMap;
41-
import java.util.List;
4245
import java.util.Map;
4346
import java.util.Set;
4447
import java.util.concurrent.TimeUnit;
@@ -50,10 +53,10 @@
5053
* Concrete implementation of the {@link Provider} used for converting dependency trees for Rust
5154
* projects (Cargo.toml) into a SBOM content for Component analysis or Stack analysis.
5255
*/
53-
public final class RustProvider extends Provider {
56+
public final class CargoProvider extends Provider {
5457

5558
private static final ObjectMapper MAPPER = new ObjectMapper();
56-
private static final Logger log = LoggersFactory.getLogger(RustProvider.class.getName());
59+
private static final Logger log = LoggersFactory.getLogger(CargoProvider.class.getName());
5760
private static final String PACKAGE_NAME = "package.name";
5861
private static final String PACKAGE_VERSION = "package.version";
5962
private static final String PACKAGE_VERSION_WORKSPACE = "package.version.workspace";
@@ -62,79 +65,6 @@ public final class RustProvider extends Provider {
6265
Long.parseLong(System.getProperty("trustify.cargo.timeout.seconds", "5"));
6366
private final String cargoExecutable;
6467

65-
private record ProjectInfo(String name, String version) {
66-
private ProjectInfo(String name, String version) {
67-
this.name = name != null ? name : "unknown-rust-project";
68-
this.version = version != null ? version : "0.0.0";
69-
}
70-
}
71-
72-
private record DependencyInfo(String name, String version) {}
73-
74-
private enum AnalysisType {
75-
STACK,
76-
COMPONENT
77-
}
78-
79-
// cargo-metadata output format https://doc.rust-lang.org/cargo/commands/cargo-metadata.html
80-
// JSON model classes for cargo metadata parsing
81-
82-
/** Root cargo metadata structure - minimal for dependency analysis */
83-
@JsonIgnoreProperties(ignoreUnknown = true)
84-
private record CargoMetadata(
85-
@JsonProperty("packages") List<CargoPackage> packages,
86-
@JsonProperty("resolve") CargoResolve resolve,
87-
@JsonProperty("workspace_members") List<String> workspaceMembers,
88-
@JsonProperty("workspace_root") String workspaceRoot) {}
89-
90-
/** Package information - only dependency analysis fields */
91-
@JsonIgnoreProperties(ignoreUnknown = true)
92-
private record CargoPackage(
93-
@JsonProperty("name") String name,
94-
@JsonProperty("version") String version,
95-
@JsonProperty("id") String id,
96-
@JsonProperty("dependencies") List<CargoDependency> dependencies) {}
97-
98-
/** Dependency declaration - core fields for dependency analysis */
99-
@JsonIgnoreProperties(ignoreUnknown = true)
100-
private record CargoDependency(
101-
@JsonProperty("name") String name,
102-
@JsonProperty("req") String req,
103-
@JsonProperty("kind") String kind,
104-
@JsonProperty("optional") Boolean optional) {}
105-
106-
/** Dependency resolution graph (contains actual resolved versions) */
107-
@JsonIgnoreProperties(ignoreUnknown = true)
108-
private record CargoResolve(
109-
@JsonProperty("nodes") List<CargoNode> nodes, @JsonProperty("root") String root) {}
110-
111-
/** Resolved dependency node - essential fields for dependency resolution */
112-
@JsonIgnoreProperties(ignoreUnknown = true)
113-
private record CargoNode(
114-
@JsonProperty("id") String id,
115-
@JsonProperty("dependencies") List<String> dependencies,
116-
@JsonProperty("deps") List<CargoDep> deps) {}
117-
118-
/** Detailed dependency information with resolved package reference */
119-
@JsonIgnoreProperties(ignoreUnknown = true)
120-
private record CargoDep(
121-
@JsonProperty("name") String name,
122-
@JsonProperty("pkg") String pkg,
123-
@JsonProperty("dep_kinds") List<CargoDepKind> depKinds) {}
124-
125-
/** Dependency kind information (normal, dev, build) */
126-
@JsonIgnoreProperties(ignoreUnknown = true)
127-
private record CargoDepKind(
128-
@JsonProperty("kind") String kind, @JsonProperty("target") String target) {}
129-
130-
private void addStackDependencies(Sbom sbom, PackageURL root, Set<String> ignoredDeps) {
131-
addDependencies(sbom, root, ignoredDeps, AnalysisType.STACK);
132-
}
133-
134-
private void addComponentDependencies(Sbom sbom, PackageURL root, Set<String> ignoredDeps) {
135-
addDependencies(sbom, root, ignoredDeps, AnalysisType.COMPONENT);
136-
}
137-
13868
private void addDependencies(
13969
Sbom sbom, PackageURL root, Set<String> ignoredDeps, AnalysisType analysisType) {
14070
try {
@@ -385,7 +315,7 @@ private void addResolvedDependencyToSbom(DependencyInfo childInfo, Sbom sbom, Pa
385315
// Use EXACT resolved version from resolve graph
386316
PackageURL packageUrl =
387317
new PackageURL(
388-
Type.RUST.getType(), null, childInfo.name(), childInfo.version(), null, null);
318+
Type.CARGO.getType(), null, childInfo.name(), childInfo.version(), null, null);
389319
sbom.addDependency(root, packageUrl, null);
390320
if (debugLoggingIsNeeded()) {
391321
log.info(
@@ -459,7 +389,7 @@ private void processDependencyNode(
459389
try {
460390
PackageURL childUrl =
461391
new PackageURL(
462-
Type.RUST.getType(), null, childInfo.name(), childInfo.version(), null, null);
392+
Type.CARGO.getType(), null, childInfo.name(), childInfo.version(), null, null);
463393

464394
// Create unique key for deduplication using stable identifiers
465395
String relationshipKey = parent.getCoordinates() + "->" + childUrl.getCoordinates();
@@ -541,7 +471,7 @@ private DependencyInfo parseSimplePackageId(String packageId) {
541471
}
542472

543473
// Just a package name without version - validate it looks like a valid package name
544-
if (!packageId.isEmpty() && isValidPackageName(packageId)) {
474+
if (!packageId.isEmpty()) {
545475
if (debugLoggingIsNeeded()) {
546476
log.info("Parsed simple package ID (name only): " + packageId + " -> " + packageId);
547477
}
@@ -593,12 +523,6 @@ private DependencyInfo parseUrlPackageId(String packageId) {
593523
}
594524
}
595525

596-
// Fragment should be just a version - validate it's not malformed
597-
if (isMalformedPackageVersion(fragment)) {
598-
log.fine("Fragment appears to be malformed package-version string: " + fragment);
599-
return null;
600-
}
601-
602526
// Fragment should not start or end with separators (indicates malformed format)
603527
if (fragment.startsWith("@")
604528
|| fragment.startsWith(":")
@@ -623,45 +547,6 @@ private DependencyInfo parseUrlPackageId(String packageId) {
623547
return null;
624548
}
625549

626-
/** Validate if string looks like a valid Rust package name */
627-
private boolean isValidPackageName(String name) {
628-
if (name == null || name.isEmpty()) {
629-
return false;
630-
}
631-
// Must start with a letter
632-
if (!Character.isLetter(name.charAt(0))) {
633-
return false;
634-
}
635-
// Can only contain letters, numbers, hyphens, and underscores
636-
if (!name.matches("^[a-zA-Z][a-zA-Z0-9_-]*$")) {
637-
return false;
638-
}
639-
// Cannot end with hyphen
640-
if (name.endsWith("-")) {
641-
return false;
642-
}
643-
// Reject overly long or obviously invalid names
644-
if (name.length() > 64 || name.contains("this-is-not")) {
645-
return false;
646-
}
647-
return true;
648-
}
649-
650-
private boolean isMalformedPackageVersion(String fragment) {
651-
// Check for patterns like "package-1.0.0" or "package_1.0.0"
652-
// where it should be "package@1.0.0" or "package:1.0.0"
653-
654-
// Look for package-name followed by dash and version-like pattern
655-
if (fragment.matches("^[a-zA-Z][a-zA-Z0-9_-]*-\\d+\\..*")) {
656-
return true;
657-
}
658-
// Look for package-name followed by underscore and version-like pattern
659-
if (fragment.matches("^[a-zA-Z][a-zA-Z0-9_-]*_\\d+\\..*")) {
660-
return true;
661-
}
662-
return false;
663-
}
664-
665550
/** Extract package name from URL path */
666551
private String extractNameFromUrl(String url) {
667552
try {
@@ -700,8 +585,8 @@ private String extractNameFromUrl(String url) {
700585
}
701586
}
702587

703-
public RustProvider(Path manifest) {
704-
super(Type.RUST, manifest);
588+
public CargoProvider(Path manifest) {
589+
super(Type.CARGO, manifest);
705590
this.cargoExecutable = Operations.getExecutable("cargo", "--version");
706591

707592
if (cargoExecutable != null) {
@@ -714,17 +599,17 @@ public RustProvider(Path manifest) {
714599

715600
@Override
716601
public Content provideComponent() throws IOException {
717-
Sbom sbom = createRustSbom(false);
602+
Sbom sbom = createSbom(false);
718603
return new Content(sbom.getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE);
719604
}
720605

721606
@Override
722607
public Content provideStack() throws IOException {
723-
Sbom sbom = createRustSbom(true);
608+
Sbom sbom = createSbom(true);
724609
return new Content(sbom.getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE);
725610
}
726611

727-
private Sbom createRustSbom(boolean includeTransitiveDependencies) throws IOException {
612+
private Sbom createSbom(boolean includeTransitiveDependencies) throws IOException {
728613
if (!Files.exists(manifest) || !Files.isRegularFile(manifest)) {
729614
throw new IOException("Cargo.toml not found: " + manifest);
730615
}
@@ -741,16 +626,16 @@ private Sbom createRustSbom(boolean includeTransitiveDependencies) throws IOExce
741626
try {
742627
var root =
743628
new PackageURL(
744-
Type.RUST.getType(), null, projectInfo.name, projectInfo.version, null, null);
629+
Type.CARGO.getType(), null, projectInfo.name(), projectInfo.version(), null, null);
745630
sbom.addRoot(root);
746631

747632
String cargoContent = Files.readString(manifest, StandardCharsets.UTF_8);
748633
Set<String> ignoredDeps = getIgnoredDependencies(tomlResult, cargoContent);
749634

750635
if (includeTransitiveDependencies) {
751-
addStackDependencies(sbom, root, ignoredDeps);
636+
addDependencies(sbom, root, ignoredDeps, AnalysisType.STACK);
752637
} else {
753-
addComponentDependencies(sbom, root, ignoredDeps);
638+
addDependencies(sbom, root, ignoredDeps, AnalysisType.COMPONENT);
754639
}
755640
return sbom;
756641
} catch (Exception e) {
@@ -833,8 +718,6 @@ private Set<String> getIgnoredDependencies(TomlParseResult result, String conten
833718
private Set<String> collectAllDependencies(TomlParseResult result) {
834719
Set<String> allDeps = new HashSet<>();
835720
addDependenciesFromSection(result, "dependencies", allDeps);
836-
addDependenciesFromSection(result, "dev-dependencies", allDeps);
837-
addDependenciesFromSection(result, "build-dependencies", allDeps);
838721
addDependenciesFromSection(result, "workspace.dependencies", allDeps);
839722
addDependenciesFromSection(result, "workspace.build-dependencies", allDeps);
840723
return allDeps;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2023-2025 Trustify Dependency Analytics Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
*
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.guacsec.trustifyda.providers.rust.model;
18+
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import java.util.List;
22+
23+
/** Detailed dependency information with resolved package reference */
24+
@JsonIgnoreProperties(ignoreUnknown = true)
25+
public record CargoDep(
26+
@JsonProperty("name") String name,
27+
@JsonProperty("pkg") String pkg,
28+
@JsonProperty("dep_kinds") List<CargoDepKind> depKinds) {}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2023-2025 Trustify Dependency Analytics Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
*
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.guacsec.trustifyda.providers.rust.model;
18+
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
22+
/** Dependency kind information (normal, dev, build) */
23+
@JsonIgnoreProperties(ignoreUnknown = true)
24+
public record CargoDepKind(
25+
@JsonProperty("kind") String kind, @JsonProperty("target") String target) {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2023-2025 Trustify Dependency Analytics Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
*
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.guacsec.trustifyda.providers.rust.model;
18+
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
22+
/** Dependency declaration - core fields for dependency analysis */
23+
@JsonIgnoreProperties(ignoreUnknown = true)
24+
public record CargoDependency(
25+
@JsonProperty("name") String name,
26+
@JsonProperty("req") String req,
27+
@JsonProperty("kind") String kind,
28+
@JsonProperty("optional") Boolean optional) {}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2023-2025 Trustify Dependency Analytics Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
*
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.guacsec.trustifyda.providers.rust.model;
18+
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import java.util.List;
22+
23+
/** Root cargo metadata structure - minimal for dependency analysis */
24+
@JsonIgnoreProperties(ignoreUnknown = true)
25+
public record CargoMetadata(
26+
@JsonProperty("packages") List<CargoPackage> packages,
27+
@JsonProperty("resolve") CargoResolve resolve,
28+
@JsonProperty("workspace_members") List<String> workspaceMembers,
29+
@JsonProperty("workspace_root") String workspaceRoot) {}

0 commit comments

Comments
 (0)