gglib_core/ports/
gguf_parser.rs

1//! GGUF parser port definition.
2//!
3//! This port abstracts the parsing of GGUF file metadata, allowing
4//! different implementations (full parser, stub, mock for testing).
5//!
6//! # Design
7//!
8//! - Domain types (`GgufMetadata`, `GgufCapabilities`) are defined in `domain::gguf`
9//! - This port only defines the trait and error type
10//! - Implementations live in `gglib-gguf` crate
11
12use std::path::Path;
13
14use thiserror::Error;
15
16// Re-export domain types for convenience
17pub use crate::domain::gguf::{GgufCapabilities, GgufMetadata};
18
19/// Errors that can occur during GGUF parsing.
20///
21/// This is the domain-facing error type. Implementations may have richer
22/// internal errors that convert to this type via `From`.
23#[derive(Debug, Error)]
24pub enum GgufParseError {
25    /// The file does not exist.
26    #[error("File not found: {0}")]
27    NotFound(String),
28
29    /// The file is not a valid GGUF file.
30    #[error("Invalid GGUF format: {0}")]
31    InvalidFormat(String),
32
33    /// IO error while reading the file.
34    #[error("IO error: {0}")]
35    Io(String),
36}
37
38/// Port for parsing GGUF file metadata.
39///
40/// This trait abstracts GGUF parsing so that different implementations
41/// can be injected (full native parser, stub for tests, etc.).
42///
43/// # Port Signature Rules
44///
45/// - All types in signatures are from `gglib-core` (domain types)
46/// - No `gglib-gguf` symbols appear in signatures
47/// - Implementations live in `gglib-gguf` and implement this trait
48pub trait GgufParserPort: Send + Sync {
49    /// Parse metadata from a GGUF file.
50    ///
51    /// # Arguments
52    ///
53    /// * `file_path` - Path to the GGUF file
54    ///
55    /// # Returns
56    ///
57    /// Returns the parsed metadata, or an error if parsing fails.
58    fn parse(&self, file_path: &Path) -> Result<GgufMetadata, GgufParseError>;
59
60    /// Detect capabilities from parsed metadata.
61    ///
62    /// Analyzes the metadata to detect model capabilities like reasoning
63    /// or tool calling support.
64    ///
65    /// # Arguments
66    ///
67    /// * `metadata` - The parsed GGUF metadata
68    ///
69    /// # Returns
70    ///
71    /// Returns structured capabilities (bitflags + extensions).
72    fn detect_capabilities(&self, metadata: &GgufMetadata) -> GgufCapabilities;
73}
74
75/// A no-op GGUF parser that returns default/empty metadata.
76///
77/// Useful for testing or when GGUF parsing is not needed.
78#[derive(Debug, Clone, Default)]
79pub struct NoopGgufParser;
80
81impl GgufParserPort for NoopGgufParser {
82    fn parse(&self, _file_path: &Path) -> Result<GgufMetadata, GgufParseError> {
83        Ok(GgufMetadata::default())
84    }
85
86    fn detect_capabilities(&self, _metadata: &GgufMetadata) -> GgufCapabilities {
87        GgufCapabilities::empty()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use std::path::PathBuf;
95
96    #[test]
97    fn test_noop_parser_returns_empty() {
98        let parser = NoopGgufParser;
99        let result = parser.parse(&PathBuf::from("/any/path.gguf"));
100        assert!(result.is_ok());
101        let meta = result.unwrap();
102        assert!(meta.architecture.is_none());
103        assert!(meta.param_count_b.is_none());
104    }
105
106    #[test]
107    fn test_noop_parser_detects_no_capabilities() {
108        let parser = NoopGgufParser;
109        let metadata = GgufMetadata::default();
110        let caps = parser.detect_capabilities(&metadata);
111        assert!(!caps.has_reasoning());
112        assert!(!caps.has_tool_calling());
113        assert!(caps.to_tags().is_empty());
114    }
115}