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        Self::IoError(err.to_string())
44    }
45}
46
47impl From<serde_json::Error> for ParseError {
48    fn from(err: serde_json::Error) -> Self {
49        Self::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    #[must_use]
71    pub const fn new(value: f32) -> Self {
72        Self(value.clamp(0.0, 1.0))
73    }
74
75    /// Get the confidence value
76    #[must_use]
77    pub const fn value(&self) -> f32 {
78        self.0
79    }
80
81    /// Check if this confidence indicates the format can be parsed
82    #[must_use]
83    pub fn can_parse(&self) -> bool {
84        self.0 >= 0.25
85    }
86}
87
88impl Default for FormatConfidence {
89    fn default() -> Self {
90        Self::NONE
91    }
92}
93
94/// Detection result from a parser
95#[derive(Debug, Clone)]
96pub struct FormatDetection {
97    /// Confidence that this parser can handle the content
98    pub confidence: FormatConfidence,
99    /// Detected format variant (e.g., "JSON", "XML", "tag-value")
100    pub variant: Option<String>,
101    /// Detected version if applicable
102    pub version: Option<String>,
103    /// Any issues detected that might affect parsing
104    pub warnings: Vec<String>,
105}
106
107impl FormatDetection {
108    /// Create a detection result indicating no match
109    #[must_use]
110    pub const fn no_match() -> Self {
111        Self {
112            confidence: FormatConfidence::NONE,
113            variant: None,
114            version: None,
115            warnings: Vec::new(),
116        }
117    }
118
119    /// Create a detection result with confidence
120    #[must_use]
121    pub const fn with_confidence(confidence: FormatConfidence) -> Self {
122        Self {
123            confidence,
124            variant: None,
125            version: None,
126            warnings: Vec::new(),
127        }
128    }
129
130    /// Set the detected variant
131    #[must_use]
132    pub fn variant(mut self, variant: &str) -> Self {
133        self.variant = Some(variant.to_string());
134        self
135    }
136
137    /// Set the detected version
138    #[must_use]
139    pub fn version(mut self, version: &str) -> Self {
140        self.version = Some(version.to_string());
141        self
142    }
143
144    /// Add a warning
145    #[must_use]
146    pub fn warning(mut self, warning: &str) -> Self {
147        self.warnings.push(warning.to_string());
148        self
149    }
150}
151
152/// Trait for SBOM format parsers
153///
154/// Implementors should provide format detection via `detect()` and parsing via `parse_str()`.
155/// The detection method allows intelligent format selection without expensive trial-and-error parsing.
156pub trait SbomParser {
157    /// Parse SBOM from a file path
158    fn parse(&self, path: &Path) -> Result<NormalizedSbom, ParseError> {
159        let content = std::fs::read_to_string(path)?;
160        self.parse_str(&content)
161    }
162
163    /// Parse SBOM from string content
164    fn parse_str(&self, content: &str) -> Result<NormalizedSbom, ParseError>;
165
166    /// Get supported format versions
167    fn supported_versions(&self) -> Vec<&str>;
168
169    /// Get format name
170    fn format_name(&self) -> &str;
171
172    /// Detect if this parser can handle the given content
173    ///
174    /// This performs lightweight structural validation without full parsing.
175    /// Returns a confidence score and any detected metadata about the format.
176    fn detect(&self, content: &str) -> FormatDetection;
177
178    /// Quick check if this parser can likely handle the content
179    ///
180    /// Default implementation delegates to `detect()`, but parsers may override
181    /// with a faster heuristic check.
182    fn can_parse(&self, content: &str) -> bool {
183        self.detect(content).confidence.can_parse()
184    }
185
186    /// Get confidence score for parsing this content
187    ///
188    /// Default implementation delegates to `detect()`.
189    fn confidence(&self, content: &str) -> FormatConfidence {
190        self.detect(content).confidence
191    }
192}