1use std::path::{Path, PathBuf};
8
9use crate::config::{
10 ConfigSource, EmbeddingConfig, FalkorConfig, QdrantConfig, resolve_embedding_config,
11 resolve_falkordb_config, resolve_qdrant_config,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct CoreContext {
17 project_root: PathBuf,
19 project_id: String,
21 database_url: Option<String>,
23 falkordb: Option<FalkorConfig>,
25 qdrant: Option<QdrantConfig>,
27 embedding: Option<EmbeddingConfig>,
29 daemon_url: String,
31}
32
33impl CoreContext {
34 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 = 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 pub fn project_root(&self) -> &Path {
58 &self.project_root
59 }
60
61 pub fn project_id(&self) -> &str {
62 &self.project_id
63 }
64
65 pub fn database_url(&self) -> Option<&str> {
66 self.database_url.as_deref()
67 }
68
69 pub fn falkordb(&self) -> Option<&FalkorConfig> {
70 self.falkordb.as_ref()
71 }
72
73 pub fn qdrant(&self) -> Option<&QdrantConfig> {
74 self.qdrant.as_ref()
75 }
76
77 pub fn embedding(&self) -> Option<&EmbeddingConfig> {
78 self.embedding.as_ref()
79 }
80
81 pub fn daemon_url(&self) -> &str {
82 &self.daemon_url
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::config::{EnvOnlySource, TEST_ENV_LOCK};
90 use std::sync::MutexGuard;
91
92 struct EnvGuard {
93 _lock: MutexGuard<'static, ()>,
94 }
95
96 impl EnvGuard {
97 fn new() -> Self {
98 let guard = Self {
99 _lock: TEST_ENV_LOCK
100 .lock()
101 .unwrap_or_else(|poisoned| poisoned.into_inner()),
102 };
103 guard.clear();
104 guard
105 }
106
107 fn clear(&self) {
108 for key in [
109 "GOBBY_FALKORDB_HOST",
110 "GOBBY_FALKORDB_PORT",
111 "GOBBY_FALKORDB_PASSWORD",
112 "GOBBY_QDRANT_URL",
113 "GOBBY_QDRANT_API_KEY",
114 "GOBBY_EMBEDDING_URL",
115 "GOBBY_EMBEDDING_MODEL",
116 "GOBBY_EMBEDDING_API_KEY",
117 ] {
118 unsafe { std::env::remove_var(key) };
119 }
120 }
121
122 fn set(&self, key: &str, value: &str) {
123 unsafe { std::env::set_var(key, value) };
124 }
125 }
126
127 impl Drop for EnvGuard {
128 fn drop(&mut self) {
129 self.clear();
130 }
131 }
132
133 #[test]
134 fn missing_optional_services_are_none() {
135 let _env = EnvGuard::new();
136 let mut source = EnvOnlySource;
137 let root = std::path::PathBuf::from("/tmp/gobby-project");
138
139 let context = CoreContext::build(root.clone(), "project-id".to_string(), None, &mut source);
140
141 assert_eq!(context.project_root(), root.as_path());
142 assert_eq!(context.project_id(), "project-id");
143 assert_eq!(context.database_url(), None);
144 assert!(context.falkordb().is_none());
145 assert!(context.qdrant().is_none());
146 assert!(context.embedding().is_none());
147 assert!(!context.daemon_url().is_empty());
148 }
149
150 #[test]
151 fn build_with_env_only_source() {
152 let env = EnvGuard::new();
153 env.set("GOBBY_FALKORDB_HOST", "env-falkor.local");
154 env.set("GOBBY_FALKORDB_PORT", "17000");
155 env.set("GOBBY_QDRANT_URL", "http://env-qdrant:6333");
156 env.set("GOBBY_EMBEDDING_URL", "http://env-embedding:11434");
157 env.set("GOBBY_EMBEDDING_MODEL", "env-model");
158
159 let mut source = EnvOnlySource;
160 let root = std::path::PathBuf::from("/tmp/gobby-project");
161
162 let context = CoreContext::build(
163 root.clone(),
164 "project-id".to_string(),
165 Some("postgres://example".to_string()),
166 &mut source,
167 );
168
169 assert_eq!(context.project_root(), root.as_path());
170 assert_eq!(context.project_id(), "project-id");
171 assert_eq!(context.database_url(), Some("postgres://example"));
172 assert_eq!(
173 context.falkordb().map(|c| c.host.as_str()),
174 Some("env-falkor.local")
175 );
176 assert_eq!(
177 context.qdrant().and_then(|c| c.url.as_deref()),
178 Some("http://env-qdrant:6333")
179 );
180 assert_eq!(
181 context.embedding().map(|c| c.api_base.as_str()),
182 Some("http://env-embedding:11434")
183 );
184 assert_eq!(
185 context.embedding().map(|c| c.model.as_str()),
186 Some("env-model")
187 );
188 assert!(!context.daemon_url().is_empty());
189 }
190}