auditable_cyclonedx/
lib.rs1#![forbid(unsafe_code)]
2
3use std::str::FromStr;
4
5pub use auditable_serde;
6use auditable_serde::{Package, Source};
7pub use cyclonedx_bom;
8
9use cyclonedx_bom::models::property::{Properties, Property};
10use cyclonedx_bom::prelude::*;
11use cyclonedx_bom::{
12 external_models::uri::Purl,
13 models::{
14 component::Classification,
15 component::Component,
16 dependency::{Dependencies, Dependency},
17 metadata::Metadata,
18 },
19};
20
21pub fn auditable_to_minimal_cdx(input: &auditable_serde::VersionInfo) -> Bom {
24 let mut bom = Bom {
25 serial_number: None, ..Default::default()
27 };
28
29 let (root_idx, root_pkg) = root_package(input);
32 let root_component = pkg_to_component(root_pkg, root_idx);
33 let metadata = Metadata {
34 component: Some(root_component),
35 ..Default::default()
36 };
37 bom.metadata = Some(metadata);
38
39 let components: Vec<Component> = input
41 .packages
42 .iter()
43 .enumerate()
44 .filter(|(_idx, pkg)| !pkg.root)
45 .map(|(idx, pkg)| pkg_to_component(pkg, idx))
46 .collect();
47 let components = Components(components);
48 bom.components = Some(components);
49
50 let dependencies: Vec<Dependency> = input
52 .packages
53 .iter()
54 .enumerate()
55 .map(|(idx, pkg)| Dependency {
56 dependency_ref: idx.to_string(),
57 dependencies: pkg.dependencies.iter().map(|idx| idx.to_string()).collect(),
58 })
59 .collect();
60 let dependencies = Dependencies(dependencies);
61 bom.dependencies = Some(dependencies);
62
63 if cfg!(debug_assertions) {
65 assert!(bom.validate().passed());
66 }
67 bom
68}
69
70fn pkg_to_component(pkg: &auditable_serde::Package, idx: usize) -> Component {
71 let component_type = if pkg.root {
72 Classification::Application
73 } else {
74 Classification::Library
75 };
76 let bom_ref = idx.to_string();
79 let mut result = Component::new(
80 component_type,
81 &pkg.name,
82 &pkg.version.to_string(),
83 Some(bom_ref),
84 );
85 let purl = purl(pkg);
87 let purl = Purl::from_str(&purl).unwrap();
88 result.purl = Some(purl);
89 match pkg.kind {
91 auditable_serde::DependencyKind::Runtime => (),
93 auditable_serde::DependencyKind::Build => {
94 let p = Property::new("cdx:rustc:dependency_kind".to_owned(), "build");
95 result.properties = Some(Properties(vec![p]));
96 }
97 }
98 result
99}
100
101fn root_package(input: &auditable_serde::VersionInfo) -> (usize, &Package) {
102 input
104 .packages
105 .iter()
106 .enumerate()
107 .find(|(_idx, pkg)| pkg.root)
108 .expect("VersionInfo contains no root package!")
109}
110
111fn purl(pkg: &auditable_serde::Package) -> String {
112 let mut purl = format!("pkg:cargo/{}@{}", pkg.name, pkg.version);
121 purl.push_str(match &pkg.source {
122 Source::CratesIo => "", Source::Git => "&vcs_url=redacted",
124 Source::Local => "&download_url=redacted",
125 Source::Registry => "&repository_url=redacted",
126 Source::Other(_) => "&download_url=redacted",
127 unknown => panic!("Unknown source: {:?}", unknown),
128 });
129 purl
130}