use crate::model::NormalizedSbom;
use std::path::Path;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ParseError {
#[error("IO error: {0}")]
IoError(String),
#[error("JSON parse error: {0}")]
JsonError(String),
#[error("XML parse error: {0}")]
XmlError(String),
#[error("YAML parse error: {0}")]
YamlError(String),
#[error("Invalid SBOM structure: {0}")]
InvalidStructure(String),
#[error("Unsupported format version: {0}")]
UnsupportedVersion(String),
#[error("Unknown SBOM format: {0}")]
UnknownFormat(String),
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Validation error: {0}")]
ValidationError(String),
}
impl From<std::io::Error> for ParseError {
fn from(err: std::io::Error) -> Self {
Self::IoError(err.to_string())
}
}
impl From<serde_json::Error> for ParseError {
fn from(err: serde_json::Error) -> Self {
Self::JsonError(err.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct FormatConfidence(f32);
impl FormatConfidence {
pub const NONE: Self = Self(0.0);
pub const LOW: Self = Self(0.25);
pub const MEDIUM: Self = Self(0.5);
pub const HIGH: Self = Self(0.75);
pub const CERTAIN: Self = Self(1.0);
#[must_use]
pub const fn new(value: f32) -> Self {
Self(value.clamp(0.0, 1.0))
}
#[must_use]
pub const fn value(&self) -> f32 {
self.0
}
#[must_use]
pub fn can_parse(&self) -> bool {
self.0 >= 0.25
}
}
impl Default for FormatConfidence {
fn default() -> Self {
Self::NONE
}
}
#[derive(Debug, Clone)]
pub struct FormatDetection {
pub confidence: FormatConfidence,
pub variant: Option<String>,
pub version: Option<String>,
pub warnings: Vec<String>,
}
impl FormatDetection {
#[must_use]
pub const fn no_match() -> Self {
Self {
confidence: FormatConfidence::NONE,
variant: None,
version: None,
warnings: Vec::new(),
}
}
#[must_use]
pub const fn with_confidence(confidence: FormatConfidence) -> Self {
Self {
confidence,
variant: None,
version: None,
warnings: Vec::new(),
}
}
#[must_use]
pub fn variant(mut self, variant: &str) -> Self {
self.variant = Some(variant.to_string());
self
}
#[must_use]
pub fn version(mut self, version: &str) -> Self {
self.version = Some(version.to_string());
self
}
#[must_use]
pub fn warning(mut self, warning: &str) -> Self {
self.warnings.push(warning.to_string());
self
}
}
pub trait SbomParser {
fn parse(&self, path: &Path) -> Result<NormalizedSbom, ParseError> {
let content = std::fs::read_to_string(path)?;
self.parse_str(&content)
}
fn parse_str(&self, content: &str) -> Result<NormalizedSbom, ParseError>;
fn supported_versions(&self) -> Vec<&str>;
fn format_name(&self) -> &str;
fn detect(&self, content: &str) -> FormatDetection;
fn can_parse(&self, content: &str) -> bool {
self.detect(content).confidence.can_parse()
}
fn confidence(&self, content: &str) -> FormatConfidence {
self.detect(content).confidence
}
}