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}