use std::path::{Path, PathBuf};
use crate::config::{
ConfigSource, EmbeddingConfig, FalkorConfig, QdrantConfig, resolve_embedding_config,
resolve_falkordb_config, resolve_qdrant_config,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoreContext {
project_root: PathBuf,
project_id: String,
database_url: Option<String>,
falkordb: Option<FalkorConfig>,
qdrant: Option<QdrantConfig>,
embedding: Option<EmbeddingConfig>,
daemon_url: String,
}
impl CoreContext {
pub fn build(
project_root: PathBuf,
project_id: String,
database_url: Option<String>,
source: &mut impl ConfigSource,
) -> Self {
let falkordb = resolve_falkordb_config(source);
let qdrant = resolve_qdrant_config(source);
let embedding = resolve_embedding_config(source);
let daemon_url = crate::daemon_url::daemon_url();
Self {
project_root,
project_id,
database_url,
falkordb,
qdrant,
embedding,
daemon_url,
}
}
pub fn project_root(&self) -> &Path {
&self.project_root
}
pub fn project_id(&self) -> &str {
&self.project_id
}
pub fn database_url(&self) -> Option<&str> {
self.database_url.as_deref()
}
pub fn falkordb(&self) -> Option<&FalkorConfig> {
self.falkordb.as_ref()
}
pub fn qdrant(&self) -> Option<&QdrantConfig> {
self.qdrant.as_ref()
}
pub fn embedding(&self) -> Option<&EmbeddingConfig> {
self.embedding.as_ref()
}
pub fn daemon_url(&self) -> &str {
&self.daemon_url
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{EnvOnlySource, TEST_ENV_LOCK, embedding_keys};
use std::collections::HashMap;
use std::sync::MutexGuard;
struct EnvGuard {
_lock: MutexGuard<'static, ()>,
}
impl EnvGuard {
fn new() -> Self {
let guard = Self {
_lock: TEST_ENV_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()),
};
guard.clear();
guard
}
fn clear(&self) {
for key in [
"GOBBY_FALKORDB_HOST",
"GOBBY_FALKORDB_PORT",
"GOBBY_FALKORDB_PASSWORD",
"GOBBY_QDRANT_URL",
"GOBBY_QDRANT_API_KEY",
] {
unsafe { std::env::remove_var(key) };
}
}
fn set(&self, key: &str, value: &str) {
unsafe { std::env::set_var(key, value) };
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
self.clear();
}
}
struct TestConfigSource {
values: HashMap<&'static str, String>,
}
impl TestConfigSource {
fn with_values(values: impl IntoIterator<Item = (&'static str, &'static str)>) -> Self {
Self {
values: values
.into_iter()
.map(|(key, value)| (key, value.to_string()))
.collect(),
}
}
}
impl ConfigSource for TestConfigSource {
fn config_value(&mut self, key: &str) -> Option<String> {
self.values.get(key).cloned()
}
fn resolve_value(&mut self, value: &str) -> anyhow::Result<String> {
Ok(value.to_string())
}
}
#[test]
fn missing_diagnostic_service_config_is_none() {
let _env = EnvGuard::new();
let mut source = EnvOnlySource;
let root = std::path::PathBuf::from("/tmp/gobby-project");
let context = CoreContext::build(root.clone(), "project-id".to_string(), None, &mut source);
assert_eq!(context.project_root(), root.as_path());
assert_eq!(context.project_id(), "project-id");
assert_eq!(context.database_url(), None);
assert!(context.falkordb().is_none());
assert!(context.qdrant().is_none());
assert!(context.embedding().is_none());
assert!(!context.daemon_url().is_empty());
}
#[test]
fn build_with_env_only_source() {
let env = EnvGuard::new();
env.set("GOBBY_FALKORDB_HOST", "env-falkor.local");
env.set("GOBBY_FALKORDB_PORT", "17000");
env.set("GOBBY_QDRANT_URL", "http://env-qdrant:6333");
let mut source = EnvOnlySource;
let root = std::path::PathBuf::from("/tmp/gobby-project");
let context = CoreContext::build(
root.clone(),
"project-id".to_string(),
Some("postgres://example".to_string()),
&mut source,
);
assert_eq!(context.project_root(), root.as_path());
assert_eq!(context.project_id(), "project-id");
assert_eq!(context.database_url(), Some("postgres://example"));
assert_eq!(
context.falkordb().map(|c| c.host.as_str()),
Some("env-falkor.local")
);
assert_eq!(
context.qdrant().and_then(|c| c.url.as_deref()),
Some("http://env-qdrant:6333")
);
assert!(context.embedding().is_none());
assert!(!context.daemon_url().is_empty());
}
#[test]
fn build_with_config_source_embedding() {
let _env = EnvGuard::new();
let mut source = TestConfigSource::with_values([
(embedding_keys::AI_API_BASE, "http://config-embedding:11434"),
(embedding_keys::AI_MODEL, "config-model"),
]);
let root = std::path::PathBuf::from("/tmp/gobby-project");
let context = CoreContext::build(root, "project-id".to_string(), None, &mut source);
let embedding = context.embedding().expect("embedding config");
assert_eq!(embedding.api_base, "http://config-embedding:11434");
assert_eq!(embedding.model, "config-model");
}
}