1use 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#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct CoreContext {
17 pub project_root: PathBuf,
19 pub project_id: String,
21 pub database_url: Option<String>,
23 pub falkordb: Option<FalkorConfig>,
25 pub qdrant: Option<QdrantConfig>,
27 pub embedding: Option<EmbeddingConfig>,
29 pub daemon_url: Option<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 = 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}