Skip to main content

cargo_util_schemas/
sbom.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::core::{CrateType, PackageIdSpec};
6
7/// Typed version of a SBOM format version number.
8#[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    /// A dependency linked to the artifact produced by this unit.
17    Normal,
18    /// A dependency needed to run the build for this unit (e.g. a build script or proc-macro).
19    /// The dependency is not linked to the artifact produced by this unit.
20    Build,
21}
22
23/// Zero-based index into `crates` array of the crate described by this SBOM.
24#[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)]
126/// This is the schema used to generate `*.cargo-sbom.json` files with -Zsbom enabled.
127pub 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}