use std::io;
use std::iter::FromIterator;
use cargo::core::Package;
use serde::{Serialize, Serializer};
use uuid::Uuid;
use xml_writer::XmlWriter;
use crate::metadata::Metadata;
use crate::{Component, ToXml};
static SPEC_VERSION: &'static str = "1.3";
#[derive(Clone, Copy, Serialize)]
enum BomFormat {
CycloneDX,
}
fn uuid_to_urn<S: Serializer>(uuid: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(&uuid.to_urn())
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Bom {
bom_format: BomFormat,
spec_version: &'static str,
#[serde(serialize_with = "uuid_to_urn")]
serial_number: Uuid,
version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
components: Vec<Component>,
}
impl<'a> Default for Bom {
fn default() -> Bom {
Self {
bom_format: BomFormat::CycloneDX,
spec_version: SPEC_VERSION,
version: 1,
serial_number: Uuid::new_v4(),
metadata: None::<Metadata>,
components: Vec::new(),
}
}
}
impl<'a> FromIterator<&'a Package> for Bom {
fn from_iter<T: IntoIterator<Item = &'a Package>>(iter: T) -> Self {
Self {
components: iter.into_iter().map(Component::from).collect(),
metadata: Some(Metadata::default()),
..Default::default()
}
}
}
impl ToXml for Bom {
fn to_xml<W: io::Write>(&self, xml: &mut XmlWriter<W>) -> io::Result<()> {
let namespace = format!("http://cyclonedx.org/schema/bom/{}", SPEC_VERSION);
xml.dtd("UTF-8")?;
xml.begin_elem("bom")?;
xml.attr("serialNumber", &self.serial_number.to_urn().to_string())?;
xml.attr("version", "1")?;
xml.attr("xmlns", namespace.as_str())?;
if let Some(metadata) = &self.metadata {
metadata.to_xml(xml)?;
}
xml.begin_elem("components")?;
for component in &self.components {
component.to_xml(xml)?;
}
xml.end_elem()?;
xml.end_elem()
}
}