Gunnar

mikronova/modgunn

.nornir/sbom-guide.md

# sbom — guide

> **Class:** `guide` · audience: someone emitting a CycloneDX SBOM. The *what* is in
> [`sbom-design.md`](sbom-design.md).

## f0 — emit a CycloneDX 1.5 document

```rust
use modgunn::sbom::{to_cyclonedx, SbomComponent, SbomInput, SbomVuln};

let input = SbomInput {
    subject: "my-app".into(),
    components: vec![
        SbomComponent { ecosystem: "cargo".into(), name: "serde".into(),
                        version: "1.0.0".into(), license: Some("MIT".into()) },
    ],
    vulns: vec![
        SbomVuln { ecosystem: "cargo".into(), name: "some-crate".into(), version: "1.2.3".into(),
                   ids: vec!["RUSTSEC-2024-0001".into()], summary: "use-after-free".into() },
    ],
};

let doc = to_cyclonedx(&input);                 // serde_json::Value (CycloneDX 1.5)
println!("{}", serde_json::to_string_pretty(&doc)?);
```

## f1 — build a purl directly

```rust
let p = modgunn::sbom::purl("cargo", "serde", "1.0.0");   // "pkg:cargo/serde@1.0.0"
```

## Gotchas

- This is the **emit** side only; to *read* the resolved component set for a repo use
  `Warehouse::read_sbom_components` (see [`warehouse-guide.md`](warehouse-guide.md)).
- Output is byte-compatible with nornir's prior cargo SBOM — nornir's `to_cyclonedx` delegates
  here, so keep the field shapes stable.