gglib_core/ports/
mod.rs

1//! Port definitions (trait abstractions) for external systems.
2//!
3//! Ports define the interfaces that the core domain expects from infrastructure.
4//! They contain no implementation details and use only domain types.
5//!
6//! # Design Rules
7//!
8//! - No `sqlx` types in any signature
9//! - No process/filesystem implementation details
10//! - Traits are minimal and CRUD-focused for repositories
11//! - Intent-based methods for process runner (not implementation-leaking)
12
13pub mod chat_history;
14pub mod download;
15pub mod download_event_emitter;
16pub mod download_manager;
17pub mod download_state;
18pub mod event_emitter;
19pub mod gguf_parser;
20pub mod huggingface;
21pub mod mcp_dto;
22pub mod mcp_error;
23pub mod mcp_repository;
24pub mod model_catalog;
25pub mod model_registrar;
26pub mod model_repository;
27pub mod model_runtime;
28pub mod process_runner;
29pub mod server_health;
30pub mod server_log_sink;
31pub mod settings_repository;
32pub mod system_probe;
33pub mod tool_support;
34
35use std::sync::Arc;
36use thiserror::Error;
37
38// Re-export repository traits for convenience
39pub use chat_history::{ChatHistoryError, ChatHistoryRepository};
40pub use download::{QuantizationResolver, Resolution, ResolvedFile};
41pub use download_event_emitter::{AppEventBridge, DownloadEventEmitterPort, NoopDownloadEmitter};
42pub use download_manager::{DownloadManagerConfig, DownloadManagerPort, DownloadRequest};
43pub use download_state::DownloadStateRepositoryPort;
44pub use event_emitter::{AppEventEmitter, NoopEmitter};
45pub use gguf_parser::{
46    GgufCapabilities, GgufMetadata, GgufParseError, GgufParserPort, NoopGgufParser,
47};
48pub use huggingface::{
49    HfClientPort, HfFileInfo, HfPortError, HfQuantInfo, HfRepoInfo, HfSearchOptions, HfSearchResult,
50};
51pub use mcp_dto::{ResolutionAttempt, ResolutionStatus};
52pub use mcp_error::{McpErrorCategory, McpErrorInfo, McpServiceError};
53pub use mcp_repository::{McpRepositoryError, McpServerRepository};
54pub use model_catalog::{CatalogError, ModelCatalogPort, ModelLaunchSpec, ModelSummary};
55pub use model_registrar::{CompletedDownload, ModelRegistrarPort};
56pub use model_repository::ModelRepository;
57pub use model_runtime::{ModelRuntimeError, ModelRuntimePort, RunningTarget};
58pub use process_runner::{ProcessHandle, ProcessRunner, ServerConfig, ServerHealth};
59pub use server_health::ServerHealthStatus;
60pub use server_log_sink::ServerLogSinkPort;
61pub use settings_repository::SettingsRepository;
62pub use system_probe::{SystemProbeError, SystemProbePort, SystemProbeResult};
63pub use tool_support::{
64    ModelSource, ToolFormat, ToolSupportDetection, ToolSupportDetectionInput,
65    ToolSupportDetectorPort,
66};
67
68/// Container for all repository trait objects.
69///
70/// This struct provides a consistent way to wire repositories across adapters
71/// without coupling them to concrete implementations. It lives in `gglib-core`
72/// so that `AppCore` can accept it without depending on `gglib-db`.
73///
74/// # Example
75///
76/// ```ignore
77/// // In gglib-db factory:
78/// pub fn build_repos(pool: &SqlitePool) -> Repos { ... }
79///
80/// // In adapter bootstrap:
81/// let repos = gglib_db::factory::build_repos(&pool);
82/// let core = AppCore::new(repos, runner);
83/// ```
84#[derive(Clone)]
85pub struct Repos {
86    /// Model repository for CRUD operations on models.
87    pub models: Arc<dyn ModelRepository>,
88    /// Settings repository for application settings.
89    pub settings: Arc<dyn SettingsRepository>,
90    /// MCP server repository for MCP server configurations.
91    pub mcp_servers: Arc<dyn McpServerRepository>,
92    /// Chat history repository for conversations and messages.
93    pub chat_history: Arc<dyn ChatHistoryRepository>,
94}
95
96impl Repos {
97    /// Create a new Repos container.
98    pub fn new(
99        models: Arc<dyn ModelRepository>,
100        settings: Arc<dyn SettingsRepository>,
101        mcp_servers: Arc<dyn McpServerRepository>,
102        chat_history: Arc<dyn ChatHistoryRepository>,
103    ) -> Self {
104        Self {
105            models,
106            settings,
107            mcp_servers,
108            chat_history,
109        }
110    }
111}
112
113/// Domain-specific errors for repository operations.
114///
115/// This error type abstracts away storage implementation details (e.g., sqlx errors)
116/// and provides a clean interface for services to handle storage failures.
117#[derive(Debug, Error)]
118pub enum RepositoryError {
119    /// The requested entity was not found.
120    #[error("Not found: {0}")]
121    NotFound(String),
122
123    /// An entity with the same identifier already exists.
124    #[error("Already exists: {0}")]
125    AlreadyExists(String),
126
127    /// Storage backend error (database, filesystem, etc.).
128    #[error("Storage error: {0}")]
129    Storage(String),
130
131    /// Serialization or deserialization failed.
132    #[error("Serialization error: {0}")]
133    Serialization(String),
134
135    /// A constraint was violated (e.g., foreign key, unique constraint).
136    #[error("Constraint violation: {0}")]
137    Constraint(String),
138}
139
140/// Domain-specific errors for process runner operations.
141///
142/// This error type abstracts away process management implementation details
143/// and provides a clean interface for services to handle process failures.
144#[derive(Debug, Error)]
145pub enum ProcessError {
146    /// Failed to start the process.
147    #[error("Failed to start: {0}")]
148    StartFailed(String),
149
150    /// Failed to stop the process.
151    #[error("Failed to stop: {0}")]
152    StopFailed(String),
153
154    /// The process is not running.
155    #[error("Process not running: {0}")]
156    NotRunning(String),
157
158    /// Health check failed.
159    #[error("Health check failed: {0}")]
160    HealthCheckFailed(String),
161
162    /// Configuration error.
163    #[error("Configuration error: {0}")]
164    Configuration(String),
165
166    /// Resource exhaustion (e.g., no available ports).
167    #[error("Resource exhaustion: {0}")]
168    ResourceExhausted(String),
169
170    /// Internal process error.
171    #[error("Internal error: {0}")]
172    Internal(String),
173}
174
175/// Core error type for semantic domain errors.
176///
177/// This is the canonical error type used across the core domain.
178/// Adapters should map this to their own error types (HTTP status codes,
179/// CLI exit codes, Tauri serialized errors).
180#[derive(Debug, Error)]
181pub enum CoreError {
182    /// Repository operation failed.
183    #[error(transparent)]
184    Repository(#[from] RepositoryError),
185
186    /// Process operation failed.
187    #[error(transparent)]
188    Process(#[from] ProcessError),
189
190    /// Settings validation error.
191    #[error(transparent)]
192    Settings(#[from] crate::settings::SettingsError),
193
194    /// Validation error (invalid input).
195    #[error("Validation error: {0}")]
196    Validation(String),
197
198    /// Configuration error.
199    #[error("Configuration error: {0}")]
200    Configuration(String),
201
202    /// External service error.
203    #[error("External service error: {0}")]
204    ExternalService(String),
205
206    /// Internal error (unexpected condition).
207    #[error("Internal error: {0}")]
208    Internal(String),
209}