Skip to main content

sbom_tools/parsers/
traits.rs

1//! Parser trait definitions and error types.
2//!
3//! This module defines the `SbomParser` trait for format-specific parsers
4//! and provides intelligent format detection through confidence scoring.
5
6use crate::model::NormalizedSbom;
7use std::path::Path;
8use thiserror::Error;
9
10/// Errors that can occur during SBOM parsing
11#[derive(Error, Debug)]
12pub enum ParseError {
13    #[error("IO error: {0}")]
14    IoError(String),
15
16    #[error("JSON parse error: {0}")]
17    JsonError(String),
18
19    #[error("XML parse error: {0}")]
20    XmlError(String),
21
22    #[error("YAML parse error: {0}")]
23    YamlError(String),
24
25    #[error("Invalid SBOM structure: {0}")]
26    InvalidStructure(String),
27
28    #[error("Unsupported format version: {0}")]
29    UnsupportedVersion(String),
30
31    #[error("Unknown SBOM format: {0}")]
32    UnknownFormat(String),
33
34    #[error("Missing required field: {0}")]
35    MissingField(String),
36
37    #[error("Validation error: {0}")]
38    ValidationError(String),
39}
40
41impl From<std::io::Error> for ParseError {
42    fn from(err: std::io::Error) -> Self {
43        ParseError::IoError(err.to_string())
44    }
45}
46
47impl From<serde_json::Error> for ParseError {
48    fn from(err: serde_json::Error) -> Self {
49        ParseError::JsonError(err.to_string())
50    }
51}
52
53/// Confidence level for format detection
54#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
55pub struct FormatConfidence(f32);
56
57impl FormatConfidence {
58    /// No confidence - definitely not this format
59    pub const NONE: Self = Self(0.0);
60    /// Low confidence - might be this format
61    pub const LOW: Self = Self(0.25);
62    /// Medium confidence - likely this format
63    pub const MEDIUM: Self = Self(0.5);
64    /// High confidence - almost certainly this format
65    pub const HIGH: Self = Self(0.75);
66    /// Certain - definitely this format
67    pub const CERTAIN: Self = Self(1.0);
68
69    /// Create a new confidence value
70    pub fn new(value: f32) -> Self {
71        Self(value.clamp(0.0, 1.0))
72    }
73
74    /// Get the confidence value
75    pub fn value(&self) -> f32 {
76        self.0
77    }
78
79    /// Check if this confidence indicates the format can be parsed
80    pub fn can_parse(&self) -> bool {
81        self.0 >= 0.25
82    }
83}
84
85impl Default for FormatConfidence {
86    fn default() -> Self {
87        Self::NONE
88    }
89}
90
91/// Detection result from a parser
92#[derive(Debug, Clone)]
93pub struct FormatDetection {
94    /// Confidence that this parser can handle the content
95    pub confidence: FormatConfidence,
96    /// Detected format variant (e.g., "JSON", "XML", "tag-value")
97    pub variant: Option<String>,
98    /// Detected version if applicable
99    pub version: Option<String>,
100    /// Any issues detected that might affect parsing
101    pub warnings: Vec<String>,
102}
103
104impl FormatDetection {
105    /// Create a detection result indicating no match
106    pub fn no_match() -> Self {
107        Self {
108            confidence: FormatConfidence::NONE,
109            variant: None,
110            version: None,
111            warnings: Vec::new(),
112        }
113    }
114
115    /// Create a detection result with confidence
116    pub fn with_confidence(confidence: FormatConfidence) -> Self {
117        Self {
118            confidence,
119            variant: None,
120            version: None,
121            warnings: Vec::new(),
122        }
123    }
124
125    /// Set the detected variant
126    pub fn variant(mut self, variant: &str) -> Self {
127        self.variant = Some(variant.to_string());
128        self
129    }
130
131    /// Set the detected version
132    pub fn version(mut self, version: &str) -> Self {
133        self.version = Some(version.to_string());
134        self
135    }
136
137    /// Add a warning
138    pub fn warning(mut self, warning: &str) -> Self {
139        self.warnings.push(warning.to_string());
140        self
141    }
142}
143
144/// Trait for SBOM format parsers
145///
146/// Implementors should provide format detection via `detect()` and parsing via `parse_str()`.
147/// The detection method allows intelligent format selection without expensive trial-and-error parsing.
148pub trait SbomParser {
149    /// Parse SBOM from a file path
150    fn parse(&self, path: &Path) -> Result<NormalizedSbom, ParseError> {
151        let content = std::fs::read_to_string(path)?;
152        self.parse_str(&content)
153    }
154
155    /// Parse SBOM from string content
156    fn parse_str(&self, content: &str) -> Result<NormalizedSbom, ParseError>;
157
158    /// Get supported format versions
159    fn supported_versions(&self) -> Vec<&str>;
160
161    /// Get format name
162    fn format_name(&self) -> &str;
163
164    /// Detect if this parser can handle the given content
165    ///
166    /// This performs lightweight structural validation without full parsing.
167    /// Returns a confidence score and any detected metadata about the format.
168    fn detect(&self, content: &str) -> FormatDetection;
169
170    /// Quick check if this parser can likely handle the content
171    ///
172    /// Default implementation delegates to `detect()`, but parsers may override
173    /// with a faster heuristic check.
174    fn can_parse(&self, content: &str) -> bool {
175        self.detect(content).confidence.can_parse()
176    }
177
178    /// Get confidence score for parsing this content
179    ///
180    /// Default implementation delegates to `detect()`.
181    fn confidence(&self, content: &str) -> FormatConfidence {
182        self.detect(content).confidence
183    }
184}