1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::core::{CrateType, PackageIdSpec};
6
7#[derive(Serialize, Deserialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
9#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
10pub struct SbomFormatVersion(pub u32);
11
12#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
15pub enum SbomDependencyType {
16 Normal,
18 Build,
21}
22
23#[derive(Serialize, Deserialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
25#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
26pub struct SbomIndex(pub usize);
27
28#[derive(Serialize, Deserialize, Clone, Debug)]
29#[serde(rename_all = "snake_case")]
30#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
31pub struct SbomDependency {
32 pub index: SbomIndex,
33 pub kind: SbomDependencyType,
34}
35
36#[derive(Clone, Debug)]
37#[cfg_attr(
38 feature = "unstable-schema",
39 derive(schemars::JsonSchema),
40 schemars(with = "Vec<String>")
41)]
42pub enum SbomTargetKind {
43 Lib(Vec<CrateType>),
44 Bin,
45 Test,
46 Bench,
47 Example,
48 CustomBuild,
49}
50
51impl Serialize for SbomTargetKind {
52 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
53 where
54 S: serde::ser::Serializer,
55 {
56 use self::SbomTargetKind::*;
57 match self {
58 Lib(kinds) => s.collect_seq(kinds.iter().map(|t| t.to_string())),
59 Bin => ["bin"].serialize(s),
60 Example => ["example"].serialize(s),
61 Test => ["test"].serialize(s),
62 CustomBuild => ["custom-build"].serialize(s),
63 Bench => ["bench"].serialize(s),
64 }
65 }
66}
67
68impl<'de> Deserialize<'de> for SbomTargetKind {
69 fn deserialize<D>(d: D) -> Result<Self, D::Error>
70 where
71 D: serde::de::Deserializer<'de>,
72 {
73 use self::SbomTargetKind::*;
74 let kind: Vec<String> = Deserialize::deserialize(d)?;
75 Ok(match kind.as_slice() {
76 [name] if name == "bin" => Bin,
77 [name] if name == "example" => Example,
78 [name] if name == "test" => Test,
79 [name] if name == "custom-build" => CustomBuild,
80 [name] if name == "bench" => Bench,
81 _ => Lib(kind.into_iter().map(CrateType::from).collect()),
82 })
83 }
84}
85
86#[derive(Serialize, Deserialize, Clone, Debug)]
87#[serde(rename_all = "snake_case")]
88#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
89pub struct SbomCrate {
90 pub id: PackageIdSpec,
91 pub features: Vec<String>,
92 pub dependencies: Vec<SbomDependency>,
93 pub kind: SbomTargetKind,
94}
95
96impl SbomCrate {
97 pub fn new(package_id: PackageIdSpec, features: Vec<String>, kind: SbomTargetKind) -> Self {
98 Self {
99 id: package_id,
100 features,
101 dependencies: Vec::new(),
102 kind,
103 }
104 }
105}
106
107#[derive(Serialize, Deserialize, Clone)]
108#[serde(rename_all = "snake_case")]
109#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
110pub struct SbomRustc {
111 pub version: String,
112 pub wrapper: Option<PathBuf>,
113 pub workspace_wrapper: Option<PathBuf>,
114 pub commit_hash: Option<String>,
115 pub host: String,
116 pub verbose_version: String,
117}
118
119#[derive(Serialize, Deserialize)]
120#[serde(rename_all = "snake_case")]
121#[cfg_attr(
122 feature = "unstable-schema",
123 derive(schemars::JsonSchema),
124 schemars(title = "Cargo SBOM precursor")
125)]
126pub struct Sbom {
128 pub version: SbomFormatVersion,
129 pub root: SbomIndex,
130 pub crates: Vec<SbomCrate>,
131 pub rustc: SbomRustc,
132 pub target: String,
133}
134
135#[cfg(feature = "unstable-schema")]
136#[test]
137fn matches_schema() {
138 let schema = schemars::schema_for!(Sbom);
139 let dump = serde_json::to_string_pretty(&schema).unwrap();
140 snapbox::assert_data_eq!(dump, snapbox::file!("../cargo-sbom.schema.json").raw());
141}