Skip to main content

gobby_core/
context.rs

1//! Shared runtime context boundary.
2//!
3//! Consumer crates keep their CLI flags and domain state locally. This module
4//! owns the public location for cross-crate project, daemon, and service context
5//! types as the Rust foundation expands.
6
7use std::path::PathBuf;
8
9use crate::config::{
10    ConfigSource, EmbeddingConfig, FalkorConfig, QdrantConfig, resolve_embedding_config,
11    resolve_falkordb_config, resolve_qdrant_config,
12};
13
14/// Resolved runtime context for any gobby-core consumer.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct CoreContext {
17    /// Project root directory containing `.gobby/`.
18    pub project_root: PathBuf,
19    /// Project ID from `.gobby/project.json`.
20    pub project_id: String,
21    /// PostgreSQL hub DSN resolved by the consumer.
22    pub database_url: Option<String>,
23    /// FalkorDB config when available.
24    pub falkordb: Option<FalkorConfig>,
25    /// Qdrant config when available.
26    pub qdrant: Option<QdrantConfig>,
27    /// Embedding API config when available.
28    pub embedding: Option<EmbeddingConfig>,
29    /// Gobby daemon base URL.
30    pub daemon_url: Option<String>,
31}
32
33impl CoreContext {
34    /// Build a context from pre-resolved project identity and DSN inputs.
35    pub fn build(
36        project_root: PathBuf,
37        project_id: String,
38        database_url: Option<String>,
39        source: &mut impl ConfigSource,
40    ) -> Self {
41        let falkordb = resolve_falkordb_config(source);
42        let qdrant = resolve_qdrant_config(source);
43        let embedding = resolve_embedding_config(source);
44        let daemon_url = Some(crate::daemon_url::daemon_url());
45
46        Self {
47            project_root,
48            project_id,
49            database_url,
50            falkordb,
51            qdrant,
52            embedding,
53            daemon_url,
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::config::{EnvOnlySource, TEST_ENV_LOCK};
62    use std::sync::MutexGuard;
63
64    struct EnvGuard {
65        _lock: MutexGuard<'static, ()>,
66    }
67
68    impl EnvGuard {
69        fn new() -> Self {
70            let guard = Self {
71                _lock: TEST_ENV_LOCK
72                    .lock()
73                    .unwrap_or_else(|poisoned| poisoned.into_inner()),
74            };
75            guard.clear();
76            guard
77        }
78
79        fn clear(&self) {
80            for key in [
81                "GOBBY_FALKORDB_HOST",
82                "GOBBY_FALKORDB_PORT",
83                "GOBBY_FALKORDB_PASSWORD",
84                "GOBBY_QDRANT_URL",
85                "GOBBY_QDRANT_API_KEY",
86                "GOBBY_EMBEDDING_URL",
87                "GOBBY_EMBEDDING_MODEL",
88                "GOBBY_EMBEDDING_API_KEY",
89            ] {
90                unsafe { std::env::remove_var(key) };
91            }
92        }
93
94        fn set(&self, key: &str, value: &str) {
95            unsafe { std::env::set_var(key, value) };
96        }
97    }
98
99    impl Drop for EnvGuard {
100        fn drop(&mut self) {
101            self.clear();
102        }
103    }
104
105    #[test]
106    fn missing_optional_services_are_none() {
107        let _env = EnvGuard::new();
108        let mut source = EnvOnlySource;
109        let root = std::path::PathBuf::from("/tmp/gobby-project");
110
111        let context = CoreContext::build(root.clone(), "project-id".to_string(), None, &mut source);
112
113        assert_eq!(context.project_root, root);
114        assert_eq!(context.project_id, "project-id");
115        assert_eq!(context.database_url, None);
116        assert!(context.falkordb.is_none());
117        assert!(context.qdrant.is_none());
118        assert!(context.embedding.is_none());
119        assert!(context.daemon_url.is_some());
120    }
121
122    #[test]
123    fn build_with_env_only_source() {
124        let env = EnvGuard::new();
125        env.set("GOBBY_FALKORDB_HOST", "env-falkor.local");
126        env.set("GOBBY_FALKORDB_PORT", "17000");
127        env.set("GOBBY_QDRANT_URL", "http://env-qdrant:6333");
128        env.set("GOBBY_EMBEDDING_URL", "http://env-embedding:11434");
129        env.set("GOBBY_EMBEDDING_MODEL", "env-model");
130
131        let mut source = EnvOnlySource;
132        let root = std::path::PathBuf::from("/tmp/gobby-project");
133
134        let context = CoreContext::build(
135            root.clone(),
136            "project-id".to_string(),
137            Some("postgres://example".to_string()),
138            &mut source,
139        );
140
141        assert_eq!(context.project_root, root);
142        assert_eq!(context.project_id, "project-id");
143        assert_eq!(context.database_url.as_deref(), Some("postgres://example"));
144        assert_eq!(
145            context.falkordb.as_ref().map(|c| c.host.as_str()),
146            Some("env-falkor.local")
147        );
148        assert_eq!(
149            context.qdrant.as_ref().and_then(|c| c.url.as_deref()),
150            Some("http://env-qdrant:6333")
151        );
152        assert_eq!(
153            context.embedding.as_ref().map(|c| c.api_base.as_str()),
154            Some("http://env-embedding:11434")
155        );
156        assert_eq!(
157            context.embedding.as_ref().map(|c| c.model.as_str()),
158            Some("env-model")
159        );
160        assert!(context.daemon_url.is_some());
161    }
162}